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