DerbyCon 4.0 CTF - TRNDOCS ELF Binary Reverse Engineering and Debugging
So this past weekend I attended DerbyCon 4.0 in Louisville, Kentucky, and was lucky enough to play the CTF along side the @bsjtf team. We were able to place 16th out of the 77 point scoring teams/individuals, which is pretty damn good I’d say. This write-up will be for a reversing challenge I solved, adding 450 points to the teams total.
One of the first things done was a scan for various services on the network. Since FTP is a common place for CTF flags to hide, went searching for any FTP servers in the environment.
1
| |
One of the FTP servers that stood out was 10.10.146.74 which has anonymous read access and quite an interesting MOTD when logging in.
230-This is a temporary ftp server, while we finish our migration off DOS
230-platforms. For now transaction documents are still availible at
230-/DRIVE_C/TRNDOCS but these are already being generated by the LINUX
230-backend.
230-
230-DOCS for DCs that have already been upgraded are ciphered with
230-OpenSSL, the utility to obtain the shared password from your
230-credentials is TRNDOCS directory.
230-Use the serer xxx.xxx.xxx.xxx to authenticate, you can manually
230-inspect a TRN document with OpenSSL once you obtain the key.
230-
230-openssl des3 -d -salt -in-k
230-
230 User logged in
Interesting, /DRIVE_C/TRNDOCS/ seems to have a lot of interesting information in it. So I pull everything down and start analyzing the file in there.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | |
So it looks like we have a combination of plaintext data, encrypted data, and an ELF binary, which is very interesting. Running strings on the binary doesn’t show anything of super interest. Lets just run it and see what happens.
1 2 3 | |
The program accepts a server (which I assume it wants to talk to), and a username and password to validate against. Running it with my localhost set as the server just lets it hang, so I’m assuming a socket is being created somewhere. Using strace we’ll be able to see more about this.
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
I was right, a socket is trying to make a connection and read back some data. If you notice in sendto, the socket is trying to send data “user password” to 127.0.0.1 on port 3000. I figured that this program was meant to talk to a server on the network, but scanning for an open port 3000 turned out no results. Figured to just keep moving on with this binary and see what else it does.
So I decided to set up a natcat listener on port 3000 and try to receive the data and send data back. One thing that tripped me up with this is that I didn’t realize you needed to tell netcat to listen as a UDP port. Before knowing that, whenever I had my netcat listener running on TCP 3000, the program would not connect to it. After finding out how to specify UDP, I was finally able to send data back and forth.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | |
So there’s a bit going on here. The netcat listener gets set up to listen on port 3000 with the -u specifing UDP. Then when the program runs, it sends along the specified arguments “user password”. I send back “JUNKDATA” and the program fails saying bad username and password. I also included the strace output, and you’ll notice that the program only reads 6 bytes, “JUNKDA”.
Here is where we start to do some debugging and reversing. I load up the program in gdb, set the required arguments, and take a look at the main method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | |
The disassembly for main is pretty crazy, but if you just walk through it, it’s not too bad. It does some argument checking, creates a socket (0x08048df5 <+368>), sends the authentication data (0x08048e53 <+462>), receives the authentication response (0x08048e63 <+478>), and some more nonsense towards the end.
Something interesting that I noticed is that right after we receive the authentication data, a test is done on eax against eax (0x08048e68 <+483>). In assembly, the return value of a function is stored in eax, so this test is looking to verify the return of the recvAuth method. I think it is safe to assume that here is where the program will decide to give us either the “bad username or password” text, or continue on to do something more.
Before I continued, I had to look up what test actually did. From this question on StackOverflow, “TEST sets the Zero Flag if the the result of the AND operation is zero. If two operands are equal, their bitwise AND is zero iff both are zero. It also sets the Sign Flag if the top bit is set in the result, and the Parity Flag if the number of set bits is even.” What this means is that in our program, if eax is set to 0x0, then the program will jump to 0x8048eb5 <main+560>, which is towards the end of the program. If we put a breakpoint at this location and look at the registers and disassembly, we can see this happen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | |
So after recvAuth eax was set to 0x0 which caused the result from test to jump to the end of the program and show the bad password message. What happens if we set eax to 0x1, maybe it will bypass the jump and continue onto something more interesting.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | |
Woah! Would you look at that, got something juicy. I tried to submit “EverybodyDoTheLimit” as a flag, but it ended up not being valid. But if you recall back to the FTP MOTD at the beginning of the post, we can now expect the TRN documents via openssl with the newly acquired secret key. After running through the various encrypted files, I finally ran into one with a flag in it.
1 2 3 4 5 6 7 8 9 | |
Submitted “CountingSheep75” to the scoring engine, and received 450 points. Woo!
Since I’ve really been getting into reverse engineering and debugging lately, this was a really fun challenge for me. Thanks to the DerbyCon CTF crew for creating this one!