Reversing as an Art

Notes on RCE fun.

Flare-On Challenge VI Solution

Back to Solutions List

Challenge #6, probably the most toughest task among the series. We are blessed with 64 bit statically linked ELF file with stripped symbols. During the challenge we will be using the following tools:

  • radare2
  • IDA
  • gdb

First things, first

Let’s execute the file (in VM of course) and see what will be the output (if any):

1
2
[test ~]$ ./c6
no

Not much, but it’s a start. This no will be our anchor and starting point in a minute.

Before continuing further I’d like to take a look statically on the binary. As already mentioned, the file comes with striped symbols, meaning we have no straight forward clues left for us. To continue, one needs to find the main function as this is the code to start from. Entry point of the execution, in most cases, will start from bootstrapping code which will prepare the environment for the programmer’s code to run. The preparation process is managed by __libc_start_main function with the following interface:

1
int __libc_start_main(int (*main) (...), int argc, char * * ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void (* stack_end));

The first parameter here is the pointer to the main function and now, fire up radare2 and let’s do some actual examinations. radare2 is able to identify main function during code analysis stage.

Entery point examination
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[0x00401058]> aa                               ; whole program analysis
[0x00401058]> pdf                               ; disassemble function
/ (fcn) entry0 67
|          0x00401058    31ed         xor ebp, ebp
|          0x0040105a    4989d1       mov r9, rdx
|          0x0040105d    5e           pop rsi
|          0x0040105e    4889e2       mov rdx, rsp
|          0x00401061    4883e4f0     and rsp, 0xfffffffffffffff0
|          0x00401065    50           push rax
|          0x00401066    54           push rsp
|          0x00401067    49c7c040e64. mov r8, 0x45e640 ;  0x0045e640
|          0x0040106e    48c7c1b0e54. mov rcx, 0x45e5b0 ;  0x0045e5b0
|          0x00401075    48c7c7e1dc4. mov rdi, main ;  0x0045dce1
|          0x0040107c    e88fcc0500   call __libc_start_main
|             __libc_start_main(unk, unk) ; main+47
|          0x00401081    f4           hlt
|          0x00401082    90           nop
|          0x00401083    90           nop
           ; CODE (CALL) XREF from 0x004002fc (fcn.004002f8)
/ (fcn) fcn.00401084 23
|          0x00401084    4883ec08     sub rsp, 0x8
|          0x00401088    488b05496f3. mov rax, [rip+0x326f49] ;  0x00407fd8
|          0x0040108f    4885c0       test rax, rax
|          0x00401092    7402         je 0x401096
|          0x00401094    ffd0         call rax
|             0x00000000()
|          0x00401096    4883c408     add rsp, 0x8
\          0x0040109a    c3           ret

So, knowing that it’s 64 bit executable with appropriate ABI, we’d expect the main function be passed in RDI register and radare2 indeed supports the assumption.

Overview of the binary

The binary is heavily obfuscated with a lot of junk instructions and spaghetti code which makes it in general not user friendly. On the figure, you are seeing starting function and yes, this is one function where even IDA complained about amount of nodes being more that 1000. Further analysis showed that most of the code is in the same unfriendly condition.

Fig. 1

As you can see, it’s easy to get lost, but still let’s dive in for a while. Let’s start randomly examine various parts of the function and look for something. After some time, reoccurring patterns start to appear which are different from other junk code.

Fig. 2

Various constants are getting updated with first letters of some predefined words and this was happening all over the place. Constant’s examination showed interesting thing, all of them are actually cells of a static array.

Fig. 3

References to most of them showed the same update pattern:

Fig. 4

Intuitively, let’s examine the head of the array to check whether it’s referenced anywhere that could be of any interest.

Fig. 5

Before moving to the dynamic part of the challenge, some of you have spotted the bingo point (as I call it). It looks, that the constant array is actually an obfuscated shellcode which will be executed at the end. Currently it’s not interesting to understanding what type of obfuscation was used. Now, I hope the general idea is clear and I’d like to sum things up, before moving on to actually verifying all the theories:

  • The binary is hardened with spaghetti and junk code
  • During the execution, static array is filled with first letters of the predefined words
  • Eventually the array will be do-obfuscated and executed – this is an educated guess which will be checked during binary execution

So now, (hopefully) you understand a little bit what is going on. At the next step, gdb will be use as primary tool to solve the challenge and IDA will accompany us on the way. The author left numerous clues to be used and help us get to the end. The first one is the no message which appeared at the start.


The goal is to breakpoint on loc_44BAB9 (Fig. 5) and get to shellcode execution.


Clue -=no=-

1
2
3
4
5
6
7
gdb$ run
Starting program: ~~~~~~~~~./c6
Got object file from memory but can't read symbols: File truncated.
no
[Inferior 1 (process 453) exited with code 064]
...
gdb$

Finding no in IDA. Analyzing the chain on (Fig. 6) one can immediately see, that there were not enough arguments given on start up. There is still no information what should be supplied, but this will definitely get there. So, let’s restart with one arguments and follow the results.

Fig. 6

Clues -=na=- and -=stahp=-

1
2
3
4
5
6
7
gdb$ run bla
Starting program: ~~~~~~~~~./c6 bla
Got object file from memory but can't read symbols: File truncated.
na
[Inferior 1 (process 447) exited with code 0247]
...
gdb$

