lundi 3 novembre 2014

NoSuchCon cracking crackmips

Yesterday was an hard day: big hangover after saturday night beer session, big headache and no internet.
So I choose to drink a lot of water and play with NoSuchCon crackmips to change my mind.

There is two major parts in this blogpost. After the initial recognition, I began to follow a lead, really instructive, but destined to fail. Then, I restate my assumption, and with the experience of the first try, I manage to crack the binary.

1) Recon target

  • You won't make it through this fight!

1-1) Installation

the binary is a MIPS binary. I used qemu-system-mipsel to work, here is a little shell script:

$ cat startme.sh
#! /bin/bash
qemu-system-mipsel -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mipsel_standard.qcow2 -append "root=/dev/sda1 console=tty0" -m 512 -net nic -net user,hostfwd=tcp::22222-:22

$

With it, I can launch qemu, and then ssh into it on localhost port 22222.

1-2) First glance

nm crackmips reveals some intersting information:
  • use of SHA256 and AES
  • presence of ptrace
  • forking
  • a function called decrypt_url
Time to try with a :
user@debian-mipsel:~$ ./crackmips
usage: ./crackmips password

user@debian-mipsel:~$ ./crackmips toto
WRONG PASSWORD
user@debian-mipsel:~$


I don't know if reversing is a kind of science or an art. How much intuition can help when you reverse something against a scientific analysisis af full code. Let's begin with intuition.

2) First try:

  • I'll show you what fighting with swords is all about.

 2-1)  Intuition

My first intuition was to think crackmips as that:
  • a (somehow useless) antidebug ptrace call: In a lot of challenge, there is an antidebug ptrace call.
  • forking of a process
  • a validation of password with an algorithm in this child
  • a SHA-256 of password which will be used as a key to decrypt a specific url

2-2) Go, go, go.

Let's get rid of ptrace with LD_PRELOAD trick, then play with binary. We quickly find that a wrong password will print "WRONG PASSWORD" (srsly?), but some passwords print "ERR". That's a good lead. If we follow crackmips with strace, we see that 'clone'  is called when password is 48 char long. to sum up:
  • The password must be 48 char long
  • The password must be in range [0-9A-F]
  • The program forks if both previous rules are right

2-3) Time to objdump

objdump -d crackmips reveals something quite interesting: A lot of break (101!) as you can see:
(snip...)
  402300:      00821021        addu    v0,a0,v0
  402304:      ac430008        sw      v1,8(v0)
  402308:      0000000d        break
  40230c:      8fc30038        lw      v1,56(s8)
  402310:      00031080        sll     v0,v1,0x2
  402314:      27c40018        addiu   a0,s8,24
  402318:      00821021        addu    v0,a0,v0
  40231c:      8c440008        lw      a0,8(v0)
  402320:      8fc20038        lw      v0,56(s8)
  402324:      00822023        subu    a0,a0,v0
  402328:      00031080        sll     v0,v1,0x2
  40232c:      27c30018        addiu   v1,s8,24
  402330:      00621021        addu    v0,v1,v0
  402334:      ac440008        sw      a0,8(v0)
  402338:      0000000d        break
  40233c:      8fc30038        lw      v1,56(s8)
  402340:      00031080        sll     v0,v1,0x2
  402344:      27c40018        addiu   a0,s8,24
  402348:      00821021        addu    v0,a0,v0
(snip...)


2-3) Let's gdb

gdb is launched with LD_PRELOAD in order to get rid of ptrace and follow-fork-mode child.If launched with a 48-char pass, it stops as expected on the first break instruction (0x40228c).

2-4) Digging into the break

The break are all based on the same scheme:
  • initialization of values
  • loading of 8 chars of password in $a0 (we notice that char are inversed, 12345678 becomes 87654321 in register)
  • simple operation (plus, xor, add, minus, leftshift of bit, rightshift, and so on)
  • saving the new password
 At the end of the 100 breaks, we have:
   0x4039e0 <main+6588>:    addiu   v0,v0,1
   0x4039e4 <main+6592>:    sw  v0,56(s8)
   0x4039e8 <main+6596>:    lw  v0,56(s8)
   0x4039ec <main+6600>:    sltiu   v0,v0,6
   0x4039f0 <main+6604>:    bnez    v0,0x40228c <main+616>

