dimanche 28 janvier 2018

Solving a CTF chall the [hard|good] way (FIC2018)

Hope you liked my last blogpost http://0x90909090.blogspot.fr/2018/01/solving-ctf-chall-easylazy-way-fic2018.html , this is the same binary, with a different analysis.

This crackme is simple enough to use it for learning purpose. I'm taking it for another round of reversing. This time, it's gdb-fu! In the CTF, this was my first approach, but pin was faster :-)

1/ Basics

We remember the function name, vm_xor and vm_cmp. Other function looks more like standard operation (JNZ, JMP, call, and so on).
The binary is not stripped, and we see a variable called 'regs'. We guess that's the registers of the VM. While running under gdb, we can print their contents with x/8wx &regs


2/ XOR part

In gdb, we have breakpoints, and we can execute commands at each breakpoints. We'll break at vm_xor, and see what's happening. I don't use any gdbinit scripts because it's a VM, and my gdbscripts are meant to be used in conjunction with known CPUs (ARM/Intel). The goal here is to count how many times the vm_xor function is called, and see if we can gain some insights of what is going on:

mitsurugi@dojo:~/chall/FIC2018/v24$ gdb -q -nx ./a.out
Reading symbols from ./a.out...(no debugging symbols found)...done.
(gdb) b * vm_xor 
Breakpoint 1 at 0x400c9e
(gdb) commands 
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>echo vm_xor is called\n
>c
>end
(gdb) r
Starting program: /home/mitsurugi/chall/FIC2018/v24/a.out 
ENTER PASS :first try

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
FAILEDn[Inferior 1 (process 4165) exited normally]
(gdb) r
Starting program: /home/mitsurugi/chall/FIC2018/v24/a.out 
ENTER PASS :1

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
FAILEDn[Inferior 1 (process 4169) exited normally]
(gdb) 

Ok, so we understand that vm_xor is called 12 times, whichever the size of the PASS is.

We hope now that vm_xor will works like the XOR: it takes two args from registers. Let's inspect this:

(gdb) commands 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>x/8wx &regs
>c
>end
(gdb) r
Starting program: /home/mitsurugi/chall/FIC2018/v24/a.out 
ENTER PASS :123456ABCDEF

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000f2 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x0000014b 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000f3 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x00000156 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000f4 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x00000161 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000f5 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x0000016c 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000f6 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x00000177 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000f7 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x00000182 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000f8 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x0000018d 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000f9 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x00000198 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000fa 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x000001a3 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000fb 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x000001ae 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000fc 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x000001b9 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000fd 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x000001c4 0x00603010 0x00000000
FAILEDn[Inferior 1 (process 4291) exited normally]
(gdb)

Ok, so we don't see any of our PASS in register. The first one seems to progress one by one, and the 6th one makes progression. It could be relative address, or offset, or anything. Let dive into the vm_xor function. It must have an XOR operation, and it could be interesting to see the operands of the command:

(gdb) disassemble vm_xor 
Dump of assembler code for function vm_xor:
   0x0000000000400c9e <+0>: mov    0x201c70(%rip),%eax        # 0x602914 <regs+20>
   0x0000000000400ca4 <+6>: lea    0x1(%rax),%edx
   0x0000000000400ca7 <+9>: mov    %edx,0x201c67(%rip)        # 0x602914 <regs+20>
   0x0000000000400cad <+15>: movslq %edx,%rdx
   0x0000000000400cb0 <+18>: mov    0x601440(%rdx),%dl
   0x0000000000400cb6 <+24>: cmp    $0xab,%dl
   0x0000000000400cb9 <+27>: jne    0x400cf0 <vm_xor+82>
   0x0000000000400cbb <+29>: lea    0x2(%rax),%edx
   0x0000000000400cbe <+32>: add    $0x3,%eax
   0x0000000000400cc1 <+35>: mov    %eax,0x201c4d(%rip)        # 0x602914 <regs+20>
   0x0000000000400cc7 <+41>: cltq   
   0x0000000000400cc9 <+43>: movslq %edx,%rdx
   0x0000000000400ccc <+46>: movzbl 0x601440(%rax),%eax
   0x0000000000400cd3 <+53>: movzbl 0x601440(%rdx),%edx
   0x0000000000400cda <+60>: mov    0x602900(,%rax,4),%eax
   0x0000000000400ce1 <+67>: movslq 0x602900(,%rdx,4),%rdx
   0x0000000000400ce9 <+75>: xor    %al,0x601440(%rdx)         //HERE
   0x0000000000400cef <+81>: retq   
   0x0000000000400cf0 <+82>: cmp    $0xbb,%dl
   0x0000000000400cf3 <+85>: jne    0x400d27 <vm_xor+137>
   0x0000000000400cf5 <+87>: lea    0x2(%rax),%edx
   0x0000000000400cf8 <+90>: add    $0x3,%eax
   0x0000000000400cfb <+93>: mov    %eax,0x201c13(%rip)        # 0x602914 <regs+20>
   0x0000000000400d01 <+99>: cltq   
   0x0000000000400d03 <+101>: movslq %edx,%rdx
   0x0000000000400d06 <+104>: movzbl 0x601440(%rdx),%edx
   0x0000000000400d0d <+111>: movslq 0x602900(,%rdx,4),%rcx
   0x0000000000400d15 <+119>: mov    0x601440(%rcx),%dl
   0x0000000000400d1b <+125>: xor    0x601440(%rax),%dl         //and HERE 
   0x0000000000400d21 <+131>: mov    %dl,0x601440(%rcx)
   0x0000000000400d27 <+137>: retq   
