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
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
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!
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!
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!