so we understand that its counter get incremented from 0 to 5 (6x8=48).

then a memcmp follow:

   0x4039f8 <main+6612>:    addiu   v0,s8,32
   0x4039fc <main+6616>:    move    a0,v0
   0x403a00 <main+6620>:    lui v0,0x40
   0x403a04 <main+6624>:    addiu   a1,v0,15524
   0x403a08 <main+6628>:    jal 0x400840 <memcmp@plt>


we find an intersting string at that point:
a0 : scrambled password string
a1 : "[ Synacktiv + NSC = <3 ]"

a2 : 24 char to analyser
Ok, so the password, once scrambled, should equals to "[ Synacktiv + NSC = <3 ]" (yeah, guys, I love you too, especially on bad afternoon like this one, with Evian and headache)

If we patch the result of the memcmp function to be true, we see some junk printed to screen. 

So some of my guesses must be right: password is scrambled with those 100 functions, then compared to a fixed string, then decrypt_url is called. 

2-5) Boring time.

The work can begin. In a window, objdump. In another, vi. 
I begin to write a veeeery boring python script. I took all parts between breaks, revert them and create a function for each of them (100 times. Boring. As. Hell. Yuck!).

I found that there is 5 different function (plus, minus, xor, leftslide, rightslide) called with different args. Some functions depends of the position of the part of the password (0->5).

So, the progs looks like a long list of:
 (...)
def func_4034f0(v0):
    moins(v0)

def func_4034a4(v0):
    rightslide(0xf)

def func_403444(v0):
    rightslide(v0+1)

 (...)
and at the end
( ... )
func_4034a4(pos)
func_403444(pos)
(... etc..)

2-6) Crack it (and fail like a boss!)

I  launch my python prog 6 times:
$ ./rev.py 7953205b 0
$ ./rev.py 6b63616e 1
etc:
so the password should be: D3CC52F8EC139ED6BA23B382F2B63ADFA9545C5C8B8BF5C2

I patched the binary and replace all breaks with nops, and within a single heartbeat (my sensei says: always give the final blow quick, strong and clean), I typed:

user@debian-mipsel:~$ ./patched_crackmips_nobreaks D3CC52F8EC139ED6BA23B382F2B63ADFA9545C5C8B8BF5C2
MP!����jU� ����� |,vB���f�� ���
                                 �؀��� / ��Q'��)g'��˞qC ��| n����/    �Lu�]��]
user@debian-mipsel:~$


Obviously, there is something wrong! That's confirmed with the unpatched binary:
user@debian-mipsel:~$ ./crackmips D3CC52F8EC139ED6BA23B382F2B63ADFA9545C5C8B8BF5C2
WRONG PASSWORD
user@debian-mipsel:~$

3) Second try:

  • I don't matter how many times you try! I'll win!
After an aspirine, I went back to crackmips. I knew that something was obviously wrong because of the breaks. How in the world can the binary continue after a break without help? And why the linear following of all those breaks doesn't points to victory?

3-1) Solving the break mystery

By digging in the code, I noticed that ptrace was called several times. I decided to launch the binary without my anti_ptrace hooking lib.
 user@debian-mipsel:~$ strace -i ./crackmips
D3CC52F8EC139ED6BA23B382F2B63ADFA9545C5C8B8BF5C2
 (... snip...)
