Welcome back to the radare2 reversing tutorials. If you’ve missed the previous parts, you can find them here and here.
Last time we’ve used the rabin2 application to view the strings found inside the challenge01 binary to find password candidates. Based on the results we looked into the assembly to find the correct password. In this post, we’ll go through the next challenge and try out some of the features provided by radare2.
As recommended by the developers, I strongly suggest that you update your radare2 installation regularly, as it gets new features and bugfixes nearly every day.
Today, we’ll have a look into some of the visualization features of radare2.
First of all, you may have been noticed that the radare2 output is rather colorful. If the current colorscheme does not fit your needs, you’ll have multiple options to modify it. Several configuration parameters exist under the scr configuration space. You can view the different settings by using e?scr:
scr.atport: V@ starts a background http server and spawns an r2 -C
scr.color: Enable colors
scr.color.bytes: Colorize bytes that represent the opcodes of the instruction
scr.color.ops: Colorize numbers and registers in opcodes
scr.columns: Force console column count (width)
scr.echo: Show rcons output in realtime to stderr and buffer
scr.feedback: Set visual feedback level (1=arrow on jump, 2=every key (useful for videos))
scr.fgets: Use fgets() instead of dietline for prompt input
scr.fix_columns: Workaround for Prompt iOS SSH client
scr.fix_rows: Workaround for Linux TTY
scr.fps: Show FPS in Visual
scr.highlight: Highlight that word at RCons level
scr.histsave: Always save history on exit
scr.html: Disassembly uses HTML syntax
scr.interactive: Start in interactive mode
scr.nkey: Select the seek mode in visual
scr.null: Show no output
scr.pager: Select pager program (when output overflows the window)
scr.pipecolor: Enable colors when using pipes
scr.prompt: Show user prompt (used by r2 -q)
scr.promptfile: Show user prompt file (used by r2 -q)
scr.promptflag: Show flag name in the prompt
scr.promptsect: Show section name in the prompt
scr.randpal: Random color palete or just get the next one from 'eco'
scr.responsive: Auto-adjust Visual depending on screen (e.g. unset asm.bytes)
scr.rgbcolor: Use RGB colors (not available on Windows)
scr.rows: Force console row count (height) (duplicate?)
scr.seek: Seek to the specified address on startup
scr.tee: Pipe output to file of this name
scr.truecolor: Manage color palette (0: ansi 16, 1: 256, 2: 16M)
scr.utf8: Show UTF-8 characters instead of ANSI
scr.wheel: Mouse wheel in Visual; temporaryly disable/reenable by right click/Enter)
scr.wheelnkey: Use sn/sp and scr.nkey on wheel instead of scroll
scr.wheelspeed: Mouse wheel speed
For example if you have a terminal supporting utf8 characters, you might want to enable scr.utf8:
Before:
;-- main:
/ (fcn) sym.main 133
| ; var int local_118h @ rbp-0x118
| ; var int local_110h @ rbp-0x110
| ; var int local_104h @ rbp-0x104
| ; var int local_100h @ rbp-0x100
| ; var int local_1h @ rbp-0x1
| ; UNKNOWN XREF from 0x00400470 (unk)
| ; DATA XREF from 0x0040073d (entry0)
| 0x004008e3 55 push rbp
| 0x004008e4 4889e5 mov rbp, rsp
| 0x004008e7 4881ec200100. sub rsp, 0x120
| 0x004008ee 89bdfcfeffff mov dword [rbp - local_104h], edi
| 0x004008f4 4889b5f0feff. mov qword [rbp - local_110h], rsi
| 0x004008fb 488995e8feff. mov qword [rbp - local_118h], rdx
| 0x00400902 b800000000 mov eax, 0
| 0x00400907 e80affffff call sym.banner
| 0x0040090c bf9d0a4000 mov edi, str.Enter_Password: ; "Enter Password: " @ 0x400a9d
| 0x00400911 b800000000 mov eax, 0
| 0x00400916 e8e5fdffff call sym.imp.printf
| 0x0040091b 488d8500ffff. lea rax, [rbp - local_100h]
| 0x00400922 4889c6 mov rsi, rax
| 0x00400925 bfae0a4000 mov edi, str._255s ; "%255s" @ 0x400aae
| 0x0040092a b800000000 mov eax, 0
| 0x0040092f e8dcfdffff call sym.imp.__isoc99_scanf
| 0x00400934 c645ff00 mov byte [rbp - local_1h], 0
| 0x00400938 488d8500ffff. lea rax, [rbp - local_100h]
| 0x0040093f 4889c7 mov rdi, rax
| 0x00400942 e808ffffff call sym.checkPassword
| 0x00400947 84c0 test al, al
| ,=< 0x00400949 740c je 0x400957
| | 0x0040094b bfb40a4000 mov edi, str.Password_accepted_ ; "Password accepted!" @ 0x400ab4
| | 0x00400950 e88bfdffff call sym.imp.puts
| ,==< 0x00400955 eb0a jmp 0x400961
| |`-> 0x00400957 bfc70a4000 mov edi, str.Wrong_ ; "Wrong!" @ 0x400ac7
| | 0x0040095c e87ffdffff call sym.imp.puts
| | ; JMP XREF from 0x00400955 (sym.main)
| `--> 0x00400961 b800000000 mov eax, 0
| 0x00400966 c9 leave
\ 0x00400967 c3 ret
After e scr.utf8=true:
;-- main:
╒ (fcn) sym.main 133
│ ; var int local_118h @ rbp-0x118
│ ; var int local_110h @ rbp-0x110
│ ; var int local_104h @ rbp-0x104
│ ; var int local_100h @ rbp-0x100
│ ; var int local_1h @ rbp-0x1
│ ; UNKNOWN XREF from 0x00400470 (unk)
│ ; DATA XREF from 0x0040073d (entry0)
│ 0x004008e3 55 push rbp
│ 0x004008e4 4889e5 mov rbp, rsp
│ 0x004008e7 4881ec200100. sub rsp, 0x120
│ 0x004008ee 89bdfcfeffff mov dword [rbp - local_104h], edi
│ 0x004008f4 4889b5f0feff. mov qword [rbp - local_110h], rsi
│ 0x004008fb 488995e8feff. mov qword [rbp - local_118h], rdx
│ 0x00400902 b800000000 mov eax, 0
│ 0x00400907 e80affffff call sym.banner
│ 0x0040090c bf9d0a4000 mov edi, str.Enter_Password: ; "Enter Password: " @ 0x400a9d
│ 0x00400911 b800000000 mov eax, 0
│ 0x00400916 e8e5fdffff call sym.imp.printf
│ 0x0040091b 488d8500ffff. lea rax, [rbp - local_100h]
│ 0x00400922 4889c6 mov rsi, rax
│ 0x00400925 bfae0a4000 mov edi, str._255s ; "%255s" @ 0x400aae
│ 0x0040092a b800000000 mov eax, 0
│ 0x0040092f e8dcfdffff call sym.imp.__isoc99_scanf
│ 0x00400934 c645ff00 mov byte [rbp - local_1h], 0
│ 0x00400938 488d8500ffff. lea rax, [rbp - local_100h]
│ 0x0040093f 4889c7 mov rdi, rax
│ 0x00400942 e808ffffff call sym.checkPassword
│ 0x00400947 84c0 test al, al
│ ┌─< 0x00400949 740c je 0x400957
│ │ 0x0040094b bfb40a4000 mov edi, str.Password_accepted_ ; "Password accepted!" @ 0x400ab4
│ │ 0x00400950 e88bfdffff call sym.imp.puts
│ ┌──< 0x00400955 eb0a jmp 0x400961
│ │└─> 0x00400957 bfc70a4000 mov edi, str.Wrong_ ; "Wrong!" @ 0x400ac7
│ │ 0x0040095c e87ffdffff call sym.imp.puts
│ │ ; JMP XREF from 0x00400955 (sym.main)
│ └──> 0x00400961 b800000000 mov eax, 0
│ 0x00400966 c9 leave
╘ 0x00400967 c3 ret
Currently, it mainly affects the drawn ascii arrows on the left hand side of the disassemblies. Of course you can also disable the coloring entirely by executing e scr.color = false
(entirely), e scr.color.bytes = false
(only the bytes in the disassembly) or e scr.color.ops = false
(only the opcodes). If you like colors, but not the current scheme, you can choose another one by using the ec commands. One option is to choose one of the predefined themes, for example eco solarized
:
;-- main:
╒ (fcn) sym.main 133
│ ; var int local_118h @ rbp-0x118
│ ; var int local_110h @ rbp-0x110
│ ; var int local_104h @ rbp-0x104
│ ; var int local_100h @ rbp-0x100
│ ; var int local_1h @ rbp-0x1
│ ; UNKNOWN XREF from 0x00400470 (unk)
│ ; DATA XREF from 0x0040073d (entry0)
│ 0x004008e3 55 push rbp
│ 0x004008e4 4889e5 mov rbp, rsp
│ 0x004008e7 4881ec200100. sub rsp, 0x120
│ 0x004008ee 89bdfcfeffff mov dword [rbp - local_104h], edi
│ 0x004008f4 4889b5f0feff. mov qword [rbp - local_110h], rsi
│ 0x004008fb 488995e8feff. mov qword [rbp - local_118h], rdx
│ 0x00400902 b800000000 mov eax, 0
│ 0x00400907 e80affffff call sym.banner
│ 0x0040090c bf9d0a4000 mov edi, str.Enter_Password: ; "Enter Password: " @ 0x400a9d
│ 0x00400911 b800000000 mov eax, 0
│ 0x00400916 e8e5fdffff call sym.imp.printf
│ 0x0040091b 488d8500ffff. lea rax, [rbp - local_100h]
│ 0x00400922 4889c6 mov rsi, rax
│ 0x00400925 bfae0a4000 mov edi, str._255s ; "%255s" @ 0x400aae
│ 0x0040092a b800000000 mov eax, 0
│ 0x0040092f e8dcfdffff call sym.imp.__isoc99_scanf
│ 0x00400934 c645ff00 mov byte [rbp - local_1h], 0
│ 0x00400938 488d8500ffff. lea rax, [rbp - local_100h]
│ 0x0040093f 4889c7 mov rdi, rax
│ 0x00400942 e808ffffff call sym.checkPassword
│ 0x00400947 84c0 test al, al
│ ┌─< 0x00400949 740c je 0x400957
│ │ 0x0040094b bfb40a4000 mov edi, str.Password_accepted_ ; "Password accepted!" @ 0x400ab4
│ │ 0x00400950 e88bfdffff call sym.imp.puts
│ ┌──< 0x00400955 eb0a jmp 0x400961
│ │└─> 0x00400957 bfc70a4000 mov edi, str.Wrong_ ; "Wrong!" @ 0x400ac7
│ │ 0x0040095c e87ffdffff call sym.imp.puts
│ │ ; JMP XREF from 0x00400955 (sym.main)
│ └──> 0x00400961 b800000000 mov eax, 0
│ 0x00400966 c9 leave
╘ 0x00400967 c3 ret
(use eco
without any parameters for a complete list). If this does not fits your need, you could also use random colors (ecr
) or set the colors yourself (ec
for a list of all available keys and ec <key> <frontcolor> (<backcolor>)
eg. ec comment rgb:ffff00 blue
(use ecs
for supported colors)).
Besides the coloring, you’ll find various settings which affect the output of the disassembly in the asm configuration space. They allow you, for example, to customize the spacing between the different metadata of the disassembly (flow arrows, bytes, addresses, opcodes, comments,…) and also turning them on and off.
After you found some pleasing settings, let’s start with the current binary (btw, you can store option commands in the $HOME/.radare2rc file for persistence). After loading the binary into radare2 (r2 -AA ./challenge02
), you should find yourself at the address 0x00400720. In case of some of you are wondering about why we are placed here and not at the main function, we’ll use different ways to get a clue. Assuming that we are currently placed inside or at the beginning of a function we could use the afi
command to give us some information about the function we are placed in:
[0x00400720]> afi
#
offset: 0x00400720
name: entry0
size: 43
realsz: 43
stackframe: 0
call-convention: @�adAV
cyclomatic-complexity: 1
bits: 64
type: fcn [NEW]
num-bbs: 1
edges: 0
end-bbs: 1
call-refs:
data-refs: 0x004009e0 0x00400970 0x004008e3 0x00600ff0
code-xrefs:
data-xrefs:
locals:0
args: 0
diff: type: new
As you may notice, besides the other different information, we get the name of the current function which is entry0. This strongly indicates that we are currently placed at the entrypoint of the application, which typically sets some things up (like parsing the commandline, retrieving environment variables etc.) and calls the actual main function afterwards. To check this assumption, we’ll use the ie
command to get the address of the entry point as specified in the file headers (elf64 in this case):
[0x00400720]> ie
[Entrypoints]
vaddr=0x00400720 paddr=0x00000720 baddr=0x00400000 laddr=0x00000000 type=program
1 entrypoints
As you can see, the vaddr (virtual address) is matching the current position, so we are actually placed at the beginning of the entry point function.
Sidenote: the virtual address is most of the time a combination of the physical address (paddr; position in the binary) and the base address (baddr).
Let’s take a look at the disassembly of this function:
;-- section_end..plt:
;-- section..text:
;-- _start:
╒ (fcn) entry0 43
│ ; UNKNOWN XREF from 0x00400440 (unk)
│ 0x00400720 31ed xor ebp, ebp ; [13] va=0x00400720 pa=0x00000720 sz=706 vsz=706 rwx=--r-x .text
│ 0x00400722 4989d1 mov r9, rdx
│ 0x00400725 5e pop rsi
│ 0x00400726 4889e2 mov rdx, rsp
│ 0x00400729 4883e4f0 and rsp, 0xfffffffffffffff0
│ 0x0040072d 50 push rax
│ 0x0040072e 54 push rsp
│ 0x0040072f 49c7c0e00940. mov r8, sym.__libc_csu_fini ; sym.__libc_csu_fini
│ 0x00400736 48c7c1700940. mov rcx, sym.__libc_csu_init ; "AWAVA..AUATL.%.. " @ 0x400970
│ 0x0040073d 48c7c7e30840. mov rdi, sym.main ; "UH..H.. ." @ 0x4008e3
│ 0x00400744 ff15a6082000 call qword [rip + 0x2008a6] ; [0x600ff0:8]=0 LEA reloc.__libc_start_main_240 ; reloc.__libc_start_main_240
╘ 0x0040074a f4 hlt
After some stack setup between lines 0x400720 and 0x400729, some function is called in line 0x400744 with the main function as the first parameter (mov rdi, sym.main) besides some other parameters. As you can see, the call is using a process counter relative addressing. This technique is typically used to produce position independent code (PIC) which is needed for the ASLR technique. As the rip is pointing to the next address to be executed, it will contain the address 0x40074a. This results in the address 0x600ff0 (to calculate in radare2: ? 0x40074a+0x2008a6
).
If we look into the sections of the binary (iS
) we’ll see that this address belongs to the the .got section. This section contains the global object table (GOT) and is filled by the linker during load. It’s used as a jump table for library functions. Radare2 is also able to parse the relocation tables to tell us which functions will be mapped at runtime (ir
command):
[Relocations]
vaddr=0x00600ff0 paddr=0x00000ff0 type=SET_64 __libc_start_main
vaddr=0x00600ff8 paddr=0x00000ff8 type=SET_64 __gmon_start__
vaddr=0x00601018 paddr=0x00001018 type=SET_64 puts
vaddr=0x00601020 paddr=0x00001020 type=SET_64 strlen
vaddr=0x00601028 paddr=0x00001028 type=SET_64 printf
vaddr=0x00601030 paddr=0x00001030 type=SET_64 __isoc99_scanf
6 relocations
Therefore the __libc_start_main function will be called at runtime :)… Some of you may already noticed that radare is smart enough to provide this information to us in the comment at the call line ;).
Radare is also able to replace the mnemonic RIP-relative addressing with the target function name. To get this, you’ve to enable the asm.relsub
option:
call qword [reloc.__libc_start_main_240] ; [0x600ff0:8]=0 LEA reloc.__libc_start_main_240 ; reloc.__libc_start_main_240
As the __libc_start_main function is a standard library function provided by the linux library =libc, we’ll jump directly into the main function (s sym.main
).
This time, we’ll try to get a fast overview of the referenced flags in this function (strings, global variables, functions, etc.). To get a graph of the references, we have two options. The first one is to generate a graphviz graph by using agc $$ | xdot -
($$
is a variable containing the current address, sym.main in this case):
The other option would be the ascii graph in the visual mode (we’ll come to this later) which you’ll get by executing VV
and then pressing >
. As this looks very similar to the last challenge, we could directly look into the checkPassword function. You can do this either by seeking to it (s sym.checkPassword
) or in the visual graph view by pressing the tab
key until the function is marked. After pressing q
, you’ll be placed inside the function (press q
multiple times if you want to return to the main interface).
As we want to take a look into some of the visual modes of radere2, we’ll stay in the graph mode this time (either by pressing q
only once or by entering VV
).
[0x0040084f]> VV @ sym.checkPassword (nodes 9 edges 11 zoom 100%) BB-NORM mouse:canvas-y movements-speed:5
=---------------------------------------------=
| 0x40084f |
| (fcn) sym.checkPassword 148 |
| ; var int local_28h @ rbp-0x28 |
| ; var int local_1ch @ rbp-0x1c |
| ; var int local_18h @ rbp-0x18 |
| ; var int local_14h @ rbp-0x14 |
| ; var int local_10h @ rbp-0x10 |
| ; var int local_4h @ rbp-0x4 |
| push rbp |
| mov rbp, rsp |
| sub rsp, 0x30 |
| mov qword [rbp - local_28h], rdi |
| mov qword [rbp - local_10h], str.Sup3rP4ss |
| mov rax, qword [rbp - local_10h] |
| mov rdi, rax |
| call sym.imp.strlen ;[a] |
| mov dword [rbp - local_14h], eax |
| mov rax, qword [rbp - local_28h] |
| mov rdi, rax |
| call sym.imp.strlen ;[a] |
| mov dword [rbp - local_18h], eax |
| mov eax, dword [rbp - local_14h] |
| cmp eax, dword [rbp - local_18h] |
| je 0x400890 ;[b] |
=---------------------------------------------=
t f
.------------' '------------------------------.
| |
| |
=-----------------------------------= =--------------------=
| 0x400890 | | 0x400889 |
| mov eax, dword [rbp - local_14h] | | mov eax, 0 |
| mov dword [rbp - local_1ch], eax | | jmp 0x4008e1 ;[f] |
| mov dword [rbp - local_4h], 0 | =--------------------=
| jmp 0x4008d4 ;[c] | v
=-----------------------------------= |
v '------.
| |
| |
.----------------. |
=-----------------------------------= |
| 0x4008d4 | | |
| mov eax, dword [rbp - local_4h] | |
| cmp eax, dword [rbp - local_1ch] | |
| jl 0x40089f ;[d] | | |
=-----------------------------------= |
t f | |
.--------------------' '------------|-------------. |
| | | |
| | | |
=-----------------------------------= | =--------------------= |
| 0x40089f | | | 0x4008dc | |
| mov eax, dword [rbp - local_4h] | | | mov eax, 1 | |
| movsxd rdx, eax | | =--------------------= |
| mov rax, qword [rbp - local_28h] | | v |
| add rax, rdx | | | |
| movzx edx, byte [rax] | | | |
| mov eax, dword [rbp - local_1ch] | | | |
| sub eax, 1 | | | |
| sub eax, dword [rbp - local_4h] | | | |
| movsxd rcx, eax | | | |
| mov rax, qword [rbp - local_10h] | | | |
| add rax, rcx | | '-----------------. |
| movzx eax, byte [rax] | | | |
| cmp dl, al | | | |
| je 0x4008d0 ;[e] | | | |
=-----------------------------------= | | |
f t | | |
'---.-------------------------. | | |
| | | | |
| | | | |
=--------------------= =-------------------------------= | |
| 0x4008c9 | | 0x4008d0 | | |
| mov eax, 0 | | add dword [rbp - local_4h], 1 | | |
| jmp 0x4008e1 ;[f] | =-------------------------------= | |
=--------------------= `-----' | |
v | |
'-------------------------------------.-----------------------------'
|
|
=--------------------=
| 0x4008e1 |
| leave |
| ret |
=--------------------=
This graph is available in different versions, which can be cycled through by pressing p
. The view we’ll focus on this time is the minimap view. You’ll get this view by pressing p
until you’ll see a BB-SMALL in the first line of the output. It should look like this:
[0x0040084f]> VV @ sym.checkPassword (nodes 9 edges 11 zoom 100%) BB-SMALL mouse:canvas-y movements-speed:5
0x40084f:
(fcn) sym.checkPassword 148
; var int local_28h @ rbp-0x28
; var int local_1ch @ rbp-0x1c
; var int local_18h @ rbp-0x18
; var int local_14h @ rbp-0x14
; var int local_10h @ rbp-0x10
; var int local_4h @ rbp-0x4
push rbp
mov rbp, rsp
sub rsp, 0x30
mov qword [rbp - local_28h], rdi
mov qword [rbp - local_10h], str.Sup3rP4ss
mov rax, qword [rbp - local_10h]
mov rdi, rax
call sym.imp.strlen ;[a] <@@@@@@>
mov dword [rbp - local_14h], eax t f
mov rax, qword [rbp - local_28h] .---------' '---------.
mov rdi, rax | |
call sym.imp.strlen ;[a] | |
mov dword [rbp - local_18h], eax [_0890_] [_0889_]
mov eax, dword [rbp - local_14h] v v
cmp eax, dword [rbp - local_18h] | |
je 0x400890 ;[b] | |
. |
[_08d4_] |
| t f |
.-------|-' '---------. |
| | | |
| | | |
[_089f_] | [_08dc_] |
f t | v |
.------' '--.| '---. |
| || | |
| || | |
[_08c9_] [_08d0_] | |
v `--' | |
'---------------------.--------------'
|
|
This view shows you a minimap in the center and the current selected node as a disassembly on the upper left (the current node is marked with @@@@@@). This view helps us to focus on smaller blocks of a function, whilst always having the overview about the execution flow.
As we are now starting with reading the assembler code, I want to show you another nice feature for those of you who aren’t able to speak fluent assembler. Radare2 has an option to replace the assembler code by a pseudo code which is simpler to read (but sometimes not as detailed). To enable this feature, press :
(colon, to open main shell) and enter e asm.pseudo = true
. After leaving the shell again (press enter) you should see something like this:
(fcn) sym.checkPassword 148
; var int local_28h @ rbp-0x28
; var int local_1ch @ rbp-0x1c
; var int local_18h @ rbp-0x18
; var int local_14h @ rbp-0x14
; var int local_10h @ rbp-0x10
; var int local_4h @ rbp-0x4
push rbp
rbp = rsp
rsp -= 0x30
qword [rbp - local_28h] = rdi
qword [rbp - local_10h] = str.Sup3rP4ss
rax = qword [rbp - local_10h]
rdi = rax
sym.imp.strlen ()
dword [rbp - local_14h] = eax
rax = qword [rbp - local_28h]
rdi = rax
sym.imp.strlen ()
dword [rbp - local_18h] = eax
eax = dword [rbp - local_14h]
if (eax == dword [rbp - local_18h]
isZero 0x400890)
The first three lines are a typical function prologue. They create a backup of the rbp and reserve 48 bytes of memory on the stack. As you may noticed, radare2 displays constant values per default in a hexadecimal representation. Similar to IDA Pro, we are able to tell radare how we want to see a value at a specific position. Sadly it isn’t easily available in the graph view (yet?), but we can set it manually in the main shell. First we need the address of the the opcodes containing the value we want to change. We can get it for example by using pd 3
(which disassembles 3 opcodes from the current position). This would result in 0x00400853. To tell radare that we want to get the value in decimal, we enter ahi d @ 0x00400853
. This stands for analysis hints intermediate, decimal at the address 0x00400853.
As we know from the last challenge, the function checkPassword gets one argument and returns if it contains the correct password. Currently, radare2 not detected that the function has an argument. Let’s tell it to do so. The command afvr rdi password const char*
tells radare2 that the rdi register is used at an argument with the name password of the type const char* (based on the System V AMD64 ABI). This means that the local variable local_28h is a pointer to the entered password. Therefore we change the name and the type of the variable (afvn local_28h password_1
and afvbt password_1 const char*
). The next line indicates that local_10h points to the string we compare with (I rename it to targetPassword). As rdi is always used in the amd64 ABI as the first argument, it becomes confusing as it is now aliased to password in the function disassembly. That’s why I prefer to leave it after I followed the dataflow (remove the alias by afvr- password
).
This means that the next 8 lines calculate the length of the different strings and stores them in local_14h and local_18h respectively. Your current disassembly should look now close to this:
(fcn) sym.checkPassword 148
; var const char* password_1 @ rbp-0x28
; var int local_1ch @ rbp-0x1c
; var int inputPwdLength @ rbp-0x18
; var int targetPwdLength @ rbp-0x14
; var const char* targetPassword @ rbp-0x10
; var int local_4h @ rbp-0x4
; UNKNOWN XREF from 0x004004b8 (unk)
; CALL XREF from 0x00400942 (sym.main)
push rbp
rbp = rsp
rsp -= 48
qword [rbp - password_1] = rdi
qword [rbp - targetPassword] = str.Sup3rP4ss
rax = qword [rbp - targetPassword]
rdi = rax
sym.imp.strlen ()
dword [rbp - targetPwdLength] = eax
rax = qword [rbp - password_1]
rdi = rax
sym.imp.strlen ()
dword [rbp - inputPwdLength] = eax
eax = dword [rbp - targetPwdLength]
if (eax == dword [rbp - inputPwdLength]
isZero 0x400890)
If we directly translate what we have so far, we get:
int checkPassword(const char* password) {
const char* password_1 = password;
const char* targetPassword = "Sup3rP4ss";
int targetPwdLength = strlen(targetPassword);
int inputPwdLength = strlen(password_1);
if(targetPwdLength == inputPwdLength) {
Let’s check what happens if the both lengths are not equal (you might already have a strong guess based on the minimap 😉 ). In the graph view you can simply press f or t to follow the true or the false branch. Pressing f brings us to a very small node. This node simply sets eax to zero and then jumps to 0x4008e1. To follow this jump, you can press g and the character(s) behind this jump (f in my case). What now happened in my current version of radare2 is that I’ve got an empty disassembly in the top left. This is one of the situations where the pseudo syntax misses some instructions. After disabling it (e asm.pseudo = false
), you’ll see the instructions leave and ret. Those instructions tell the CPU to restore the stack based on the stored registers and jump back to the code which called the current function. In combination with the previous eax line, we have:
int checkPassword(const char* password) {
const char* password_1 = password;
const char* targetPassword = "Sup3rP4ss";
int targetPwdLength = strlen(targetPassword);
int inputPwdLength = strlen(password_1);
if(targetPwdLength == inputPwdLength) {
} else {
return 0;
}
}
Current state: 3 blocks decompiled (0x4084f, 0x40889 and 0x408e1).
Let’s continue with the next one (the true case of the comparison).
Sidenote: You can always navigate through the blocks by using tab and TAB (shift+tab)
This block consists of three instructions which take a copy of the targetPwdLength variable and set another variable to zero. This is a typical intro of a loop which sets up the counter variable(s). If we take a look on the minimap, we can see that this is actually the intro to a loop (this block jumps to block 0x4008d4 and block 0x408d0 also jumps back to block 0x4008d4). So let’s rename the variables accordingly (another block finished 🙂 ).
The next one is again a small one. It compares the counter variable to the copy of the length. If it is lower, it seems to continue with another check (based on the minimap) if not, it goes to a block which ends up in the function epilog. I’ll go with this one first as it seems to be a quick one… It stores the value on into eax, which means that the function returns true. Our current decompiled code:
int checkPassword(const char* password) {
const char* password_1 = password;
const char* targetPassword = "Sup3rP4ss";
int targetPwdLength = strlen(targetPassword);
int inputPwdLength = strlen(password_1);
if(targetPwdLength == inputPwdLength) {
int counter = 0;
int length = targetPwdLength;
while(counter < length) {
}
return 1;
} else {
return 0;
}
}
The true block is a larger one with some calculations and one comparison:
eax = dword [rbp - counter]
rdx = eax
rax = qword [rbp - password_1]
rax += rdx
edx = byte [rax]
eax = dword [rbp - length]
eax -= 1
eax -= dword [rbp - counter]
rcx = eax
rax = qword [rbp - targetPassword]
rax += rcx
eax = byte [rax]
if (dl == al
isZero 0x4008d0)
The first five lines store the character from the password_1 array at a position based on the current counter value. The next lines simply takes a character from the targetPassword array, but this time counting from the end. Those values are then compared for equality. Again, let’s check the inequality case: zero is stored in eax and the function returns false. The true case simply increments the counter by one and starts over at the block 0x408d4.
We are now able to decompile all blocks of the function:
int checkPassword(const char* password) {
const char* password_1 = password;
const char* targetPassword = "Sup3rP4ss";
int targetPwdLength = strlen(targetPassword);
int inputPwdLength = strlen(password_1);
if(targetPwdLength == inputPwdLength) {
int counter = 0;
int length = targetPwdLength;
while(counter < length) {
if(password_1[counter] == targetPassword[length-1-counter]) {
counter += 1;
} else {
return 0;
}
}
return 1;
} else {
return 0;
}
}
Or after some manual reordering:
int checkPassword(const char* password) {
const char* targetPassword = "Sup3rP4ss";
int targetPwdLength = strlen(targetPassword);
int inputPwdLength = strlen(password);
if(targetPwdLength != inputPwdLength) {
return 0;
}
for(int counter = 0; counter < targetPwdLength; counter++) {
if(password[counter] != targetPassword[targetPwdLength - 1 - counter])
return 0;
}
return 1;
}
Have a clue what this function does?
- It checks if the entered password is as long as the required password
- It compares the entered password with the reverse of “Sup3rP4ss”
This means our required password is “ss4Pr3puS” :
$ ./challenge02
##################################
# Challenge 2 #
# #
# (c) 2016 Timo Schmid #
##################################
Enter Password: ss4Pr3puS
Password accepted!
That’s it!
The last command I would like to show you today is the experimental pseudo code feature:
[0x004008d0]> pdc
function sym.checkPassword () {
loc_0x40084f:
; UNKNOWN XREF from 0x004004b8 (unk)
; CALL XREF from 0x00400942 (sym.main)
push rbp
rbp = rsp
rsp -= 0x30
qword [rbp - password_1] = password
qword [rbp - targetPassword] = 0x400a93
rax = qword [rbp - targetPassword]
password = rax
0x4006f0 () ; sym.imp.strlen
dword [rbp - targetPwdLength] = eax
rax = qword [rbp - password_1]
password = rax
0x4006f0 () ; sym.imp.strlen
dword [rbp - inputPwdLength] = eax
eax = dword [rbp - targetPwdLength]
if (eax == dword [rbp - inputPwdLength]
isZero 0x400890) {
loc_0x400890:
eax = dword [rbp - targetPwdLength]
dword [rbp - length] = eax
dword [rbp - counter] = 0
goto 0x4008d4
do {
loc_0x4008d4:
; JMP XREF from 0x0040089d (sym.checkPassword)
eax = dword [rbp - counter]
if (eax == dword [rbp - length]
jl 0x40089f
} while (?);
} while (?);
}
return;
}
It’s currently very incomplete in terms of instruction coverage, but it shows where radare2 features might go in the future (there was a GSoC running which started to create a radare2 based decompiler named radeco).
Challenge 0x03
You will find the next challenge here (linux64 dynamically linked, linux64 statically linked, win64):
https://github.com/bluec0re/reversing-radare2/tree/master/sources/challenge03
MD5 (challenge03) = 748f98ced3ca0b9178f8d85cdcc9d750
MD5 (challenge03.exe) = 2a1dde4f0de546216779394ce8960aa5
MD5 (challenge03-static) = 8c48ac034d6cefe135e9f22d6431e930
SHA1 (challenge03) = 0a2317bfb952f64dcd8ca50d2a084275cf8e7816
SHA1 (challenge03.exe) = 1c00a49d1c2249cb528a06c08cce1d15b062f59c
SHA1 (challenge03-static) = 78fcffdbe56d1cea29723df30dc14dfe6baf101f
SHA256 (challenge03) = 00f7662f22a3d305575a0117ea17b8d7801c133ffa2201986d62ebade65bdbf7
SHA256 (challenge03.exe) = 0c4a760a4a8653c4a26d07e0970a3ef7474cbdb53596166a670f5c08453eb57e
SHA256 (challenge03-static) = cbea4018b2451e84b789bd909f892f77486e8d42aee659e175da942a555c97ae
The goal is again to find the correct password for the login into the binary. Next time, I’ll give you a walkthrough for the third challenge and challenge #4.
Happy reversing!
Best,
Timo
@bluec0re
Oh yes! Let’s play with the new challenge!
It was so fun reversing the #2 !
<3 Thank's 🙂