picoCTF 2018 - be-quick-or-be-dead-2

As you enjoy this music even more, another executable be-quick-or-be-dead-2 shows up. Can you run this fast enough too? You can also find the executable in /problems/be-quick-or-be-dead-2_2_7e92e9cc48bad623da1c215c192bc919.

Solution

——> ./be-quick-or-be-dead-2
Be Quick Or Be Dead 2
=====================

Calculating key...
You need a faster machine. Bye bye.

okay same thing as be-quick-or-be-dead-1, let’s see if i can solve it the same way, by putting a high value for the timer

with ltrace ./be-quick-or-be-dead-2 we can see the timer is set with alarm(3), so let’s geedeebee

setting the breakpoint on alarm we can see on the backtrace where it’s called

Breakpoint 1, 0x00007ffff7e9c4d0 in alarm () from /usr/lib/libc.so.6
gdb-peda$ bt
#0  0x00007ffff7e9c4d0 in alarm () from /usr/lib/libc.so.6
#1  0x00000000004007cb in set_timer ()
#2  0x0000000000400882 in main ()
#3  0x00007ffff7df8223 in __libc_start_main () from /usr/lib/libc.so.6
#4  0x00000000004005c9 in _start ()
Dump of assembler code for function set_timer:
   0x000000000040077a <+0>:	push   rbp
   [..]
   0x00000000004007c1 <+71>:	mov    eax,DWORD PTR [rbp-0xc]
   0x00000000004007c4 <+74>:	mov    edi,eax
   0x00000000004007c6 <+76>:	call   0x400550 <alarm@plt>
   0x00000000004007cb <+81>:	nop
   0x00000000004007cc <+82>:	leave  
   0x00000000004007cd <+83>:	ret    
End of assembler dump.

and breaking before the call we can change the timer value from 3 to 9999

Breakpoint 2, 0x00000000004007c6 in set_timer ()
gdb-peda$ p $rax
$4 = 0x3
gdb-peda$ set $rax=9999
gdb-peda$ c
Continuing.
Calculating key...

Program received signal SIGALRM, Alarm clock.

but it doesn’t work. the program doesn’t stop, but it keeps calculating for a looong time for a fibonacci function fib(). we can see that in gdb with bt.

gdb-peda$ disas 0x0000000000400759
Dump of assembler code for function calculate_key:
   0x000000000040074b <+0>:	push   rbp
   0x000000000040074c <+1>:	mov    rbp,rsp
   0x000000000040074f <+4>:	mov    edi,0x402
   0x0000000000400754 <+9>:	call   0x400706 <fib>
   0x0000000000400759 <+14>:	pop    rbp
   0x000000000040075a <+15>:	ret    
End of assembler dump.

and fib(0x402) is a number with 215 digits in base 10. we gotta find another way. lets’s have a closer look at what the program does. using radare2 we see the main structure is header()->set_timer()->get_key()->print_flag()

[0x004005a0]> s main
[0x0040085f]> pdf
            ;-- main:
/ (fcn) sym.main 62
|   sym.main (int argc, char **argv, char **envp);
|           ; var char **local_10h @ rbp-0x10
|           ; var int local_4h @ rbp-0x4
|           ; arg int argc @ rdi
|           ; arg char **argv @ rsi
|           ; DATA XREF from entry0 (0x4005bd)
|           0x0040085f      55             push rbp
|           0x00400860      4889e5         mov rbp, rsp
|           0x00400863      4883ec10       sub rsp, 0x10
|           0x00400867      897dfc         mov dword [local_4h], edi   ; argc
|           0x0040086a      488975f0       mov qword [local_10h], rsi  ; argv
|           0x0040086e      b800000000     mov eax, 0
|           0x00400873      e8a9ffffff     call sym.header
|           0x00400878      b800000000     mov eax, 0
|           0x0040087d      e8f8feffff     call sym.set_timer
|           0x00400882      b800000000     mov eax, 0
|           0x00400887      e842ffffff     call sym.get_key
|           0x0040088c      b800000000     mov eax, 0
|           0x00400891      e863ffffff     call sym.print_flag
|           0x00400896      b800000000     mov eax, 0
|           0x0040089b      c9             leave
\           0x0040089c      c3             ret