End of assembler dump.
(gdb)

Easy, disable breakpoint 1, and create two more:

(gdb) disable 1
(gdb) b * 0x0000000000400ce9
Breakpoint 2 at 0x400ce9
(gdb) commands
Type commands for breakpoint(s) 2, one per line.
End with a line saying just "end".
>echo first XOR in vm_xor\n
>info reg al
>x/x 0x601440+$rdx
>c
>end
(gdb) b * 0x0000000000400d1b
Breakpoint 3 at 0x400d1b
(gdb) commands
Type commands for breakpoint(s) 3, one per line.
End with a line saying just "end".
>echo second XOR in vm_func\n
>x/x 0x601440+$rax
>info reg dl
>c
>end
(gdb) r
Starting program: /home/mitsurugi/chall/FIC2018/v24/a.out 
ENTER PASS :ABCDEF123456

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x60158e <g_data+334>: 0x13
dl             0x41 65                    //seems our key

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x601599 <g_data+345>: 0x37
dl             0x42 66                    //Yup it is

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x6015a4 <g_data+356>: 0xd3                  //So this one is the xor key
dl             0x43 67

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x6015af <g_data+367>: 0x3d
dl             0x44 68

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x6015ba <g_data+378>: 0xc0
dl             0x45 69

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x6015c5 <g_data+389>: 0xde
dl             0x46 70

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x6015d0 <g_data+400>: 0xab
dl             0x31 49

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x6015db <g_data+411>: 0xad
dl             0x32 50

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x6015e6 <g_data+422>: 0x1d
dl             0x33 51

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x6015f1 <g_data+433>: 0xea
dl             0x34 52

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x6015fc <g_data+444>: 0x13
dl             0x35 53

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x601607 <g_data+455>: 0x37
dl             0x36 54
FAILEDn[Inferior 1 (process 4575) exited normally]
(gdb) 

Well, only the second XOR is used, but it's not really important at this point. The important point is to see that each of our PASS has been XORed with a constant string (you can repeat to veroify this).

So, we have an XOR key, if we copy it we have: 1337d33dc0deabad1dea1337

(1337d33dc0de?? 1337d34dc0de would have sound better, I think. Another bug? ^_^ )

Now we have to find the expected solution, because PASS^key=solution and with simple math, we can say that: PASS = key ^ solution.

3/ CMP part

Well, we use the same technics. Let's break on vm_cmp and see what happens in registers:

(gdb) disable 2
(gdb) disable 3
(gdb) b * vm_cmp 
Breakpoint 4 at 0x400852
(gdb) commands 
Type commands for breakpoint(s) 4, one per line.
End with a line saying just "end".
>x/8wx &regs
>c
>end
(gdb) r
Starting program: /home/mitsurugi/chall/FIC2018/v24/a.out 
ENTER PASS :123456ABCDEF

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x00000022 0x0000007a 0x00000005 0x00000060
0x602910 <regs+16>: 0x00000000 0x000001eb 0x00603010 0x00000000
FAILEDn[Inferior 1 (process 4746) exited normally]
(gdb) 
(gdb) r
Starting program: /home/mitsurugi/chall/FIC2018/v24/a.out 
ENTER PASS :ABCDEF123456

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x00000052 0x0000007a 0x00000075 0x00000060
0x602910 <regs+16>: 0x00000000 0x000001eb 0x00603010 0x00000000
FAILEDn[Inferior 1 (process 4751) exited normally]
(gdb) 

