PlaidCTF 2016 - quite quixotic quest writeup
19 Apr 2016Well 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}
.