SWFバイナリのパース
はじめに
今までバイナリに触れる機会がなかったのでとりあえずPythonでバイナリを読めるようになろうかなって思った
とりあえず仕様が公開されてるSWFの画像・音声(動画)をバイナリから引っこ抜くところまでを目標にする
ちなみにバイナリについてバイナリエディタで開いてほーん…ってなるくらいしか知らない
仕様書
Adobeがファイルフォーマットの仕様書を公開しているので基本はこれを読みながらやる
http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/swf/pdf/swf-file-format-spec.pdf
読み方としては15ページのInteger Typesは先に目を通してから27ページのSWFのフォーマットについて読むと良い
それ以外は読み飛ばして必要に応じて読めば良い
レポジトリ
とりあえずの作業スペース
https://github.com/skyblue3350/swf
作業
最低限のバイナリの読み込み
とりあえずバイナリエディタみたいに16進数で表示してみる
structモジュールとかあるけど使わないで書いてみる
1fp = open("test.swf", "rb")
2while True:
3 b = fp.read(1)
4 if b == "": break
5 print hex(ord(b))
6fp.close()
1バイトずつ読み出してordで10進数にしてそれをhexで16進数に変換する
ヘッダの読み込み
ヘッダは以下で構成される
- Signature
圧縮されているかどうか - Version
このSWFファイルのバージョン情報 - FileLength
このSWFファイルの大きさ - FrameSize
フレームの縦横の大きさ - FrameRate
FPS - FrameCount
フレームの数
1個ずつ読んでいく
Signature
Sinatureは3バイトで表される
1バイト目が圧縮 2バイト目(W)と3バイト目(S)は固定(仕様書27ページ参照)
1バイト目がFの時 未圧縮 Cの時 zlib圧縮 Zの時 LZMA圧縮
UI8が8bit→8*3で24ビット→8ビット1バイトで3バイトなのでreadで3バイト読み出す
1fp = open("test.swf", "rb")
2
3signature = fp.read(3)
4print "Signature", signature
5fp.close()
1FWS
未圧縮であることが分かる
もしCWSなら9バイト目以降をzlibモジュールをインポートして
1zlib.decompress(fp.read())
する
Version
この辺から差分だけ
1print ord(fp.read(1))
FileLength
これはリトルエンディアンで表される
1バイト:A
2バイト:B
だったら
BAにしてこれを2つつなげて10進数に直す
1little = [ hex(ord(fp.read(1)))[2:].zfill(2) for x in range(4)][::-1]
2filelen = int("".join(little), 16)
3print filelen
今後もリトルエンディアンは出てくるので関数化しておくと良い
10進数→16進数→10進数ってやり方しか思いつかなかったけどもっと簡単にできそう
ここまで読み込んだらCWSなどの圧縮時はこれ以降のバイナリが圧縮されているので展開する
1if signature == "CWS":
2 b = fp.read()
3 fp.close()
4 fp = StringIO(zlib.decompress(b))
とかしておくと未圧縮と圧縮の差を考えなくて良くなるので多少楽
こんな感じでヘッダの残りも読む
Tagの読み込み
SWFファイルは[Headter][Tag][Tag]...[Tag](28ページ)
とデータが続くようになっている
Tagには種類が設定されていてENDというTagが出て来るまでひたすら読み込んでいけば良い
typeとlength
2バイト読み込んで最初の10ビットを10進に直したものがTagの種類
残りの6ビットがそのTagのサイズ
1info = "".join([ bin(ord(f.read(1)))[2:].zfill(8) for x in range(2) ][::-1])
2tagtype = int(info[:10], 2)
3taglength = int(info[-6:], 2)
またtaglength変数の中身が6ビットで表せる最大の数(2進:111111、10進:63)の時は更に4バイトリトルエンディアンで読み込んだものが実際のサイズになります
これを反映すると以下のようになります
1info = "".join([ bin(ord(fp.read(1)))[2:].zfill(8) for x in range(2) ][::-1])
2tagtype = int(info[:10], 2)
3taglength = int(info[-6:], 2)
4
5# サイズが63なら追加4ビットを使用する
6if taglength == 63:
7 taglength = int("".join([ hex(ord(fp.read(1)))[2:].zfill(2) for x in range(4)][::-1]), 16)
8
9tagbody = fp.read(taglength)
Tag全体の読み込み
tagtypeがSWFにおけるタグの種類です(仕様書235ページに一覧あり)
ファイルの末尾はEND(id:0)となるはずなのでtagtypeが0になるまでループします
1while True:
2 fileinfo = "".join([ bin(ord(fp.read(1)))[2:].zfill(8) for x in range(2) ][::-1])
3 tagtype = int(fileinfo[:10], 2)
4 taglength = int(fileinfo[-6:], 2)
5
6 # TagType:0がデータの末尾
7 if tagtype == 0:
8 break
9
10 if taglength == 63:
11 taglength = int("".join([ hex(ord(fp.read(1)))[2:].zfill(2) for x in range(4)][::-1]), 16)
12
13 tagbody = fp.read(taglength)
これでひとまずファイルの全体の読み込みが出来ました
あとは必要なデータに応じてtagbodyを良い感じに加工するとデータが抽出出来ます
実装済みの部分はレポジトリの方に反映してあるので興味があればどうぞ
この記事の解説はいい加減なので以下の参考サイトを参考に自分で書いた方が分かりやすいと思います
参考記事
以下2サイトは大変お世話になりました