Rating:


<span>
This last weekend, a couple of us participated in the Google Capture the Flag 2016 competition. I thought I'd share a little write-up of an interesting reverse engineering problem from the comp!

This problem was (unbeknownst to me) a teaser released prior to the competition—it wasn't really intended to be solved in its entirety during the 48 hours we had to work. While we didn't solve it during the comp, we nailed down a solution shortly after; as you'll see, the problem was significantly more complex and time-intensive than the 15 points it was worth.

That said, it's quite entertaining!



If it is taken down, it should still be available from other CTF archives.

<span>The Problem:
</span></span>----------------------------------------<span>
<span>Jump Outdated Elephants
</span><span>15 points / Solved 70 times
</span><span>Subcategory: Reversing
</span><span>https://storage.googleapis.com/ctf-teasers/154817c0b652025ff4590759439810ebd4a658bd190508ac22aaf8f414766468
</span>
This task provided you a binary (auspiciously named 154817c0b652025ff4590759439810ebd4a658bd190508ac22aaf8f414766468 ). Running <i>file</i> on it informed us that it was a linux executable:

<span>
$ file 154817c0b652025ff4590759439810ebd4a658bd190508ac22aaf8f414766468</span>
<span>154817c0b652025ff4590759439810ebd4a658bd190508ac22aaf8f414766468: ELF 32-bit LSB &amp;nbsp;executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=c448b30706799fa1c26e6badce98f5504f9b654a, stripped
</span> 32-bit, and stripped—this application won't have any convenient debug symbols.


Running strings produces a number of indicators that it's a linux app, a few basic messages about being on the wrong track, and a sentence that's clearly the flag delivery printf. It also has a few distinctive strings:

...
wub4uiaoiq9uhywibWb9y
Tei5kechei4ahVoy9thie
Boemeit0ohCh9opohriet
li0Li6chahPha0sagohto
aethee1thaehaiCheifeH
zeechair8Eigh0iegughi
<span>...
</span>Interesting, but not super helpful. Running it produces no output. Sooooooo....in we go.

<span>
Debugging in Release
</span>----------------------------------------
Launch the application in GDB; our first goal is to get main() for the application--because this is a release executable, we have to find that the hard way. We can get the entry point by running
<span>(gdb) info files 
</span>Grab the address marked as the Entry point, and set a break point:

<span>(gdb) break *0x080485f6
</span></span><span>
Now, we need to find main—the address of main should be the last argument passed into libc. You can print the next 13 instructions (for example) from the program counter with

(gdb) x/13i $pc
<span>
Breakpoint 1, 0x080485f6 in ?? ()</span>
(gdb) x/13i $pc
=&gt; 0x80485f6: xor %ebp,%ebp
0x80485f8: pop %esi
0x80485f9: mov %esp,%ecx
0x80485fb: and $0xfffffff0,%esp
0x80485fe: push %eax
0x80485ff: push %esp
0x8048600: push %edx
0x8048601: push $0x8048930
0x8048606: push $0x80488c0
0x804860b: push %ecx
0x804860c: push %esi
0x804860d: push $0x80484e0
0x8048612: call 0x8048470 <__libc_start_main@plt> 
<span>
Set your breakpoint at main() ((gdb) break *$0x80484e0), and hit '(gdb) c' to continue. Use your nifty examine command to take a look at main:</span>

<span>
Breakpoint 2, 0x080484e0 in ?? ()</span>
(gdb) x/23i $pc
=&gt; 0x80484e0: push %ebp
0x80484e1: mov %esp,%ebp
0x80484e3: push %edi
0x80484e4: push %esi
0x80484e5: push %ebx
0x80484e6: and $0xfffffff0,%esp
0x80484e9: sub $0x70,%esp
0x80484ec: mov 0xc(%ebp),%ebx
0x80484ef: mov %gs:0x14,%eax
0x80484f5: mov %eax,0x6c(%esp)
0x80484f9: xor %eax,%eax
0x80484fb: cmpl $0x2,0x8(%ebp)
0x80484ff: je 0x804851c
0x8048501: xor %eax,%eax
0x8048503: mov 0x6c(%esp),%edx
0x8048507: xor %gs:0x14,%edx
0x804850e: jne 0x80485f1
0x8048514: lea -0xc(%ebp),%esp
0x8048517: pop %ebx
0x8048518: pop %esi
0x8048519: pop %edi
0x804851a: pop %ebp
<span> 0x804851b: ret
</span><span>
As you can see, main isn't very large. The %gs:0x14,%eax instructions are stack protectors; the second JNE bails out of the application in the event of a smashed stack. All of our real work comes from the first JE. Shortly after using stepi to step through main and into JE 0x804851c, we hit our first wall:

</span>0x8048528: cmp $0xffffffe9,%ecx
<span>0x804852b: jne 0x8048501

</span>This is a string length check; forcing your way past it by setting $eip crashes shortly thereafter. So....it wants a string, but the string it's checking is currently null. What gives?

The easiest way to pass in a string would be a command line argument--let's try it. Rerun the application with an argument:

<span>(gdb) run AAAA

</span> Work your way back to the string comparison (a breakpoint helps) and examine the registers with info registers. Looking at %ecx, which is what the cmp call is checking, you'll notice that running with AAAA will give you something like '-6'. If you continue, the check will fail but not crash. Play with the input string, and you'll see that %ecx is always -(arg_len+2). If so, $0xffffffe9 (-23) would compare properly to a 21 character string. Try AAAAAAAAAAAAAAAAAAAAA, come back, and check your register:

(gdb) info registers
...
<span>ecx 0xffffffe9 -23
</span>
 <span>Bingo. The check passes. We need a 21 character argument.
</span><span>
Continuing
</span></span>----------------------------------------<span>
Immediately after, we have a ptrace to avoid:

0x8048534: call 0x80484b0 <ptrace@plt> 0x8048539: test %eax,%eax
<span> 0x804853b: js 0x8048501
</span>When running in gdb, this fails promptly (no debugger for you!); we can skip it by setting our EIP (instruction pointer) past the jump:
set $eip = 0x804853d

 Next comes a sequences of calls, most of which appear to be red herrings:

<span>0x8048560: call 0x80487d0
</span>This gem prints "Hehe, you're on the wrong track..."

<span>
0x8048584: call 0x8048710</span>
This one mocks you with “I've tricked you once again!”


 Stepping through these, each has a number of calls that can be navigated, and they allocate memory a couple of times, but they don't seem to affect the success of subsequent checks; after a little prodding, I decided to ignore them for the moment.

However, what follows is a string comparison. On the first pass, I simply assumed this was a "password" check and bypassed it by setting the EIP since I knew I didn't have the password. <b>NOTE: red herring/rabbit hole to follow.</b>


<span>
set $eip = 0x80485ad</span>

 I then step into a call:

<span>0x80485cf: call 0x8048860
</span> 
At this point, the application proceeds to set up a processing loop on our string:

0x8048888: movzbl 0x0(%ebp,%edx,1),%ecx
0x804888d: add $0x1,%eax
0x8048890: xor (%edi,%edx,1),%cl
0x8048893: cmp %esi,%eax
0x8048895: mov %cl,(%ebx,%edx,1)
0x8048898: mov %eax,%edx
0x804889a: jne 0x8048888</pre> 

This loop iterates over each character and xors it with another value. Ah, so this is how they hid the flag! It then prints the exciting 'Gratz, the flag is' message...followed by unprintable garbage. I get excited, and decide that all I need is to discover the secret value it's using to XOR the input. After a short bout of mathiness, I XOR out a secret key used by the application. I can predict the garbage that will be generated for any given input!

Unfortunately, this doesn't help me, since it's not decrypting a stored key—it's waiting for an input to decrypt with it. So NOW I remember that strncmp from before. Are they just checking for the correct input?


 (Spoiler alert: it's not so easy).

<span>
It Just Didn't Register
</span></span>----------------------------------------
<span>
 Let's take another look at that strncmp:

0x8048589: movl $0x15,0x8(%esp)
0x8048591: movl $0x80489f4,0x4(%esp)
0x8048599: mov %esi,(%esp)
0x804859c: call 0x80484a0 &lt;strncmp@plt&gt;
0x80485a1: mov 0x1c(%esp),%edx
0x80485a5: test %eax,%eax
0x80485a7: jne 0x8048501

Breaking on the CALL, we can expect two strings to be passed in and compared. One is clearly the constant. The other, %esi.


(gdb) x/s 0x80489f4
<span>0x80489f4: "wub4uiaoiq9uhywibWb9y"
</span>(gdb) x/s $esi
<span>0xffffd176: "UUUUUUUUUUUUUUUUUUUUU"

</span>Welp, that's weird. Let's try changing my input to B*21 and rerunning:

<span>
(gdb) x/s 0x80489f4</span>
<span>0x80489f4: "wub4uiaoiq9uhywibWb9y"
</span>(gdb) x/s $esi
<span>0xffffd176: "VVVVVVVVVVVVVVVVVVVVV"
</span> Delightful. The application is processing my input with one of those earlier calls, and comparing it to one of those mystery strings we saw at the beginning. I grab the string, and adjust each by 20 ('U'-'A'=20) then check my result (caN aUM[U]%aTecUNCN%e ). After processing (remember that XOR loop from before?), the first two letters are decrypted to CT, and the last to '}' – but the rest isn't right. I check my registers to find I have a discrepancy:

(gdb) x/s 0x80489f4
<span>0x80489f4: "wub4uiaoiq9uhywibWb9y"
</span>(gdb) x/s $esi
<span>0xffffd176: "wuT&amp;uOG[O]%uNywOHWH%y"
</span>
 A little parsing in excel gets the offsets needed between the result and the desired result and adds it to the base character (=CHAR(CODE(A2)-CODE(B2)+CODE(C2)  for target_letter, current_letter, and base_letter respectively), and prints me the resulting character. Turns out that after parsing, each character of the argument is adjusted by 20, -6, or 0. After fiddling to get the right adjustments for each letter, the final argument string was:

<span>
cah4aoguow9anecohCh9e
</span><span>
¡pǝɹǝǝuᴉƃuƎ
</span></span>----------------------------------------
<span>
 You can run this outside of the debugger, or skip the ptrace; all other checks will pass (though it will still enter the various functions that mock you before finally entering the proper processing loop):

$ ./154817c0b652025ff4590759439810ebd4a658bd190508ac22aaf8f414766468 cah4aoguow9anecohCh9e
Hehe, you're on the wrong track...
I've tricked you once again!
Hmmm, I don't think that's correct...
Gratz, the flag is 'CTF{hah7PohNooMe6ol4}'
$
</span>