SECCON 2015 Online Quals Writeup

http://ctf.seccon.jp/2015/

チームnicklegrで個人参加。
1000点で190位(872チーム中)でした。

# Start SECCON CTF (Exercises 50)
換字式暗号。親切に全文字のテーブルが用意されている。

```ruby
c1 = "PXFR}QIVTMSZCNDKUWAGJB{LHYEO"
p1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ{}"

prob = "A}FFDNEVPFSGV}KZPN}GO"

prob.each_char do |c|
  i = c1.index(c)
  raise if i == -1
  print p1[i]
end

puts ""
```

# SECCON WARS 2015 (Stegano 100)
https://youtu.be/8SFsln4VyEk

文字が流れる部分に、QRコードの形にマスクがかかってる。
全フレームを重ねてRGBの最大値を取れば、QRコードだけ黒で出てきそう。
と思ったけど圧縮ノイズのせいかうまくいかなかったので平均値に変更。
最初の25秒はロゴが動いて邪魔をしてくるので除外。

```
% ffmpeg -i SECCON\ WARS\ 2015.mp4 -ss 26 -f image2 dir/%d.png
% convert *.png -average ../output.png
```

平均なので、結果が暗かったのでPhotoshopのトーンカーブで明るくして、スマホのQRコードリーダーに読ませた。

