Plaid CTF 2012 – Override

One look in this program and our whole understanding of how robots run has been shaken. Maybe you will have better luck than us.
Title: Override (300)
Category: Pirating

For this challenge we had to find a passphrase which was accepted by a 64-bit binary.

The challenge consists of a broken self-extracting bash script, if we look in the input we see that it contains some gzipped data which starts at file offset 803.

We extract as follows:

$ dd bs=1 skip=803 if=override | zcat > scrambled
$ file scrambled
scrambled: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked 
(uses shared libs), for GNU/Linux 2.6.18, stripped

Next step is to see what we can do with this binary, looking at the code in a disassembler shows it is heavily obfuscated, this will take some time to figure out. Let’s try a different approach.

Let’s try to solve this one old skool keygen style, like +orc and +fravia told is in the good old days:
– Break on getting the input. In the good old days on Windows this was some GetWindowText function, but here in Linux that’s just read/gets/fgets
– Step through to see where the input is used

We run through objdump and see what library functions are imported:

objdump -d scrambled | grep "plt.*>:"
0000000000400558 <printf@plt-0x10>:
0000000000400568 <printf@plt>:
0000000000400578 <puts@plt>:
0000000000400588 <__libc_start_main@plt>:
0000000000400598 <fgets@plt>:
00000000004005a8 <strlen@plt>:
00000000004005b8 <srand@plt>:
00000000004005c8 <rand@plt>:

So the input is retrieved using fgets, let’s try to attack from there..

gdb$ b 'fgets@plt'
Breakpoint 1 at 0x400598
gdb$ r
Please enter your password: ABCDEFG
Breakpoint 1, 0x0000000000400598 in fgets@plt ()
Buffer address for fgets is $rdi
gdb$ p/x $rdi
$1 = 0x600dd8
gdb$ rwatch *0x600dd8
Hardware read watchpoint 2: *0x600dd8

The breakpoint triggers in strlen a few times, but then this is triggered:

=> 0x400805:    mov    eax,DWORD PTR [rbp-0x4]
   0x400808:    cdqe   
   0x40080a:    add    rax,QWORD PTR [rbp-0x20]
   0x40080e:    movzx  eax,BYTE PTR [rax]
   0x400811:    push   0x40081b
   0x400816:    jmp    0x4006f0
   0x40081b:    je     0x40082a

Tracing through that for a few instructions, lands us at:

  RAX: 0x0000000000000042  RBX: 0x0000000000000000  RCX: 0x0000003FA5F9B064  RDX: 0x0000000000000041  o d I t s z A P c 
  RSI: 0x00007FFFFFFFE494  RDI: 0x0000000000600DD8  RBP: 0x00007FFFFFFFE4E0  RSP: 0x00007FFFFFFFE4C0  RIP: 0x0000000000400811
  R8 : 0x0000003FA5F9B064  R9 : 0x0000003FA5F9B0E0  R10: 0x00007FFFFFFFE238  R11: 0x0000003FA5C36C40  R12: 0x0000003FA5F9B6A0
  R13: 0x00007FFFFFFFE5D0  R14: 0x0000000000000000  R15: 0x0000000000000000
  CS: 0033  DS: 0000  ES: 0000  FS: 0000  GS: 0000  SS: 002B				
=> 0x400811:    push   0x40081b
   0x400816:    jmp    0x4006f0
   0x40081b:    je     0x40082a

Hmmm $rdx 0x41 is the first letter of our input (A) and it is compared against $rax 0x42.

Let's hack together a quick gdb script which does the following:
- Log the value of $rax since that is the expected input
- Patch $rdx so it is equal and the comparison continues

b *0x400811
commands 1
printf "%c", $eax
set $rdx=$rax

Running this script in gdb:

Starting program: /root/scrambled 

Hmm so that looks like a nice password. Let's try inputting it:

Please enter your password: Bg8Ph#xnr||l*YjV|9K#RRfh6XhnhK8*%f:h5AAUgg%t5K3%xRnR%Xh|iU#W6h3kU8A*KU|YR:l83g

Hmmm, this looked to good to be wrong... What could be the problem? Maybe our tricks fooled the check a little bit too much and made the program accept a longer password than expected, let's try some substrings.

import os
s = 'Bg8Ph#xnr||l*YjV|9K#RRfh6XhnhK8*%f:h5AAUgg%t5K3%xRnR%Xh|iU#W6h3kU8A*KU|YR:l83g'
for x in range(len(s)):
	sub = s[:-x]
	os.system('echo "%s"; (echo "%s" | ./scrambled)' % (sub,sub))
$ python sub.py | grep -B1 Success
Please enter your password: Success! You are now logged into the system.

Success! Again proof the gdbscripts are more useful than actual reverse engineering! 😀

{2 Responses to “Plaid CTF 2012 – Override”}

  1. I solved it exactly the same way, however, the second strlen-call checks whether the input is > 64 characters long, just fyi 🙂


Trackbacks & Pings