【pwn学习】堆溢出(三)- Unlink和UAF

前置学习

什么是Unlink?

执行free(chunk)会发生什么呢?

  • glibc会先判断要释放的chunk的类型,如果是small chunk或者large chunk的话需要进行合并
  • 判断前向合并(低地址),如果前一个chunk处于空闲状态,则进行前向合并
  • 判断后向合并(高地址),如果后一个chunk处于空闲状态,则进行后向合并
  • 堆需要合并的chunk进行unlink操作

unlink()是glibc中的一个宏,其目的是将某一个空闲 chunk 从其所处的 bin 中脱链。在 malloc_consolidate() 函数中将 fastbin 中的空闲 chunk 整理到 unsorted_bin,在 malloc() 函数中用于将 unsorted_bin 中的空闲 chunk 整理到 smallbin 或者 largebin,以及在 mallo() 中获得堆空间时,均有可能调用 unlink() 宏。

Unlink()的流程大概如下图所示

在这里插入图片描述

  • 首先根据P的fd和bk参数确定chunk P在bin中的前后chunk分别为FD和BK;
  • 然后让chunk FD的bk参数指向chunk BK;
  • Chunk BK的fd参数指向chunk FD;

到这里为止,small bin的处理就完成了,但是Large bin还没有。在之前学习bin的相关知识的时候提到,fastbin,smallbin的每一个bin中的chunk大小都是相同的,但是largebin中的一个bin可能包含不同大小的chunk,因此对large chunk的块首,还有额外两个参数fd_nextsizebk_nextsize需要处理。

假设一个large bin中的分布如下,其中数字相同的代表大小一致。

largebin -> A1 -> B1 -> C1 -> A2 ->  B3 -> A4 -> C4 -> tail

那么在这一组largebin中,A1,A2,B3,A4的fd_nextsizebk_nextsize是有效的,其中A1->fd_nextsize == A2, A2->fd_nextsize==B3以此类推

Unlink如何利用?

我们以下面的程序为例来演示unlink是如何在攻击中利用的

// gcc -m32 unlink.c -o unlink
#include <stdlib.h>
#include <string.h>

int main( int argc, char * argv[] )
{
        char * first, * second;

/*[1]*/ first = malloc( 666 );
/*[2]*/ second = malloc( 12 );
        if(argc!=1)
/*[3]*/         strcpy( first, argv[1] );
/*[4]*/ free( first );
/*[5]*/ free( second );
/*[6]*/ return( 0 );
}

上面程序中第3行的位置没有对strcpy的长度进行限制,因此向chunk first中写入数据的时候可能发生溢出。在向first写入前,堆的结构示意图应如下所示。
在这里插入图片描述

利用gdb调试,可以看到执行/*[4]*/ free( first );前的的heap分布如下所示,和上面的图基本类似。

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5655c000
Size: 0x2a1

Allocated chunk | PREV_INUSE
Addr: 0x5655c2a0
Size: 0x11

Top chunk | PREV_INUSE
Addr: 0x5655c2b0
Size: 0x20d51

如果first发生了溢出,首先会覆盖second chunk的块首,然后会覆盖second的块身。假设我们静心构造了如下的溢出覆盖second chunk的内容

  • second的prev_size字段:用一个偶数覆盖,这样保证最低bit为0
  • second的size字段:用数值 -4 覆盖
  • second的ptr + 8 的地址,这里对于一个释放状态的chunk来说,表示fd指针:用地址 (free的GOT表地址 - 12)覆盖原数据
  • second的ptr + 12的地址,这里对于一个释放状态的chunk来说,表示bk指针:用攻击者写入的shellcode的地址覆盖

按照上面覆盖后,执行free(first)的流程如下所示

  • glibc判断这个块不是fast chunk,需要合并

  • 判断前向合并。fisrt chunk的size字段的PREV_INUSE字段为1,因此不需要前向合并;

  • 判断后向合并。下一个chunk是second,second是否为空需要判断second chunk的下一个chunk的PREV_INUSE标志位。如何从当前释放的地址,即first chunk索引到second chunk的下一个chunk呢?

    • 首先要获取当前chunk的下一个chunk的起始地址

      next_chunk = first_ptr + first_size
      
    • 然后获取下一个的下一个chunk起始地址,计算方法类似上一步

      next_next_chunk_ptr = next_chunk + next_chunk_size
      
    • 获取next_next_chunk_ptr之后,next_next_chunk_ptr地址加上一个地址就是next_next_chunk_size,从而根据next_next_chunk的PREV_INUSE位可以判断next_chunk的状态。

    • 在上面的假设中,second的size已经被覆盖为了-4,因此最终获得的next_next_chunk_ptr的实际上位于second_ptr的上一个地址,这样一来,next_next_chunk的PREV_INUSE实际上就是second_ptr(即second_prev_size)的最低位。second_prev_size的最低位置为了0,因此glibc这时会认为second chunk是处于free状态,这时就需要执行unlink(second)的操作,将second从bin中脱链,从而与first进行合并。

  • unlink(second)

    • 定义FD和BK变量

      FD = second_fd = free_GOT - 12
      BK = second_bk = shellcode_address
      
    • 下一步要执行FD -> bk = BK

      对于一个chunk来说,bk距离chunk的指针的距离为12个地址,如下图所示

      |	prev_size		|  <=== ptr
      | size				|	 <=== ptr + 4
      |	fd					|  <=== ptr + 8
      | bk					|	 <=== ptr + 12
      

      这样一来相当于执行了如下的赋值

      • FD -> bk = BK中的 FD -> bk转化为[FD + 12]
      • FD在第一步的已经赋值为free_GOT-12
      • 所以FD -> bk = BK实际上等同于free_GOT = BK
      • BK在第一步的时候赋值为shellocde_address
      • 因此free_GOT=BK进一步转化为free_GOT = shellcode_address

      而这一个过程**,实际上就是用shell code的地址覆盖了free的GOT表地址。**

