Real World CTF Quals 2018 - SCSI

pernicious

SCSI

To improve disk I/O performance, I wrote a SCSI device. Do you want to have a try?

This challenge was from Real World CTF 2018. RPISEC was the only solve.
I probably spent upwards of 20 hours on this challenge. Needless to say, this will be a somewhat lengthy writeup.

Initial Overview and Analysis

We are given a qemu-system-x86_64 binary and a shell script to run it, along with the associated files (lines split up for readability):

#!/bin/sh
./qemu-system-x86_64 --enable-kvm -L ./dependences -initrd ./rootfs.cpio \
    -kernel ./vmlinuz-4.13.0-38-generic \
    -append 'console=ttyS0 root=/dev/ram oops=panic panic=1' \
    -m 56M --nographic -device ctf-scsi,id=bus0 \
    -drive file=test.img,if=none,id=d0 -device scsi-disk,drive=d0,bus=bus0.0

We see it’s adding a device ctf-scsi. Normal qemu certainly doesn’t have this device, meaning it’s probably a part of the qemu binary itself. Sure enough:

pernicious@debian:~/Desktop/CTF/realworld18/scsi$ strings ./qemu-system-x86_64 | grep ctf
ctf-scsi
hw/scsi/ctf.c
ctf_class_init
ctf.c
ctf_dma_write
hw/scsi/ctf.c
ctf_process_reply
ctf_mmio_read
...

In fact, the binary wasn’t stripped, making reversing and debugging a lot less painful.

We start at ctf_class_init. This sets up things like the device_id and vendor_id of the device, and specifies a function that ‘realizes’ the device.

ctf_realize (which we assume gets called at some point) then interprets the PCIDevice structure as a CTFState structure, initializing some internal state variables.

For future reference, the structs of interest are (assume typedefs are properly inserted):