[MMAの去年のWriteup](https://wiki.mma.club.uec.ac.jp/CTF/Writeup/SECCON%202014%20Quals%20Online%20Winter#CTF.2FWriteup.2FSECCON_2014_Quals_Online_Winter.2FSECCON_Wars:_THE_Flag_Awakens.SECCON_Wars:_THE_Flag_Awakens_.28QR_300pts.29)が参考になりました。

# Reverse-Engineering Android APK 1 (Binary 100)
[Android APK Decompiler](http://www.decompileandroid.com/)に投げる。

src/com/example/seccon2015/rock_paper_scissors/MainActivity.java

```
if (1000 == cnt)
{
    textview.setText((new StringBuilder()).append("SECCON{").append(String.valueOf((cnt + calc()) * 107)).append("}").toString());
}

public native int calc();

static 
{
    System.loadLibrary("calc");
}
```

`calc()`は、apkをzipとして解凍すると `lib/x86/libcalc.so` 内にある。
Hopperに投げると

```
             Java_com_example_seccon2015_rock_1paper_1scissors_MainActivity_calc:
00000400         mov        eax, 0x7
00000405         ret        
                        ; endp
```

それだけ。

# Connect the server (Web/Network 100)

```
login.pwn.seccon.jp:10000
```

Webブラウザでアクセスすると、BackSpaceの制御コードが混じったテキストが出てくる。
制御コードを取り除くとフラグ。

ncやtelnetでアクセスするとフラグ部分が見えなくなる仕組み。
問題ジャンルがヒントか。

# Command-Line Quiz (Unknown 100)
Linuxのお勉強。

```
% telnet caitsith.pwn.seccon.jp

$ cat stage1.txt 
What command do you use when you want to read only top lines of a text file?

Set your answer to environment variable named stage1 and execute a shell.

  $ stage1=$your_answer_here sh

If your answer is what I meant, you will be able to access stage2.txt file.

$ stage1=head sh       
$ cat stage2.txt 
What command do you use when you want to read only bottom lines of a text file?

Set your answer to environment variable named stage2 and execute a shell.

  $ stage2=$your_answer_here sh

If your answer is what I meant, you will be able to access stage3.txt file.

$ stage2=tail sh 
$ cat stage3.txt 
What command do you use when you want to pick up lines that match specific patterns?

Set your answer to environment variable named stage3 and execute a shell.

  $ stage3=$your_answer_here sh

If your answer is what I meant, you will be able to access stage4.txt file.

$ stage3=grep sh
$ cat stage4.txt 
What command do you use when you want to process a text file?

Set your answer to environment variable named stage4 and execute a shell.

  $ stage4=$your_answer_here sh

If your answer is what I meant, you will be able to access stage5.txt file.

$ stage4=sed sh 
$ cat stage5.txt 
cat: can't open 'stage5.txt': Operation not permitted

$ stage4=awk sh
$ cat stage5.txt 
OK. You reached the final stage. The flag word is in flags.txt file.

flags.txt can be read by only one specific program which is available
in this server. The program for reading flags.txt is one of commands
you can use for processing a text file. Please find it. Good luck. ;-)

$ sed -e 's/A/A/g' flags.txt  
OK. You have read all .txt files. The flag word is shown below.

SECCON{CaitSith@AQUA}
```

# QR puzzle (Nonogram) (Unknown 300)
ののぐらむ(お絵かきロジック)を解くとQRコードが出てくる。それを30問。問題は毎回ランダム。
下記を自動化した。

- スクレイピングして問題を抽出
- [オンラインソルバー](http://www.lancs.ac.uk/~simpsons/nonogram/auto.htmlz.en-GB)に投げる
- [強力なQRコードデコーダ](https://github.com/waidotto/strong-qr-decoder)に投げる

面倒なのが、解が一意じゃない問題があること。(市販の問題にもときどきある)
以下でフォローした。

- 前述のソルバーが4〜5通りの解を返してくれる(全パターンじゃないっぽい)ので全部試す
- QRデコーダのエラー訂正
- 解答は `/^\w+$/` っぽいので、マッチしないのを弾く
  - ちなみに最後の問題だけ例外。 `SECCON{YES_WE_REALLY_LOVE_QR_CODE_BECAUSE_OF_ITS_CLEVER_DESIGN}`

あとは運頼みでゴリ押し。10回くらいリトライしたら通った。

```ruby
# coding: utf-8

require 'open-uri'
require 'nokogiri'
require 'json'
require 'mechanize'
require 'pp'
require 'logger'
require "open3"

def parse_nonogram(html)
  ret = []

  doc = Nokogiri::HTML(html)
  cols = doc.css("th.cols")
  rows = doc.css("th.rows")

  ret << "width #{cols.size}"
  ret << "height #{rows.size}"

  ret << "rows"
  rows.each do |row|
    value = row.css("span").map do |e| e.inner_text end
    ret << value.join(",")  
  end

  ret << "columns"
  cols.each do |col|
    value = col.css("span").map do |e| e.inner_text end
    ret << value.join(",")
  end

  ret.join("\n")
end

def solve_nonogram(nonogram)
  agent = Mechanize.new
  # agent.log = Logger.new(STDERR)

  page = agent.get("http://www.lancs.ac.uk/~simpsons/nonogram/auto.htmlz.en-GB")
  form = page.forms.first

  form.radiobutton_with(:value => "field").check
  form["field"] = nonogram
  form["cr"] = false
  form["cb"] = false

  # Gateway timeout 対策
  5.times do
    begin
      page = agent.submit(form)
      break
    rescue
      next
    end
  end

  ret = []
  doc = Nokogiri::HTML(page.body)
  doc.css("pre").each do |pre|
    ret << pre.inner_text.strip
  end

  ret
end

def decode_qr(str)
  o, e, s = Open3.capture3("python sqrd.py", :stdin_data => str)

  unless s.success?
    puts o
    puts e
    nil
  end

  o.scrub("?").strip
end

agent = Mechanize.new
# agent.log = Logger.new(STDERR)
page = agent.get("http://qrlogic.pwn.seccon.jp:10080/game/")

loop do
  if page.body.match(/Stage: \d+ \/ \d+/)
    puts $&
  else
    puts page.body
    break
  end

  nonogram = parse_nonogram(page.body)
# puts nonogram

  solutions = solve_nonogram(nonogram)
pp solutions

  flags = solutions.map do |e|
    decode_qr(e)
  end

pp flags

  flags.compact!
  flags.select! do |e|
    e.match(/^\w+$/)
  end
  raise if flags.empty?

  puts "try with #{flags.first}"

  form = page.forms.first
  form.ans = flags.first

  page = agent.submit(form)
end
```

# Steganography 1 (Stegano 100)
与えられる`MrFusion.gpjb`に、gif, png, jpg, bmp画像が各4個*4セット入ってて、それぞれ1文字ずつフラグが入ってる。
拡張子がヒントか。

スクリプトで切り分けたけど、シグネチャが短くて正確にいかず、一部手作業で切り出し。

```rb
require "pp"

str = File.binread("MrFusion.gpjb").unpack("H*").first
# puts str

# pngs = str.scan(/89504e47.+?49454e44ae426082/)
# pp pngs.size

# jpgs = str.scan(/ffd8ff.+?ffd9/)
# pp jpgs.size

# gifs = str.scan(/474946383961.+?3b/)
# pp gifs.size

# bmps = str.scan(/3630\w{4}/)
# pp bmps
# ["36302a00", "36304c0e", "36302a00", "36302a00"]
# 19470 - 6

# images = str.scan(/89504e47.+?49454e44ae426082|ffd8ff.+?ffd9|474946383961.+?3b|3630.+?(?=89504e47|ffd8ff|474946383961)/)
# images = str.scan(/89504e47.+?49454e44ae426082|ffd8ff.+?ffd9|474946383961.+?(?=89504e47|ffd8ff|3630)|3630.+?(?=89504e47|ffd8ff|474946383961)/)
# images = str.scan(/(89504e47|ffd8ff|474946383961|3630).+?(?=89504e47|ffd8ff|474946383961|3630)/)
images = str.scan(/89504e47.+?49454e44ae426082|ffd8ff.+?ffd9|474946383961.+?(?=89504e47|ffd8ff|424d3630)|424d3630.+?(?=89504e47|ffd8ff|474946383961)/)
pp images.size

i = 0
images.each do |e|
  # e = e.join("")

  puts e[0, 20]
  bin = [e].pack("H*")

  ext =
    case e
    when /^89504e47/
      "png"
    when /^ffd8ff/
      "jpg"
    when /^474946383961/
      "gif"
    when /^424d3630/
      "bmp"
    end

  File.binwrite("image#{sprintf("%02d", i)}.#{ext}", bin)

  i += 1
end
```

# Steganography 3 (Stegano 100)
elfファイルのダンプ画像。

![desktop_capture.png](https://qiita-image-store.s3.amazonaws.com/0/10750/a3c5cafd-b846-5318-307d-332e9a746415.png)

ジャンルがSteganoだし、「小学生なら5分で解けるがプログラマは1時間かかる」がいかにもヒント。
が、愚直に写経してみた。
元画像と写経結果を画像で重ねてtypoを探したりした。

![syakyou.png](https://qiita-image-store.s3.amazonaws.com/0/10750/5a895f42-272d-9d23-1067-e3832cac49c2.png)

で、実行すると

```
$ ./bin.elf
Rmxvb2QgZmlsbA0K

$ echo "Rmxvb2QgZmlsbA0K" | base64 -D
Flood fill
```

…あ！

![answer.png](https://qiita-image-store.s3.amazonaws.com/0/10750/79831c49-02c5-75b7-5152-2fe015557caf.png)

納得でした。良問。

# Last Challenge (Thank you for playing) (Exercises 50)
最初と同じ。でも演出として好き。

```ruby
c1 = "PXFR}QIVTMSZCNDKUWAGJB{LHYEO"
p1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ{}"

prob = "A}FFDNEA}}HDJN}LGH}PWO"

prob.each_char do |c|
  i = c1.index(c)
  raise if i == -1
  print p1[i]
end

puts ""
```

# 他の方のWriteup
- [SECCON 2015 Online CTFのWrite-upが自然に集まる不思議なスプレッドシート](https://docs.google.com/spreadsheets/d/1IEKOBftoAMnOocH10Q2r8sjC54sprRJJsXt9KMnwmBQ)