[77e92e70] clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x77fee068) = 2111
[77e93000] --- SIGCHLD (Child exited) @ 0 (0) ---
[77e9257c] waitpid(2111, [{WIFSTOPPED(s) && WSTOPSIG(s) == SIGTRAP}], __WALL) = 2111
[77ecd52c] ptrace(PTRACE_GETREGS, 2111, 0, 0x7fff6484) = 0
[77ecd52c] ptrace(PTRACE_SETREGS, 2111, 0, 0x7fff6484) = 0
[77ecd52c] ptrace(PTRACE_CONT, 2111, 0, SIG_0) = 0
[77ecd52c] --- SIGCHLD (Child exited) @ 0 (0) ---
[77e9257c] waitpid(2111, [{WIFSTOPPED(s) && WSTOPSIG(s) == SIGTRAP}], __WALL) = 2111
[77ecd52c] ptrace(PTRACE_GETREGS, 2111, 0, 0x7fff6484) = 0
[77ecd52c] ptrace(PTRACE_SETREGS, 2111, 0, 0x7fff6484) = 0
[77ecd52c] ptrace(PTRACE_CONT, 2111, 0, SIG_0) = 0
[77ecd52c] --- SIGCHLD (Child exited) @ 0 (0) ---
[77e9257c] waitpid(2111, [{WIFSTOPPED(s) && WSTOPSIG(s) == SIGTRAP}], __WALL) = 2111
[77ecd52c] ptrace(PTRACE_GETREGS, 2111, 0, 0x7fff6484) = 0
[77ecd52c] ptrace(PTRACE_SETREGS, 2111, 0, 0x7fff6484) = 0
[77ecd52c] ptrace(PTRACE_CONT, 2111, 0, SIG_0) = 0
[77ecd52c] --- SIGCHLD (Child exited) @ 0 (0) ---
 (... snip...)


We can see a pattern here, GETREGS, SETREGS, CONT called multiples times.

So, my assumption about ptrace used as antidebug is false! ptrace is used in order to drive the child process. The father waitpid for the child, then get its regs, modify them and send a PTRACE_CONT.

3-2) Let's debug father

With gdb, I choose to break at debug function (once again, driven by intuition).

After some sanity checks (did ptrace success, did the child is in waitpid etc..) we see (again) a looooong list af scrambling data beginning at:
  400b8c:       8fc2014c        lw      v0,332(s8)
  400b90:       afc2001c        sw      v0,28(s8)
  400b94:       3c020040        lui     v0,0x40
  400b98:       24420a78        addiu   v0,v0,2680
  400b9c:       8fc3001c        lw      v1,28(s8)
  400ba0:       00621023        subu    v0,v1,v0
  400ba4:       afc2001c        sw      v0,28(s8)
  400ba8:       8fc20018        lw      v0,24(s8)
  400bac:       3c03446f        lui     v1,0x446f
  400bb0:       34638657        ori     v1,v1,0x8657

 (...snip)



But in the middle of this long scrambling, there is a check:
  401440:       08100529        j       4014a4 <debug+0xa44>
  401444:       afc00020        sw      zero,32(s8)
  401448:       8fc20020        lw      v0,32(s8)
  40144c:       00021840        sll     v1,v0,0x1
  401450:       3c020041        lui     v0,0x41
  401454:       00031880        sll     v1,v1,0x2
  401458:       24424130        addiu   v0,v0,16688
  40145c:       00621021        addu    v0,v1,v0
  401460:       8c430000        lw      v1,0(v0)
  401464:       8fc2001c        lw      v0,28(s8)
  401468:       1462000b        bne     v1,v0,401498 <debug+0xa38>
  40146c:       00200825        move    at,at
  401470:       8fc20020        lw      v0,32(s8)
  401474:       00021040        sll     v0,v0,0x1
  401478:       24430001        addiu   v1,v0,1
  40147c:       3c020041        lui     v0,0x41
  401480:       00031880        sll     v1,v1,0x2
  401484:       24424130        addiu   v0,v0,16688
  401488:       00621021        addu    v0,v1,v0
  40148c:       8c420000        lw      v0,0(v0)
  401490:       0810052d        j       4014b4 <debug+0xa54>
  401494:       afc2001c        sw      v0,28(s8)
  401498:       8fc20020        lw      v0,32(s8)
  40149c:       24420001        addiu   v0,v0,1
  4014a0:       afc20020        sw      v0,32(s8)
  4014a4:       8fc20020        lw      v0,32(s8)
  4014a8:       2c42025e        sltiu   v0,v0,606
  4014ac:       1440ffe6        bnez    v0,401448 <debug+0x9e8>
  4014b0:       00200825        move    at,at
  4014b4:       8fc30020        lw      v1,32(s8)
  4014b8:       2402025e        li      v0,606
  4014bc:       1462000c        bne     v1,v0,4014f0 <debug+0xa90>
  4014c0:       00200825        move    at,at
  4014c4:       3c020040        lui     v0,0x40
  4014c8:       24433c00        addiu   v1,v0,15360
  4014cc:       3c020041        lui     v0,0x41
  4014d0:       8c4254a0        lw      v0,21664(v0)
  4014d4:       00602021        move    a0,v1
  4014d8:       24050001        li      a1,1
  4014dc:       24060004        li      a2,4
  4014e0:       0c100230        jal     4008c0 <fwrite@plt>
  4014e4:       00403821        move    a3,v0
  4014e8:       0c100248        jal     400920 <exit@plt>
  4014ec:       24040001        li      a0,1

