Description
Can you beat the timer? Exploit a time-sensitive vulnerability to get the flag.
Setup
Download the binary.
wget https://mercury.picoctf.net/static/.../horsepowerchmod +x horsepowerchecksec horsepowerSolution
Walk me through it- Step 1Identify the timing primitiveDisassemble and look for calls to alarm, setitimer, or setrlimit. strace also reveals the timer call at runtime. The exact primitive determines whether you can race it, overwrite its GOT entry, or just let it fire and reconnect.bash
./horsepowerbashobjdump -d horsepower | grep -E 'call.*(alarm|setitimer|setrlimit)@plt'bashstrace -e trace=alarm,setitimer,setrlimit ./horsepower 2>&1 | head -10Learn more
Time-limited challenges use the
alarm()system call to set a timer that sends SIGALRM after N seconds, terminating the program. This is a common CTF pattern to prevent brute-force attacks. The exploit must be fast enough to complete before the alarm fires, or must find a way to avoid the alarm.Strategies for time-limited exploits:
- Scripted exploit: Automate with pwntools - scripted exploits run in milliseconds, easily beating a 5-60 second timer.
- Disable alarm: If you can overwrite the alarm() GOT entry (via format string write) with a no-op function, the timer never fires.
- Signal handler: In some scenarios, overwriting the SIGALRM handler to jump to a useful function instead of terminating.
- Step 2Find the unsafe input read in the disassemblyHunt the disassembly for unsafe input functions and trace where the result lands on the stack. The classic shape is a call to gets or read into a fixed-size buffer with no length check.bash
# Look for the usual suspects directly:bashobjdump -d horsepower | grep -E 'call.*(gets|read|scanf|sprintf)@plt'bash# Then disassemble the calling function and see the buffer size:bashobjdump -d horsepower | sed -n '/<vuln>:/,/^$/p'Learn more
What a vulnerable read looks like in objdump. A classic gets-into-stack-buffer call site looks like this:
0000000000401196 <vuln>: 401196: f3 0f 1e fa endbr64 40119a: 55 push rbp 40119b: 48 89 e5 mov rbp,rsp 40119e: 48 83 ec 30 sub rsp,0x30 ; 48 bytes of locals 4011a2: 48 8d 45 d0 lea rax,[rbp-0x30] ; &buf 4011a6: 48 89 c7 mov rdi,rax ; arg0 = &buf 4011a9: e8 a2 fe ff ff call 401050 <gets@plt> ; UNBOUNDED READ 4011ae: 90 nop 4011af: c9 leave 4011b0: c3 retThe buffer is 48 bytes (
0x30) and gets has no length cap, so anything beyond 48 bytes overruns into the saved RBP and saved RIP - the standard ret2win shape.Race conditions are another form of time-sensitive vulnerability. If the program has a TOCTOU (time-of-check to time-of-use) race, checking a condition then acting on it non-atomically, another thread can change the state between the check and the use. For single-threaded programs, this requires signal handler races or filesystem-level races (e.g., symlink attacks on files checked with access() then opened with open()).
- Step 3Build and automate the exploitWrite a pwntools script that fully automates the exploit within the time limit. Test locally to confirm it completes before the alarm fires.python
python3 - <<'EOF' from pwn import * import time start = time.time() e = ELF('./horsepower') p = remote('mercury.picoctf.net', <PORT_FROM_INSTANCE>) # Move quickly - don't add unnecessary sleeps # ... log.info(f"Exploit ran in {time.time()-start:.2f}s") p.interactive() EOFLearn more
pwntools'
remote()connection and subsequent I/O operations are fast enough to exploit services with 30-60 second timers. For very tight timers (1-5 seconds), precompute all payloads before connecting so no computation happens after the connection is established, only the send and receive operations. For more on pwntools timing tricks and connect-and-go benchmarks, see the pwntools guide.
Flag
picoCTF{...}
Time-limited exploits are defeated by scripting with pwntools - automated exploits complete in milliseconds, easily within any reasonable timer window.