picoCTF2019 writeup
はじめに
picoCTF 2019に2人チームで参加して、20151点で15929チーム中274位でした!
久しぶりにしては、かなり頑張れたと思います。難易度が自分に合っていて、とても楽しかったです。
せっかくの機会なので、復習がてら自分の解いた問題を全て解説します!
また、チームメイトもブログを書くそうなので、公開されたらリンクを張ります。
(追記)チームメイトのブログ記事はこちらです!
解いた問題リスト
# Binary Exploitation practice-run-1 - Points: 50 # Cryptography Flags - Points: 200 # Forensics What Lies Within - Points: 150 extensions - Points: 150 shark on wire 1 - Points: 150 WhitePages - Points: 250 like1000 - Points: 250 shark on wire 2 - Points: 300 WebNet0 - Points: 350 WebNet1 - Points: 450 # General Skills The Factory's Secret - Points: 1 First Grep - Points: 100 strings it - Points: 100 what's a net cat? - Points: 100 First Grep: Part II - Points: 200 # Reverse Engineering droids0 - Points: 300 droids1 - Points: 350 Time's Up - Points: 400 # Web Exploitation Insp3ct0r - Points: 50 dont-use-client-side - Points: 100 logon - Points: 100 where are the robots - Points: 100 Client-side-again - Points: 200 picobrowser - Points: 200 Irish-Name-Repo 1 - Points: 300 Irish-Name-Repo 2 - Points: 350 Irish-Name-Repo 3 - Points: 400 JaWT Scratchpad - Points: 400 Java Script Kiddie - Points: 400 Java Script Kiddie 2 - Points: 450
Binary Exploitation
practice-run-1 - Points: 50
fileコマンドに投げると、ELF 32-bit LSB shared object
とわかる。dockerを立てるのがめんどくさかったのでサーバー上のShellで実行
betit0919@pico-2019-shell1:/problems/practice-run-1_0_62b61488e896645ebff9b6c97d0e775e$ ./run_this picoCTF{g3t_r3adY_2_r3v3r53}
Cryptography
Flags - Points: 200
国際信号旗でフラグが与えられる。wikipediaを見て解読する。
PICOCTF{F1AG5AND5TUFF}
Forensics
What Lies Within - Points: 150
与えられた画像にステガノグラフィーでフラグが埋め込まれている。
ここのサイトでデコードする。picoCTF{h1d1ng_1n_th3_b1t5}
extensions - Points: 150
file flag.txt flag.txt: PNG image data, 1697 x 608, 8-bit/color RGB, non-interlaced
より、拡張子をpngに変更して開けばいいとわかる。picoCTF{now_you_know_about_extensions}
shark on wire 1 - Points: 150
wiresharkで開いて、UDP streamを眺めるとフラグが見つかる。picoCTF{StaT31355_636f6e6e}
WhitePages - Points: 250
文字コードの異なる2種類のスペースで構成されたテキストファイルが与えられる。16進数として解釈して、ASCIIコードに変換する。
irb(main):001:0> f=File.read("whitepages.txt") => " " irb(main):002:0> f.chars.map{|c| c.ord==32 ? "1" : "0"}.each_slice(8).map{|cs| cs.join.to_i(2).chr}.join => "\n\t\tpicoCTF\n\n\t\tSEE PUBLIC RECORDS & BACKGROUND REPORT\n\t\t5000 Forbes Ave, Pittsburgh, PA 15213\n\t\tpicoCTF{not_all_spaces_are_created_equal_0696a7c2dfa36b081b44603b8aa78efd}\n\t\t"
like1000 - Points: 250
与えられたtarファイルを1000回解凍するだけ。Rubyからシステムコールした。
irb(main):001:0> (1..1000).reverse_each{|i| system("tar zxvf #{i}.tar")}
picoCTF{l0t5_0f_TAR5}
shark on wire 2 - Points: 300
wiresharkで開いて、通信ポートの一覧を眺めると、怪しい通信がある。UDP streamを眺めていると、これらの通信の前後にstartとendという文字列の通信があるため、そこで気づけるようになっている。
下3桁をASCIIに変換して、picoCTF{p1LLf3r3d_data_v1a_st3g0}
WebNet0 - Points: 350
秘密鍵が与えられるので、HTTPS通信を解析せよという話。
こちらの記事の通りに鍵の設定をする。
HTTP通信を見るとResponse HeaderにPico-Flag: picoCTF{nongshim.shrimp.crackers}
とある。
WebNet1 - Points: 450
WebNet0とほぼ同じ。通信中にvulture.jpgというファイルがあるのでdumpし、stringsに投げる。
strings vulture.jpg | grep picoCTF picoCTF{honey.roasted.peanuts}
General Skills
The Factory's Secret - Points: 1
ゲームをする。謎解きがよく分からなかったので、だいたい総当りでなんとかした。
QRコードが出てくるので、読み取るとpassword: xmfv53uqkf621gakvh502gxfu1g78glds
となり、最初の部屋のPCに打ち込む。
flagはpicoCTF{zerozerozerozero}
だった。
First Grep - Points: 100
grepするだけ。
betit0919@pico-2019-shell1:/problems/first-grep_2_04dbf496b78e6c37c0097cdfef734d88$ grep picoCTF file picoCTF{grep_is_good_to_find_things_bf6aec61}
strings it - Points: 100
stringsするだけ。
betit0919@pico-2019-shell1:/problems/strings-it_0_b76c77672f6285e3a39c188481cdff99$ strings strings | grep picoCTF picoCTF{5tRIng5_1T_f1527258}
what's a net cat? - Points: 100
ncするだけ。
nc 2019shell1.picoctf.com 47229 You're on your way to becoming the net cat master picoCTF{nEtCat_Mast3ry_cc4ad2c7}
First Grep: Part II - Points: 200
grep -rするだけ。
betit0919@pico-2019-shell1:/problems/first-grep--part-ii_3_b4bf3244c2886de1566a28c1b5a465ae$ grep -r picoCTF files files/files2/file5:picoCTF{grep_r_to_find_this_3675d798}
Reverse Engineering
droids0 - Points: 300
logcatを走らせながら、アプリを起動しボタンを押すと、logにI PICO : picoCTF{a.moose.once.bit.my.sister}
と出力される。
droids1 - Points: 350
デコンパイルすると、resources内にパスワードが書いてある。
アプリを起動し、パスワードを入力すると、フラグが出力される。
Time's Up - Points: 400
実行ファイルと通信し、計算結果を返すスクリプトを書けば良い。シェルスクリプトのexpectを使うのが辛かったので、expect.rbというRubyの標準ライブラリを使ったらあっさり。
require 'pty' require 'expect' PTY.spawn("./times-up") do |r_f,w_f,pid| w_f.sync = true $expect_verbose = false r_f.expect(/\)\)\)\)\)/) {|m| w_f.puts(eval(m[0].gsub("Challenge: ", "")))} r_f.expect(/picoCTF\{.*\}/) {|m| puts m } end
Congrats! Here is the flag! picoCTF{Gotta go fast. Gotta go FAST. #1626a7fb}
と出力される。
Web Exploitation
Insp3ct0r - Points: 50
検証ツールを開くと、htmlとcssとjsにフラグの一部が埋め込まれているので、結合する。
picoCTF{tru3_d3t3ct1ve_0r_ju5t_lucky?d3db9182}
dont-use-client-side - Points: 100
検証ツールからjsを確認する。
function verify() { checkpass = document.getElementById("pass").value; split = 4; if (checkpass.substring(0, split) == 'pico') { if (checkpass.substring(split*6, split*7) == '0ff3') { if (checkpass.substring(split, split*2) == 'CTF{') { if (checkpass.substring(split*4, split*5) == 'ts_p') { if (checkpass.substring(split*3, split*4) == 'lien') { if (checkpass.substring(split*5, split*6) == 'lz_9') { if (checkpass.substring(split*2, split*3) == 'no_c') { if (checkpass.substring(split*7, split*8) == '4}') { alert("Password Verified") } } } } } } } } else { alert("Incorrect password"); }
この関数でverifyされる文字列はpicoCTF{no_clients_plz_90ff34}
とわかる。
logon - Points: 100
適当なユーザーでログインすると、権限がない旨を言われる。そこでcookieを眺めると、admin: Falseとあるので、Trueに書き換える。
picoCTF{th3_c0nsp1r4cy_l1v3s_6f2c20e9}
where are the robots - Points: 100
タイトルと、ページの内容から、/robots.txtにアクセスすれば良いとわかる。
robots.txtに書かれているpathにアクセスすると、picoCTF{ca1cu1at1ng_Mach1n3s_713d3}
と書いてある。
Client-side-again - Points: 200
難読化されたJSを解読する。
var _0x5a46=['0a0d8}','_again_4','this','Password\x20Verified','Incorrect\x20password','getElementById','value','substring','picoCTF{','not_this'];(function(_0x4bd822,_0x2bd6f7){var _0xb4bdb3=function(_0x1d68f6){while(--_0x1d68f6){_0x4bd822['push'](_0x4bd822['shift']());}};_0xb4bdb3(++_0x2bd6f7);}(_0x5a46,0x1b3));var _0x4b5b=function(_0x2d8f05,_0x4b81bb){_0x2d8f05=_0x2d8f05-0x0;var _0x4d74cb=_0x5a46[_0x2d8f05];return _0x4d74cb;};function verify(){checkpass=document[_0x4b5b('0x0')]('pass')[_0x4b5b('0x1')];split=0x4;if(checkpass[_0x4b5b('0x2')](0x0,split*0x2)==_0x4b5b('0x3')){if(checkpass[_0x4b5b('0x2')](0x7,0x9)=='{n'){if(checkpass[_0x4b5b('0x2')](split*0x2,split*0x2*0x2)==_0x4b5b('0x4')){if(checkpass[_0x4b5b('0x2')](0x3,0x6)=='oCT'){if(checkpass[_0x4b5b('0x2')](split*0x3*0x2,split*0x4*0x2)==_0x4b5b('0x5')){if(checkpass['substring'](0x6,0xb)=='F{not'){if(checkpass[_0x4b5b('0x2')](split*0x2*0x2,split*0x3*0x2)==_0x4b5b('0x6')){if(checkpass[_0x4b5b('0x2')](0xc,0x10)==_0x4b5b('0x7')){alert(_0x4b5b('0x8'));}}}}}}}}else{alert(_0x4b5b('0x9'));}}
頑張って読んでいくと、picoCTF{not_this_again_40a0d8}
となる。
picobrowser - Points: 200
UserAgentをpicobrowserにしてアクセスすれば良い。
curl https://2019shell1.picoctf.com/problem/47202/flag -A "picobrowser" picoCTF{p1c0_s3cr3t_ag3nt_6b1d92d7}
Irish-Name-Repo 1 - Points: 300
' OR 1 = 1; #
と入力すると、全体のSQL文は
SELECT * FROM users WHERE username = '' OR 1 = 1; #' AND password = ''
といった形になり、ログインに成功する。
picoCTF{s0m3_SQL_96ab211c}
Irish-Name-Repo 2 - Points: 350
admin' --
と入力すると、全体のSQL文は
SELECT * FROM users WHERE username = 'admin' --' AND password = ''
といった形になり、ログインに成功する。
Your flag is: picoCTF{m0R3_SQL_plz_c1c3dff7}
Irish-Name-Repo 3 - Points: 400
'!='
('<>'
でも可)と入力すると、全体のSQL文は
SELECT * FROM admin where password = ''!=''
となり、ログインに成功する。
picoCTF{3v3n_m0r3_SQL_a9766759}
JaWT Scratchpad - Points: 400
やることは単純で、JWTのnameをadminにすれば良い。
AlgorithmをNoneに変更する手法も使えず。元からHS256のため、RS256からHS256に変換する手法も使えず。
ページで示唆されているように、john the ripperを用いて、パスワードを解析するしかなかった。
jwt2johnコマンドでJWTをjohnが使える形式に変更する。johnに食わせると、6時間弱でパスワードが解析された
0g 0:00:17:08 3/3 0g/s 3753Kp/s 3753Kc/s 3753KC/s 147501502..147509227 ilovepico (?) 1g 0:05:49:23 DONE 3/3 (2019-10-10 22:24) 0.000047g/s 4451Kp/s 4451Kc/s 4451KC/s ilovepint..ilovaa1ry
最後に、jwt_toolでnameを変更して得られたtokenをcookieに設定してアクセスすると、フラグが得られる。
Java Script Kiddie - Points: 400
横幅が16pxのpng画像を、縦側にシフトしている。pngファイルの先頭16バイトは固定なので、各縦列に対してシフト量の候補を求めることができる。コンソールに以下のソルバーを流し込む。
function assemble_png_simulate(u_in){ var LEN = 16; var key = "0000000000000000"; var shifter; if(u_in.length == LEN){ key = u_in; } var result = []; for(var i = 0; i < LEN; i++){ shifter = key.charCodeAt(i) - 48; for(var j = 0; j < (bytes.length / LEN); j ++){ result[(j * LEN) + i] = bytes[(((j + shifter) * LEN) % bytes.length) + i] } } while(result[result.length-1] == 0){ result = result.slice(0,result.length-1); } return new Uint8Array(result); } var header = [137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82] function solve(){ var ans = [] var LEN = 16; for(var i = 0; i < LEN; i++){ var kouho = []; for(var k = 0; k < 10; k++){ var key = '0'.repeat(i) + k.toString() + '0'.repeat(15 - i); if(assemble_png_simulate(key)[i] == header[i]){ kouho.push(k.toString()) } } ans.push(kouho) } return ans }
solve関数を実行するといくつか候補が出てきます。7447829109135496
を入力するとQRコードが出力されるので、読み取るとpicoCTF{9e627a851b332d57435b6c32d7c2d4af}
となる。
Java Script Kiddie 2 - Points: 450
1とほぼ同じ。修正したコードはこちら。
function assemble_png_simulate(u_in){ var LEN = 16; var key = "00000000000000000000000000000000"; var shifter; if(u_in.length == key.length){ key = u_in; } var result = []; for(var i = 0; i < LEN; i++){ shifter = Number(key.slice((i*2),(i*2)+1)); for(var j = 0; j < (bytes.length / LEN); j ++){ result[(j * LEN) + i] = bytes[(((j + shifter) * LEN) % bytes.length) + i] } } while(result[result.length-1] == 0){ result = result.slice(0,result.length-1); } return new Uint8Array(result); } var header = [137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82] function solve(){ var ans = [] var LEN = 16; for(var i = 0; i < LEN; i++){ var kouho = []; for(var k = 0; k < 100; k++){ var key = '0'.repeat(i*2) + ("00" + k).slice( -2 ) + '0'.repeat((15 - i)*2); if(assemble_png_simulate(key)[i] == header[i]){ kouho.push(k.toString()) } } ans.push(kouho) } return ans }
50706000107050006010009030000030
を入力するとQRコードが出力されるので、読み取るとpicoCTF{b19be0d3b70ffc63b6367ecf136e853e}
となる。
おわりに
あんまり練習もできずに、良さげなやつにふらっと出ているのですが、CTFは出るだけでも学びが多くて最高ですね。
特にAndroid問は初めて触ったのでとても勉強になりました。
運営の皆様、ありがとうございました!