SECCON Beginners CTF 2020 WriteUpのようなもの
SECCON Beginners CTF 2020に参加しました!
SECCON Beginners CTF 2020にチームMIS.Wとしてsoeki、musaprg、shiro、tommi、aoiと参加しました。結果は700点、144位(/1009)でした。
WriteUpとか言っていますが、力業のごり押しなので悪しからず。想定解は私以外の人の方がもっと詳しくわかりやすく書いてあると思います。
SECCON Challenges
解けたもの
[MISC] emoemoencode
emoemoencodeをとりあえず
$ cat emoemoencode.txt
してみました。すると絵文字がたくさん現れてきました。
🍣🍴🍦🌴🍢🍻🍳🍴🍥🍧🍡🍮🌰🍧🍲🍡🍰🍨🍹🍟🍢🍹🍟🍥🍭🌰🌰🌰🌰🌰🌰🍪🍩🍽
栗が多い。ま、当然なんのこっちゃわからないのでバイナリエディタを使ってバイナリを見てみました。
ここで私は最初、全角英数を変換してこのバイナリにしていると思い、ctf4b{
のバイナリと比較してみましたが、変換法則が見つからなかったので断念。
次に半角英数を見てみました。ctf4b{
のバイナリと比べてみると、いい感じに規則が見つかりました。
F0 9F 8D XX
の場合は
XX
がA0
なら全体を60
に置き換え、B0
なら全体を70
に置き換え、のように行いました。
例: F0 9F 8D A0
→ 60
F0 9F 8C XX
の場合は
XX
がB0
なら全体を30
に置き換え。
例: F0 9F 8C B0
→ 30
これによって得られたフラッグは
ctf4b{stegan0graphy_by_em000000ji}
[REVERSING] mask
とりあえず./mask
をして実行すると、
$ ./mask Usage: ./mask [FLAG]
と返ってくる。ので今度は[FLAG]に文字を入れて実行。
$ ./mask ctf4b{} Putting on masks... atd4`qu c`b bki Wrong FLAG. Try again.
と返ってきた。何かしら変換を行って2つの何かと比較しているようだ。
$ strings mask
を実行し、文字列を見てみると怪しい2つの文字列を発見。
atd4`qdedtUpetepqeUdaaeUeaqau c`b bk`kj`KbababcaKbacaKiacki
あとはこれに合うように[FLAG]を決めれば良いと思い、すべてのアルファベットを探索。 ← これを力業という。
元の文字 | 変化後A | 変化後B |
---|---|---|
a | a | a |
b | ` | b |
c | a | c |
d | d | ` |
e | e | a |
f | d | b |
g | e | c |
h | ` | h |
i | a | i |
j | ` | j |
k | a | k |
l | d | h |
m | e | i |
n | d | j |
o | e | k |
p | p | ` |
q | q | a |
r | p | b |
s | q | c |
t | t | ` |
u | u | a |
v | t | b |
w | u | c |
x | p | h |
y | q | i |
z | p | j |
ここから存在しないものは記号をテキトーに入れていき、得られたフラッグは
ctf4b{dont_reverse_face_mask}
[REVERSING] yakisoba
とりあえず./yakisoba
をして実行すると、
$ ./yakisoba
FLAG: ctf4b{}
Wrong!
FLAGにはとりあえずテキトーに入れてみた。
今回はmaskとは違い、何も出てこない。strings yakisoba
をしても出てこない。
何も出てこないのは困るので逆アセンブルしました。
IDAを使用して逆アセンブルをし、グラフとして出てきたもの見ていたら、1文字ずつ正誤判定しているようでした。
ので、グラフをたどっていって無事フラグが手に入りました。
ctf4b{sp4gh3tt1_r1pp3r1n0}
↑これをちまちま一つずつ見ていったという、どう考えても時間かかる力業で解きました。
え?アセンブリを読めって?嫌だよ面倒くさい()
解けなかったもの
[REVERSING] siblangs
siblangs.apkだったのでとりあえず手持ちのXperia XZ Premiumにインストール、実行。
ここでVALIDATE AはiOSで動かせと言われる。apkなので当然動くわけない。(持ってないから動かせないしね)
とりあえず動くVALIDATE Bの方を使うことにした。
.apkファイルはただの.zipなので解凍して、そして中のソースファイル(classes.dex)を.jarに変換後、デコンパイルしてみた。
そこで死ぬほど怪しいものを発見。
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.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 = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES"); private final SecureRandom secureRandom = new SecureRandom(); public ValidateFlagModule(ReactApplicationContext paramReactApplicationContext) { super(paramReactApplicationContext); this.reactContext = paramReactApplicationContext; } public String getName() { return "ValidateFlagModule"; } @ReactMethod public void validate(String paramString, Callback paramCallback) { byte[] arrayOfByte = new byte[43]; arrayOfByte[0] = 95; arrayOfByte[1] = -59; arrayOfByte[2] = -20; arrayOfByte[3] = -93; arrayOfByte[4] = -70; arrayOfByte[5] = 0; arrayOfByte[6] = -32; arrayOfByte[7] = -93; arrayOfByte[8] = -23; arrayOfByte[9] = 63; arrayOfByte[10] = -9; arrayOfByte[11] = 60; arrayOfByte[12] = 86; arrayOfByte[13] = 123; arrayOfByte[14] = -61; arrayOfByte[15] = -8; arrayOfByte[16] = 17; arrayOfByte[17] = -113; arrayOfByte[18] = -106; arrayOfByte[19] = 28; arrayOfByte[20] = 99; arrayOfByte[21] = -72; arrayOfByte[22] = -3; arrayOfByte[23] = 1; arrayOfByte[24] = -41; arrayOfByte[25] = -123; arrayOfByte[26] = 17; arrayOfByte[27] = 93; arrayOfByte[28] = -36; arrayOfByte[29] = 45; arrayOfByte[30] = 18; arrayOfByte[31] = 71; arrayOfByte[32] = 61; arrayOfByte[33] = 70; arrayOfByte[34] = -117; arrayOfByte[35] = -55; arrayOfByte[36] = 107; arrayOfByte[37] = -75; arrayOfByte[38] = -89; arrayOfByte[39] = 3; arrayOfByte[40] = 94; arrayOfByte[41] = -71; arrayOfByte[42] = 30; try { Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); GCMParameterSpec gCMParameterSpec = new GCMParameterSpec(128, arrayOfByte, 0, 12); cipher.init(2, this.secretKey, gCMParameterSpec); arrayOfByte = cipher.doFinal(arrayOfByte, 12, arrayOfByte.length - 12); byte[] arrayOfByte1 = paramString.getBytes(); for (int i = 0;; i++) { if (i < arrayOfByte.length) { if (arrayOfByte1[i + 22] != arrayOfByte[i]) { paramCallback.invoke(new Object[] { Boolean.valueOf(false) }); return; } } else { paramCallback.invoke(new Object[] { Boolean.valueOf(true) }); return; } } } catch (Exception exception) { paramCallback.invoke(new Object[] { Boolean.valueOf(false) }); return; } } }
どうもエンコードされたByte列をデコードしてから入力文字列の23文字目から比較しているらしい。
ということで自前でJavaでその部分だけ実行してみた。
import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; class cyph { private static final SecretKey secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES"); public static void main(String args[]) throws Exception { byte[] arrayOfByte = new byte[43]; arrayOfByte[0] = 95; arrayOfByte[1] = -59; arrayOfByte[2] = -20; arrayOfByte[3] = -93; arrayOfByte[4] = -70; arrayOfByte[5] = 0; arrayOfByte[6] = -32; arrayOfByte[7] = -93; arrayOfByte[8] = -23; arrayOfByte[9] = 63; arrayOfByte[10] = -9; arrayOfByte[11] = 60; arrayOfByte[12] = 86; arrayOfByte[13] = 123; arrayOfByte[14] = -61; arrayOfByte[15] = -8; arrayOfByte[16] = 17; arrayOfByte[17] = -113; arrayOfByte[18] = -106; arrayOfByte[19] = 28; arrayOfByte[20] = 99; arrayOfByte[21] = -72; arrayOfByte[22] = -3; arrayOfByte[23] = 1; arrayOfByte[24] = -41; arrayOfByte[25] = -123; arrayOfByte[26] = 17; arrayOfByte[27] = 93; arrayOfByte[28] = -36; arrayOfByte[29] = 45; arrayOfByte[30] = 18; arrayOfByte[31] = 71; arrayOfByte[32] = 61; arrayOfByte[33] = 70; arrayOfByte[34] = -117; arrayOfByte[35] = -55; arrayOfByte[36] = 107; arrayOfByte[37] = -75; arrayOfByte[38] = -89; arrayOfByte[39] = 3; arrayOfByte[40] = 94; arrayOfByte[41] = -71; arrayOfByte[42] = 30; Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); GCMParameterSpec gCMParameterSpec = new GCMParameterSpec(128, arrayOfByte, 0, 12); cipher.init(2, secretKey, gCMParameterSpec); arrayOfByte = cipher.doFinal(arrayOfByte, 12, arrayOfByte.length - 12); String text = new String(arrayOfByte); System.out.println(text); } }
すると
1pt_3verywhere}
とだけ出てきた。後ろ半分はこれでわかった。
ただ、前半分はわかりませんでした。他の人のWriteUpを見るとReact Nativeがーーーって書いてあってたしかにそんな文字見たなとなりました。
くやちーーーーー
SECCONを終えて
REVERSING、MISCが解けてちょっと嬉しい。前回はMISCしか解けなかったので…
PWNやりたいって言っていた気がするけどまだ実力不足で何もわかりませんでした(´;ω;`)