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:
AAAAAAAAAAAAAAAAAAAAAA
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>"
cliarg = "AAAAAAAAAAAAAAAAAAAAAA"
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:
AAAAAAAAAAAAAAAAAAAA
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
sorry_this_is_not_the_flag_u_are_looking_for
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\x0fv@cx~3H\x0bN0\x02eA>\x16&F\x7f{u:dxmg/;-vVonz\x1fd/1tyTy,5&nYC~iz$Y
\x03\x0c
Now score with r2con{x0r_1s_Sup3r_K00l_&_s3cur3_4lg0r1thm!}