swiss hacking challenge 2024 - v8
Difficulty: medium
Category: re
Author: Kiwi
Yesterday I was approached by Harper from accounting that the printer does not work. How often do I have to tell them that I’m a software engineer and not IT support but she has good connections to Liam (who didn’t bring cookies for his birthday like our company tradition states) which approves all the projects and I really really want a new macbook so maybe if I help her he budgets it for me.
Environment
We only get a website, where we can input JavaScript. This then gets passed to Frida as a script.
Exploitation
Let’s look at the available modules:
const modules = Process.enumerateModules();
for (const module of modules) {
console.log(module.name)
}
____
/ _ | Frida 16.2.1 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
. . . .
. . . . Connected to Local System (id=local)
Spawning `./secureprint-abi.bin`...
secureprint-abi.bin
linux-vdso.so.1
libcurl.so.4.7.0
libc.so.6
libnghttp2.so.14.20.1
libidn2.so.0.3.7
librtmp.so.1
libssh.so.4.8.7
libpsl.so.5.3.2
libssl.so.3
libcrypto.so.3
libgssapi_krb5.so.2.2
libldap-2.5.so.0.1.11
liblber-2.5.so.0.1.11
libzstd.so.1.4.8
libbrotlidec.so.1.0.9
libz.so.1.2.11
ld-linux-x86-64.so.2
libunistring.so.2.2.0
libgnutls.so.30.31.0
libhogweed.so.6.4
libnettle.so.8.4
libgmp.so.10.4.1
libkrb5.so.3.3
libk5crypto.so.3.1
libcom_err.so.2.1
libkrb5support.so.0.1
libsasl2.so.2.0.25
libbrotlicommon.so.1.0.9
libp11-kit.so.0.3.0
libtasn1.so.6.6.2
libkeyutils.so.1.9
libresolv.so.2
libffi.so.8.1.0
libdl.so.2
librt.so.1
libm.so.6
libpthread.so.0
Spawned `./secureprint-abi.bin`. Resuming main thread!
[Local::secureprint-abi.bin ]-> Enter the secret password: Sorry, that's not the correct password. Try again.
Process terminated
Thank you for using Frida
We dump the main module:
const moduleName = "secureprint-abi.bin";
const module = Process.getModuleByName(moduleName);
const baseAddress = module.base;
const size = module.size;
console.log(`Dumping ${moduleName}...`);
const buffer = Memory.readByteArray(baseAddress, size);
const view = new Uint8Array(buffer);
let hexDump = '';
for (let i = 0; i < view.length; i++) {
hexDump += ('0' + view[i].toString(16)).slice(-2);
if (i % 16 === 15) hexDump += '\n';
else if (i % 2 === 1) hexDump += ' ';
}
console.log(hexDump);
Afterwards, we can copy the hexdump into a file and use xxd
to convert it into an actual binary:
$ xxd -r -p code.hexdump secureprint-abi.bin
$ binaryninja secureprint-abi.bin
Looking at the decompilation, we notice some XOR shenanigans:
{
void* fsbase;
int64_t rax = *(uint64_t*)((char*)fsbase + 0x28);
int64_t var_8a8;
__builtin_strncpy(&var_8a8, "zbh>=<;kz! dJbea(t|Cm*s]", 0x18);
printf("Enter the secret password: ", argv, 0x6165624a6420217a);
void var_888;
__isoc99_scanf("%99s", &var_888);
uint64_t rax_3 = strlen(&var_888);
char var_8d9 = 9;
for (int64_t i = 0; i < rax_3; i = (i + 1))
{
*(uint8_t*)(i + &var_888) = (*(uint8_t*)(i + &var_888) ^ var_8d9);
var_8d9 = (var_8d9 + 1);
}
*(uint8_t*)(rax_3 + &var_888) = 0;
if (strncmp(&var_888, &var_8a8, 0x18) != 0)
{
puts("Sorry, that's not the correct pa…");
}
else
{
puts("Congratulations! You've passed t…");
int64_t rax_13 = curl_easy_init();
if (rax_13 != 0)
{
void s;
snprintf(&s, 0x400, "http://localhost:1337/?password=…", curl_easy_escape(rax_13, &var_888, 0, &var_888));
int32_t var_8d8_1 = 0x2712;
curl_easy_setopt(rax_13, 0x2712, &s, 0x2712);
int32_t var_8d4_1 = 0x4e2b;
curl_easy_setopt(rax_13, 0x4e2b, sub_1415, 0x4e2b);
int32_t var_8d0_1 = 0x2711;
void var_818;
curl_easy_setopt(rax_13, 0x2711, &var_818, 0x2711);
int32_t rax_24 = curl_easy_perform(rax_13);
if (rax_24 != 0)
{
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(((uint64_t)rax_24)), "curl_easy_perform() failed: %s\n");
}
curl_easy_cleanup(rax_13);
}
}
*(uint64_t*)((char*)fsbase + 0x28);
if (rax == *(uint64_t*)((char*)fsbase + 0x28))
{
return 0;
}
__stack_chk_fail();
/* no return */
}
Reversing the XOR in python:
enc = "zbh>=<;kz! dJbea(t|Cm*s]"
start = 9
for c in enc:
print(chr(ord(c) ^ start), end='')
start += 1
shc2024{k33p_try1ng_p4l}
Sadly this isn’t the flag though. We can use the following code to hook the __isoc99_scanf
function with Frida to get the actual flag:
Interceptor.attach(Module.findExportByName(null, "__isoc99_scanf"), {
onEnter: function(args) {
// Save the pointer to the buffer (second argument of __isoc99_scanf) for use in onLeave
this.buffer = args[1];
},
onLeave: function(retval) {
// Define the flag you want to write into the buffer
var flag = "shc2024{k33p_try1ng_p4l}";
// Write the flag to the buffer saved in onEnter
Memory.writeUtf8String(ptr(this.buffer), flag);
}
});
Flag
Conclusion
Nice challenge, I didn’t know how capable Frida was before as I mainly used it to bypass SSL pinning with premade scripts before.