Csaw 2017 Prophecy

The goal of this challenge is to print the contents of the flag file.

Let's take a look at the binary:

$ file prophecy
prophecy: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, not stripped
$ ./prophecy
----------------------------------------------
|PROPHECY PROPHECY PROPHECY PROPHECY PROPHECY|
----------------------------------------------
[*]Give me the secret name
>>guyinatuxedo
[*]Give me the key to unlock the prophecy
>>supersecretkey

So we can see that it prompts us for a name and a key. When we look at the code in Ghidra, it is clear that the binary has been obfuscated. The program is run in a while true loop, and the code has been split into a lot of different sections. Which section runs depends on the value of the integer codeFlow. Also most of the code we are interested in is ran in the parser function, which is called in main. With that knowledge, let's find the pieces of code that scan in our name and secret.

Name: (address: 0x40254b)

                                                  this = operator<<<std--char_traits<char>>
                                                                   (cout,
                                                  "|PROPHECY PROPHECY PROPHECY PROPHECY PROPHECY| ",
                                                  puVar5[0x2fffffab8]);
                                                  *(undefined8 *)(puVar5 + 0x2fffffab8) = 0x4024ab;
                                                  local_3a0 = operator<<(this,
                                                  _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
                                                  ,puVar5[0x2fffffab8]);
                                                  *(undefined8 *)(puVar5 + 0x2fffffab8) = 0x4024cb;
                                                  this = operator<<<std--char_traits<char>>
                                                                   (cout,
                                                  "----------------------------------------------",
                                                  puVar5[0x2fffffab8]);
                                                  *(undefined8 *)(puVar5 + 0x2fffffab8) = 0x4024dd;
                                                  local_3a8 = operator<<(this,
                                                  _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
                                                  ,puVar5[0x2fffffab8]);
                                                  *(undefined8 *)(puVar5 + 0x2fffffab8) = 0x4024fd;
                                                  this = operator<<<std--char_traits<char>>
                                                                   (cout,
                                                  "[*]Give me the secret name",puVar5[0x2fffffab8]);
                                                  *(undefined8 *)(puVar5 + 0x2fffffab8) = 0x40250f;
                                                  local_3b0 = operator<<(this,
                                                  _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
                                                  ,puVar5[0x2fffffab8]);
                                                  *(undefined8 *)(puVar5 + 0x2fffffab8) = 0x40252f;
                                                  local_3b8 = operator<<<std--char_traits<char>>
                                                                        (cout,&DAT_0040647e,
                                                                         puVar5[0x2fffffab8]);
                                                  *(undefined8 *)(puVar5 + 0x2fffffab8) = 0x40254b;
                                                  sVar3 = read(0,local_d8,200,puVar5[0x2fffffab8]);
                                                  codeFlow = 0xac75072e;
                                                  local_49 = 0 < sVar3;
                                                  bVar9 = (x.28 * (x.28 + -1) & 1U) == 0;
                                                  puVar5 = (undefined *)local_58;
                                                  if (bVar9 != y.29 < 10 || bVar9 && y.29 < 10) {
                                                    codeFlow = 0xa0ebe5ab;

Here we can see that it prompts for the secret name. It scans in 200 bytes into name_input 200 bytes, then checks to see if it scanned in more than 0 bytes. Checking the references for name_input we find the following code block.

address: 0x402b57

                                                        containsStarcraft =
                                                             strstr(nameInput,".starcraft",
                                                                    puVar4[-8]);
                                                        starcraftCheck =
                                                             containsStarcraft != (char *)0x0;

Looking here, we can see that it checks to see if nameInput contains the string .starcraft. So the name we need to input is probably .starcraft

Secret: (address: 0x40289d)

                                                        this = operator<<<std--char_traits<char>>
                                                                         (cout,
                                                  "[*]Give me the key to unlock the prophecy",
                                                  puVar5[-8]);
                                                  *(undefined8 *)(puVar5 + -8) = 0x402866;
                                                  local_3d8 = operator<<(this,
                                                  _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
                                                  ,puVar5[-8]);
                                                  *(undefined8 *)(puVar5 + -8) = 0x402886;
                                                  local_3e0 = operator<<<std--char_traits<char>>
                                                                        (cout,&DAT_0040647e,
                                                                         puVar5[-8]);
                                                  *(undefined8 *)(puVar5 + -8) = 0x4028a2;
                                                  sVar3 = read(0,keyInput,300,puVar5[-8]);
                                                  codeFlow = 0x661c008b;
                                                  local_48 = 0 < sVar3;
                                                  bVar9 = (x.28 * (x.28 + -1) & 1U) == 0;
                                                  if (bVar9 != y.29 < 10 || bVar9 && y.29 < 10) {
                                                    codeFlow = 0xc0f1dacd;

Here we can see that it prints out [*]Give me the key to unlock the prophecy. Proceeding that it makes a read call, which it will scan 300 (0x12c) bytes into keyInput. It then make sures that the read scanned in more than 0 bytes. Checking the references for keyInputwe find a bit of code that alters keyInput:

address: 0x402a3d

                                                              keyLen = strlen(keyInput,puVar5[-8]);
                                                              keyInput[keyLen + local_3e8 + -1] = 0;

This line of code will essentially set the byte directly before the first null byte equal to a null byte. This is because strlen will count the amount of bytes until a null byte. Read by itself does not null terminate. Proceeding that, after checking the references for keyInput we find the next code block:In

HERE!!!!

address: 0x402f08

            nameInputTrsfr = nameInput;
            *(undefined8 *)(puVar4 + -8) = 0x402e94;
            nameInputTransfer = strlen(nameInput,puVar4[-8]);
            *(undefined8 *)(puVar4 + -8) = 0x402eaa;
            appendedFilename = strncat(tmp,nameInputTrsfr,nameInputTransfer,puVar4[-8]);
            *local_c0 = appendedFilename;
            __s = *local_c0;
            *(undefined8 *)(puVar4 + -8) = 0x402ecd;
            filePointer = strtok(__s,&DAT_004064d5,puVar4[-8]);
            *(undefined8 *)(puVar4 + -8) = 0x402edf;
            __s_00 = fopen(filePointer,&DAT_004064d7,puVar4[-8]);
            *local_f0 = __s_00;
            __s_00 = *local_f0;
            *(undefined8 *)(puVar4 + -8) = 0x402f0d;
            local_418 = fwrite(keyInput,1,300,__s_00,puVar4[-8]);
            __s_00 = *local_f0;
            *(undefined8 *)(puVar4 + -8) = 0x402f23;
            local_41c = fclose(__s_00,puVar4[-8]);
            __s = *local_c0;
            *(undefined8 *)(puVar4 + -8) = 0x402f42;
            __s_00 = fopen(__s,&DAT_004064da,puVar4[-8]);

So we can see here some manipulation going on with our two inputs. First it takes nameInput (which because of a previous check should be .starcraft) and appends it to the end of /tmp/ (look at it's value in gdb). Proceeding that, it strips a newline character from the appended filename. After that it opens up the appended string as a writable file, then writes 0x12c bytes of keyInput to it (it will write more bytes ). Later on it opens the same file as a readable file.

tl;dr If the name you input is .starcraft it will create the file /tmp/.starcraft and write the input you gave it as a key to it (plus the difference from the length of the input to 0x12c). It ends off with opening the file you created as readable,.

So the file it created is probably read later on in the code. We see in the imports that the function fread is in the code. Let's run the binary in gdb and set a breakpoint for fread so we can see where our input is read:

gef➤  b *fread
Breakpoint 1 at 0x400b30
gef➤  r
Starting program: /Hackery/pod/modules/obfuscated_reversing/csaw17_prophecy/prophecy
----------------------------------------------
|PROPHECY PROPHECY PROPHECY PROPHECY PROPHECY|
----------------------------------------------
[*]Give me the secret name
>>.starcraft
[*]Give me the key to unlock the prophecy
>>15935728
[*]Interpreting the secret....

Breakpoint 1, __GI__IO_fread (buf=0x7fffffffd3a0, size=0x1, count=0x4, fp=0x619e70) at iofread.c:32
32  iofread.c: No such file or directory.
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x4               
$rbx   : 0x0               
$rcx   : 0x0000000000619e70  →  0x00000000fbad2488
$rdx   : 0x4               
$rsp   : 0x00007fffffffd248  →  0x0000000000403197  →  <parser()+8455> mov r8d, 0x1cd65a05
$rbp   : 0x00007fffffffdec0  →  0x00007fffffffdf40  →  0x0000000000406380  →  <__libc_csu_init+0> push r15
$rsi   : 0x1               
$rdi   : 0x00007fffffffd3a0  →  0x00000000001722af
$rip   : 0x00007ffff7b028a0  →  <fread+0> push r14
$r8    : 0xced24a00        
$r9    : 0xced24a01        
$r10   : 0x6               
$r11   : 0x00007ffff7b028a0  →  <fread+0> push r14
$r12   : 0x0000000000400f01  →  <_GLOBAL__sub_I_prophecy.cpp+273> add ecx, esi
$r13   : 0x00007fffffffe001  →  0xb900000000000000
$r14   : 0xffffffff        
$r15   : 0xffffff01        
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
───────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffd248│+0x0000: 0x0000000000403197  →  <parser()+8455> mov r8d, 0x1cd65a05   ← $rsp
0x00007fffffffd250│+0x0008: 0x00007fffffffd280  →  0x00007ffff7fb5ee0  →  "/lib/x86_64-linux-gnu/libc.so.6"
0x00007fffffffd258│+0x0010: 0x00007fffffffd27f  →  0x007ffff7fb5ee000
0x00007fffffffd260│+0x0018: 0x00007ffff7fb59d0  →  "/lib/x86_64-linux-gnu/libgcc_s.so.1"
0x00007fffffffd268│+0x0020: 0x0000000000000000
0x00007fffffffd270│+0x0028: 0x00007fffffffd2a0  →  0x0000000000000000
0x00007fffffffd278│+0x0030: 0x00007fffffffd29f  →  0x0000000000000003
0x00007fffffffd280│+0x0038: 0x00007ffff7fb5ee0  →  "/lib/x86_64-linux-gnu/libc.so.6"
─────────────────────────────────────────────────────────────── code:x86:64 ────
   0x7ffff7b0288d <fputs+333>      jmp    0x7ffff7aa5796 <__GI__IO_fputs+4294586454>
   0x7ffff7b02892                  nop    WORD PTR cs:[rax+rax*1+0x0]
   0x7ffff7b0289c                  nop    DWORD PTR [rax+0x0]
 → 0x7ffff7b028a0 <fread+0>        push   r14
   0x7ffff7b028a2 <fread+2>        push   r13
   0x7ffff7b028a4 <fread+4>        push   r12
   0x7ffff7b028a6 <fread+6>        push   rbp
   0x7ffff7b028a7 <fread+7>        push   rbx
   0x7ffff7b028a8 <fread+8>        mov    rbx, rsi
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "prophecy", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7ffff7b028a0 → __GI__IO_fread(buf=0x7fffffffd3a0, size=0x1, count=0x4, fp=0x619e70)
[#1] 0x403197 → parser()()
[#2] 0x40629d → main()
────────────────────────────────────────────────────────────────────────────────

So we can see from the stack section of the output from gdb, that there is a call to fread at 0x403197. Note that this is the only fread call we get. When we go to the section of code in Ghidra, we see the following:

address: 0x403197

                                                      local_430 = fread(input0,1,4,__s_00,puVar4[-8]
                                                                       );
                                                      codeFlow = 0x1cd65a05;
                                                      *input0Transfer = *input0;
                                                      check0 = *input0Transfer == 0x17202508;

So we can see here that it will read 4 bytes of data from the file /tmp/.starcraft and then creates a bool check:0 that is true if the 4 bytes of data it scans in is equal to the hex string 0x17202508. We can continue where we left off in gdb to see exactly what data it's scanning in. After the fread call finishes, set a breakpoint for the cmp instruction for the bool:

gdb-peda$ finish

. . .

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x403188 <parser()+8440>  mov    rcx, QWORD PTR [rbp-0xe0]
     0x40318f <parser()+8447>  mov    rcx, QWORD PTR [rcx]
     0x403192 <parser()+8450>  call   0x400b30 <fread@plt>
 →   0x403197 <parser()+8455>  mov    r8d, 0x1cd65a05
     0x40319d <parser()+8461>  mov    r9d, 0x643f2c50
     0x4031a3 <parser()+8467>  mov    r10b, 0x1
     0x4031a6 <parser()+8470>  mov    rcx, QWORD PTR [rbp-0xc0]
     0x4031ad <parser()+8477>  mov    r11d, DWORD PTR [rcx]
     0x4031b0 <parser()+8480>  mov    rcx, QWORD PTR [rbp-0xb0]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "prophecy", stopped, reason: TEMPORARY BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x403197 → parser()()
[#1] 0x40629d → main()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  b *0x4031c1
Breakpoint 2 at 0x4031c1
gef➤  c
Continuing.

and once we reach the compare

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x4031b0 <parser()+8480>  mov    rcx, QWORD PTR [rbp-0xb0]
     0x4031b7 <parser()+8487>  mov    DWORD PTR [rcx], r11d
     0x4031ba <parser()+8490>  mov    rcx, QWORD PTR [rbp-0xb0]
 →   0x4031c1 <parser()+8497>  cmp    DWORD PTR [rcx], 0x17202508
     0x4031c7 <parser()+8503>  sete   bl
     0x4031ca <parser()+8506>  and    bl, 0x1
     0x4031cd <parser()+8509>  mov    BYTE PTR [rbp-0x3d], bl
     0x4031d0 <parser()+8512>  mov    r11d, DWORD PTR ds:0x607234
     0x4031d8 <parser()+8520>  mov    r14d, DWORD PTR ds:0x607224
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "prophecy", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x4031c1 → parser()()
[#1] 0x40629d → main()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  x/x $rcx
0x7fffffffd380: 0x33393531

So we can see that the values it's compared against the hex string 0x17202508 are 1593 which are the first four characters we inputted. So now that we know that the first four characters So with this, we now know what we need to input to pass the first check.

Now this isn't the only check the binary does. It does six more checks, so these are all of the checks:

0x4031c1:    input = 0x17202508
0x4034eb:    input = 0x4b
0x403cb4:    input = 0x3
0x404296:    input = 0xe4ea93
0x40461d:    input = "LUTAREX"
0x4049bc:    input = 0x444556415300
0x404d60:    input = 0x4c4c4100

So there are a couple of formatting errors you have to worry about, but once you put it all together you get this:

#First import pwntools
from pwn import *

#Establish the target, either remote connection or local process
target = process('./prophecy')
#target = remote("reversing.chal.csaw.io", 7668)

#Attach gdb
gdb.attach(target)

#Print out the starting menu, prompt for input from user, then send filename
print target.recvuntil(">>")
raw_input()
target.sendline(".starcraft")

#Prompt for user input to pause
raw_input()

#Form the data to pass the check, then send it
check0 = "\x08\x25\x20\x17"
check1 = "\x4b"*4 + "\x00"  +  "\x4b"*4
check2 = "\x03"*1
check3 = "\x93\xea\xe4\x00"
check4 = "\x5a\x45\x52\x41\x54\x55\x4c"
check5 = "\x00\x53\x41\x56\x45\x44"
check6 = "\x00\x41\x4c\x4c"
target.send(check0 + check1 + check2 + check3 + check4 + check5 + check6)

#Drop to an interactive shell
target.interactive()

and when we run it against the server:

$ python rev.py
[+] Starting local process './prophecy': pid 4763
----------------------------------------------
|PROPHECY PROPHECY PROPHECY PROPHECY PROPHECY|
----------------------------------------------
[*]Give me the secret name
>>


[*] Switching to interactive mode
[*]Give me the key to unlock the prophecy
>>[*]Interpreting the secret....
[*]Waiting....
[*]I do not join. I lead!
[*]You'll see that better future Matt. But it 'aint for the likes of us.
[*]The xel'naga, who forged the stars,Will transcend their creation....
[*]Yet, the Fallen One shall remain,Destined to cover the Void in shadow...
[*]Before the stars wake from their Celestial courses,
[*]He shall break the cycle of the gods,Devouring all light and hope.
==========================================================================================================
[*]ZERATUL:flag{N0w_th3_x3l_naga_that_f0rg3d_us_a11_ar3_r3turn1ng_But d0_th3y_c0m3_to_sav3_0r_t0_d3str0y?}
==========================================================================================================
[*]Prophecy has disappered into the Void....
[*] Process './prophecy' stopped with exit code 0 (pid 4763)
[*] Got EOF while reading in interactive
$  

Just like theat, we captured the flag!