PlaidCTF 2016 - quite quixotic quest writeup

Well yes, it certainly is quite quixotic. (Yes, the flag format is PCTF{} )

It’s a reversing task. We have an x86 ELF binary. I’ve just ran it firsthand.

$ ./qqq
curl: try 'curl --help' or 'curl --manual' for more information
$ ./qqq --version
curl 7.49.0-DEV (i686-pc-linux-gnu) libcurl/7.49.0-DEV
Protocols: dict file ftp gopher http imap pop3 rtsp smtp telnet tftp
Features: IPv6 Largefile UnixSockets

It seems the binary is a modified curl. Since the problem mentioned about the flag format, I naturally searched for “PCTF” string in the binary.

$ strings qqq | grep PCTF

Nothing. At this point, I’ve decided to compare this binary against the legit curl binary of the same version. I’ve statically compiled a curl binary from source. Then I used Diaphora to compare two binaries. There were so many differences, so after about an hour, I gave up diffing.

Then I tried to look for “PCTF” string in the binary again, just in case.

$ strings qqq | grep pctf
pctfkey
     --pctfkey KEY   Validate KEY as the PlaidCTF flag for this challenge

Important lesson learned. Always use grep -i (ignore case). Now I get to real reversing.

$ ./qqq --pctfkey asdf
Validating key...
wrong

The message “Validating key” is printed at operate_do function at 0x8052a20. Here’s what happens.

08052AA8  test    eax, eax
08052AAA  jz      short loc_8052AE4
08052AAC  mov     dword ptr [esp], offset aValidatingKey_ ; "Validating key...\n"
08052AB3  call    curl_mprintf
08052AB8  mov     edx, [ebx+128h]
08052ABE  mov     eax, offset magic_buf
08052AC3  mov     esp, eax
08052AC5  mov     eax, edx
08052AC7  retn

After it prints the message, it sets esp to magic_buf and return. magic_buf looks like this:

.data:0818C080 magic_buf dd 80AD0DFh
.data:0818C084           dd 80AC554h
.data:0818C088           dd 804820Ah
.data:0818C08C           dd 0
.data:0818C090           dd 805DB7Dh
.data:0818C094           dd offset loc_8187036
.data:0818C098           dd offset unk_81CC444
.data:0818C09C           dd 811CB0Dh
.data:0818C0A0           dd 0
.data:0818C0A4           dd 80ADD96h
.data:0818C0A8           dd 80A2311h
		...

So validating happens using ROP. Now I see the meaning of this challenge’s title. It must have been built with Q, ROP compiler.

Since ROP is Turing-complete, it is totally possible to implement arithmetics, memory load/store and conditional jumps using ROP. For example, this program handles conditional jump like below. Instead of conditionally changing eip, it conditinally changes esp.

# eax = 0x94, ebx = 0
0x80ab65e: cmovne eax, ebx
	...
0x80ad35d: mov edi, eax
	...
0x81887e4: add esp, edi

I followed the execution in gdb. The validation program is quite long, but it summarizes to this short code:

void die()
{
    write(1, "wrong\n", 6);
    _exit();
}

void check(char* s)
{
    if (strlen(s) != 0x35)
        die();

    int sum = 0;  // .bss:0x081ccf10
    int i = 0x36;
    while (--i)
        sum += s[i];

    uint32_t sum_mix;  // .bss:0x081ccf60
    uint8_t hash[16];  // .bss:0x081ccf90
    sum = (sum & 0xffffff00) | ROR_byte(sum & 0xff, 0x5f);
    sum = ROL_dword(sum, 1) ^ 0x01f9933d;
    sum_mix = sum ^ 0xc7fffffa;
    Curl_md5it(hash, (uint8_t*)&sum_mix);

    // sum must be 5215 to pass this check.
    // Then the hash must be c0050bdd747721646f14ff008c6978b9.
    if (*(uint32_t*)hash != 0xdd0b05c0)
        die();

    for (i=0; i<0x35; i++)
        s[i] ^= hash[i % 16];

    uint32_t correct[] = {
        0x9b5f4690,0x17541d0f,0x5f9e4b1b,0xcd0c58e0,0xa95460ac,
        0x034f1e1c,0x6ca02530,0xe61d02bd,0xbe5435b4,0x3b4d1b15,
        0x668f7b1d,0xd81b1af9,0xb3646cb4,0x00000009
    };
    uint32_t diff = 0;
    uint32_t* iptr = (uint32_t*)s;
    for (i=0; i<14; i++)
        diff |= iptr[i] - correct[i];
    if (diff)
        die();
}

The flag is PCTF{just_a_l1ttle_thing_1_l1ke_t0_call_ropfuscation}.

Other posts (list)


성질 급한 사람을 위한 LaTeX
Building Android App Without an IDE
PlaidCTF 2016 - quite quixotic quest writeup
DEF CON 2016 Quals - feedme
Running ARM in QEMU