Edited at

SECCON 2015 Online Quals Writeup

More than 3 years have passed since last update.

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

チームnicklegrで個人参加。

1000点で190位(872チーム中)でした。


Start SECCON CTF (Exercises 50)

換字式暗号。親切に全文字のテーブルが用意されている。

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が参考になりました。


Reverse-Engineering Android APK 1 (Binary 100)

Android APK Decompilerに投げる。

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問。問題は毎回ランダム。

下記を自動化した。

面倒なのが、解が一意じゃない問題があること。(市販の問題にもときどきある)

以下でフォローした。


  • 前述のソルバーが4〜5通りの解を返してくれる(全パターンじゃないっぽい)ので全部試す

  • QRデコーダのエラー訂正

  • 解答は /^\w+$/ っぽいので、マッチしないのを弾く


    • ちなみに最後の問題だけ例外。 SECCON{YES_WE_REALLY_LOVE_QR_CODE_BECAUSE_OF_ITS_CLEVER_DESIGN}



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

# 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文字ずつフラグが入ってる。

拡張子がヒントか。

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

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

ジャンルがSteganoだし、「小学生なら5分で解けるがプログラマは1時間かかる」がいかにもヒント。

が、愚直に写経してみた。

元画像と写経結果を画像で重ねてtypoを探したりした。

syakyou.png

で、実行すると

$ ./bin.elf

Rmxvb2QgZmlsbA0K

$ echo "Rmxvb2QgZmlsbA0K" | base64 -D
Flood fill

…あ!

answer.png

納得でした。良問。


Last Challenge (Thank you for playing) (Exercises 50)

最初と同じ。でも演出として好き。

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