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.