NYU Poly 2010 CTF Finals - Reversing 2 Writeup

A big thanks to NYU Poly for hosting a great CSAW CTF. :) Here's a writeup for Erik C's reversing 2 challenge. Our team did not complete the challenge in time, but instead took it home and continued to work. If you'd like to play along download the challenge here: reversing2.tgz

As with everything new, start with file (but don't always believe it 100%).

$ file reversing2.tgz
reversing2.tgz: gzip compressed data, from Unix, last modified: Wed Oct 27 23:54:42 2010

$ tar -xvzf reversing2.tgz
release/and_his_buddy
release/some_dude

$ file *
and_his_buddy: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV),
  dynamically linked (uses shared libs), for GNU/Linux 2.6.15, stripped
some_dude: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux),
  statically linked, stripped

Look like they are Linux executables (ELF 32-bit LSB Executable). Now let's open them in IDA Pro and have a peek? Well feel free but they a both packed with some unknown packing signature. I wonder if it's a modified version of UPX? :P During the competition our team spent a considerable amount of time trying to unpack the executables when we should had been running them.

$ strace ./some_dude
...
bind(224, {sa_family=AF_INET, sin_port=htons(64009), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 225
bind(225, {sa_family=AF_INET, sin_port=htons(64516), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 226
bind(226, {sa_family=AF_INET, sin_port=htons(65025), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xdb7000
write(1, "somedude: y0z, help me talk to m"..., 42
  somedude: y0z, help me talk to my friend!) = 42
write(1, "somedude: What is HTTP/1.1 minus"..., 64
  somedude: What is HTTP/1.1 minus MIME Object Security Services?) = 64
write(1, "somedude: ....if you don't know "..., 83
  somedude: ....if you don't know where to go by now then you should figure that out) = 83
recvfrom(3,
$ strace ./and_his_buddy
...
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7827000
fstat64(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7826000
write(1, "Please describe my friend's part"..., 87
  Please describe my friend's particularly interesting use of communications in one word:)
  = 87
read(0,

Now there are a few routes one can take from here. There is a clue written to standard out of 'some_dude' (think RFC numbers) and the process is waiting for some input. Though, if you pay attention to the UDP ports opened by 'some_dude' you'll notice a pattern. These numbers are coded in the program, they don't change; try a quick Google search of some of the numbers. One of the clues released later in the competition was "Once you figure out communication, think of math properties to answer the question." Well, as it turns out all the port numbers are perfect squares. Funny: I wrote a small script to parse the output of netstat (I didn't think to run strace at the time) and order the UDP ports. I then saw that the increments of the first few port numbers were 65, 67, 69, 71, ... I saw the input needed to be in 'one word' so I mused that 'one word' meant two bytes and that I should try 65 and 2 in binary representation: 01000001 00000010, that didn't work.

Let's feed the word perfect to 'and_his_buddy' to move on to the next part of the challenge.

"perfect\n", 1024)                       = 8
socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP) = 3
sendto(3, "...."..., 1024, 0, {sa_family=AF_INET, sin_port=htons(1024),
  sin_addr=inet_addr("0.0.0.0")}, 16) = 1024
close(3)                                = 0
socket(PF_FILE, SOCK_STREAM, 0)         = 3
bind(3, {sa_family=AF_FILE, path="/tmp/.LUKE_AND_LEIA_WERE_MORE_THAN_FRIENDS"}, 44) = 0
listen(3, 5)                            = 0
accept(3,

And then 'some_dude' replies with:

write(1, "somedude: w00t, thanks for that!"..., 89
  somedude: w00t, thanks for that! Now there is some data that you should review carefully
) = 89
open("/tmp/.HAN_ONLY_SHOOTS_HIS_FRIENDS_FIRST.11243", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 227
fstat64(227, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x538000
write(227, "..."..., 1024) = 1024
close(227)                              = 0
munmap(0x538000, 4096)                  = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8 ) = 0
rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0 }, 8 ) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8 ) = 0
nanosleep({1, 0 }, 0xbf8c97d4)          = 0
write(1, "somedude: BTW what was the origi"..., 77
  somedude: BTW what was the original name of my friend's favorite 1970s band?
) = 77
fstat64(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xce2000
read(0,

Now above we see that 'and_his_buddy' is bound to /tmp/.LUKE_AND_LEIA_WERE_MORE_THAN_FRIENDS. Funny: You can actually see this string by running strings on 'and_his_buddy'. This caused our team to waste a few minutes trying related relationships for the original input to 'and_his_buddy', oh well our loss. So what do we do here? Did you know netcat can bind to UNIX sockets, well it can!

$ nc -U /tmp/.LUKE_AND_LEIA_WERE_MORE_THAN_FRIENDS
aGludDogV2h5IENhbid0IFdlIEJlIEZyaWVuZHM/

Awesome, that revealed some data, let's review it carefully. ;) Whenever you find a nice 'ascii-formatted' string that seems to mean nothing, suspect some sort of encoding. In this case it's everyone's favorite, Base64.

$ echo -n "aGludDogV2h5IENhbid0IFdlIEJlIEZyaWVuZHM/" | base64 -d -
hint: Why Can't We Be Friends?

That works nicely with expected input for 'some_dude', which was: "BTW what was the original name of my friend's favorite 1970s band?" The decoded Base64 string is a name of a song, by War, formed in 1969. A quick look at the 'War' Wikipedia page says they were also known as 'Eric Burdon and War'.  The band switched to the name 'War' in 1971 after Eric left the band during their European tour. If you try entering different forms of War, Eric Burdon, Eric Burdon and War, Eric Burdon & War, and other names found on the Wikipedia article you'll become a bit frustrated. There's actually a small glitch which requires to user to remove all the spaces, so try: "ericburdonandwar".

"ericburdonandwar\n", 1024)             = 17
open("./enc", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 227
fstat64(227, {st_mode=S_IFREG|0777, st_size=0, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x662000
write(227, "U2FsdGVkX18qt9C5Zz+4RwUBLQ27e7xa"..., 110) = 110
_llseek(227, 0, [0], SEEK_SET)          = 0
close(227)                              = 0
munmap(0x662000, 4096)                  = 0
sync()                                  = 0
rt_sigaction(SIGINT, {SIG_IGN, [], 0}, {SIG_DFL, [], 0}, 8 ) = 0
rt_sigaction(SIGQUIT, {SIG_IGN, [], 0}, {SIG_DFL, [], 0}, 8 ) = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8 ) = 0
clone(child_stack=0, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0xbf8c97d4) = 5892
waitpid(5892, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0) = 5892
rt_sigaction(SIGINT, {SIG_DFL, [], 0}, NULL, 8 ) = 0
rt_sigaction(SIGQUIT, {SIG_DFL, [], 0}, NULL, 8 ) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8 ) = 0
--- SIGCHLD (Child exited) @ 0 (0) ---
open("./decrypted", O_RDONLY)           = 227
fstat64(227, {st_mode=S_IFREG|0777, st_size=55, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xae6000
read(227, "key: Great, now go get bow hunti"..., 4096) = 55
read(227, "", 4096)                      = 0
write(1, "key: Great, now go get bow hunti"..., 55
  key: Great, now go get bow hunting and nunchuku skills
) = 55
write(1, "\n", 1) = 1

You win! Overall, that was a fun challenge, thanks Erik, and NYU CSAW.