执行free(first)之后,free的GOT表被劫持,这样在下一次调用free函数,即/*[5]*/ free( second );的时候实际上会执行shellcode,从而获取shell。

加入错误检查

这样就可以了吗?如果是古老的glibc版本到这里就可以了,但是在现在版本的unlink中,有这样一段判断语句

FD = P->fd;								      \
BK = P->bk;	
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))             \
  malloc_printerr (check_action, "corrupted double-linked list", P, AV);
else {
    ...
}

而在构造溢出覆盖second的内容后,执行unlink(second)时

FD = second_fd = free_GOT - 12
BK = second_bk = shellcode_address

此时

FD -> bk == [free_GOT - 12 + 12] == free_GOT != second
BK -> fd == [shellcode_addr + 8] != second

因此在现代版本的glibc中直接使用上面的方法会报错,需要先改变一下覆盖的second chunk的fd和bk部分,改造成满足下面的条件

  • FD -> bk == second => [FD + 12] == second
  • BK -> fd == second => [FD + 8] == second

这样就能通过上面的判断。

溢出

我们来重新梳理下,考虑到检查后的溢出内容:

如果first发生了溢出,覆盖second chunk的内容

  • second的prev_size字段:用一个偶数覆盖,这样保证最低bit为0
  • second的size字段:用数值 -4 覆盖
  • second的ptr + 8 的地址,这里对于一个释放状态的chunk来说,表示fd指针:一个fakeFD覆盖,且满足[fakeFD + 12] == second
  • second的ptr + 12的地址,这里对于一个释放状态的chunk来说,表示bk指针:用伪造的bk地址 fakeBK覆盖,且满足[fake + 8] == second

free(first)

free流程的前半部分没有发生改变,和之前一样。

unlink(second)

  • 第一步不变,依然是定义FD和BK

    FD = second -> fd ==> FD = fakeFD
    BK = second -> bk ==> FD = fakeBK
    
  • 执行检查FD->bk != P || BK->fd != P,在本题环境中,相当于做了如下运算

    • FD -> bk==>[fakeFD + 12] == second
    • BK -> fd ==> [fakeBK + 8] == second
  • FD -> bk = BK

    • FD -> bk => [fakeFD + 12] = fakeBK
  • BK -> fd = FD

    • BK -> fd => [fakeBK + 8] == fakeFD

至此,虽然不能再实现任意地址写,但是我们实现了在free(first)的时候,修改chunk的fd和bk指针。但是这有什么用呢,我们在后面的例题中继续学习。

什么是Use-After-free?

在继续学习unlink的利用之前,我们先了解一下Use-After-Free(UAF)。如字面意思,Use-after-free是指当一个内存块被释放后再次被使用。可能存在如下三种情况:

  • 内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。
  • 内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转
  • 内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题

而我们一般所指的 Use After Free 漏洞主要是后两种。此外,我们一般称被释放后没有被设置为 NULL 的内存指针为 dangling pointer。

例题

  1. 2014 HITCON stkof
  2. 2016 ZCTF note2
  3. 2017 insomni’hack wheelofrobots

Reference

https://blog.csdn.net/Plus_RE/article/details/79270350

https://heap-exploitation.dhavalkapil.com/attacks/unlink_exploit

https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/unlink/

https://elixir.bootlin.com/glibc/glibc-2.23.90/source/malloc/malloc.c#L3841

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在计算机安全领域,"unlink"是一种常见的堆溢出攻击利用技术。它利用了"Use-After-Free"漏洞,该漏洞在释放内存后仍然使用该内存的指针。 在具体的实现中,"unlink"攻击需要使用全局变量,并进行多次写入。攻击者通过修改堆上的数据结构,使得被释放的堆块的前后指针指向了攻击者想要控制的地址。 下面是"unlink"攻击的主要步骤: 1. 攻击者需要一个全局指针变量p,该变量指向一个堆块。 2. 攻击者通过修改p的fd和bk指针,将p链接到自身的前后堆块上。 3. 攻击者检查p->fd->bk和p->bk->fd是否都指向p,如果不是,则意味着堆块链表被破坏,攻击失败。 4. 如果上述检查通过,攻击者将p->fd->bk指向p的前一个堆块,将p->bk->fd指向p的后一个堆块,完成"unlink"操作。 5. 攻击者可以利用这个被解链的堆块进行任意的内存写入或执行其他操作,从而实现对程序的控制。 总结起来,"unlink"攻击利用了堆溢出漏洞中的"Use-After-Free"漏洞,并通过修改堆块的前后指针来实现对程序的控制。这是一种常见的攻击技术,需要仔细的堆结构分析和理解。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [【pwn学习】堆溢出(三)- UnlinkUAF](https://blog.csdn.net/Morphy_Amo/article/details/122631424)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [pwn unlink](https://blog.csdn.net/qq_37433000/article/details/103500857)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Morphy_Amo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值