Reverse engineering practice: revv
This evening I am taking a look at a reverse engineering challenge that should not be too difficult. Reverse engineering is one of those skills that I find I don’t often get an opportunity to practice and have considered myself a novice in for some time. So to that end, let’s get some practice in.
Binary: revv | Author: 0xtamsee1
$ file revv
revv: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=880705483a490610c428f5ec2d3dcb3a3bde0218, for GNU/Linux 3.2.0, stripped
Running the application provides the following output.
$ ./revv
Enter the Password: TestThisMess
Length mismatch! :(
Interestingly the output appears to give us some info around the checking criteria as it lets us know there is a length mismatch. Just for fun let’s see if we can determine what input length it is looking for before we open up the binary.
$ for i in {1..30}; do echo -n "$i: "; python -c "print('#'*$i)" | ./revv; done
1: Enter the Password: Length mismatch! :(
2: Enter the Password: Length mismatch! :(
3: Enter the Password: Length mismatch! :(
4: Enter the Password: Length mismatch! :(
5: Enter the Password: Length mismatch! :(
6: Enter the Password: Length mismatch! :(
7: Enter the Password: Length mismatch! :(
8: Enter the Password: Length mismatch! :(
9: Enter the Password: Length mismatch! :(
10: Enter the Password: Length mismatch! :(
11: Enter the Password: Length mismatch! :(
12: Enter the Password: Length mismatch! :(
13: Enter the Password: Length mismatch! :(
14: Enter the Password: Length mismatch! :(
15: Enter the Password: Length mismatch! :(
16: Enter the Password: Length mismatch! :(
17: Enter the Password: Length mismatch! :(
18: Enter the Password: Length mismatch! :(
19: Enter the Password: Length mismatch! :(
20: Enter the Password: Length mismatch! :(
21: Enter the Password: Come on, Try Again!! :(
22: Enter the Password: Length mismatch! :(
23: Enter the Password: Length mismatch! :(
24: Enter the Password: Length mismatch! :(
25: Enter the Password: Length mismatch! :(
26: Enter the Password: Don't try to hack me :D
27: Enter the Password: Don't try to hack me :D
28: Enter the Password: Don't try to hack me :D
29: Enter the Password: Don't try to hack me :D
30: Enter the Password: Don't try to hack me :D
Looks like the binary is expecting a password that is 21 characters in length. Just as a note, in a situation with more complex code having additional information like the expected password length could help us locate code sections when we dive into the binary. Presented with the opportunity to learn additional info, we might as well take it.
Let’s open our challenge binary up in radare2, the (-AAA) argument analyzes reference code and will give us more information while looking at the assembly.
$ radare2 -AAA ./revv
[0x000010e0]> pdf @ main
; DATA XREF from entry0 @ 0x1101
┌ 116: main ();
│ ; var int64_t var_3f0h @ rbp-0x3f0
│ ; var int64_t var_8h @ rbp-0x8
│ 0x000013ba f30f1efa endbr64
│ 0x000013be 55 push rbp
│ 0x000013bf 4889e5 mov rbp, rsp
│ 0x000013c2 4881ecf00300. sub rsp, 0x3f0
│ 0x000013c9 64488b042528. mov rax, qword fs:[0x28]
│ 0x000013d2 488945f8 mov qword [var_8h], rax
│ 0x000013d6 31c0 xor eax, eax
│ 0x000013d8 488d3d760c00. lea rdi, str.Enter_the_Password:_ ; 0x2055 ; "Enter the Password: "
│ 0x000013df b800000000 mov eax, 0
│ 0x000013e4 e8d7fcffff call sym.imp.printf
│ 0x000013e9 488d8510fcff. lea rax, [var_3f0h]
│ 0x000013f0 4889c6 mov rsi, rax
│ 0x000013f3 488d3d700c00. lea rdi, str._30s ; 0x206a ; "%30s"
│ 0x000013fa b800000000 mov eax, 0
│ 0x000013ff e8ccfcffff call sym.imp.__isoc99_scanf
│ 0x00001404 488d8510fcff. lea rax, [var_3f0h]
│ 0x0000140b 4889c7 mov rdi, rax
│ 0x0000140e e8b6fdffff call fcn.000011c9
│ 0x00001413 b800000000 mov eax, 0
│ 0x00001418 488b55f8 mov rdx, qword [var_8h]
│ 0x0000141c 644833142528. xor rdx, qword fs:[0x28]
│ ┌─< 0x00001425 7405 je 0x142c
│ │ 0x00001427 e884fcffff call sym.imp.__stack_chk_fail
│ │ ; CODE XREF from main @ 0x1425
│ └─> 0x0000142c c9 leave
└ 0x0000142d c3 ret
Disassembling the main function we can see the application sets up 0x000013d8
and print 0x000013e4
the “Enter the password:” prompt. We can also see where it uses scanf 0x000013ff
to read user input and then calls an unknown function call fcn.000011c9
. Given that right after this function call the application performs a check that either jumps or fails it is a pretty safe bet this function contains our validation code.
[0x000010e0]> pdf @ fcn.000011c9
; CALL XREF from main @ 0x140e
┌ 497: fcn.000011c9 (int64_t arg1);
│ ; var int64_t var_8h @ rbp-0x8
│ ; arg int64_t arg1 @ rdi
│ 0x000011c9 f30f1efa endbr64
│ 0x000011cd 55 push rbp
│ 0x000011ce 4889e5 mov rbp, rsp
│ 0x000011d1 4883ec10 sub rsp, 0x10
│ 0x000011d5 48897df8 mov qword [var_8h], rdi ; arg1
│ 0x000011d9 488b45f8 mov rax, qword [var_8h]
│ 0x000011dd 4889c7 mov rdi, rax
│ 0x000011e0 e8bbfeffff call sym.imp.strlen
│ 0x000011e5 4883f819 cmp rax, 0x19
│ ┌─< 0x000011e9 7611 jbe 0x11fc
│ │ 0x000011eb 488d3d120e00. lea rdi, str.Dont_try_to_hack_me_:D ; 0x2004 ; "Don't try to hack me :D"
│ │ 0x000011f2 e899feffff call sym.imp.puts
│ ┌──< 0x000011f7 e9bc010000 jmp 0x13b8
│ ││ ; CODE XREF from fcn.000011c9 @ 0x11e9
│ │└─> 0x000011fc 488b45f8 mov rax, qword [var_8h]
│ │ 0x00001200 4889c7 mov rdi, rax
│ │ 0x00001203 e898feffff call sym.imp.strlen
│ │ 0x00001208 4883f815 cmp rax, 0x15
│ │┌─< 0x0000120c 7411 je 0x121f
│ ││ 0x0000120e 488d3d070e00. lea rdi, str.Length_mismatch__:_ ; 0x201c ; "Length mismatch! :("
│ ││ 0x00001215 e876feffff call sym.imp.puts
│ ┌───< 0x0000121a e999010000 jmp 0x13b8
│ │││ ; CODE XREF from fcn.000011c9 @ 0x120c
│ ││└─> 0x0000121f 488b45f8 mov rax, qword [var_8h]
│ ││ 0x00001223 4883c011 add rax, 0x11
│ ││ 0x00001227 0fb600 movzx eax, byte [rax]
│ ││ 0x0000122a 3c31 cmp al, 0x31
│ ││┌─< 0x0000122c 0f8579010000 jne 0x13ab
│ │││ 0x00001232 488b45f8 mov rax, qword [var_8h]
│ │││ 0x00001236 0fb600 movzx eax, byte [rax]
│ │││ 0x00001239 3c41 cmp al, 0x41
│ ┌────< 0x0000123b 0f856a010000 jne 0x13ab
│ ││││ 0x00001241 488b45f8 mov rax, qword [var_8h]
│ ││││ 0x00001245 4883c001 add rax, 1
│ ││││ 0x00001249 0fb600 movzx eax, byte [rax]
│ ││││ 0x0000124c 3c43 cmp al, 0x43
│ ┌─────< 0x0000124e 0f8557010000 jne 0x13ab
│ │││││ 0x00001254 488b45f8 mov rax, qword [var_8h]
│ │││││ 0x00001258 4883c002 add rax, 2
│ │││││ 0x0000125c 0fb600 movzx eax, byte [rax]
│ │││││ 0x0000125f 3c54 cmp al, 0x54
│ ┌──────< 0x00001261 0f8544010000 jne 0x13ab
│ ││││││ 0x00001267 488b45f8 mov rax, qword [var_8h]
│ ││││││ 0x0000126b 4883c008 add rax, 8
│ ││││││ 0x0000126f 0fb600 movzx eax, byte [rax]
│ ││││││ 0x00001272 3c63 cmp al, 0x63
│ ┌───────< 0x00001274 0f8531010000 jne 0x13ab
│ │││││││ 0x0000127a 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x0000127e 4883c003 add rax, 3
│ │││││││ 0x00001282 0fb600 movzx eax, byte [rax]
│ │││││││ 0x00001285 3c46 cmp al, 0x46
│ ────────< 0x00001287 0f851e010000 jne 0x13ab
│ │││││││ 0x0000128d 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x00001291 4883c00d add rax, 0xd
│ │││││││ 0x00001295 0fb600 movzx eax, byte [rax]
│ │││││││ 0x00001298 3c76 cmp al, 0x76
│ ────────< 0x0000129a 0f850b010000 jne 0x13ab
│ │││││││ 0x000012a0 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x000012a4 4883c005 add rax, 5
│ │││││││ 0x000012a8 0fb600 movzx eax, byte [rax]
│ │││││││ 0x000012ab 3c4e cmp al, 0x4e
│ ────────< 0x000012ad 0f85f8000000 jne 0x13ab
│ │││││││ 0x000012b3 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x000012b7 4883c006 add rax, 6
│ │││││││ 0x000012bb 0fb600 movzx eax, byte [rax]
│ │││││││ 0x000012be 3c30 cmp al, 0x30
│ ────────< 0x000012c0 0f85e5000000 jne 0x13ab
│ │││││││ 0x000012c6 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x000012ca 4883c00b add rax, 0xb
│ │││││││ 0x000012ce 0fb600 movzx eax, byte [rax]
│ │││││││ 0x000012d1 3c52 cmp al, 0x52
│ ────────< 0x000012d3 0f85d2000000 jne 0x13ab
│ │││││││ 0x000012d9 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x000012dd 4883c007 add rax, 7
│ │││││││ 0x000012e1 0fb600 movzx eax, byte [rax]
│ │││││││ 0x000012e4 3c31 cmp al, 0x31
│ ────────< 0x000012e6 0f85bf000000 jne 0x13ab
│ │││││││ 0x000012ec 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x000012f0 4883c00f add rax, 0xf
│ │││││││ 0x000012f4 0fb600 movzx eax, byte [rax]
│ │││││││ 0x000012f7 3c72 cmp al, 0x72
│ ────────< 0x000012f9 0f85ac000000 jne 0x13ab
│ │││││││ 0x000012ff 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x00001303 4883c009 add rax, 9
│ │││││││ 0x00001307 0fb600 movzx eax, byte [rax]
│ │││││││ 0x0000130a 3c65 cmp al, 0x65
│ ────────< 0x0000130c 0f8599000000 jne 0x13ab
│ │││││││ 0x00001312 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x00001316 4883c004 add rax, 4
│ │││││││ 0x0000131a 0fb600 movzx eax, byte [rax]
│ │││││││ 0x0000131d 3c7b cmp al, 0x7b
│ ────────< 0x0000131f 0f8586000000 jne 0x13ab
│ │││││││ 0x00001325 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x00001329 4883c00a add rax, 0xa
│ │││││││ 0x0000132d 0fb600 movzx eax, byte [rax]
│ │││││││ 0x00001330 3c5f cmp al, 0x5f
│ ────────< 0x00001332 7577 jne 0x13ab
│ │││││││ 0x00001334 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x00001338 4883c00c add rax, 0xc
│ │││││││ 0x0000133c 0fb600 movzx eax, byte [rax]
│ │││││││ 0x0000133f 3c33 cmp al, 0x33
│ ────────< 0x00001341 7568 jne 0x13ab
│ │││││││ 0x00001343 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x00001347 4883c002 add rax, 2
│ │││││││ 0x0000134b 0fb600 movzx eax, byte [rax]
│ │││││││ 0x0000134e 3c54 cmp al, 0x54
│ ────────< 0x00001350 7559 jne 0x13ab
│ │││││││ 0x00001352 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x00001356 4883c00e add rax, 0xe
│ │││││││ 0x0000135a 0fb600 movzx eax, byte [rax]
│ │││││││ 0x0000135d 3c33 cmp al, 0x33
│ ────────< 0x0000135f 754a jne 0x13ab
│ │││││││ 0x00001361 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x00001365 4883c010 add rax, 0x10
│ │││││││ 0x00001369 0fb600 movzx eax, byte [rax]
│ │││││││ 0x0000136c 3c35 cmp al, 0x35
│ ────────< 0x0000136e 753b jne 0x13ab
│ │││││││ 0x00001370 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x00001374 4883c012 add rax, 0x12
│ │││││││ 0x00001378 0fb600 movzx eax, byte [rax]
│ │││││││ 0x0000137b 3c5e cmp al, 0x5e
│ ────────< 0x0000137d 752c jne 0x13ab
│ │││││││ 0x0000137f 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x00001383 4883c013 add rax, 0x13
│ │││││││ 0x00001387 0fb600 movzx eax, byte [rax]
│ │││││││ 0x0000138a 3c67 cmp al, 0x67
│ ────────< 0x0000138c 751d jne 0x13ab
│ │││││││ 0x0000138e 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x00001392 4883c014 add rax, 0x14
│ │││││││ 0x00001396 0fb600 movzx eax, byte [rax]
│ │││││││ 0x00001399 3c7d cmp al, 0x7d
│ ────────< 0x0000139b 750e jne 0x13ab
│ │││││││ 0x0000139d 488d3d8c0c00. lea rdi, str.Nice_job__:_ ; 0x2030 ; "Nice job! :)"
│ │││││││ 0x000013a4 e8e7fcffff call sym.imp.puts
│ ────────< 0x000013a9 eb0c jmp 0x13b7
│ │││││││ ; XREFS(22)
│ └└└└──└─> 0x000013ab 488d3d8b0c00. lea rdi, str.Come_on__Try_Again___:_ ; 0x203d ; "Come on, Try Again!! :("
│ ││ 0x000013b2 e8d9fcffff call sym.imp.puts
│ ││ ; CODE XREF from fcn.000011c9 @ 0x13a9
│ ────────> 0x000013b7 90 nop
│ ││ ; CODE XREFS from fcn.000011c9 @ 0x11f7, 0x121a
│ └└──> 0x000013b8 c9 leave
└ 0x000013b9 c3 ret
This function starts by comparing our password input in two broad ways, first it checks our password length. And performs a JBE (jump if below or equal). If we fail this check it exits the program with the “Dont try to hack me :D” message.
call sym.imp.strlen ; "Get string length"
cmp rax, 0x19 ; "Compare to 0x19, 25 in hex"
jbe 0x11fc ; "Jump to 0x11fc if below or equal to 25"
lea rdi, str.Dont_try_to_hack_me_:D ; "Don't try to hack me :D"
call sym.imp.puts ; "Print the dont hack me message"
jmp 0x13b8 ; "Jump to exit"
If we make the jbe 0x11fc
jump then we perform our second broad check. This check is similar to the first but this time we check that our string is equal to 21 characters in length.
call sym.imp.strlen ; "Get string length"
cmp rax, 0x15 ; "Compare to 0x15, 21 in hex"
je 0x121f ; "Jump to 0x121f if equal to 21"
lea rdi, str.Length_mismatch__:_ ; "Length mismatch! :("
call sym.imp.puts ; "Print the length mismatch if we fail check"
jmp 0x13b8 ; "Jump to exit"
From here we begin to test the content of our password, this appears to be done by checking individual characters out of order.
We can analyze these checks and retrieve the password. I would like to point out that we move our password into the 32-bit register “eax” and we check the lower 8 bits by checking against the 8-bit “al” register. This can seem confusing if you are not aware of how these are used.
For a quick reference, this might clear up some confusion for anyone new to assembly. And here is a link that goes into some more detail. Assembly - Registers
- EAX = 32-bit register
- AX = 16-bit register (Which is the lower 16 bits of EAX)
- AH = 8-bit register (High half of AX)
- AL = 8-bit register (Low half of AX, also making it the lowest 8 bits of EAX)
mov rax, qword [var_8h] ; "Move our pass into rax register"
add rax, 0x11 ; "Get char at 17th index (hex 0x11)"
movzx eax, byte [rax] ; "Move result into eax register and fill rest of register space with zero's"
cmp al, 0x31 ; "Check if equals 1 (hex 0x31)"
jne 0x13ab ; "Jump if not equal, to Try Again"
mov rax, qword [var_8h]
movzx eax, byte [rax] ; "Note we did not do an add, so we check index 0"
cmp al, 0x41 ; "Check if index 0 character equals A (hex 0x41)"
jne 0x13ab
mov rax, qword [var_8h]
add rax, 1 ; "index 1"
movzx eax, byte [rax]
cmp al, 0x43 ; "char = C"
jne 0x13ab
mov rax, qword [var_8h]
add rax, 2 ; "index 2"
movzx eax, byte [rax]
cmp al, 0x54 ; "char = T"
jne 0x13ab
mov rax, qword [var_8h]
add rax, 8 ; "index 8"
movzx eax, byte [rax]
cmp al, 0x63 ; "char = c"
jne 0x13ab
mov rax, qword [var_8h]
add rax, 3 ; "index 3"
movzx eax, byte [rax]
cmp al, 0x46 ; "char = F"
jne 0x13ab
mov rax, qword [var_8h]
add rax, 0xd ; "index 13"
movzx eax, byte [rax]
cmp al, 0x76 ; "char = v"
jne 0x13ab
mov rax, qword [var_8h]
add rax, 5 ; "index 5"
movzx eax, byte [rax]
cmp al, 0x4e ; "char = N"
jne 0x13ab
mov rax, qword [var_8h]
add rax, 6 ; "index 6"
movzx eax, byte [rax]
cmp al, 0x30 ; "char = 0"
jne 0x13ab
mov rax, qword [var_8h]
add rax, 0xb ; "index 11"
movzx eax, byte [rax]
cmp al, 0x52 ; "char = R"
jne 0x13ab
mov rax, qword [var_8h]
add rax, 7 ; "index 7"
movzx eax, byte [rax]
cmp al, 0x31 ; "char = 1"
jne 0x13ab
mov rax, qword [var_8h]
add rax, 0xf ; "index 15"
movzx eax, byte [rax]
cmp al, 0x72 ; "char = r"
jne 0x13ab
mov rax, qword [var_8h]
add rax, 9 ; "index 9"
movzx eax, byte [rax]
cmp al, 0x65 ; "char = e"
jne 0x13ab
mov rax, qword [var_8h]
add rax, 4 ; "index 4"
movzx eax, byte [rax]
cmp al, 0x7b ; "char = {"
jne 0x13ab
mov rax, qword [var_8h]
add rax, 0xa ; "index 10"
movzx eax, byte [rax]
cmp al, 0x5f ; "char = _"
jne 0x13ab
mov rax, qword [var_8h]
add rax, 0xc ; "index 12"
movzx eax, byte [rax]
cmp al, 0x33 ; "char = 3"
jne 0x13ab
mov rax, qword [var_8h]
add rax, 2 ; "index 2"
movzx eax, byte [rax]
cmp al, 0x54 ; "char = T"
jne 0x13ab
mov rax, qword [var_8h]
add rax, 0xe ; "index 14"
movzx eax, byte [rax]
cmp al, 0x33 ; "char = 3"
jne 0x13ab
mov rax, qword [var_8h]
add rax, 0x10 ; "index 16"
movzx eax, byte [rax]
cmp al, 0x35 ; "char = 5"
jne 0x13ab
mov rax, qword [var_8h]
add rax, 0x12 ; "index 18"
movzx eax, byte [rax]
cmp al, 0x5e ; "char = ^"
jne 0x13ab
mov rax, qword [var_8h]
add rax, 0x13 ; "index 19"
movzx eax, byte [rax]
cmp al, 0x67 ; "char = g"
jne 0x13ab
mov rax, qword [var_8h]
add rax, 0x14 ; "index 20"
movzx eax, byte [rax]
cmp al, 0x7d ; "char = }"
jne 0x13ab
lea rdi, str.Nice_job__:_ ; 0x2030 ; "Nice job! :)" ;
call sym.imp.puts ;
jmp 0x13b7 ;
Now that we have done the tedious work, If we place each character in its appropriate index location we get the following string.
ACTF{N01ce_R3v3r51^g}
$ ./revv
Enter the Password: ACTF{N01ce_R3v3r51^g}
Nice job! :)
And that is it. I’m currently putting some effort into learning how to better use radare2, so I suspect there will be more of these in the future.