ライブハッキング
2019-11-23 東京大学駒場祭 TSG出展企画「TSG LIVE! 4」
@hakatashi
気軽にコメントしてね♪
↘
パーソナリティ
紹介①
@hakatashi
- TSG LIVE! 発起人
- CTF歴7年
- 得意ジャンル: Web/Stego
パーソナリティ
紹介②
@JP3BGY
- CTF歴2年
- 得意ジャンル: Pwn
ライブハッキング
CTFについて
もっと知りたくなって
きましたね?
この枠では、
TSGが SECCON CTF で
出題した問題の解説や
裏話をします
メモ: ここでコメントを拾う
SECCON CTF 2019 予選で出題された問題
27問!
その1
Tanuki
ジャンル: Misc
配点: 439pt
作問者: @hakatashi
Tanuki 問題文
ご覧ください!
我々は今回のSECCONのために、
超難解かつスーパー安全、
そしてウルトラ解読困難な
新しい暗号を発明しました!
名付けて、Tanukiです!
+[添付ファイル]
Tanuki 問題文
暗号文1: たたせくたこたたたんた
平文1: せくこん
暗号文2: たSたEたたたたたCCたたたたたOたNたたた
平文2: SECCON
Tanuki暗号のひみつ
実は、文章から「た」を「抜く」だけで
暗号を解読できる!
暗号文1: たたせくたこたたたんた
平文1: せくこん
暗号文2: たSたEたたたたたCCたたたたたOたNたたた
平文2: SECCON
Tanuki
当然ですが、これだけなら
何も難しくない⋯⋯
ここで与えられた
添付ファイルを見てみると⋯⋯
tanuki.txt.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz
- .gzは圧縮ファイルの一種
- 一言でいうと.zipの亜種
- つまりtanuki.txtというファイルを
何度も何度も圧縮したもの
- このファイル自体は20.0KBだが、
展開すると⋯⋯
tanuki.txt.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz
- このファイルを実際に展開すると
(できたなら) なんと
2,906,376,313,410,896,280,535,164バイト (=約2.40ヨタバイト)
のテキストファイルになる
2𥝱9063垓7631京3410兆8962億8053万5164バイト
脱線: ヨタバイトとは
どれくらい大きい?
- 2019年11月現在、最もバイト単価の安いHDDは
7580円/4TB。 (kakaku.com調べ) - 展開したtanuki.txtを保存するために必要な費用は、
2,906,376,313,410,896,280,535,164B × (7580円 ÷ 4TiB) = 5,009,117,661,675,580円- =5009兆円
Tanuki 解法
- このサイズだと、ファイルを保存して
処理するのはおろか、
ストリーミングで処理することも困難 - なんとか工夫して処理する必要がある
そもそも
圧縮ファイルって
どうやってデータを圧縮してるか
ご存知ですか?
実は
Zip, GZip, PNG などのファイルでは
同じDeflateという圧縮アルゴリズムが
採用されている
パンダコアラタスマニアタイガーパンダタスマニアデビル
- パンダ
- コアラ
- タスマニアタイガー
- パンダ (25文字前から3文字コピー)
- タスマニア (12文字前から5文字コピー)
- デビル
パンダコアラタスマニアタイガー(25,3)(12,5)デビル
パンダパンダパンダパンダパンダパンダパンダ
- パンダ
- パンダ (3文字前から3文字コピー)
- パンダパンダ (3文字前から6文字コピー)
- パンダパンダパンダパンダパンダパンダ (3文字前から18文字コピー)
パンダ(3,18)
パンダ(3,18)
は、「パンダ」を7回繰り返すという意味
→コピー元までの距離を超えてコピーする場合、
Deflate圧縮は実質的にランレングス圧縮と
考えることができる
Tanukiを解くには
- 「パンダを1000回繰り返した文字列」を
「パンダパンダパンダパンダ⋯⋯」というデータで
持っておくのは圧倒的に非効率 - ランレングス表現をランレングス表現のまま
「パンダ×1000」のように持っておくことで
効率的にこのようなデータを処理することができる- 同じ手法をTanukiにも適用可能!
Tanuki想定解①
- ランレングス表現のままDeflate展開を処理すると
最終的に、- (た×24984550042641701427744)S(た×9475249114589618367172)E(た×15098770148016195475279)C(た×14457375234513681877381)⋯⋯
- のようなデータが出てくる
-
ここから「た」を「抜」けば、フラグが得られる
- 解ける!
Tanuki想定解①の問題点
しんどい
他の解法
- ランレングス表現のまま展開を実装するのは
非常にしんどい。もっと楽な解き方を考える。 - 今回の問題文から、最終的なファイルが
(た×...)S(た×...)E(た×...)C(た×...)C(た×...)⋯⋯
のような形をしていることは明らか- 本質部分は間の文字なので、
繰り返し部分の回数や場所は無視していい - →繰り返す必要ないのでは⋯⋯?
- 本質部分は間の文字なので、
Tanuki想定解②
-
ランレングス表現とみなせる部分の
符号を、データ中から完全に取り除いてから
通常の展開処理を行う- これなら既存の実装を使い回せるので
実装はとても楽になる
- これなら既存の実装を使い回せるので
- ランレングス表現とみなせない符号を
取り除いてはいけない- これを守れば最後までエラー無く
展開できるように作問されています
- これを守れば最後までエラー無く
デモ
一見ただのお遊びだが⋯⋯
実はけっこう重要なこと
Zip Bomb に対する防御
- このように「展開すると爆発的に膨れ上がる
圧縮ファイル」のことを、Zip Bomb などと呼ぶ - 一般にアンチマルウェアソフトはZipファイルの
中身まで見てウイルスファイルの検知を行うので、
Zip Bomb を用いると展開処理が終わらず
これらの処理をハングさせることが可能- →現在では多くのソフトで対策されている
- このように圧縮アルゴリズムの中身まで理解して
ファイルの本質情報を抜き出すことは
セキュリティの観点からも重要
参考: 「非再帰的ZIP爆弾」は10MBのファイルが281TBに膨らむ - GIGAZINE https://gigazine.net/news/20190705-zip-bomb/
ちなみに⋯⋯
ちなみに
作問者的な観点から言うと、
この前後、類題が多くのCTFで出題されてつらかった
- SECCON 2019 Online: pngbomb
- Google CTF 2019 Finals: Stuffed
- ENCRYPT CTF 2019: Journey to the centre of the file
→圧縮ファイルブーム? (ほんまか)
裏話: Tanuki
Q.どうやって作問したの?
A.気合
Tanuki作問
- 解法①のしんどい方法の逆をする
- ランレングス表現のままでできる
Deflateの圧縮処理を実装 - 実質zlib相当のコードをRubyで再実装した
- ランレングス表現のままでできる
- ここでは解説しないが、Deflateの実際の処理は
もっと複雑- ハフマン符号
- ハフマン符号のハフマン符号
- セクション分割
- CRC
「嘘のない」問題にする
- 「ランレングス表現を除けば展開できるファイル」という
仕様を満たすだけならもっと簡単に実装できる - が、この手の問題において「嘘」はつきたくなかった
- CRCやデータの中身も含めて、
ちゃんと (5000兆円あれば) 実際に
2.4ヨタバイトのデータを得ることができる
ファイルになってます
- CRCやデータの中身も含めて、
- なおこの時のCRCに関する考察が別の問題の
作問アイデアになっている
メモ: ここでコメントを拾う
その2
fileserver
ジャンル: Web
配点: 345pt
作問者: @hakatashi
fileserver 問題文
ApacheとかNginXとかよくわからないけど、
要は自分で実装すればいいんでしょ?
ほら、簡単!
Webサイトが与えられます
重要なのはこのあたり
files = Dir.glob(".#{req.path}*")
res['Content-Type'] = 'text/html'
res.body = ERB.new(File.read('index.html.erb')).result(binding)
Dir.globのドキュメントを確認
!?
パスにNULL文字を突っ込むことで
本来読めないはずのファイルを読める
files = Dir.glob(".#{req.path}*")
req.path = '/%00/tmp/flags/'
→ Dir.glob("./%00/tmp/flags/*")
デモ
しかし
- このままではファイル名しかわからない
- 中身を読むにはもう一つの脆弱性を
利用する必要がある
もう一つの脆弱性
def is_bad_path(path)
bad_char = nil
%w(* ? [ { \\).each do |char|
if path.include? char
bad_char = char
break
end
end
if bad_char.nil?
false
else
# check if brackets are paired
if bad_char == ?{
path[path.index(bad_char)..].include? ?}
elsif bad_char == ?[
path[path.index(bad_char)..].include? ?]
else
true
end
end
end
早期breakの仕様を利用
- unmatchedな孤立{を含めることで
[]のフィルタを回避することができる
その3
Crazy Repetition
of Codes
ジャンル: Crypto
配点: 326pt
作問者: @hakatashi
本質はこの部分
def crc32(crc, data):
crc = 0xFFFFFFFF ^ crc
for c in data:
crc = crc ^ ord(c)
for i in range(8):
crc = (crc >> 1) ^ (0xEDB88320 * (crc & 1))
return 0xFFFFFFFF ^ crc
crc = 0
for i in range(int("1" * 10000)):
crc = crc32(crc, "TSG")
int("1" * 10000)回のループ
- 10000回?
- ではなく
- 111111111111⋯(10000個)⋯11111111111回のループ
crc = 0
for i in range(int("1" * 10000)):
crc = crc32(crc, "TSG")
CRC (巡回冗長検査、
Cyclic Redundancy Check)
- 主にデータの破損等を検証するために
計算される数値 - 数学的にはGF(2)上の多項式環として表現される
問題では
- 与えられたコードはTSGという文字列を
ループが回るたびに連結している - つまり1111111111⋯(10000文字)⋯1111111111個の
TSGを連結した文字列のCRCを計算している
数式で表すと⋯⋯
デモ
いかがでしたか?
- 明日は TSG LIVE! 最終日
- 今回ご紹介したCTFを、
今度は生放送で対戦する様子をお届け - 視聴者も参加することができます!
おしまい
TSG LIVE! 4 「ライブハッキング」解説スライド
By Koki Takahashi
TSG LIVE! 4 「ライブハッキング」解説スライド
- 1,353