Basically, it just checks that the word loaded at $s8+332 after scrambling is in a list.
There is 606 tries (noticed that 101x6=606?). The question is: what represents $s8+332? It doesn't take long to understand that $s8+332 is $pc of the child. Also, the list of reference values is called "pcs", which is another good hint.

Ok, let's play with it, without trying to following it step by step.
If at $s8+332 I give a random value, then this check fails, and "ERR\n" is printed to screen: logic.
If I give one of any break address, then the check doesn't fail.
And, at the end of this loooong scrambling, I have another address which equals to another break address+4 (but not the initial break+4)


3-3)Final understanding

The best place to read the modified $pc of the child is right after the PTRACE_CONT, so I wrote some lines of gdb to log all of them in a log file.
user@debian-mipsel:~$ gdb -q crackmips
Reading symbols from /home/user/crackmips...(no debugging symbols found)...done.
(gdb) b * 0x00401e00
Breakpoint 1 at 0x401e00
(gdb) commands
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>x/x $s8+332
>c
>end
(gdb) set log file breaks

(gdb) set log on
(gdb) r AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 ( ...snip)


I have now a long list of pcs:
$ grep '0x7fff6564:' breaks
0x7fff6564:     0x0040228c
0x7fff6564:     0x004022bc
0x7fff6564:     0x00402d0c
0x7fff6564:     0x00402dd4
0x7fff6564:     0x0040357c
0x7fff6564:     0x00402f34
0x7fff6564:     0x004037a8
0x7fff6564:     0x00403728
0x7fff6564:     0x0040244c
 (... snip)


and we see that we jump from one break to another one in the binary.

3-4) Break it like a boss:

My list of breaks is 606 lines long. I can see that all breaks are called 6 times.

Here we have the explanation of why the previous password fails: all the parts between breaks are not lineary called.
I can also see that for each part of the password, the series of $pc is not the same.

In order to break, I can reused my previous script, with a little help of formatting (remember how I called all of the parts with the adress :-) )
$ head -101 ptrace7 | cut -d "x" -f3 | sed s/$/\(v0\)/ | sed s/^00/func_/ | tac > zero
$ head -202 ptrace7 | tail 101 | cut -d "x" -f3 | sed s/$/\(v0\)/ | sed s/^00/func_/ | tac > one
etc..
 
I reused all the function written in the rev.py script :
cat rev2.py zero > hundred_func.py
python ./hundred_func.py 7953205b 0
0xfe446223

cat rev2.py one > hundred_func.py
(etc..)
Once each series of char inversed (remember that I found that the password is inversed), we can crack it.

I wait until the sun set to gave the final blow without hate, without anger, just with peace in my minds and a sharpened sword:

user@debian-mipsel:~$ ./crackmips 322644EF941077AB1115AB575363AE87F58E6D9AFE5C62CC
good job!
Next level is there: http://nsc2014.synacktiv.com:65480/oob4giekee4zaeW9/
user@debian-mipsel:~$

(unfortunately, no internet yesterday to continue.)

4) Conclusion

  • Name's Mitsurugi... REMEMBER IT!
This challenge was an opportunity for me to learn some MIPS assembly.

It was very interesting, and it teach me to not blindly follow my intuitions. Always verify!
There is a lot of code I didn't read, I didn't check how the scheme of the password is made, and maybe there is some black magic inside the debug function (I didn't try hard to reverse it).

It was fun, except the 100 function to write (meh!) The challenge would be exactly the same with only 10 functions. Maybe there is a quicker way (with Sublime Text 3? ;) ) , but it doesn't matter: as my sensei says:
"No matter the road, only the purpose counts"

Thanks to Synacktiv and NoSuchcon!