前書き
本記事は、TryHackMeのRoom W1seGuyのwriteupです。
難易度Easyで、おそらくCrypto系では簡単な部類だと思います。(Cryptは普段やらないので知らんけど...)
暗号自体は割と単純ですが、pythonのコードの読み書きの練習はできたかなと思います(ベースはChatGPTですが..)
ソースコード
まずはターゲットで稼働しているサービスのソースコードを下記に示します。
import random
import socketserver
import socket, os
import string
flag = open('flag.txt','r').read().strip()
def send_message(server, message):
enc = message.encode()
server.send(enc)
def setup(server, key):
flag = 'THM{thisisafakeflag}'
xored = ""
for i in range(0,len(flag)):
xored += chr(ord(flag[i]) ^ ord(key[i%len(key)]))
hex_encoded = xored.encode().hex()
return hex_encoded
def start(server):
res = ''.join(random.choices(string.ascii_letters + string.digits, k=5))
key = str(res)
hex_encoded = setup(server, key)
send_message(server, "This XOR encoded text has flag 1: " + hex_encoded + "\n")
send_message(server,"What is the encryption key? ")
key_answer = server.recv(4096).decode().strip()
try:
if key_answer == key:
send_message(server, "Congrats! That is the correct key! Here is flag 2: " + flag + "\n")
server.close()
else:
send_message(server, 'Close but no cigar' + "\n")
server.close()
except:
send_message(server, "Something went wrong. Please try again. :)\n")
server.close()
class RequestHandler(socketserver.BaseRequestHandler):
def handle(self):
start(self.request)
if __name__ == '__main__':
socketserver.ThreadingTCPServer.allow_reuse_address = True
server = socketserver.ThreadingTCPServer(('0.0.0.0', 1337), RequestHandler)
server.serve_forever()
ソースコードの解析
大まかな構造から掴みましょう。主要な関数は3つです。
send_message
setup
start
send_message
- 引数のメッセージをUTF-8エンコードし送信する。
それほど重要ではなさそう。
setup
- flagが鍵でXORされ、16進エンコードしたものを返す。
暗号化処理に関わる部分。
start
- 鍵の生成:英数字ランダムな5文字
- 暗号文の表示
- キーの入力を求めて正しければ、flag2を返す
- 正しい鍵で暗号文を復号するとflag1が得られる。
==>正しい鍵が分かれば、2つのflagが得られる。
解読のポイント
目的:鍵の特定
鍵に関する手掛かり
- 鍵は、英数字62個からランダムで5文字の文字列で構成されている
('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') -
setup
から暗号化手順が分かるので鍵が分かれば逆の手順で復号可能 - 復号結果はFlag1なのでおそらく「THM{」で始まり、「}」で終わる
したがって、目的は、復号した文字列が「THM{--------}」となるような英数字5文字から成る文字列を探すことになります。
やみくもに英数字5文字を探すと、625の候補が存在しますが、条件をもとに復号した文字列が「THM{」で始まる部分鍵を探してから、「}」で終わる鍵を探すという方法にすることで、鍵の候補が62個に絞られます。
さて、方針が定まったので復号する為のスクリプトを作成します。
以下がChatGPTに助けてもらって作成したスクリプトです。
# decoder.py
import binascii
import string
import argparse
def decode_hex_encoded(hex_encoded, key):
xored_bytes = binascii.unhexlify(hex_encoded)
return ''.join(chr(b ^ ord(key[i % len(key)])) for i, b in enumerate(xored_bytes))
def brute_force_optimized(hex_encoded):
characters = string.ascii_letters + string.digits
known_plaintext = "THM{"
xored_bytes = binascii.unhexlify(hex_encoded)
# "THM{"を手掛かりに暗号鍵の最初の4文字を絞る
partial_key = ''.join(chr(xored_bytes[i] ^ ord(known_plaintext[i])) for i in range(len(known_plaintext)))
# フラグが"}"で終わるような残りの一文字を探す
for char in characters:
key = partial_key + char
flag = decode_hex_encoded(hex_encoded, key)
if flag.startswith("THM{") and flag.endswith("}"):
print("Key found:", key)
print("Decoded flag:", flag)
return True
return False
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="XOR cipher brute force attack")
parser.add_argument("hex_encoded", help="Encrypted string in hex format")
args = parser.parse_args()
if not brute_force_optimized(args.hex_encoded):
print("Key not found.")
netcatなので1337/TCPに接続して得られる暗号文を上記スクリプトで復号することで最初のフラグが得られます。
鍵はランダムなので、毎回異なります。
python decoder.py "暗号文"
Key found: f6EoL
Decoded flag: THM{__1st_FLAG__}
鍵が得られたので、鍵を送信すると、2つ目のフラグも得られます。
Congrats! That is the correct key! Here is flag 2: THM{__2nd_FLAG__}