SITW#30
CTF勢がいるらしいけど、この問題面白いから解いてみてよという事で解いた。
- とりあえず開いてみる: 開けない
- 修復を試みてみる: 開けない
- 本当にpngか
- ヒントを見ると「正方形の枠」とあるのでQRコードかサイズだと思った
- 前者だとよくわかんないからまずは後者
- ここでfile出力に戻ると500x468(500と468のどちらに合わせるべきだろう?)
- 問題文のjpgは400x400なのでどっちとも違うけど500に合わせてみる
- ここみたいにしてviで以下を修正
0000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR -0000010: 0000 01f4 0000 01d4 0806 0000 00cb d6df ................ +0000010: 0000 01f4 0000 01f4 0806 0000 00cb d6df ................
開いてみるとFLAG{QWERTYUIOP}が取れておしまい。
追記: Windowsだと修正前でも開けるらしい。なるほど、高さが足りなくてフラグが隠れるのね。面白い。じゃあ400の行は根拠としては有効ではないや。
dashでRails Guides
あ、dashでRails Guidesを見る事もできるようになってる。自分でできる人は自分で生成して見たらよくて、そうじゃない人はRailsチュートリアルの電子版を買ったらよさそう。という事で早速試してみた。
(bundle installに失敗するようなパッケージは自分で入れてくだされ(sqlite3コマンドが叩かれるのでこれだけ書いてる)) $ sudo apt-get install sqlite3 $ git clone https://github.com/yasslab/railsguides.jp.git $ bundle install --path ./.bundle $ cd guides $ bundle exec rake guides:generate:dash GUIDES_LANGUAGE=ja
guides/output/dash/ja/ruby_on_rails_guides_local.ja.docset というディレクトリが生成されるのでそれをdashから開いておしまい。
いい感じになった。ありがたやありがたや。でも本日時点だと左側のメニューから見ると階層がフラットだから検索以外のユースケースだと読み辛い。PRとか送れるといいなぁ。
CTFスコアサーバ
CTFのスコアサーバを使って遊んでみる。
yoggy/ctf-scoreserver
サイトにあるままにセットアップしたら動いた。機能がすくないけどそれが強みっぽい。以下にアクセスして使う。
- :4567/: ユーザ向け
- :4567/admin/: 管理者向け
以下のような感じだった。
- カテゴリが設定できない
- 個人で参加
- 添付ファイルがあれば public 以下に自分で置いて問題文からリンクする必要がありそう
- このサーバを攻撃してくれみたいな問題は別途サーバを立ててリンクする必要がありそう
機能が大変にシンプルだがそれは入門者に対して逆に情報過多にならない配慮かもしれないと思った。
isislab/CTFd
こちらもサイトにあるままにセットアップしたら動いた。docker-compose.yml見るにmariadbとか立ててるけどインストールしてないけどはて?と思ったらCTFd/config.pyを見るにDBが選択できるようだ。デフォルトだとsqliteみたい。
アクセスが少なければsqliteでもいいかもしれない。デモサイトもあるので試しやすい。
docker-compose.ymlが既にあるのがいいなと思ったので試してみようかと思ったけど、こけるのでとりあえずは諦めた。もう少し追いかけて必要ならPR送るなりしよう。
$ sudo apt-get install docker-compose git $ git clone https://github.com/isislab/CTFd.git $ cd CTFd && sudo docker-compose build Service "db" mounts volumes from "data:rw", which is not the name of a service or container.
以下のような感じだった。入門向けじゃなくなってくると使ってみたい気持ちになる気がした。
- 見た目はすっきりしててとてもいい
- チームごとに参加する必要がある
- CSSをいじる機能がある
- ファイルを添付できる
- このサーバを攻撃してくれみたいな問題は別途サーバを立ててリンクする必要がありそう
- カテゴリが設定できる
- 開催期間が設定できる
PTCoreSec/CTF-Scoreboard
自分ではインストールしてないけどYoutubeの最後の方を見るに使いやすそうだが3年前が最後のコミットみたいなので今は動かないかもしれないと思って試さなかった。
他にも色々あるんだけどCTF for ビギナーズ 2015 札幌で使ってるものが知りたいな。シンプルかつ一番見やすそう。少なくともTDUCTFは違ってそうだ。
場阿忍愚CTF(BurningCTF) Write Up
SITW#29で教えてもらっていたBurningCTFに参加した。ちびちびとしかできなくて一応100位以内には入ったけど、もう少しそれっぽい問題解きたかった。まだまだだなぁ。
初めて使ったライブラリ等あっていいきっかけになった。あと、典型的なものは解き方をストックしておくと別のCTFで役に立ちそうだ。次は何にしようかな。write up書いて残したいから何か締め切りがあるものがいい。
以下、解いた問題に関する記録(1つ解けてないのが混じってるけど)。練習と芸術は省略。
121-二進術-100
対話的に問題を解くことになるけど、問題文から1万回やらないといけないぽいのでプログラムで解いた(hdすると10 27な行があるからここ書き換えたらいいかもしれんけどなるべく機械的に解きたかったので試さなかった)。今回はexpectライブラリで解いてみた。
$ cat solve.rb require "pty" require "expect" PTY.getpty("./121-calculation") do |i, o| n = 10000 n.times do |j| i.expect(/((?:\d+)?\s*[-+*\/%]\s*\d+)\s*=\s*/, 10) do |line, question| o.puts(eval(question).to_s) end puts "#{j+1}/#{n}" end i.readline puts "flag: #{i.readline}" end $ ruby solve.rb ... flag: FLAG_5c33a1b8860e47da864714e042e13f1e
IO#expectは期待する出力があるまで待つ処理らしいので、1万回実行した。初めて使ったけど普通に使う分には便利っぽい。
132-解読術-100
忍者といえば以下らしいという事をぐぐったので、
- 忍者といえば「山」「川」の符丁
- 忍者なら当時は神代文字が読めた
見比べながら読むと「やまといえば」という事に見えたので「かわ」と入力。
flag: かわ
133-解読術-200(これは解けてないのでダラダラと残してある作業記録)
とりあえず中身を見てみる。本当に公開鍵のようだ。
$ openssl rsa -inform pem -in Decrypt_RSA/public-key.pem -text -pubin ...
秘密鍵で暗号化してある場合に備えて念のため以下を実行。
$ openssl rsautl -decrypt -inkey Decrypt_RSA/public-key.pem \ -in Decrypt_RSA/flag.txt unable to load Private Key 6629:error:0906D06C:PEM routines:PEM_read_bio:no start line:/SourceCache/OpenSSL098/OpenSSL098-52.8.4/src/crypto/pem/pem_lib.c:648:Expecting: ANY PRIVATE KEY
とりあえず思いつくのはどれかの素因数分解アルゴリズムで見つけやすいnになってるというところだろうか。
以下など見ながら何パターンか試してみる。
まずはnを確認。上を加工してもいいけど、以下で直接。
$ ruby -r openssl -e 'p OpenSSL::PKey::RSA.new(File.open("Decrypt_RSA/public-key.pem")).n.to_i' 3107418240490043721350750035888567930037346022842727545720161948823206440518081504556346829671723286782437916272838033415471073108501919548529007337724822783525742386454014691736602477652346609
一応既知のものでないかnでぐぐってみるも特になし。(検索語が長過ぎると言われるので適当に切って検索)
何パターンか試してみる。
フェルマー法
pとqが平方根に近いと速そう。
$ cat fermat.rb def prime_division(n) x = Math.sqrt(n).to_i + 1 y = Math.sqrt((x ** 2 - n).abs).to_i loop do w = x ** 2 - n - y ** 2 if w == 0 return x+y, x-y elsif w > 0 y = y + 1 elsif w < 0 x = x + 1 end end end p prime_division(ARGV[0].to_i) $ ruby fermat.rb 3107418240490043721350750035888567930037346022842727545720161948823206440518081504556346829671723286782437916272838033415471073108501919548529007337724822783525742386454014691736602477652346609 (しばらく待ったけど解けないので違ってそう)
pari/gp
> factor(3107418240490043721350750035888567930037346022842727545720161948823206440518081504556346829671723286782437916272838033415471073108501919548529007337724822783525742386454014691736602477652346609)
残念ながら手元だとメモリ不足で死んでしまった。
msieve
$ msieve -n -e -v 3107418240490043721350750035888567930037346022842727545720161948823206440518081504556346829671723286782437916272838033415471073108501919548529007337724822783525742386454014691736602477652346609
残念ながらやってる範囲では終わらなかった。
もし素因数分解に成功していたら
以下で復号しようと思って準備はしていたがその夢はかなわなかった。残念。
試しに128bitの鍵を作ってpublic_encrypt→公開鍵の情報からmsieveで素因数分解→private_decryptで復号まではできたのだが、素因数分解アルゴリズムの事を調べきれないうちにおしまい。似たような事をする機会がまたありそうなので、その時にでも調べよう。
$ cat solve.rb require 'openssl' module RSAPrivateKeyAttrRecalculatable def p=(val) recalc_attributes do super end end def q=(val) recalc_attributes do super end end private def recalc_attributes res = yield if self.p && self.q self.d = e.mod_inverse( (p-1)*(q-1) ) self.dmp1 = d % (p - 1) self.dmq1 = d % (q - 1) self.iqmp = q.mod_inverse(p) end res end end class OpenSSL::PKey::RSA prepend RSAPrivateKeyAttrRecalculatable end pubkey = OpenSSL::PKey::RSA.new(File.open("Decrypt_RSA/public-key.pem")) pubkey.p = ARGV[0].to_i pubkey.q = ARGV[1].to_i puts "flag: #{pubkey.private_decrypt(File.read("Decrypt_RSA/flag.txt"))}"
opensslライブラリ便利だ。今回は以下の事を調べたので次回は調べなくていいのは楽。mod_inverseが用意されてるのがすごいうれしい。どこかで一度正面から突破してみたいな。
- pとqが入ってれば秘密鍵とみなされる
- p=やq=を呼んでもdなどは再計算されない
- というのも、なんの分岐もないから
- 一番嬉しいのはp=とq=をしたらよしなにしてくれる事だけどそこまではしてくれないようだ
- newの時にpとqが指定できるでもよかったんだけど、それも見当たらなかった
以上から自分で再計算するとto_dem後にopensslコマンド叩くなり、private_decryptなりで復号できそうだ(上記)。
Module#prepend使うとこういうのが書きやすくていいので最近多用してる気がするな...
でも残念ながら素因数分解が(手元では)終わらなかったのでアプローチが間違ってるのかもしれない。とここまでが独力。
ひとさまのwrite upを見た後
pとqは既知だったらしい(上でぐぐってるけど、やり方がわるかったかなぁ...)。
$ ruby solve.rb 1634733645809253848443133883865090859841783670033092312181110852389333100104508151212118167511579 1900871281664822113126851573935413975471896789968515493666638539088027103802104498957191261465571 flag: FLAG_IS_WeAK_rSA
次はfactordbも使ってみよう。
161-電網術-50
- Wiresharkで161-problem.pcapを開く
- ftpでフィルタリングして99フレーム目にFLAG.tarを見ている事を確認
- 100フレーム目以降のパケットを生で見てFLAG.tarとして保存
- 保存したファイルを開くと RkxBR3tYVEluWDY5bnF2RmFvRXd3TmJ9Cg== という文字列が現れる
- $ ruby -r base64 -e 'print Base64.decode64("<4.の文字列>")'
flag: FLAG{XTInX69nqvFaoEwwNb}
162-電網術-75
- Wiresharkで162-basic.pcapを開く
- httpでフィルタリングして39フレーム目にBasic認証に成功したパケットを確認
- ヒントがbasicという事なのでベーシック認証に渡した文字列を確認(aHR0cDovL2J1cm5pbmcubnNjLmdyLmpw保存したファイルを開くと )
- $ ruby -r base64 -e 'print Base64.decode64("<3.の文字列>")'
- http://burning.nsc.gr.jp にアクセスして、ユーザ名に"http"、パスワードに"//burning.nsc.gr.jp"を入力(※Basic認証は:区切り)
5.ではまずパケットが接続してる先を見るべきだったかもしれない。
flag: flag={BasicIsNotSecure}
171-諜報術-100
web.archive.org。
flag: ソフトウェア開発エンジニア
181-記述術-100
大分非効率な感じのスクリプトで見つけた。もっと効率的な方法があるといいなぁ。
$ cat solve.rb substr = nil s = ARGF.read s_len = s.length (2..(s_len)).each do |n| candidates = (0..(s_len - n)).to_a.collect { |i| s[i..(i+n)]} candidates.each_with_index do |s, i| if candidates[(i+1)..-1].index(s) if substr.nil? || s.length > substr.length substr = s break end end end puts "#{n}: #{substr}" end $ ruby solve.rb (8文字目くらいまで見て、条件を満たす範囲の続きの文字を回答)
flag: f_sz!bp_$gufl=b?za>is#c|!?cxpr!i><
182-記述術-200
- 1. 'alert'がないので数値から組み立てると仮定して文字っぽいものを確認
$ irb > 0x52.chr # => "R" > 0x54.chr # => "T" > 0b1001100.chr # => "L" > 101.chr # => "e" > 0O000101.chr # => "A" > 1.chr # => "\x01"
- 2. 最初の3つくらいを1つずつブラウザで試す
window["map"] => undefined window["eval"] => function eval() { [native code] } window["eval"]["call"] ...
- 3. chrっぽい事をして「荒痕(1)」なだけに "alert(1)" を組み立てるようなものにする
window["eval"]["call"]`${ [ (0O000101), (0b1001100), (101), 0x52, 0x54 ] ["map"](x=>String["fromCodePoint"](x))["join"]("")["toLowerCase"]() +"(1)" }`;
以下が出てくる。
flag: Flag={4c0bf259050d08b8982b6ae43ad0f12be030f191}
183-記述術-100
- 1. 1文字ずつ入力すると何文字ありそうかわかる。長い...(ので総当りを諦める)
$ cat find_chars.rb require "http" ((?a..?z).to_a + [?_, ?{, ?}]).each do |c| url = "http://210.146.64.36:30840/count_number_of_flag_substring/?str=#{c}" puts "#{c}: #{HTTP.get(url).to_s.lines.grep(/member/).first}" sleep 5 end $ find_chars.rb (この結果をsolve.rbに反映)
- 2. 1文字ずつ先頭から見ていけば無駄な試行回数を減らせそうだなと思ったのでそうする
$ cat solve.rb require "http" # find_chars.rb の結果を参照(flagは除外)。 chars = [ ?_ * 6, ?a * (10 - 1), ?d * 9, ?e * 4, ?f * (9 - 1), ?g * (3 - 1), ?h * 3, ?i * 7, ?j * 2, ?k * 4, ?l * (1 - 1), ?n * 6, ?o * 1, ?r * 8, ?s * 8, ?u * 3, ?x * 5, ].join.chars class RetryError < StandardError; end res = "flag={" begin chars.uniq.each do |c| s = res + c # puts "try: #{s}" url = "http://210.146.64.36:30840/count_number_of_flag_substring/?str=#{s}" if md = /member of "[^&]+" are (\d+)/.match(HTTP.get(url).to_s) if md[1].to_i > 0 res += c chars.delete_at(chars.index(c)) puts "current: #{res}" raise RetryError end end sleep 5 end if chars.empty? res += "}" puts "flag: #{res}" end rescue RetryError retry end $ ruby solve.rb ... flag: flag={afsfdsfdsfso_idardkxa_hgiahrei_nxnkasjdx_hfuidgire_anreiafn_dskafiudsurerfrandskjnxxr}
184-記述術-100
- 1. とりあえず正直に手で解凍してしばらく様子を見る
$ bunzip2 < 184-flag.txt| bunzip2 | funzip | tar xOf - | gunzip -c | funzip | tar xOf - | bunzip2 | tar xOf - | tar xOf - | funzip | funzip | funzip | bunzip2 | funzip | tar xOf - | bunzip2 | tar xOf - | tar xOf - | tar xOf - | gunzip -c | bunzip2 | tar xOf - | ...
- 2. 1.で特定の方式しか使われなかったのでサブコマンドでやろうとする(標準入力は複製されないんだったか)
$ bunzip2 < 184-flag.txt| (bunzip2; funzip; tar xOf -; gunzip -c) 2> /dev/null | (bunzip2; funzip; tar xOf -; gunzip -c) 2> /dev/null | ...
- 3. コードを書いて自動でファイル判別→解凍を繰り返す
$ cat solve.rb require "tempfile" def extract(path) f = Tempfile.create("184") f.close case `file #{path}` when /bzip2/ system("bunzip2 < #{path} > #{f.path}") return f.path when /gzip/ system("gunzip -c < #{path} > #{f.path}") return f.path when /tar/ system("tar xOf - < #{path} > #{f.path}") return f.path when /Zip archive data/ system("funzip < #{path} > #{f.path}") return f.path else system("cat #{path}") return true end end f = Tempfile.create("184") f.close system("bunzip2 < 184-flag.txt > #{f.path}") res = extract(f.path) while res != true do res = extract(res) end $ ruby solve.rb
もう少し短く書けばいいのにと思ったけど頑張らなかった。なお、1024回くらい圧縮してあった。追記されるヘッダ分からもう少し多めの回数で圧縮されてるのかと思ってたけど、案外少なかった。
flag: flag={6aKuZrEqxvBZUIqBOXgMclLwpQCo8OXi}
192-超文書転送術-100
- 問題文の最後に記載されたURL(省略)/about に隠されたヒントを見逃すなとある
- HTMLの中身を見ても.hintクラスを含めて特にないので大文字の部分だけ抜き出して読む
- 実際にしでかしてみる(flag.txtをfindで探してcatした)
flag: flag={Update bash to the latest version!}
SECCON 2015 Write Up
SECCON 2015オンライン予選にRoute9チームで参加してきた。結果は99位。みんなすごいなぁという感じで面白かった。
以下、解いた問題に関する記録。
Exec dmesg
問題文
秘密のメッセージをLinuxのisoイメージの中から見つけてください。
記録
以下のようにして解いた。
- 問題文がexec dmesgなのでDLしたイメージをvirtualboxで起動してdmesgすると、dmesg: applet not foundとか言われる
- /var/log/messageみたいなものは存在しない
- dmesgが該当するかは知らなかったが、busyboxが面倒を見てるコマンドかもしれないのでls -l /bin/dmesgするとbusyboxへのsymlink
- busybox lsなどはできるがbusybox dmesgだとapplet not foundのためbusyboxに細工がしてあると推測
- strings busybox すると確かにdmesgがない(lsとかcatとかはあるのでここでなにか不要なコマンドをdmesgに置き換えてみる事にした)
- 手元の環境でstrings busyboxするとdmesgが存在した
- 5.の時に辞書順に並んでるコマンド郡の中でdmesgっぽい位置にあるenfth*1にとりあえず狙いを定める
- hexdump busyboxして置き換えるコマンドの位置を調べる(0x68b52(=428882)の位置を書き換えることにする
- sudo cp /bin/busybox /bin/busybox.new*2
- echo -n "dmesg" | sudo dd of=busybox.new bs=1 seek=428882 conv=notrunc
- busybox.new dmesg
- 結果の中にSECCONで始まる文字列がないか見る: SECCON{elf32-i386}
5.の辺りで以下のようなので、0x68b50=428880。+ 2バイトかな?という感じ。
$ hexdump busybox ... 00068b50 e.enfth.dnsdomain ...
しぶい問題だった。しかし、置き換えた後で思ったけどその辺からbusybox取ってきても良かったんじゃないだろうか?でも実は更にgrepだけして即回答してる人もいるみたいなので上には上がいるなぁと思った。
4042
問題文
謎の文章が2005年に古代遺跡から発見された。 これは何を意味している?
記録
- 4042と2005とくれば「RFC4042の事だ...」という感じでチームメンバが疑い始める*3
- no-network.txtというファイル名からRFC4042中のnonetの事だろうなぁという事で「絶対RFC4042の事だ...」と思う
- no-network.txtの中身を3文字ずつにしてRFC4042中の関数を使って読んでみるとSECCON{A_GROUP_OF_NINE_BITS_IS_CALLED_NONET}が取れる
なお、以下のような感じで作った。pack("U")してるけど、UTF-8じゃないのでまぁ化けるよねという感じだが100点なのでそんなに頑張らなかった。一応iconv -f UCS-4 -t UTF-8して失敗するのでfrom辺りが間違ってるかpack("U")してるのが問題かだと思うんだけどそこまでとした。
def digits_to_utf9_no_nets(s) return s.delete("\n").chars.each_slice(3).collect {|n| Integer("0" + n.join) } end def utf9_nonets_to_ucs4(no_nets) res = "" while !no_nets.empty? do no_net = no_nets.shift ucs4 = no_net & 0xff while (!no_nets.empty? && (no_net & 0x100) != 0) do ucs4 <<= 8 no_net = no_nets.shift & 0xff ucs4 |= no_net end res << [ucs4].pack("U") end return res end no_nets = digits_to_utf9_no_nets(File.read('no-network.txt')) puts utf9_nonets_to_ucs4(no_nets)
再びMatsue.rbのメンバーでRails Girls Matsueのお手伝いをした
11/13(金)、14(土)に行われたRails Girls Matsueのお手伝いを再びMatsue.rbのメンバー何人かでしてきた。いろいろとよかった。松江では特にそうは思わなかったし、別に特定の地域についてそう感じた訳でもないんだけど、「皆楽しんで終わり。何も残りませんでした」というだけにならないように気をつけないとなと思いながら参加した。でもそれは杞憂で、特にLTを聞きながらよい回なんじゃないかと思った。その他よかった点。
- 結束力は相変わらずですごい
- 他県の方まで手伝ってくださるのがすごい
- メンツが固定じゃない(新陳代謝がある)
- 直前に気分が悪くなる事があったけど、元気をもらってしまった
そうじゃない点として松江に限った事ではない点ではちょっと思ったのが、ガイドの修正についてはもう少し頑張るべきかもしれない。「教える側がどこを目指していて、教えられる側をどこに導きたいのか」という気持ちとコーチにjoinするという点について誰か矛盾を抱えたままやってないかしらと感じたような気がした。
「Windowsはなぁ」とか「これはちょっとバグがあるからこう回避」みたいなものには人は来ない気がする(ので根絶して忘れるのが一番)けど、それを放置して成せる何かを考えてみても思いつくものはなかった。「楽しんで終わり」以外は。という事を開催前に考えてたけど、ガイド以外でそれを感じる点はなかったから、まぁよかったという結論でいい感じ。ガイドについては全国で数人activeならなんとかならなくもないからなぁ。
松江Ruby会議のプロコンで負けた
これが自分の結果(以下にコードのみ原文まま)。再起で関数呼び出してるところが他の人と大きく異なる点だけど、解き方自体はあまり他の人と変わらなかったなぁ。0.09秒を切ってみたかったけど、それはあたわず...
def f(a,r=[*a]) s=r.shift x=s.reverse r.delete_at(r.rindex(x)||999) ? (s+f(a,r)+x) : (s==x ? s : (r[0] ? f(a,r) : "")) end puts f(readlines[1..-1].map!(&:chomp!).sort!)
しかしスタッフ容赦なかった。ルール的には問題ないけど。
# ところで今更気付いたけど結果ページに松江テルサ別館ってラボの事な気がするのであった...しまった...orz