Description
If we can crack the password checker on this remote host, we will be able to infiltrate deeper into this criminal organization. The catch is it only functions properly on the host on which we found it.
Setup
Launch the challenge instance and SSH into the server.
Find the password checker binary on the remote host.
Solution
- Step 1Understand why it only works on the remote hostThe binary spawns 65 checker functions (checker_0 through checker_64) as separate processes and reads their function pointer table through /proc/<pid>/mem. Each checker encodes one character of the 33-character password. The function pointer table only contains valid addresses at a specific timing window after the process starts.ssh <user>@<HOST> -p <PORT_FROM_INSTANCE>ls /proc/$(pgrep passwordchecker)/mem
- Step 2Read the function pointer table via /proc/<pid>/mem at t = j + 0.5sEach checker process j has its function pointer table populated at time j seconds after startup. Read /proc/pid/mem at t = j + 0.5 seconds to catch the window when the table is ready. The 65 checker PIDs are predictable since they're spawned sequentially.python3 << 'EOF' import os, time, struct # Find checker PIDs (spawned sequentially after the main process) main_pid = int(open("/proc/self/stat").read().split()[3]) # adjust checker_pids = list(range(main_pid + 1, main_pid + 66)) password_chars = [] for j, pid in enumerate(checker_pids): # Wait until t = j + 0.5s time.sleep(0.5) try: mem_path = f"/proc/{pid}/mem" maps_path = f"/proc/{pid}/maps" # Read the function pointer table address from /proc/pid/maps with open(maps_path) as f: for line in f: if "r-xp" in line: addr = int(line.split("-")[0], 16) break with open(mem_path, "rb") as f: f.seek(addr) ptr = struct.unpack("<Q", f.read(8))[0] # Decode one password character from the function pointer password_chars.append(chr(ptr & 0xFF)) except: pass print("Password:", "".join(password_chars)) EOF
- Step 3Submit the recovered passwordUse the reconstructed 33-character password to authenticate to the binary. The binary verifies it and prints the flag.echo '<RECOVERED_PASSWORD>' | ./passwordchecker
Flag
picoCTF{pr0cf5_d36ugg3r_1687e00c}
JITFP exploits a timing window: the 65 checker functions populate a function pointer table in /proc/pid/mem at t = j + 0.5s after start. Reading that table at the right time for each of the 65 checker PIDs reconstructs the full 33-character password.