stackrip

A Practical Introduction to Radare2 - Static Analysis

This article focuses on the analysis of a Linux executable using radare2. If you're not using Linux, you can still analyze ELF binaries with radare2 out of the box. The best way to install radare2 is from source at github.com/radare/radare2

The executable we're analyzing is not especially difficult to crack. This is because we are focused less on the binary's challenge and more around the different approaches for static analysis using r2. For this tutorial we will use the stackrip starter binary x86-0000-unlockme.

This is an x86 ELF binary which slowly types a request for a password when executed. Stackrip Executable Running

The binary suggests using rabin2. This is a radare2 binary examination tool. When used with the -z flag it finds and prints all human readable strings from the data section of the binary.


$ rabin2 -z ./x86-0000-unlockme
000 0x000008b0 0x080488b0  22  23 (.rodata) ascii this_is_not_a_password
001 0x000008c8 0x080488c8  32  33 (.rodata) ascii Hello.\nI have a flag for you...\n
002 0x000008ec 0x080488ec  71  72 (.rodata) ascii Unfortunately before I can give you it, you must give something to me.\n
003 0x00000934 0x08048934  35  36 (.rodata) ascii Do you have what I'm looking for?\n\n
004 0x0000095c 0x0804895c  47  48 (.rodata) ascii Good job. I believe this is what you came for:\n
005 0x0000098c 0x0804898c  51  52 (.rodata) ascii Hmm. I don't think this is what I was looking for.\n
006 0x000009c0 0x080489c0  36  37 (.rodata) ascii Take a look inside of me. rabin2 -z\n

There's no flag provided in the data section, but we can now see the success message and a new string "this_is_not_a_password" which looks promising.

We can further analyze the binary using the rabin2 -I command which provides helpful information around the security and structure of the file.


$ rabin2 -I ./x86-0000-unlockme
arch     x86
binsz    6621
bintype  elf
bits     32
canary   false
class    ELF32
crypto   false
endian   little
havecode true
intrp    /lib/ld-linux.so.2
lang     c
linenum  true
lsyms    true
machine  Intel 80386
maxopsz  16
minopsz  1
nx       true
os       linux
pcalign  0
pic      false
relocs   true
relro    partial
rpath    NONE
static   false
stripped false
subsys   linux
va       true

Running rabin2 shows this is an x86 binary with no stack canary and the "nx", or no-execute bit, is enabled. We also see there is no position independence set and symbols are not stripped. This is all great news for finding vulnerabilities.

It's time to put the binary into r2. We'll prepare the binary for static analysis by running r2 -AA ./x86-0000-unlockme. Running this will perform analysis over the binary and drop us into a r2 prompt showing the current seek address in the binary.

Let's take a look at the functions used by the binary using the afl or "analyze function list" command.


[0x08048540]> afl
0x08048438    3 35           sym._init
0x08048470    1 6            sym.imp.setbuf
0x08048480    1 6            sym.imp.strcmp
0x08048490    1 6            sym.imp.printf
0x080484a0    1 6            sym.imp.free
0x080484b0    1 6            sym.imp.fgets
0x080484c0    1 6            sym.imp.sleep
0x080484d0    1 6            sym.imp.usleep
0x080484e0    1 6            sym.imp.malloc
0x080484f0    1 6            sym.imp.puts
0x08048500    1 6            sym.imp.strlen
0x08048510    1 6            sym.imp.__libc_start_main
0x08048520    1 6            sym.imp.putchar
0x08048530    1 6            sub.__gmon_start_530
0x08048540    1 33           entry0
0x08048570    1 4            sym.__x86.get_pc_thunk.bx
0x08048580    4 43           sym.deregister_tm_clones
0x080485b0    4 53           sym.register_tm_clones
0x080485f0    3 30           sym.__do_global_dtors_aux
0x08048610    4 43   -> 40   entry1.init
0x0804863b    4 104          main
0x080486a3    4 91           sym.print_intro
0x080486fe    4 80           sym.slow_type
0x0804874e    4 125          sym.check_password
0x080487cb    1 93           sym.print_flag
0x08048830    4 93           sym.__libc_csu_init
0x08048890    1 2            sym.__libc_csu_fini
0x08048894    1 20           sym._fini

Right away we see the sym.check_password and sym.print_flag functions. Both of which likely contain our solution and are certainly helpful, but we came here to learn, not to win. To avoid spoilers, we will seek to the main function and try to determine the flow of this application from the start. We can seek to the main function in r2 using the s main command. Let's print the disassembly of this function to see the inner workings using the pdf or "print disassembly of function" command. Stackrip binary assembly

This function makes a call to sym.print_intro which we can safely assume prints the welcome message, then calls sym.check_password. Next it tests sym.check_password's return value held in the eax register. If eax is zero, we jump to the failure message we saw earlier. If it's not, we continue to the next line which says good job and calls the sym.print_flag function. The goal appears to be to get sym.check_password to return 1.

We'll seek to sym.check_password and see what's going on inside by running s sym.check_password and pdf. Stackrip password check binary assembly

It calls fgets to read user input into ebx - 0x107. It then writes a null-terminator to the value of a strlen call offset by ebx - 0x107. This is same offset holding our fgets result. These instructions appear to swap the final character of our fgets string, which is always a newline, with a null terminator.

Lastly, this function calls strcmp with two arguments: our newline-stripped fgets input and a reference to an object called the_password. Radare2 helpfully displays the ascii representation of this value and we can see it's the string we saw earlier: "this_is_not_a_password". I'm starting to suspect this may be the password.

Our function ends by checking if the result of strcmp is 0. If it is, it sets eax to 1, otherwise it sets eax to 0. As we learned earlier, our goal is to have this function set eax to 1. This proves the password must be "this_is_not_a_password".

Running the binary with the new password flag confirms this is true. Stackrip binary crackme solution

We've already beaten this trivial crackme, but let's take a look at how the flag is provided for extra credit. If we seek and print sym.print_flag we can see the function calls malloc with a 255 byte buffer. It then manually moves little endian ordered bytes onto this buffer as 5 separate dwords. Each dword chunk is a subset of our flag. Finally it pushes the address of the malloc buffer onto the stack and calls puts. The function finally frees the pointer and returns. Stackrip print flag dissasembly