Interesting. We see that register 1 changes, and register 2 stays the same.
One more check, because the key is 1337d33dc0deabad1dea1337:
  •  '1' XOR '0x13' => 0x22
  •  'A' XOR '0x13' => 0x52
So, we know that register 1 is PASS XOR key and register 2 is solution.

But, have you spotted something else really weird?

Register 3 changes too!!
And it don't take a lot of time to understand that register 3 holds the "PASS XOR key":
  •  '2' XOR '0x37' => 0x05
  •  'B' XOR '0x37' => 0x75
We know how to extract all bytes of the solution.
Once again, we use the gdb commands function to force register to have the good values, and let print this value.

(gdb) commands
Type commands for breakpoint(s) 4, one per line.
End with a line saying just "end".
>x/8wx &regs
>set *(char *) 0x602900 = *0x602904
>set *(char *) 0x602908 = *0x60290c
>c
>end
(gdb) r 
Starting program: /home/mitsurugi/chall/FIC2018/v24/a.out 
ENTER PASS :123456123456

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x00000022 0x0000007a 0x00000005 0x00000060
0x602910 <regs+16>: 0x00000000 0x000001eb 0x00603010 0x00000000

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x0000007a 0x0000007a 0x00000060 0x00000060
0x602910 <regs+16>: 0x00000000 0x000001f5 0x00603010 0x00000000

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x000000e0 0x000000b2 0x00000009 0x0000004e
0x602910 <regs+16>: 0x00000000 0x0000021b 0x00603010 0x00000000

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x000000b2 0x000000b2 0x0000004e 0x0000004e
0x602910 <regs+16>: 0x00000000 0x00000225 0x00603010 0x00000000

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x000000f5 0x000000b4 0x000000e8 0x000000bb
0x602910 <regs+16>: 0x00000000 0x00000255 0x00603010 0x00000000

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x0000009a 0x000000e6 0x0000009f 0x000000d4
0x602910 <regs+16>: 0x00000000 0x0000027b 0x00603010 0x00000000

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x000000e6 0x000000e6 0x000000d4 0x000000d4
0x602910 <regs+16>: 0x00000000 0x00000285 0x00603010 0x00000000

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x0000002e 0x00000049 0x000000de 0x00000083
0x602910 <regs+16>: 0x00000000 0x000002ab 0x00603010 0x00000000

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x00000049 0x00000049 0x00000083 0x00000083
0x602910 <regs+16>: 0x00000000 0x000002b5 0x00603010 0x00000000

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x00000026 0x0000007e 0x00000001 0x00000052
0x602910 <regs+16>: 0x00000000 0x000002db 0x00603010 0x00000000

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x0000007e 0x0000007e 0x00000052 0x00000052
0x602910 <regs+16>: 0x00000000 0x000002e5 0x00603010 0x00000000
WINn[Inferior 1 (process 5104) exited with code 0377]
(gdb)


Ok, job done, we just have to copy bytes from register 2 and 4. vm_cmp has two times two bytes to compare, but function is called twice. I really should dig inside this func to understand how this work :)

solution is: 7a60b24eb4bbe6d449837e52

4/ Victory


#! /usr/bin/python
# First you have to be hit to know how to defend.
#                                     0xMitsurugi

key='1337d33dc0deabad1dea1337'.decode('hex')
sol='7a60b24eb4bbe6d449837e52'.decode('hex')

sol=[]
for i in range(len(sol)):
    c=chr(ord(sol[i]) ^ ord(key[i]))
    sol.append(c)

print ''.join(sol)

And without any surprise:

mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.py 
iWasteMyTime
mitsurugi@dojo:~/chall/FIC2018/v24$

Job done, time to drink beer for victory.
I have not failed 700 times, I have not failed once.
I have succeeded in proving those 700 ways will not work.
When I have eliminated the ways that will not work,
I will find the way that will work 
0xMitsurugi

Aucun commentaire:

Publier un commentaire