inspecting more we come across the function decrypt_flag() inside print_flag(). this is its pseudo C code:

__int64 result; // rax
  int argv1_; // [rsp+0h] [rbp-14h]
  unsigned int i; // [rsp+10h] [rbp-4h]

  argv1_ = argv1;
  for ( i = 0; ; ++i )
  {
    result = i;
    if ( i > 0x38 )
      break;
    flag[i] ^= *((_BYTE *)&argv1_ + (signed int)i % 4);
    if ( (signed int)i % 4 == 3 )
      ++argv1_;
  }
  return result;

so it’s a classic XOR between a string stored inside the program and a buffer calculated from argv1. going back to print_flag() we can see that argv1 actually is the key calculated before by fib(), as r2 identified obj.key

|           0x00400802      e829fdffff     sym.imp.puts ()             ; int puts(const char *s)
|           0x00400807      8b05b3082000   eax = dword [obj.key]       ; obj.__TMC_END ; [0x6010c0:4]=0
|           0x0040080d      89c7           edi = eax
|           0x0040080f      e882feffff     sym.decrypt_flag ()

having any 2 of the 3 variables in the XOR operation we can get the remaining one. we can access the encoded flag string with radare2

[0x004007f9]> s obj.flag
[0x00601080]> px
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x00601080  28f2 6998 1acf 4c8c 2ef3 6fa8 3df2 6898  (.i...L...o.=.h.
0x00601090  32fa 6994 34c4 7992 2fee 6f99 3cfe 5594  2.i.4.y./.o.<.U.
0x006010a0  01f5 5595 04c4 6e98 0cfe 5591 02e8 7ea8  ..U...n...U...~.
0x006010b0  53fe 3bcf 5da3 39c3 1b00 0000 0000 0000  S.;.].9.........

and we know the flag starts with “picoCTF{“, so we can calculate the first 8 bytes of the key with a simple python script

bytes = ['0x28','0xf2','0x69','0x98','0x1a','0xcf','0x4c','0x8c']
flag = 'picoCTF{'
l = []

for i in range(0,8):
    l.append(ord(flag[i]) ^ int(bytes[i], 16))

s = '0x'
for e in l[::-1]:
    sn = str(hex(e)).replace('0x', '')
    if len(sn) < 2:
        s += '0'
    s += sn

print(s)
print(str(int(s,16)))

that gives us 0xf70a9b59f70a9b58 = 17801211287834368856. let’s try to patch it into the program to see if we get at least the first part of the flag right; but first we gotta get rid of the fib()

[0x0040085f]> s 0x0040087d
[0x0040087d]> wa call sym.header
Written 5 byte(s) (call sym.header) = wx e89fffffff
[0x0040087d]> s 0x00400887
[0x00400887]> wa call sym.header
Written 5 byte(s) (call sym.header) = wx e895ffffff
[0x00400887]> pdf
            ;-- main:
[..]
|           0x0040086e      b800000000     mov eax, 0
|           0x00400873      e8a9ffffff     call sym.header
|           0x00400878      b800000000     mov eax, 0
|           0x0040087d      e89fffffff     call sym.header
|           0x00400882      b800000000     mov eax, 0
|           0x00400887      e895ffffff     call sym.header
|           0x0040088c      b800000000     mov eax, 0
|           0x00400891      e863ffffff     call sym.print_flag
[..]

we can just call header() three times instead of calling set_timer() and get_key().

now, back in gdb we set a breakpoint on print_flag, before the call to decrypt_flag, so we can change the value of the key

0x400802 <print_flag+9>:	call   0x400530 <puts@plt>
0x400807 <print_flag+14>:	mov    eax,DWORD PTR [rip+0x2008b3]        # 0x6010c0 <key>
0x40080d <print_flag+20>:	mov    edi,eax
=> 0x40080f <print_flag+22>:	call   0x400696 <decrypt_flag>
0x400814 <print_flag+27>:	mov    edi,0x601080
0x400819 <print_flag+32>:	call   0x400530 <puts@plt>

then we set it

gdb-peda$ set $edi=0xf70a9b59f70a9b58
gdb-peda$ c
Continuing.
picoCTF{the_fibonacci_sequence_can_be_done_fast_7e188834}
[Inferior 1 (process 20880) exited normally]

the 8 byte value was enough for the entire flag decryption