swiss hacking challenge 2024 - v8

Posted on May 1, 2024

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

shc2024{PRINTERZ_ARE_SUCH_A_HECKIN_PAIN}

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.