SECCON Beginners CTF 2021 WriteUpのようなもの
SECCON Beginners CTF 2021に参加しました!
SECCON Beginners CTF 2021にチームM15.(W|X)として参加し、3327点で23位(/943)でした!
チームメイトはsoeki, siro53, eren53でした。
今回は前回と比べ結構解けたかなという感じですね
嬉しい
SECCON Challenges
解けたもの
[reversing] only_read
名前の通り、challを読めばいい(機械語で)
ま、読むわけないんですけどね機械語でなんて。
ということでghidraにかけます
途中省略しています。
if (((((((char)local_28 == 'c') && (local_28._1_1_ == 't')) && (local_28._2_1_ == 'f')) && (((local_28._3_1_ == '4' && (local_28._4_1_ == 'b')) && ((local_28._5_1_ == '{' && ((local_28._6_1_ == 'c' && (local_28._7_1_ == '0')))))))) && (((char)local_20 == 'n' && ((((((local_20._1_1_ == '5' && (local_20._2_1_ == 't')) && (local_20._3_1_ == '4')) && ((local_20._4_1_ == 'n' && (local_20._5_1_ == 't')))) && ((local_20._6_1_ == '_' && ((local_20._7_1_ == 'f' && ((char)local_18 == '0')))))) && (local_18._1_1_ == 'l')))))) && ((((local_18._2_1_ == 'd' && (local_18._3_1_ == '1')) && ((char)local_14 == 'n')) && ((local_14._1_1_ == 'g' && (local_12 == '}')))))) { puts("Correct"); }
全部書いてありますね!
ctf4b{c0n5t4nt_f0ld1ng}
[reversing] children
10個の子プロセスを生成し、そのPIDを入力する問題。
ps aux
コマンドでプロセスを確認してPIDを入れるだけの簡単なお仕事。
最後にプロセスをいくつ生成した?って聞かれるけどそれもps aux
で出てきたのを数えれば終わり。
ctf4b{p0werfu1_tr4sing_t0015_15_usefu1}
[reversing] please_not_trace_me
これはプログラムはちゃんとflagを生成しているのにもかかわらず出力しないタイプのやつ。
でも問題名の通りトレースするとトレースすんなやゴラって怒られます。(prease not trace me...
っていう文言でやさしかったわ)
ということでいつものghidra
local_30 = 9; _global_argv = param_2; _global_argc = param_1; _global_envp = param_3; do { switch(local_30) { case 0: local_50 = 2; local_30 = 0xb; break; case 2: local_30 = _1_main_flag_func_0(local_38,0xffffffffffffffff,0x10,5); break; case 5: if (local_50 == 6) { local_30 = 0x12; } else { local_30 = 8; } break; case 6: if (local_40 == 0) { local_30 = 0; } else { local_30 = 0xb; } break; case 8: fwrite("prease not trace me...\n",1,0x17,stderr); /* WARNING: Subroutine does not return */ exit(1); case 9: local_54 = 0; local_30 = 10; break; case 10: switch(local_54) { case 0: local_30 = 0x16; break; case 1: local_30 = 0xf; break; case 2: local_30 = 0x13; break; case 3: local_30 = 0x14; break; case 4: local_30 = 0x11; break; case 5: local_30 = 0x15; break; default: local_30 = 0x12; } break; case 0xb: local_38 = ptrace(PTRACE_TRACEME,0,1,0); local_30 = 2; break; case 0xf: local_48 = malloc(0x10); local_30 = 0x12; break; case 0x10: local_50 = local_50 * 3; local_30 = 5; break; case 0x11: puts("flag decrypted. bye."); local_30 = 0x12; break; case 0x12: local_54 = local_54 + 1; local_30 = 10; break; case 0x13: generate_key(local_48); local_30 = 0x12; break; case 0x14: rc4(e,local_48); local_30 = 0x12; break; case 0x15: /* WARNING: Subroutine does not return */ exit(0); case 0x16: local_50 = 0; local_40 = ptrace(PTRACE_TRACEME,0,1,0); local_30 = 6; } } while( true );
これを見るとlocal_30というものでswitch文を回していることが分かる。
なので、local_30の値を弄ってptraceを通らないようにしてあげればいい。
具体的には0x0F→0x13→0x14って通ればよい。
で、弄ってからトレースする
ctf4b{d1d_y0u_d3crypt_rc4?}
[reversing] firmware
何かのファームウェアのバイナリが落ちてくる。(firmware.bin)
で、これは複数のファイルを一つにまとめたものであることが分かる。
なので、全部分割しちゃえ!!!ってことで分割をするのです。
firmware.binの1行目途中からファイル名とそのMD5ハッシュが記録されているのでそれを頼りに分割する。
んで、こうなる
これを見るとfirm以外重要そうなものが見つからないのでfirm(32bit ELF ARM)をghidraで見てみる(いつもの)
recv(local_11d8,abStack4116,0x1000,0); printf("%s",abStack4116); memcpy(auStack4520,&DAT_00010ea4,0xf4); __n = strlen((char *)abStack4116); if (__n != 0x3d) { local_10b4 = 0x6f636e49; uStack4272 = 0x63657272; uStack4268 = 0x61702074; uStack4264 = 0x6f777373; local_10a4 = 0xa2e6472; local_10a0 = 0; __n = strlen((char *)&local_10b4); send(local_11d8,&local_10b4,__n,0); close(local_11d8); } local_11e0 = 0; while (local_11e0 < 0x3d) { if ((uint)(abStack4116[local_11e0] ^ 0x53) != auStack4520[local_11e0]) { local_10b4 = 0x6f636e49; uStack4272 = 0x63657272; uStack4268 = 0x61702074; uStack4264 = 0x6f777373; local_10a4 = 0xa2e6472; local_10a0 = 0; __n = strlen((char *)&local_10b4); send(local_11d8,&local_10b4,__n,0); close(local_11d8); } local_11e0 = local_11e0 + 1; } local_10b4 = 0x72726f43; uStack4272 = 0x20746365; uStack4268 = 0x73736170; uStack4264 = 0x64726f77; local_10a4 = 0xa212121; local_10a0 = 0; __n = strlen((char *)&local_10b4); send(local_11d8,&local_10b4,__n,0); close(local_11d8);
すると、文字数(0x3d)と文字列を判別していることが分かる。
文字列については0x53とxor取ってから判別しているので、プログラムに埋め込まれているものに0x53でxorして出力。
#include <stdio.h> int main(int argc, char *argv[]) { char tmp[] = {0x30, 0x27, 0x35, 0x67, 0x31, 0x28, 0x3a, 0x63, 0x27, 0x0c, 0x37, 0x36, 0x25, 0x62, 0x30, 0x36, 0x0c, 0x35, 0x3a, 0x21, 0x3e, 0x24, 0x67, 0x21, 0x36, 0x0c, 0x32, 0x3d, 0x32, 0x62, 0x2a, 0x20, 0x3a, 0x60, 0x0c, 0x21, 0x36, 0x25, 0x60, 0x32, 0x62, 0x20, 0x0c, 0x32, 0x0c, 0x3f, 0x63, 0x27, 0x0c, 0x3c, 0x35, 0x0c, 0x66, 0x36, 0x30, 0x21, 0x36, 0x64, 0x20, 0x2e, 0x59}; int s = 61; for(int i = 0; i < s; i++) { printf("%c", (tmp[i] ^ 0x53)); } printf("\n"); }
で、終わり。
ctf4b{i0t_dev1ce_firmw4re_ana1ysi3_rev3a1s_a_l0t_of_5ecre7s}
[misc] Mail_Address_Validator
rubyのプログラムを拾えるので見てみると、正規表現を使ってチェックしてるだけ。
チェックがタイムアウト(5秒)するとflagが落ちてくるらしい。
正規表現でタイムアウトさせるためにクソ長メールアドレスっぽい文字列を入れた。(約80万文字)
するとあっさり表示された。
ctf4b{1t_15_n0t_0nly_th3_W3b_th4t_15_4ff3ct3d_by_ReDoS}
[misc] fly
Wolf RPGエディタのゲーム内のオブジェクトにflagが隠されているのだけが分かる。
でも壁があって通り抜けられないし煽ってくるキャラクターもいる。
Wolf RPGエディタは起動時引数にTest_Of_Main
を指定するとデバッグモードで起動することができるので、これを使わない手はない。
デバッグモード時では、F11キーを押すことで固定データのみ更新することができる(可変データはそのまま残る)。
ということで、Wolf RPGエディタをダウンロードした時にあったサンプルのデータを利用して起動する。
この時、Data.wolf
はData.wolf1
などと改名しておき、Game.exe
から見えないようにしておく。
その後、Game.exe Test_Of_Main
コマンドを利用して起動する。
するとサンプルデータのゲームが起動するので、スタートする。
ここでさっき改名しておいたData.wolf1
をData.wolf
に戻してF11キーを押すと楽しいことが起きる。
動けない場合は動けるような場所に最初から移動しておいてください。
ちなみに青い階段にはイベントが設定されていないのでflagを見つけることができない。
なので、サンプルデータのゲームで場面転換しておくといい。
サンプルデータのゲームで上へ行き、上のマップへ移動してください。
ここからはさっきと同じで、改名しておいたData.wolf1
をData.wolf
に戻してF11キーを押す。
すると…?
ctf4b{b3_c4r3ful_0f_fl135_wh3n_73l3p0r71n6}
[misc] depixelization
pythonのプログラムとflagの画像が落とせる。
ここで普通はプログラムで行っている処理の逆を行いflagを復元しよう!となるだろう。
私はこのプログラムには好きな文字列に同様の処理をすることができるのに気付いたので、全アルファベットと数字と{}_を処理した画像を出力した。
あとはこれのどれと完全一致するか、というのを判別するプログラムを書き、終わり。
import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; public class Main { public static void main(String[] args) { try { String flagFile = "ファイル名書き換えて"; String alphabetFile = "ファイル名書き換えて"; BufferedImage flagImg = ImageIO.read(new File(flagFile)); BufferedImage alphabetImg = ImageIO.read(new File(alphabetFile)); int flagWidth = flagImg.getWidth(); int alphabetWidth = alphabetImg.getWidth(); for (int i = 0; i < flagWidth; i += 85) { boolean match = false; int matchnum = 0; for (int j = 0; j < alphabetWidth && !match; j += 85) { boolean ok = true; for (int x = 5; x < 85 && ok; x += 10) { for (int y = 5; y < 100 && ok; y += 10) { if (flagImg.getRGB(i + x, y) != alphabetImg.getRGB(j + x, y)) { ok = false; } } } if (ok) { match = true; matchnum = j / 85; } } if (match) { if (matchnum < 'z' - 'a') { System.out.print((char) (matchnum + 'a')); } else { matchnum = matchnum - 'z' + 'a' - 1; if (matchnum < 10) { System.out.print(matchnum); } else { char a = ' '; switch(matchnum) { case 10: a = '{'; break; case 11: a = '}'; break; case 12: a = '_'; break; } System.out.print(a); } } } else { System.out.print("."); } } } catch (Exception e) { e.printStackTrace(); } } }
これだとnとmの判別がつかないらしく、実際nがmとして認識された。
ctf4b{1f_y0u_p1x_y0u_c4m_d3p1x}
と出力されたが、間違いだったのでmをnに修正。
ctf4b{1f_y0u_p1x_y0u_c4n_d3p1x}
SECCONの感想
今回は前回と比べ倍以上の問題が解けたので大満足
PWNは前回同様解けませんでした。
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やりたいって言っていた気がするけどまだ実力不足で何もわかりませんでした(´;ω;`)
SECCONに初めて参加した参加記録 【SECCON 2019】
SECCONに参加しました!
SECCON2019に、チームMIS.Wとしてmusaprg、soeki、shiroと一緒に参加しました。結果は456点で148位でした。 ほぼ初めてな人ばかりが集まるチームで「目標は500点くらい取れればいいよね」と言っていたので、良い方ではないかと思っています。
SECCON Challenges
Beeeeeeeeeer
まずはBeeeeeeeeeerです。 Let's decode!
と問題分に書かれています。
echo -e "\033#8" sleep 1 C=$(tput cols) L=$(tput lines) for ID in $(seq $(($L * $C * 6))); do x=$(($RANDOM % $C)) y=$(($RANDOM % $L)) printf "\033[${y};${x}f " done for ID in $(seq $(($L * $C * 6))); do x=$(($RANDOM % $C)) y=$(($RANDOM % $L)) printf "\033[${y};${x}fF" done clear echo TGV0J3MgZGVjb3JkaW5nISjiiafiiIDiiaYqKQo= | base64 -d read $'\164\162\141\160' '' $'\61' $'\62' $'\63' $'\x31\x35' $'\x31\x38' $'\u0031\u0039' $(echo MjAK | base64 -d) echo $- | grep x && exit $(echo =btB | rev | tr A-Za-z N-ZA-Mn-za-m | base64 -d) | $(echo =bNpyW3M | rev | tr A-Za-z N-ZA-Mn-za-m | base64 -d) $(echo XRKY | rev | tr A-Za-z N-ZA-Mn-za-m | base64 -d) $(echo =ogO | rev | base64 -d) && exit if [ -z "$1" ]; then ID=$'\x6e\x61\x6e\x64\x6f\x6b\x75'; else ID="$1"; fi if whoami | grep -e root -e user -e adm -e nobody -e test -e "$ID" >/dev/null; then :; else exit; fi for i in $($'\x73\x65\x71' $((RANDOM % 10))); do $($'\x65\x63\x68\x6f' c2xlZXAK | $'\x62\x61\x73\x65\x36\x34' -d) $((RANDOM % 300)); done $'\145\143\150\157' $- | $(echo =oAclJ3Z | rev | base64 -d) $(echo =oAe | rev | base64 -d) && $($'\x65\x63\x68\x6f' ZXhpdAo= | $'\x62\x61\x73\x65\x36\x34' $'\x2d\x64') $(echo =btB | rev | tr A-Za-z N-ZA-Mn-za-m | base64 -d) | $(echo M3WypNb= | tr A-Za-z N-ZA-Mn-za-m | base64 -d) $(echo YKRX | tr A-Za-z N-ZA-Mn-za-m | base64 -d) $(echo Btb= | tr A-Za-z N-ZA-Mn-za-m | base64 -d) && exit $'\145\170\160\157\162\164' $'\u0053\u0031'=$(echo aG9nZWZ1Z2EK | base64 -d)
Beeeeeeeeeerを見る限り、Shell Scriptであることがわかるので、実行権限を与えて実行してみると、
Let's decording!(≧∀≦*)
というメッセージが。
また、 read
以降の文が全てエンコードされていることがわかります。なのでとりあえずdecodeします。
read trap '' 1 2 3 15 18 19 20 echo $- | grep x && exit : | grep -q : && exit if [ -z "$1" ]; then ID=nandoku; else ID="$1"; fi if whoami | grep -e root -e user -e adm -e nobody -e test -e "$ID" >/dev/null; then :; else exit; fi for i in $(seq $((RANDOM % 10))); do sleep $((RANDOM % 300)); done echo $- | grep x && exit : | grep -q : && exit export S1=hogefuga
いらないコードばっかりですね。必要なのは export S1=hogefuga
だけです。 なんで執拗にexitさせてくるんだ
更にその後のコードもデコードしてみると、
: killall sh : shutdown $(: pkill sh) : shutdown : shutdown : poweroff : poweroff : exit : poweroff : shutdown : shutdown : shutdown : pkill sh : shutdown
こうなっていました。これ以上にこの並びが続いていて、どんだけ電源落としたいんだってなります…
重要なのは export S1=hogefuga
とその後のechoの1行だけです。
その場所だけ隔離してbashで実行してみたところ、ビープ音のなった回数をランダムで聞いてくるとても面倒くさいプログラムにぶち当たります。(間違えるとexitされるのでたちが悪い)
| base64 -d | gunzip | bash
を
| base64 -d | gunzip > Beeeeeeeeeer2.sh
として出力してみると
for k in $(seq $((RANDOM % 10 + 1))); do l=$((RANDOM % 10 + 1)) for m in $($(echo ==gCxV2c | rev | base64 -d) $l); do echo -ne '\a' sleep 1 done echo "How many beeps?" read n </dev/tty export n if [ "$n" -ne "$l" ]; then exit; fi done echo -ne '\a' sleep 1 echo -ne '\a' sleep 1 echo -ne '\a' sleep 1 echo "How many beeps?" read n </dev/tty export n
何だこれは!!!私の邪魔をしてくるコードがたくさん
export n
だけが必要であり、n=3であることがわかるので、 export 3
だけを残して削除します。
更にその後にもechoがあり、そこがまた重要なのでそこだけを隔離して実行してみます。
Enter the password
と出てきますが、パスワードなんて知りません。ので、やっぱり
| base64 -d | openssl aes-256-cbc -d -pass pass:$(echo -n $n | md5sum | cut -c2,3,5,12) -md md5 2>/dev/null | bash
を
| base64 -d | openssl aes-256-cbc -d -pass pass:cccc -md md5 2>/dev/null > Beeeeeeeeeer3.sh
として見てみます。
__=$(. 2>&1);__=${__##*.};__=$(. 2>&1);__=${__##*.};${__:$(($[($[$$/$$]<<$[$$/$$]<<$[$$/$$]<<$[$$/$$])+$[$$/$$]]+$[($[$$/$$]<<$[$$/$$]<<$[$$/$$]<<$[$$/$$])+$[$$/$$]]+$[$$/$$])):$((___=___^___||++___))}${__:$[$[$$/$$]<<$[$$/$$]<<$[$$/$$]]:$((___=___^___||++___))}${__:$(($[($[$$/$$]<<$[$$/$$]<<$[$$/$$]<<$[$$/$$])+$[$$/$$]]+$[($[$$/$$]<<$[$$/$$]<<$[$$/$$]<<$[$$/$$])+$[$$/$$]])):$((___=___^___||++___))} -- {z..A};${@:$((____=(____^____||++____)+
のような感じで、何もわかりません。が、一番最後に echo SECCON{$S1$n$_____}
という文字列を発見。
S1 = hogefuga
n = 3
であるので、最後の____だけわかればいいことになります。
またこれをデコードすると
__=$(. 2>&1) __=${__##*.} __=$(. 2>&1) __=${__##*.} set -- {z..A} echo Enter the password read _____ : password is bash read _____ echo -e '\033[?7h'
となりました。ちょこっと内容が消え去っていて私はここで戸惑いました。(←ちゃんとデコードしろ)
: password is bash
、神かこれ。ということで Enter the password
で bash
と入れると Good Job!
と帰ってくるので正解だとわかります。
これらからflagは
SECCON{hogefuga3bash}
となります。
coffee_break
何がコーヒーだ!コーヒー要素なんてないじゃないか!と思われた方、正解です。
とりあえず配布されたpythonコードを見てみる。
import sys from Crypto.Cipher import AES import base64 def encrypt(key, text): s = '' for i in range(len(text)): s += chr((((ord(text[i]) - 0x20) + (ord(key[i % len(key)]) - 0x20)) % (0x7e - 0x20 + 1)) + 0x20) return s key1 = "SECCON" key2 = "seccon2019" text = sys.argv[1] enc1 = encrypt(key1, text) cipher = AES.new(key2 + chr(0x00) * (16 - (len(key2) % 16)), AES.MODE_ECB) p = 16 - (len(enc1) % 16) enc2 = cipher.encrypt(enc1 + chr(p) * p) print(base64.b64encode(enc2).decode('ascii'))
cipherとbase 64を使用したところはすぐにデコードできそうですね。なので、
key1 = "SECCON" key2 = "seccon2019" text = sys.argv[1] cipher = AES.new(key2 + chr(0x00) * (16 - (len(key2) % 16)), AES.MODE_ECB) dec1 = cipher.decrypt(base64.b64decode(text))
これだけでデコードできます。問題は def encrypt(key, text):
でエンコードされた文字列。
でもこれ、ただの数値計算なので逆算してあげる。すると
def decrypt(key, text): s = '' for i in range(len(text)): s += chr(ord(text[i]) + 0x20 - (ord(key[i % len(key)]) - 0x20) + 0x3F) return s
はい。これでOK。 def encrypt
が95で割ったあまりを出力しているので正確にはデコードできないが、推測ができれば問題ないのでOK。
またこれはアルファベットしかないとして仮定した場合である。すると
FyRyZNBO2MG6ncd3hEkC/yeYKUseI/CxYoZiIeV2fe/Jmtwx+WbWmU1gtMX9m905
が
S�����{�uccess_�ecryption_�eah_�eah_S�����}
になった。これから推測するとflagは
SECCON{Success_Decryption_Yeah_Yeah_SECCON}
となる。
pngbomb
画像データがgzで圧縮されているので解凍して表示しようとすると、IHDRが壊れていることがわかる。これしかわからなかったのでダメです。12時間を溶かした問題。
SECCONを終えて
初めて出場したCTFの大会でしたが、とても楽しく、寝る時間を惜しんでいました。
miscしか解いていないけれど、それが一番楽しかった気がする。
Pwn、メモリリークできないです勉強してきます…。