Radare2 overview

Radare2 is a reverse engineering framework build by "Pancake" and all the people contributing code on the public github repo. Radare2 is a second version of a project called "Radare" which was created in 2006 by "Pancake" for the purpose of creating a simple hexadecimal editor with support for searching patterns and dumping the search results to disk to recover some PHP files deleted from an HFS partition. Because of that, Radare stands for RAw DAta REcovery.

Radare was a simple tool at first but was quickly enhanced with many useful features. When it was obvious that implementing features like scripting language is not really possible with the current codebase a rewrite was considered. And thus how Radare2 was born. Radare2 began as a rewrite of the Radare code base with the main purpose of following a modular design which would allow the project to be highly extended in the future. In 2014 project gained a lot of traction and to this day is one of the most popular and used binary analysis / reverse engineering framework. You can find Radare2's source code in it's repository on github: https://github.com/radareorg/radare2 Project is licensed under the LGPLv3 license.

Note about Rizin

In the December of 2020 some of the main radare2 contributors and developers of the official radare2 GUI (Cutter) decided they were not happy with the direction the project was going towards so in the spirit of Open-Source they decided to fork the project and give it a name "rizin". At the time of writing the project didn't changed dramatically so you should be able to follow along using either of them (note this may change in the future).

Installation

Linux

The recommended way of installing radare2 on Linux is to clone the radare2 git repository and build the project from the source using the build script.

If you have git, make and gcc already installed you should be able to install radare2 using this commands:

$ git clone https://github.com/radareorg/radare2
$ cd radare2 ; sys/install.sh

Other way of installing radare2 on Linux is to use the prebuilt binaries from the repository. Depending on what distro are you running the installation command could be sudo apt install radare2 or sudo pacman -S radare2, or other, you should know what package manager you are using.

Using radare2 from package manager often means you are using very old release and radare2 developers won't accept any issues you may have as they are very likely to be fixed already on the main branch in the repository.

MacOS

The recommended way to install radare2 is the same as on Linux.

Alternatively you could use package manager like brew but this way of installing is not supported by radare2 developers.

Windows

On windows you can use prebuilt binaries which you can download from the radare2 github repo release page.

Package manager like Chocolatey is also an option.

Source code for today's challenge binary

I encourage you to follow along as we go through a cracking a simple crackme to learn basic usage of Radare2. This is the source code for today's challenge:

#include <stdio.h>
#include <string.h>

int main(int argc, char** argv) {
    printf("PASSWORD: ");

    char password[64];
    int ret;

    scanf("%s", password);
    ret = strncmp(password, "S3CR3T_P455W0RD", 64);

    if (ret == 0) {
        printf("Access granted\n");
    }
    else {
        printf("Try again\n");
    }
    return 0;
}

To compile this code on linux you can execute:

gcc file_name.c -o crackme

Readme

Addresses presented in the disassembly below could be different on your machine, keep that in mind!

Opening binary to analysis

Before opening our binary you need to either make sure you have radare2 binary in your PATH or you copy our binary to the build directory where the radare2 binary is stored. Then you can simply open the binary using:

r2 crackme

or on Windows:

.\radare2.exe crackme.exe

Help system

I think the single most helpful tip I can give you on how to use r2 is that you can always type/append your command with the ? character and you will get a list of things you can use to get things done. For example to get an overall overview of what functions you can use simple type ? and you will get such a list. Another example: if you want to know more about analyzing options just use a? and you will get an answer.

Analyzing the binary

Process of analyzing binary is the process of extracting information like position and names of functions and more.

To analyze our binary we will use the aaa command. If everything went correctly you should see and output like the out you see here:

[0x00001080]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for vtables
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.
[0x00001080]>

Printing functions

To print all functions found by radare2 we will use the afl command. (afl? describes it as: list functions) Here is the output:

[0x00001080]> afl
0x00001080    1 46           entry0
0x000010b0    4 41   -> 34   sym.deregister_tm_clones
0x000010e0    4 57   -> 51   sym.register_tm_clones
0x00001120    5 65   -> 55   sym.__do_global_dtors_aux
0x00001170    1 9            entry.init0
0x00001000    3 27           sym._init
0x00001290    1 5            sym.__libc_csu_fini
0x00001298    1 13           sym._fini
0x00001220    4 101          sym.__libc_csu_init
0x00001179    6 167          main
0x00001030    1 6            sym.imp.puts
0x00001040    1 6            sym.imp.__stack_chk_fail
0x00001050    1 6            sym.imp.printf
0x00001060    1 6            sym.imp.strcmp
0x00001070    1 6            sym.imp.__isoc99_scanf
[0x00001080]>

We can see the biggest function here is the main function. This is the function which gets executed first after launching the binary. This is a great point to start analyzing our binary.

Going to main function

Currently we are at the at the 0x00001080 address inside of our binary (entry point). To change that and move our context into the main function, we will use the seek command which is s. Simply type s main and now our current address should print 0x00001179 (main).

