Reverse engineering practice: revv

reversingcrackmeradare2

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

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.