Well, they look so similar... siblangs.apk
apkファイルはただのzipファイルなのでとりあえずunzipします
ctf4b{
で展開したフォルダを検索に掛けるとassets/index.android.bundle
が見つかります
どうやらReact Native製のアプリのようです
さっそく周辺の処理を見てみましょう
function v() {
var t;
(0, l.default)(this, v);
for (var o = arguments.length, n = new Array(o), c = 0; c < o; c++) n[c] = arguments[c];
return (t = y.call.apply(y, [this].concat(n))).state = {
flagVal: "ctf4b{",
xored: [34, 63, 3, 77, 36, 20, 24, 8, 25, 71, 110, 81, 64, 87, 30, 33, 81, 15, 39, 90, 17, 27]
}, t.handleFlagChange = function(o) {
t.setState({
flagVal: o
})
}, t.onPressValidateFirstHalf = function() {
if ("ios" === h.Platform.OS) {
for (var o = "AKeyFor" + h.Platform.OS + "10.3", l = t.state.flagVal, n = 0; n < t.state.xored.length; n++)
if (t.state.xored[n] !== parseInt(l.charCodeAt(n) ^ o.charCodeAt(n % o.length), 10)) return void h.Alert.alert("Validation A Failed", "Try again...");
h.Alert.alert("Validation A Succeeded", "Great! Have you checked the other one?")
} else h.Alert.alert("Sorry!", "Run this app on iOS to validate! Or you can try the other one :)")
}, t.onPressValidateLastHalf = function() {
"android" === h.Platform.OS ? p.default.validate(t.state.flagVal, function(t) {
t ? h.Alert.alert("Validation B Succeeded", "Great! Have you checked the other one?") : h.Alert.alert("Validation B Failed", "Learn once, write anywhere ... anywhere?")
}) : h.Alert.alert("Sorry!", "Run this app on Android to validate! Or you can try the other one :)")
}, t
}
とても怪しげなxord
という数列と"AKeyFor" + h.Platform.OS + "10.3"
を鍵にしてxorを掛けてることが分かります
エラー表示からiOS上で実行することを想定してるみたいなのでh.Platform.OS
にはios
が入り鍵はAkeyForios10.3
となります
では復号しましょう
#!/usr/bin/env python3
from Crypto.Cipher import AES
c1 = [34, 63, 3, 77, 36, 20, 24, 8, 25, 71, 110, 81, 64, 87, 30, 33, 81, 15, 39, 90, 17, 27]
o = "AKeyForios10.3"
flag = ""
for i in range(len(c1)):
flag += chr(c1[i] ^ ord(o[i % len(o)]))
print(flag)
ctf4b{jav4_and_j4va5cr
正しそうですが後ろが欠けています
どうやら後半は別のところにあるようです
xor以降の処理を追ってみるとp.default.validate
という関数があるのが分かります
しかし、assets/index.android.bundle
内でvalidate
を検索を掛けても関数が見つかりません
どうやらネイティブメソッドを呼んでいるようです
dex2jarを使いネイティブメソッドを解析してきます
es/o0i/challengeapp/nativemodule/
にValidateFlagModule.class
という見るからに怪しいファイルがあるのでcfrでデコンパイルしてみましょう
/*
* Decompiled with CFR 0.149.
*
* Could not load the following classes:
* com.facebook.react.bridge.Callback
* com.facebook.react.bridge.ReactApplicationContext
* com.facebook.react.bridge.ReactContextBaseJavaModule
* com.facebook.react.bridge.ReactMethod
*/
package es.o0i.challengeapp.nativemodule;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.security.Key;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class ValidateFlagModule
extends ReactContextBaseJavaModule {
private static final int GCM_IV_LENGTH = 12;
private final ReactApplicationContext reactContext;
private final SecretKey secretKey;
private final SecureRandom secureRandom = new SecureRandom();
public ValidateFlagModule(ReactApplicationContext reactApplicationContext) {
super(reactApplicationContext);
this.secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES");
this.reactContext = reactApplicationContext;
}
public String getName() {
return "ValidateFlagModule";
}
@ReactMethod
public void validate(String arrby, Callback callback) {
byte[] arrby2;
byte[] arrby3 = arrby2 = new byte[43];
arrby2[0] = 95;
arrby3[1] = -59;
arrby3[2] = -20;
arrby3[3] = -93;
arrby3[4] = -70;
arrby3[5] = 0;
arrby3[6] = -32;
arrby3[7] = -93;
arrby3[8] = -23;
arrby3[9] = 63;
arrby3[10] = -9;
arrby3[11] = 60;
arrby3[12] = 86;
arrby3[13] = 123;
arrby3[14] = -61;
arrby3[15] = -8;
arrby3[16] = 17;
arrby3[17] = -113;
arrby3[18] = -106;
arrby3[19] = 28;
arrby3[20] = 99;
arrby3[21] = -72;
arrby3[22] = -3;
arrby3[23] = 1;
arrby3[24] = -41;
arrby3[25] = -123;
arrby3[26] = 17;
arrby3[27] = 93;
arrby3[28] = -36;
arrby3[29] = 45;
arrby3[30] = 18;
arrby3[31] = 71;
arrby3[32] = 61;
arrby3[33] = 70;
arrby3[34] = -117;
arrby3[35] = -55;
arrby3[36] = 107;
arrby3[37] = -75;
arrby3[38] = -89;
arrby3[39] = 3;
arrby3[40] = 94;
arrby3[41] = -71;
arrby3[42] = 30;
byte[] arrby4 = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec gCMParameterSpec = new GCMParameterSpec(128, arrby2, 0, 12);
arrby4.init(2, (Key)this.secretKey, gCMParameterSpec);
arrby4 = arrby4.doFinal(arrby2, 12, arrby2.length - 12);
arrby = arrby.getBytes();
int n = 0;
do {
if (n >= arrby4.length) break;
if (arrby[n + 22] != arrby4[n]) {
callback.invoke(new Object[]{false});
return;
}
++n;
} while (true);
try {
callback.invoke(new Object[]{true});
}
catch (Exception exception) {
callback.invoke(new Object[]{false});
}
}
}
AESで暗号化された配列を復号して入力文字列と比較しているようです
復号された文字を抜くためにちょっとコードを弄ります
import java.security.Key;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
class ValidateFlagModule {
private static final int GCM_IV_LENGTH = 12;
private final SecureRandom secureRandom = new SecureRandom();
public static void main(String[] args) {
byte[] arrby2;
byte[] arrby3 = arrby2 = new byte[43];
arrby2[0] = 95;
arrby3[1] = -59;
arrby3[2] = -20;
arrby3[3] = -93;
arrby3[4] = -70;
arrby3[5] = 0;
arrby3[6] = -32;
arrby3[7] = -93;
arrby3[8] = -23;
arrby3[9] = 63;
arrby3[10] = -9;
arrby3[11] = 60;
arrby3[12] = 86;
arrby3[13] = 123;
arrby3[14] = -61;
arrby3[15] = -8;
arrby3[16] = 17;
arrby3[17] = -113;
arrby3[18] = -106;
arrby3[19] = 28;
arrby3[20] = 99;
arrby3[21] = -72;
arrby3[22] = -3;
arrby3[23] = 1;
arrby3[24] = -41;
arrby3[25] = -123;
arrby3[26] = 17;
arrby3[27] = 93;
arrby3[28] = -36;
arrby3[29] = 45;
arrby3[30] = 18;
arrby3[31] = 71;
arrby3[32] = 61;
arrby3[33] = 70;
arrby3[34] = -117;
arrby3[35] = -55;
arrby3[36] = 107;
arrby3[37] = -75;
arrby3[38] = -89;
arrby3[39] = 3;
arrby3[40] = 94;
arrby3[41] = -71;
arrby3[42] = 30;
try{
Cipher arrby4 = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec gCMParameterSpec = new GCMParameterSpec(128, arrby2, 0, GCM_IV_LENGTH);
SecretKey secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES");
arrby4.init(javax.crypto.Cipher.DECRYPT_MODE, (Key)secretKey, gCMParameterSpec);
byte[] d = arrby4.doFinal(arrby2, 12, arrby2.length - 12);
int n = 0;
System.out.println(new String(d));
} catch (Exception e) {}
}
}
1pt_3verywhere}
2つ合わせるとflag
ctf4b{jav4_and_j4va5cr1pt_3verywhere}