[0x00001080]> s main
[0x00001179]>

Printing disassembly of a function (pdf)

Command pdf (stands for Print Disassembly Function) will allow us to print disassembled instructions in the main function. Use it and the output should be:

[0x00001179]> pdf
            ; DATA XREF from entry0 @ 0x10a1
167: int main (int argc, char **argv);
│           ; var char **var_70h @ rbp-0x70
│           ; var int64_t var_64h @ rbp-0x64
│           ; var uint32_t var_54h @ rbp-0x54
│           ; var char *s1 @ rbp-0x50
│           ; var int64_t canary @ rbp-0x8
│           ; arg int argc @ rdi
│           ; arg char **argv @ rsi
0x00001179      55             push rbp
0x0000117a      4889e5         mov rbp, rsp
0x0000117d      4883ec70       sub rsp, 0x70
0x00001181      897d9c         mov dword [var_64h], edi    ; argc
0x00001184      48897590       mov qword [var_70h], rsi    ; argv
0x00001188      64488b042528.  mov rax, qword fs:[0x28]
0x00001191      488945f8       mov qword [canary], rax
0x00001195      31c0           xor eax, eax
0x00001197      488d05660e00.  lea rax, str.PASSWORD:_     ; 0x2004 ; "PASSWORD: "
0x0000119e      4889c7         mov rdi, rax                ; const char *format
0x000011a1      b800000000     mov eax, 0
0x000011a6      e8a5feffff     call sym.imp.printf         ; int printf(const char *format)
0x000011ab      488d45b0       lea rax, [s1]
0x000011af      4889c6         mov rsi, rax
0x000011b2      488d05560e00.  lea rax, [0x0000200f]       ; "%s"
0x000011b9      4889c7         mov rdi, rax                ; const char *format
0x000011bc      b800000000     mov eax, 0
0x000011c1      e8aafeffff     call sym.imp.__isoc99_scanf ; int scanf(const char *format)
0x000011c6      488d45b0       lea rax, [s1]
0x000011ca      488d15410e00.  lea rdx, str.S3CR3T_P455W0RD ; 0x2012 ; "S3CR3T_P455W0RD"
0x000011d1      4889d6         mov rsi, rdx                ; const char *s2
0x000011d4      4889c7         mov rdi, rax                ; const char *s1
0x000011d7      e884feffff     call sym.imp.strcmp         ; int strcmp(const char *s1, const char *s2)
0x000011dc      8945ac         mov dword [var_54h], eax
0x000011df      837dac00       cmp dword [var_54h], 0
│       ┌─< 0x000011e3      7511           jne 0x11f6
│       │   0x000011e5      488d05360e00.  lea rax, str.Access_granted ; 0x2022 ; "Access granted"
│       │   0x000011ec      4889c7         mov rdi, rax                ; const char *s
│       │   0x000011ef      e83cfeffff     call sym.imp.puts           ; int puts(const char *s)
│      ┌──< 0x000011f4      eb0f           jmp 0x1205
│      ││   ; CODE XREF from main @ 0x11e3
│      │└─> 0x000011f6      488d05340e00.  lea rax, str.Try_again      ; 0x2031 ; "Try again"
│      │    0x000011fd      4889c7         mov rdi, rax                ; const char *s
│      │    0x00001200      e82bfeffff     call sym.imp.puts           ; int puts(const char *s)
│      │    ; CODE XREF from main @ 0x11f4
│      └──> 0x00001205      b800000000     mov eax, 0
0x0000120a      488b55f8       mov rdx, qword [canary]
0x0000120e      64482b142528.  sub rdx, qword fs:[0x28]
│       ┌─< 0x00001217      7405           je 0x121e
│       │   0x00001219      e822feffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│       │   ; CODE XREF from main @ 0x1217
│       └─> 0x0000121e      c9             leave
0x0000121f      c3             ret
[0x00001179]>

OK, that's quite a lot at first glance. Let's try to understand it.

Understanding the binary

