TCPサーバの接続情報と、サーバのプログラム vuln.py およびテキストファイル pow-solver.cpp が与えられた。
vuln.py は、以下の処理をするものだった。
q を用意する。10**6未満の乱数 password を生成する。password のSHA-256値を整数として g に格納する。privA を生成する。g を privA 乗して q で割った余り pubA を求め、出力する。q 未満の整数 pubB を読み込む。pubB を privA 乗して q で割った余り shared を求める。g を shared**3 乗して q で割った余り verA を求め、出力する。verB を読み込む。verB が g を shared**5 乗して q で割った余りと一致した場合、以下の処理を行う。
password の十進数表現、"\0"、shared の十進数表現をこの順番で連結した文字列のSHA-256値 key を求める。flag.txt の内容を読み込み、flag に格納する。flag の内容を、鍵 key とnonce b'' を用いて AES.MODE_CTR で暗号化し、出力する。この処理は、BuckeyeCTF 2021 の Key exchange 2 に似ているようだった。
今回の定数 q を小さい値で割って余りをチェックした結果、4で割ると余りが1になった。
そこで、以下のコードをq における1の4乗根を求めた。
Information for connecting to a TCP server, a program for the server vuln.py, and a text file pow-solver.cpp were given.
What vuln.py does is:
q.password which is smaller than 10**6.password to g as an integer.privA which is a multiple of 40.pubA which is g to the privA-th power modulo q and print that.pubB which is greater than 1 and smaller than q.shared which is pubB to the privA-th power modulo q.verA which is g to the shared**3-th power modulo q and print that.verB.verB equals to g to the shared**5-th power modulo q, perform this:
key which is a SHA-256 value of a string created by concatenating a decimal representation of password, "\0", and a decimal representation of shared in this order.flag.txt and store that to flag.flag via AES.MODE_CTR using a key key and nonce b'' and print that.This process looks similar to what was used in Key exchange 2 (BuckeyeCTF 2021).
I divided the constant value q with smaller values and checked the remainders. As a result, I found that the remainder becomes 1 when q is divided by 4.
Seeing this, I calculated the 4th root of 1 modulo q by putting this code to
その結果、以下の4乗根が求まった。
Here are the 4th roots:
求めた4乗根のうち1でないもの(どれか1個)を pubB として入力すると、privA は40の倍数、すなわち4の倍数なので、shared は必ず1になる。
すると、1を3乗しても1なので、g の1乗、すなわち g の値が verA として出力される。
同様に g の shared**5 乗も g になるので、verB には出力された verA の値をそのまま入力するとよい。
Putting one of the 4th roots except for 1 as pubB will have shared become 1 because privA is a multiple of 40, which is a multiple of 4.
Then, since 1 to the 3rd power is 1, g to the 1st power, which is g, is printed as verA.
Since g to the shared**5-th power is also g, entering the printed value of verA as verB will work.
Connecting to the specified server via
そこで、この問題を解くため、以下のプログラムを作成した。
Seeing this, I created this program to solve this question.
このプログラムを12並列に設定して実行した結果、5分程度でSが求まった。
(なお、今回短時間で求まったのは解が探索範囲の最初の方にあったからであり、求まるまで30分程度かかることもありそうである)
求まった b88c3eee をサーバに送信すると、vuln.py が実行されたようで、以下のデータ (送信したデータを含む) が得られた。
I ran this program, setting it to use 12 processes, and it found the S after about 5 minutes.
(It looks like the answer was found early in this case because it was in the early part of the search space. It may take about 30 minutes to obtain S.)
After sending the answer b88c3eee to the server, it looked like vuln.py was executed and I obtained this data (including data I sent):
verA の値(0xは除く)が password のSHA-256値になっているので、これをファイル hashcat_target.txt に保存し、
以下のようにpassword の値を求めた。
The value of verA (excluding 0x) is the SHA-256 value of password.
I saved the SHA-256 value to a file hashcat_target.txt and obtained the value of password via
得られた password の値 919081 と、shared の値 1 を用い、以下のようにkey を求めた。
Then, I obtained the value of key via password (919081) and the value of shared (1) in this way:
Find / Replace, SHA2 - CyberChef
この key を用いて flag を復号しようとすると、16バイトのIVを要求された。
IVとして全部ゼロのデータを入力すると、flagが得られた。
CyberChef asked a 16-byte IV when I tried to decode flag using this key.
I obtained the flag by entering 16 byts of zero as IV.