r2con-ctf writeup: Australia

This is a write-up for the Australia challenge of r2con 2018 CTF.

We developed this Perfectly Secure Vault (or PVS for short) for storing your secrets
securely and safe from hackers. We even use a super-secure secret key to protect it.
We are sure you won't be able to recover said key... will you?

binary: psv_fc64b1cfd34704a72b2180982e418e9c

$ ./psv_fc64b1cfd34704a72b2180982e418e9c
Welcome to PSV (Perfectly Secure Vault)!
Enter your secret key to unlock:
Sorry, try again.

Open the binary with radare2 and list the functions

$ radare2 psv_fc64b1cfd34704a72b2180982e418e9c
[0x00400630]> aaa
[0x00400630]> e asm.pseudo = 1
[0x00400630]> e asm.bytes = 0
[0x00400630]> afl
0x00400580    3 26           sub.__gmon_start_580
0x004005b0    1 6            sym.imp.free
0x004005c0    1 6            sym.imp.puts
0x004005d0    1 6            sym.imp.__stack_chk_fail
0x004005e0    1 6            sym.imp.__libc_start_main
0x004005f0    1 6            sym.imp.fgets
0x00400600    1 6            sym.imp.malloc
0x00400610    1 6            sym.imp.fwrite
0x00400620    1 6            sub.__gmon_start_620
0x00400630    1 41           entry0
0x00400660    4 50   -> 41   fcn.00400660
0x004006e0    3 28           entry2.fini
0x00400700    8 38   -> 90   entry1.init
0x00400726    1 27           sub.Welcome_to_PSV__Perfectly_Secure_Vault_726
0x00400741    9 317          sub.__stack_chk_fail_741
0x0040087e    9 252          main
[0x00400630]> s main
[0x00400630]> pdf

Exploring the main quickly shows that the function at 0x400925 leads to either str.Correct__You_got_the_flag (the string "Correct! You got the flag!") or str.Sorry__try_again. (the string "Sorry, try again.\n").

; CODE XREF from main (0x4008f2)
0x00400925      eax = dword [size]
0x00400928      edx = [rax - 1]
0x0040092b      rax = qword [ptr]
0x0040092f      esi = edx
0x00400931      rdi = rax
0x00400934      sub.__stack_chk_fail_741 ()
0x00400939      var = eax & eax
0x0040093b      if (!var) goto 0x400949
0x0040093d      edi = str.Correct__You_got_the_flag ; 0x400ac5 ; "Correct! You got the flag!"

The function sub.__stack_chk_fail_741 (which determines if we got the correct flag or not) starts by setting a few local variables and then applies a xor on the arg1 (the string provided at the command line) and the string pointed by str.sorry_this_is_not_the_flag_u_are_looking_for.

[0x0040087e]> s sub.__stack_chk_fail_741
[0x00400741]> pdf
0x00400749      qword [local_58h] = rdi                    ; arg1
0x0040075f      qword [local_48h] = str.sorry_this_is_not_the_flag_u_are_looking_for
0x00400817      dword [local_4ch] = 0  <---- THIS IS THE COUNTER
; CODE XREF from sub.__stack_chk_fail_741 (0x400861)
0x00400820      eax = dword [local_4ch]
0x00400823      rdx = eax
0x00400826      rax = qword [local_48h]
0x0040082a      rax += rdx
0x0040082d      edx = byte [rax]                  <----- edx = local_48h[local_4ch]
0x00400830      eax = dword [local_4ch]
0x00400833      rcx = eax
0x00400836      rax = qword [local_58h]
0x0040083a      rax += rcx
0x0040083d      ecx = byte [rax]                <------- ecx = local_58h[local_4ch]
0x00400840      eax = dword [local_4ch]
0x00400843      cdqe
0x00400845      eax = byte [rbp + rax - 0x40]   <----- eax = one of the local var on the stack
0x0040084a      eax ^= ecx                            <----- XOR
0x0040084c      var = dl - al
0x0040084e      if (!var) goto 0x400857
0x00400850      eax = 0
0x00400855      goto 0x400868

What it does is taking one byte from the string "sorry_this_is_not_the_flag_u_are_looking_for" at index local_4ch, one byte from the entered password at index local_4ch and one of the local variables on the stack assigned at the beginning of the function. It then xors the last two and compares the result with the first string.

In other words:

string = "sorry_this_is_not_the_flag_u_are_looking_for"
somebytes = "<some-bytes>"

for loop on all chars:
    tmp = cliarg[index] ^ somebytes[index]
    if tmp != string[index]:
        return False
return True

The password can thus be retrieved by simply doing somebytes ^ string, where somebytes are all the variables set at the beginning of the function that stands on the stack.

Let's do this with radare2. Start the debugger

$ radare2 -d psv_fc64b1cfd34704a72b2180982e418e9c

Add a breakpoint on the xor line

[0x7f6c279f0000]> db 0x40084a

Then execute the program and enter a fake password:

[0x7f6c279f0000]> dc
Welcome to PSV (Perfectly Secure Vault)!
Enter your secret key to unlock:
hit breakpoint at: 40084a

Now we need to xor some bytes on the stack (the local variables) with the string pointed by str.sorry_this_is_not_the_flag_u_are_looking_for. The local variables are located at rbp + rax - 0x40 (rax is 0).

In radare2, it goes like this: seek to the string str.sorry_this_is_not_the_flag_u_are_looking_for

[0x0040084a]> s str.sorry_this_is_not_the_flag_u_are_looking_for
[0x00400a60]> ps

retrieve the length of the string

[0x00400a60]> iz ~sorry
002 0x00000a60 0x00400a60  44  45 (.rodata) ascii sorry_this_is_not_the_flag_u_are_looking_for

xor the string with 45 bytes at rbp-0x40 and store the result here

[0x00400a60]> wox `p8 45 @ rbp-0x40`
[0x00400a60]> ps
r2con{x0r_1s_Sup3r_K00l_&_s3cur3_4lg0r1thm!}lB2dqs\x04b7o\x0c\x[email protected]~3H\x0bN0\x02eA>\x16&F\x7f{u:dxmg/;-vVonz\x1fd/1tyTy,5&nYC~iz$Y

Now score with r2con{x0r_1s_Sup3r_K00l_&_s3cur3_4lg0r1thm!}