struct CTFState {
    PCIDevice pdev;
    MemoryRegion mmio;
    SCSIBus bus;
    uint64_t high_addr;
    int state;
    int register_a;
    int register_b;
    int register_c;
    int pwidx;
    char pw[4];
    SCSIRequest* cur_req;
    int (*dma_read)(void*, char*, int);
    int (*dma_write)(void*, char*, int);
    CTF_req req;
    char* dma_buf;
    int dma_buf_len;
    int dma_need;
};
struct CTF_req {
    CTF_req_head head …
...Read more

CSAW Quals 2017 FuntimeJS

pernicious

FuntimeJS

This challenge was formally Part 2 of LittleQuery (Web). Description:

JavaScript is memory safe, right? So you can’t read the flag at physical address 0xdeadbeeeef, right? Right?

This was a very interesting challenge from CSAW Quals 2017 (although whether a funtime was had is still questionable…). We are given a web page where we can submit javascript, and a link to the open source project that will run it, runtime.js, an “operating system…that runs JavaScript.” Because running javascript in ring 0 is just what this world needs… This writeup is a bit long, skimming is not discouraged.

Step 1: Arbitrary Read/Write

Finding a bug was a lengthy process of going through the source and trying things out. The syscalls seemed a good place to start, especially a few:

// runtime.js syscalls: Low level system access
DECLARE_NATIVE(BufferAddress);       // Get buffer physical address
...
DECLARE_NATIVE(GetSystemResources);  // Get low-level system resources

console.log(__SYSCALL.bufferAddress(new Uint8Array(17))) => [ 17, 510626304, 0, 0, 0, 0 ]

Huh, that second entry looks suspicously like a memory address… Looking a bit into the source for getSystemResources (some stuff is cut out):

NATIVE_FUNCTION(NativesObject, GetSystemResources) {
  LOCAL_V8STRING(s_memory_range, "memoryRange");
                                       //     vvvvv   memoryRanges's type
  obj->Set(context, s_memory_range, (new ResourceMemoryRangeObject(Range<size_t>(0, 0xffffffff)))
           ->BindToTemplateCache(th->template_cache())
           ->GetInstance());
}

and following the bread crumbs…

NATIVE_FUNCTION(ResourceMemoryRangeObject, Block) {
  auto base = static_cast<uint64_t>(arg0->NumberValue(context).FromJust());
  auto size = static_cast<uint32_t>(arg1->Uint32Value(context).FromJust());
  Range<size_t> subrange(base, base + size);
  if (!subrange.IsSubrangeOf(that->memory_range_)) {
    THROW_RANGE_ERROR("block: out of bounds");
  }                             //    vvvvv  return type of memoryRange.block()
  args.GetReturnValue().Set((new ResourceMemoryBlockObject(
                               MemoryBlock<uint32_t>(reinterpret_cast<void*>(base), size)))
                            ->BindToTemplateCache(th->template_cache())
                            ->GetInstance());
}

NATIVE_FUNCTION(ResourceMemoryBlockObject, Buffer) {
  PROLOGUE;
  void* ptr = that->memory_block_.base(); // <---- uses raw void* ???
  auto length = that->memory_block_.size();
  RT_ASSERT(ptr);
  RT_ASSERT(length > 0);
  auto abv8 = v8::ArrayBuffer::New …
...Read more

DEFCON Finals 2017 - Intro & Rubix

Toshi Piazza

Over the weekend, July 28-31 2017 RPISEC competed with Lab RATs and Techsec in DEFCON Finals, one of the most important CTFs of every year. Only 15 teams in the world get to qualify for the event each year, and our team under Lab RATs was able to earn the right to compete among 14 other globally professional teams.

This writeup covers the first challenge presented at DEFCON Finals, which kept us on our toes throughout the competition. It was deceptively simple, and also served as an introduction to cLEMENCy, a terrifying 9-bit middle endian architecture, as well as to the toolchain that was used to create all future binaries.

The majority of this writeup will involve getting our feet wet with cLEMENCy while setting up a comfortable environment for reverse engineering the later challenges released at DEFCON.

DEFCON Challenge Format

A link to the challenge binary can be found here

For the CTF we are given only an emulator and a debugger to solve each of the challenges; these challenges are compiled for a 9-bit middle endian architecture, cLEMENCy. When we first run the rubix challenge under this emulator, we note that only garbage gets printed to the terminal. This output doesn’t change each run, and it’s puzzling what exactly this is. What if it’s the emulator printing out 9-bit ascii instead of 8? Indeed, when we transform from 9-bit to 8-bit between the debugger and our terminal, we end up with the following:

This service implements a rubix cube. Solve the cube and win.

Give me 54 Bytes(ie: 53,12,5,etc): <input 54 comma-delimited chars>

                Top

             R6 R7 B0
             L1 T4 R5
             A0 B7 R2

  Left         Front       Right       Back

L8 L7 L0     T0 A7 F0    R8 F3 L6    B6 T3 T6
T5 L4 …
...Read more

BCTF 2017 BOJ

Toshi Piazza

This weekend we played in BCTF, though we took it a little easy in preparation for upcoming CTFs. However, we were able to solve a challenge with only one other solve, so we decided to write up our progress on it. It essentially boiled down to a sandbox escape.

Escaping the First Sandbox

When we first go to the link in the problem description, we see a web form which presumably compiles and runs C code which we submit. Initially, it does not seem like we can get any output from the server other than “Compilation Error” or “Wrong Answer.” However, it does not seem that there are many security restrictions on the binary; initial tests show that we can’t execve, but we can at least open a socket to create a writeback. First, we attempt to learn a bit more about our environment. In particular, we write our uid to our connect back. It seems we’re running as root! Let’s get a directory structure listing:

/etc/{group,passwd}
/usr/sbin/chroot
/usr/bin/{whoami,id}
/lib64/ld-linux-x86-64.so.2
/root/scf.so
/root/1492329438123677.c.bin
/lib/x86_64-linux-gnu/(lots of shared libs)
/bin/{cp,mkdir,chmod,bash,cat,sh,ls}

At this point, we’re interested by a few things related to the directory structure; first, there’s no flag!? No sweat, we’re probably in a chroot (the flood of messages on IRC from people asking where the flag is confirms this theory). More alarming perhaps is the scf.so file that we see in the same directory as the file that was compiled for us. We dump it to our writeback, and immediately disassemble it.

Initially, it’s apparent that this shared object file is meant to be used as an LD_PRELOAD; normal shared …

...Read more

Boston Key Party 2017 hiddensc

Mike Macelletti

This is a writeup for the binary exploitation challenge “hiddensc” we solved during Boston Key Party 2017.

hiddensc [200]

Boston Key Party 2017 pwn 200

Description

I know I hid that pesky shellcode page somewhere… (the hidden page will change every ~~10~~ 20 minutes). Please don’t brute force the hundreds of GB of space…

First Look

Running the Binary

We’re given two files, hiddensc which is the binary, and poop.sc, which is the shellcode that runs /bin/sh. Looking at the challenge description, it appears the program puts the shellcode somewhere in memory.

Running the binary locally on port 9001 gives us the following output:

$ ./hiddensc 9001

seeding with 942fa402
[!] shellcode is at 0x2fa5a2820000
[!] page size is 0x1000
[!] pid is 29166
[+] listening on 0.0.0.0 9001

Running subsequent times shows the shellcode does move around and is exactly one page (0x1000 bytes) long. Presumably we want somehow return to the shellcode which will give us a shell on the server.

Main Menu

After we have the binary running on a port we can interact with it. The program is seemingly quite simple

$ nc localhost 9001

[a]lloc, [j]ump : 

We have two options, alloc and jump. Testing each option shows alloc will malloc an array of the size we request and jump will set RIP to the address we give. We also have the option to free the array we allocate, but if we choose not to we cannot free it later.

[a]lloc, [j]ump : a
sz? 1234
free? n

[a]lloc, [j]ump : j
sz? 1094795585
Stopped reason: SIGSEGV
0x0000000041414141 in ?? ()

Running the binary in gdb and connecting shows the program does let us jump to any address we want. Problem is the shellcode moves around every 20 minutes on the remote server …

...Read more