はじめまして
情報系大学院1年生のtamiといいます
2024年開催の UofTCTF に、チームsayonaraとして参加しました
僕自身は、revを5問中4問解いて、チーム順位は1225チーム中94位でした
ググってもCEO’s Lost Passwordのwriteupがあまりなかったので、自分で書きました
ブログ・writeupを書くのは初めてなので、温かい目で見守ってください
目次
実行・動作確認
問題からBankChallenge.jarファイルをダウンロードします
まずは実行して動作確認してみます
ユーザ名はadminらしいですね
$ java -jar BankChallenge.jar
==============================
Welcome to TotallySecureBank™
==============================
Please enter your username:
admin (入力)
Please enter your password:
hogehoge (入力)
Incorrect password!
hogehogeではダメでした…
jarファイル展開
jarファイルを以下コマンドでzipに変換して展開します
$ cp BankChallenge.jar BankChallenge.zip
$ unzip BankChallenge.zip
結果、Main.classとa.classが出現します
.classファイル逆コンパイル
jd-guiを用いてMain.classファイルを逆コンパイルします
main関数の解析
最初に、main関数を解析します (字が小さくてごめんなさい)
main関数の処理は以下と予想します
- System.out.println関数で、b関数によって復号した文字列を表示
- a関数を実行し、戻り値をexit関数に渡して終了
b関数の解析
次に、b関数を解析します
いくつか無駄な処理や謎の処理があるので、修正します。
- 配列bを用いた難読化
- ifとlabelを用いた難読化
難読化解除後のb関数
上記2つを解除したb関数のコードは以下です
public static String b(String paramString) {
StringBuilder stringBuilder = new StringBuilder();
int i = 0;
while (i < paramString.length()) {
char c = paramString.charAt(i);
stringBuilder.append((char)(c ^ 0xCC77));
i++;
}
return stringBuilder.toString();
}
結果、文字 xor 0xCC77 を計算していることが分かりました
よく見る形式ですね
b関数の動作確認
実装したb関数を、main関数の引数を設定して呼び出してみます
public static void main(String[] args) {
String messEnc;
messEnc = "\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A";
System.out.println(b(messEnc));
messEnc = "\uCC20\uCC12\uCC1B\uCC14\uCC18\uCC1A\uCC12\uCC57\uCC03\uCC18\uCC57\uCC23\uCC18\uCC03\uCC16\uCC1B\uCC1B\uCC0E\uCC24\uCC12\uCC14\uCC02\uCC05\uCC12\uCC35\uCC16\uCC19\uCC1C\uED55";
System.out.println(b(messEnc));
messEnc = "\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A\uCC4A";
System.out.println(b(messEnc));
}
これを下記コマンドでコンパイル・実行します
$ javac b_dec.java
$ java b_dec
==============================
Welcome to TotallySecureBank™
==============================
動作確認で出現した文字列です!
うまく復号できています
引数無しのMain.a関数の解析
次に、main関数から呼ばれている引数無しのa関数を解析します
これも読みにくくなっているため、解除したものは以下です
難読化解除後の引数無しMain.a関数
static int a() {
String uname, pass;
Scanner scanner = new Scanner(System.in);
Map<String, a> map = Map.of(
"user", new a("j2ob77p+Pw==", 10.0F),
"admin", new a("te/fJg8aP0VUSVGCcI2Ll8erutizvrnJ", 100000.0F)
);
while (true) {
System.out.println("Please enter your username:");
uname = scanner.nextLine();
if (!map.containsKey(uname)) {
System.out.println("User not found")
continue;
}
break;
}
while (true) {
System.out.println("Please enter your password:");
str1 = scanner.nextLine();
if (((a)map.get(uname)).a(pass)) {
break;
} else {
System.out.println("Incorrect password!");
}
}
System.out.println("Welcome back " + uname + "! your balance is " + ((a)map.get(uname)).a());
return 0;
}
補足 : 2バイトを1文字として捉えられた
5, 6行目のa関数に渡している文字列に注目します
b関数で復号した文字列を見ようとしました
しかし、ただb関数に入力するだけでは、戻り値がアルファベットで表示されませんでした
2バイト分が1文字として出力されていたため、以下のコードで2文字に強制分割して表示しました
String mess = b(messEnc);
int i=0;
while (i < mess.length()) {
char c = mess.charAt(i);
System.out.print((char)((c & 0xFF00) >> 8));
System.out.print((char)((c & 0x00FF)));
i++;
}
結果、文字化けからbase64っぽいアルファベットに変換されました
user について
樲潢㜷瀫偷㴽 (2バイト1文字として表示)
j2ob77p+Pw== (1バイト1文字として表示)
admin について
瑥⽦䩧㡡倰噕卖䝃捉㉌永敲畴楺癲湊 (2バイト1文字として表示)
te/fJg8aP0VUSVGCcI2Ll8erutizvrnJ (1バイト1文字として表示)
a関数のコードを見ると、ユーザ名とパスワードを入力させ、照合している処理だと分かります。
アカウントは、”user”と”admin”の2つ存在します。
次に注目すべきは、mapの初期化と、パスワード比較処理です
Map<String, a> map = Map.of(
"user", new a("j2ob77p+Pw==", 10.0F),
"admin", new a("te/fJg8aP0VUSVGCcI2Ll8erutizvrnJ", 100000.0F)
);
------------------------------------------------------------------------
if (((a)map.get(uname)).a(pass))
map初期化処理
mapの初期化から見ていきます
base64文字列と数値をa関数に渡して初期化しています
aクラスはa.classファイルに記述されています
aクラスのコンストラクタで引数をフィールド変数に設定しています
パスワード比較処理
パスワード比較処理では、入力したパスワードをaクラスのa関数に渡しています
a.classファイルのa関数を読むと、以下のことが分かります
- Mainで記述されている引数有りのa関数に受け取った文字列を渡す
- a関数の結果と、コンストラクタで設定したフィールド変数を比較する
- 等しい場合はtrue, 異なる場合はfalseを返す
言い換えると、以下の関係式になるpasswordを特定する問題になります
Main.a(password) == a.a
a.aの内容 : "j2ob77p+Pw==" or "te/fJg8aP0VUSVGCcI2Ll8erutizvrnJ"
a.aについては、map初期化の際に渡しているbase64っぽい文字列なので
Main.a関数の処理が分かればpasswordが分かりそうです
引数有りMain.a関数の解析
main関数から呼ばれている引数有りのa関数を解析します
この処理が分かれば、ソルバが書けそうです
これも読みにくくなっているため、解除したものは以下です
難読化解除後の引数有りMain.a関数
static String a(String paramString) {
byte[] arrayOfByte = paramString.getBytes(StandardCharsets.UTF_8);
int i = 1;
while (i <= paramString.length()) {
int j = 0;
while (j < arrayOfByte.length) {
arrayOfByte[j] = (byte)(arrayOfByte[j] + ((i - 0xC) * j + 6));
j++;
}
i++;
}
return new String(Base64.getEncoder().encode(arrayOfByte),
StandardCharsets.UTF_16);
}
結果、文字列を以下の処理で1文字ずつ暗号化し、base64エンコーディングしていることが分かります
arrayOfByte[j] = (byte)(arrayOfByte[j] + ((i - 0xC) * j + 6));
この逆操作をすれば復号できそうです
arrayOfByteに足されている数値を引けば良さそうです
arrayOfByte[j] = (byte)(arrayOfByte[j] - ((i - 0xC) * j + 6));
ソルバの作成
情報を以下にまとめます
パスワードはMain.a関数によって暗号化、base64エンコーディングされている
この処理の結果、赤マークの文字列が出力される
"user", "j2ob77p+Pw=="
"admin","te/fJg8aP0VUSVGCcI2Ll8erutizvrnJ"
復号にはbase64デコーディング、暗号化の逆演算を記述すれば良さそう
ということで、ソルバは以下です
ソルバ (DecryptFunction.java)
public class DecryptFunction {
public static void main(String[] args) {
// Main.a関数によって暗号化されたパスワード
String encryptedInput = "te/fJg8aP0VUSVGCcI2Ll8erutizvrnJ";
// 復号
String decryptedOutput = decrypt(encryptedInput);
System.out.println("Decrypted Output: " + decryptedOutput);
}
private static String decrypt(String base64Input) {
// Base64デコード
byte[] decodedBytes = java.util.Base64.getDecoder().decode(base64Input);
// 逆変換(加工の逆操作)
int i = 1;
while (i <= decodedBytes.length) {
int j = 0;
while (j < decodedBytes.length) {
decodedBytes[j] = (byte) (decodedBytes[j] - ((i - 0xC) * j + 6));
j++;
}
i++;
}
// バイト列をUTF-8文字列に変換
String decryptedString = new String(decodedBytes,
java.nio.charset.StandardCharsets.UTF_8);
return decryptedString;
}
}
実行結果は以下の通りです
$ javac DecryptFunction.java
$ java DecryptFunction
Decrypted Output: %S7rONgadMInPaSSwORd32%
flag : uoftctf{%S7rONgadMInPaSSwORd32%}
拙い文章ですが、最後まで読んでいただき、ありがとうございました。
久しぶりに java 系の問題を解いたので、変なこと書いてないか心配で心臓がバクバクしています
コメント