This view (list) is useful but a different view I think will help use more in this case. The graph view. To use it, execute the VV command and you should see this: (navigate using arrow keys)

               ┌──────────────────────────────────────────────┐
               │ [0x1179]                                     │
               │   ; DATA XREF from entry0 @ 0x10a1167: int main (int argc, char **argv);       │
               │ ; var char **var_70h @ rbp-0x70               │ ; var int64_t var_64h @ rbp-0x64               │ ; var uint32_t var_54h @ rbp-0x54               │ ; var char *s1 @ rbp-0x50               │ ; var int64_t canary @ rbp-0x8               │ ; arg int argc @ rdi                         │
               │ ; arg char **argv @ rsi                      │
               │ push rbp                                     │
               │ mov rbp, rsp                                 │
               │ sub rsp, 0x70               │ ; argc                                       │
               │ mov dword [var_64h], edi                     │
               │ ; argv                                       │
               │ mov qword [var_70h], rsi                     │
               │ mov rax, qword fs:[0x28]                     │
               │ mov qword [canary], rax                      │
               │ xor eax, eax                                 │
               │ ; 0x2004               │ ; "PASSWORD: "               │ lea rax, str.PASSWORD:_                      │
               │ ; const char *format                         │
               │ mov rdi, rax                                 │
               │ mov eax, 0               │ ; int printf(const char *format)             │
               │ call sym.imp.printf;[oa]                     │
               │ lea rax, [s1]                                │
               │ mov rsi, rax                                 │
               │ ; "%s"               │ lea rax, [0x0000200f]                        │
               │ ; const char *format                         │
               │ mov rdi, rax                                 │
               │ mov eax, 0               │ ; int scanf(const char *format)              │
               │ call sym.imp.__isoc99_scanf;[ob]             │
               │ lea rax, [s1]                                │
               │ ; 0x2012               │ ; "S3CR3T_P455W0RD"               │ lea rdx, str.S3CR3T_P455W0RD               │ ; const char *s2                             │
               │ mov rsi, rdx                                 │
               │ ; const char *s1                             │
               │ mov rdi, rax                                 │
               │ ; int strcmp(const char *s1, const char *s2) │
               │ call sym.imp.strcmp;[oc]                     │
               │ mov dword [var_54h], eax                     │
               │ cmp dword [var_54h], 0               │ jne 0x11f6               └──────────────────────────────────────────────┘
                       f t
                       │ │
                       │ └───────────────────┐
       ┌───────────────┘                     │
       │                                     │
   ┌────────────────────────────────┐    ┌────────────────────────────────┐
0x11e5 [of]                   │    │  0x11f6 [og]                   │
   │ ; 0x2022                       │    │ ; CODE XREF from main @ 0x11e3   │ ; "Access granted"             │    │ ; 0x2031   │ lea rax, str.Access_granted    │    │ ; "Try again"   │ ; const char *s                │    │ lea rax, str.Try_again   │ mov rdi, rax                   │    │ ; const char *s                │
   │ ; int puts(const char *s)      │    │ mov rdi, rax                   │
   │ call sym.imp.puts;[oe]         │    │ ; int puts(const char *s)      │
   │ jmp 0x1205                     │    │ call sym.imp.puts;[oe]         │
   └────────────────────────────────┘    └────────────────────────────────┘
       v                                     v
       │                                     │
       └────────────────────┐                │
                            │ ┌──────────────┘
                            │ │
                      ┌────────────────────────────────┐
0x1205 [oh]                   │
                      │ ; CODE XREF from main @ 0x11f4                      │ mov eax, 0                      │ mov rdx, qword [canary]        │
                      │ sub rdx, qword fs:[0x28]       │
                      │ je 0x121e                      └────────────────────────────────┘
                              f t
                              │ │
                              │ └─────────────┐
    ┌─────────────────────────┘               │
    │                                         │
┌────────────────────────────────────┐    ┌────────────────────────────────┐
0x1219 [oj]                       │    │  0x121e [ok]                   │
│ ; void __stack_chk_fail(void)      │    │ ; CODE XREF from main @ 0x1217│ call sym.imp.__stack_chk_fail;[oi] │    │ leave                          │
└────────────────────────────────────┘    │ ret                            │
                                          └────────────────────────────────┘

(to quit from that view press the q key)

In the first (top most) block we can see come calls to printf and scanf functions. This tells us that this pice of code is displaying something to stdout and the getting the user input from stdin. That's exactly what our program does! It prints PASSWORD: and waits for us to type one. Then the program does some checks and based on the result it jumps either to the Access granted block or the Try again! block.

The check which decides what block gets executed is our point of interest right now. Instruction which checks that is the one on the 0x000011e3 address (see disassembly list), that's the jne or Jump Not Equal instruction. To "crack" the binary we will change this instruction to je - Jump Equal instruction which is the opposite of the jne. But to do that we need to reopen the binary in the "writeable" mode.

Opening binary in write mode

To make our binary writeable we can use the oo+ command. (see oo+?). This will enable us to modify the binary.

Cracking binary

Now that we know what we need to do in order to crack our simple crackme, we can start doing it. Firstly we need to "seek" into the address where we want to modify something. s 0x000011e3 (address might be different on your system) will do the trick. Then as we said, we can swap the jne instruction with the je one using wa je 0x11f6 (again, check the address). Now, going back to the graph view (VV) we can see that instruction has in deed changed.

Quitting

Our work is now done, so to exit use the q command and we will be back on the shell prompt.

Checking results

Executing the crackme and passing random password will now lead to printing Access granted which means we succeed in cracking it. Hooray!

Ending

That was by any means not an complete radare2 course but an quick overview of some of the basic radare2 functions. I hope you learned something. I encourage you to read more about radare2 and binary exploitation in general as it is a very interesting topic. Happy hacking!