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