This time we explore the previous finding, where on error, the message was printed with print (Fig. 6). Using IDA’s xRef feature, we got the explanation for the na – this also shows insufficient parameters (Fig. 7) we supplied, so another one is needed.

Fig. 7

Just to check how many parameters there actually are, try to add more than two and it always will generate :

1
2
3
4
5
6
7
8
9
10
11
12

gdb$ run bla foo vvv
Starting program: ~~~~~~~~~./c6 4815162342 bbb vvv
Got object file from memory but can't read symbols: File truncated.
stahp
[Inferior 1 (process 583) exited with code 016]

gdb$ run bla foo vvv zzz
Starting program: ~~~~~~~~~./c6 4815162342 bbb vvv
Got object file from memory but can't read symbols: File truncated.
stahp
[Inferior 1 (process 583) exited with code 016]

and code confirmation:

So, there are only 2 parameters to work with.

Some anti-Debugging

Adding two parameters, got us to the next trouble:

1
2
3
4
5
6
7
gdb$ run bla foo
Starting program: ~~~~~~~~~./c6 bla foo
Got object file from memory but can't read symbols: File truncated.
Program received signal SIGSEGV, Segmentation fault
[Inferior 1 (process 457) exited with code 051]
gdb$

Let’s try once again and check why we got this output (Fig. 8) by using the Xref for print function.

Fig. 8

What we have here is actually a ptrace (0x65 system call) call with PTRACE_TRACEME request.

1
2
PTRACE_TRACEME
    Indicate that this process is to be traced by its parent. (pid, addr, and data are ignored.)

As you probably understood, current process is already traced by parent (gdb), so the new call to ptrace will result in failure. The solution for this trick is actually quiet easy, just overwrite set $EAX = 1 after return from ptrace or patch jz short loc_41f232 (Fig. 8) to jmp short loc_41f232 with your favorite hex editor. Assuming that this trouble was solved, let’s continue.

Clue -=bad=-

So, now we know that the application expects 2 arguments and was protected with anti-debugging. We continue now with the following:

1
2
3
4
5
6
7
gdb$ run bla foo
Starting program: ~~~~~~~~~./c6 bla foo
Got object file from memory but can't read symbols: File truncated.
bad
[Inferior 1 (process 488) exited with code 0244]
...
gdb$

Repeating the previous technique, it could be seen (Fig. 9) that some buffer is compared to bngcg`debd.

Fig. 9

Backtracking, leads to the fact , that the first parameter is actually xor’ed with V and stored in buffer before checking with bngcg`debd.

Fig. 10

To reveal the first parameter, let’s XOR the bngcg`debd with V and get 4815162342.

Sleeping

Once the application re-executed with new parameter, it freezes. Breaking in gdb reveals the issue.

Freezing examination
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
gdb$ run 4815162342 foo
Starting program: ~~~~~~~~~./c6 4815162342 foo
Got object file from memory but can't read symbols: File truncated.
^C
Program received signal SIGINT, Interrupt.
-----------------------------------------------------------------------------------------------------------------------[regs]
  RAX: 0xFFFFFFFFFFFFFDFC  RBX: 0x00007FFFFFFFDD50  RCX: 0xFFFFFFFFFFFFFFFF  RDX: 0x0000000000000000  o d I t s Z a P c
  RSI: 0x00007FFFFFFFDE50  RDI: 0x00007FFFFFFFDE50  RBP: 0x00000000FFFFFFFF  RSP: 0x00007FFFFFFFDCA8  RIP: 0x0000000000473D50
  R8 : 0x00007FFFFFFFDCB0  R9 : 0x0000000000000003  R10: 0x0000000000000008  R11: 0x0000000000000246  R12: 0x000000000045E5B0
  R13: 0x0000000000000000  R14: 0x0000000000000000  R15: 0x0000000000000000
  CS: 0033  DS: 0000  ES: 0000  FS: 0063  GS: 0000  SS: 002B
-----------------------------------------------------------------------------------------------------------------------[code]
=> 0x473d50: cmp    rax,0xfffffffffffff001
   0x473d56: jae    0x476c30
   0x473d5c: ret
   0x473d5d: sub    rsp,0x8
   0x473d61: call   0x475940
   0x473d66: mov    QWORD PTR [rsp],rax
   0x473d6a: mov    eax,0x23
   0x473d6f: syscall

0x23 system call is actually nanosleep which is called from within sub_473B70. Sleeping is easily neutralized by supplying small sleep time.

Fig. 11

Shellcode

Finally, after all the adventures, gdb stopped on 0x44bab9 – just before decoding the static array with presumable shellcode.

The contents of the array @ 0x729900 are likely to be base64 encoded (I did not find the need to check the algo). After the decoding, the following shellcode will be executed (only part of it is show here).

The idea here is to take the obfuscation algorithm and execute it backwards with the help of the pen or python. It’s not very complex, so I leave it for you to implement. If everything is done right, you will get the following mail:

l1nhax.hurt.u5.a1l@flare-on.com

Bonus – back-connect code

As a bonus, the author left for us some back-connect code, sort of a prize as it will be activated only when the right 2nd parameter was supplied (which is the email).

Fig. 12 – back-connect code or here

This how it looks when executed:

So be careful and always use a controlled (to some degree) environment!!!

Comments