SWFバイナリ編集のススメ第0回 (解析編)

SWFバイナリ編集のススメ第0回 (解析編)

こんにちは、クライアント基盤チームのよやです。

昨今、Flash Player の自作が流行りのようですが、デバッグやテストに苦労されているとも聞き及んでいます。

丁度タイミング良く、SWFバイナリ編集のススメでサンプルとして引用している IO_SWF パッケージが SWF 解析ツールとして充実してきました。折角ですので、この場で紹介させて下さい。

野良 Flash Player の開発者であれば自前で SWF 解析ツールを用意出来ると思いますが、何かあった時に verify check として利用頂けると幸いです。

IO_SWF のインストール

php と pear コマンドが入っていれば3つのコマンドでインストール出来ます。

% sudo pear channel-discover openpear.org
% sudo pear install openpear/IO_Bit
% sudo pear install openpear/IO_SWF

これで、 /usr/share/php/sample/ 辺りに php の解析ツールがインストールされます。

これと別に IO_SWF を Subversion で取ってきて実行すると、必要に応じて PHP のコードを弄る事で、より深い SWF 解析が出来ます。お勧めです。

~/svn% svn checkout http://svn.openpear.org/IO_SWF/trunk IO_SWF
~/svn% cd IO_SWF 
~/svn/IO_SWF% php sample/swfdump -f hoge.swf

サンプル SWF

ケルベロス開発チームから頂いた SWF ファイル(CerberusQuest.swf)をサンプルにして説明します。

SWF 全体

swfdump.php

  • SWF の生の情報が分かります。
~/svn/IO_SWF% php sample/swfdump.php -f quest.swf
Signature: FWS
Version: 4
FileLength: 97620
FrameSize: Xmin: 0 Xmax: 240 Ymin: 0 Ymax: 240
FrameRate: 12
FrameCount: 101
Tags:
Code: 9(SetBackgroundColor)  Length: 3
        Color: #000000
Code: 24(Protect)  Length: 0
Code: 12(DoAction)  Length: 2075
    Actions:
        [0] Push(Code:0x96) (Length:9) (String)NextURL
        [1] Push(Code:0x96) (Length:27) (String)http://labs.gree.jp/blog/
<略>
  • -h を付けると16進数ダンプの表示がつきます。
~/svn/IO_SWF% php sample/swfdump.php -h -f CerberusQuest.swf
Signature: FWS
Version: 4
FileLength: 96523
FrameSize: Xmin: 0 Xmax: 240 Ymin: 0 Ymax: 240
FrameRate: 12
FrameCount: 101
             0  1  2  3  4  5  6  7   8  9  a  b  c  d  e  f  0123456789abcdef
0x00000000  46 57 53 04 0b 79 01 00  70 00 09 60 00 00 96 00  FWS  y  p  `    
0x00000010  00 0c 65 00                                        e 
Tags:
<略>
  • DefineMorphShape も解析できます。
<略>
Code: 46(DefineMorphShape)  Length: 345
    ShapeId: 102
    StartBounds: Xmin: -44.95 Xmax: 12.55 Ymin: -20.95 Ymax: 21
    EndBounds: Xmin: -44.95 Xmax: 12.55 Ymin: -20.95 Ymax: 21
    Offset:183
    FillStyles:
        Bigmap(64):   BitmapId: 101
        StartBitmapMatrix:
                | 20.000 0.000 |  -36.50
                | 0.000 20.000 |  -38.75
        EndBitmapMatrix:
                | 20.000 0.000 |  -36.50
                | 0.000 20.000 |  -38.75
    LineStyles:
        Width: 0 => 0 Color: #000000(00) => #000000(00)
    StartEdge:
        ChangeStyle: MoveTo: (-1.95, -18.2)  FillStyle: 1|0  LineStyle: 1
        StraightEdge: MoveTo: (-9.35, -19.45)
<略>

swfheader.php

  • SWF のヘッダ情報を表示します。
~/svn/IO_SWF% php sample/swfheader.php CerberusQuest.swf
SWF Headers:
   Signature:FWS
   Version:4
   FileLength:96523
   FrameSize.Xmin:0
   FrameSize.Xmax:4800
   FrameSize.Ymin:0
   FrameSize.Ymax:4800
   FrameRate:3072
   FrameCount:101
  • FrameRate を書き換える事も出来ます。

FrameRate の 3072 は 0x100(=256) 倍の数が入っていて、12 frames/sec 相当の値です。これを 256 で上書きすると 1 frames/sec になります。

~/svn/IO_SWF% php sample/swfheader.php CerberusQuest.swf FrameRate=256 > CerberusQuest-1fps.swf

  • FrameSize も書き換えられます。

FrameSize.Xmax:4800 とありますが、内部的に TWIPS という単位で座標値が格納されていて、通常の pixel の 20倍です。ですので、240 相当です。
FrameSize.Xmin を負の値で上書きする事により、Flash が通常表示する枠より左側を表示させてみます。

~/svn/IO_SWF% php sample/swfheader.php CerberusQuest.swf FrameSize.Xmin=-4800 > CerberusQuest-4800.swf

Flash によっては表示枠外に何か隠されている事がありますが、このファイルには隠し事はないようです。

swfgreptags.php

  • 特定のタグを持つ SWF を探す

例えば、カレントディレクトリで DefineMorphShape タグを持つ SWF ファイルを以下のように探せます。

~/svn/IO_SWF% php sample/swfgreptags.php DefineMorphShape  *.swf
CerberusQuest.swf: DefineMorphShape(code:46, length:345)
CerberusQuest.swf: DefineMorphShape(code:46, length:119)
CerberusQuest.swf: DefineMorphShape(code:46, length:147)

コンテンツ系

swfextract.php

  • SWF に含まれるコンテンツを吸い出します。画像、音声、MovieClip に対応しています。
~/svn/IO_SWF% php sample/swfextract.php -f CerberusQuest.swf -p "t-"
t-129.png
t-133.png
t-135.png
<略>

結果

  • 指定した prefix (上の例では “t-” )に CharacterID を付けたファイル名です。
  • -m を付けると、MovieClip を SWF 形式で出力します。
~/svn/IO_SWF% php sample/swfextract.php -f CerberusQuest.swf -p "t-" -m
t-1.swf
t-30.swf
t-33.swf
<略>
  • 骸骨のキャラです。> t-41.swf (SpriteId:41 の MovieClip)

殆どの部分が上の方(Y < 0)に隠れてしまっています。

0, 0 をベースにデザインされているのが予想出来るので、先程の swfheader.php で FrameSize を弄ります。

~/svn/IO_SWF% php sample/swfheader.php t-41.swf FrameSize.Xmin=-2400 FrameSize.Ymin=-2400 FrameSize.Xmax=2400 FrameSize.Ymax=2400 > t-41-2400.swf 


表示フレーム枠を左上に半分シフトして (-120, -120)-(120, 120) にする事で、画像の全体が表示されました。

ムービークリップ系

swflistmovieclip.php

  • MovieClip の一覧を表示します。
~/svn/IO_SWF% php sample/swflistmovieclip.php CerberusQuest.swf
SpriteId:1 FrameCount:1 TagCount:2 name:dum0
	dum0
SpriteId:30 FrameCount:5 TagCount:14
SpriteId:33 FrameCount:4 TagCount:10
SpriteId:36 FrameCount:18 TagCount:28
SpriteId:131 FrameCount:3 TagCount:7
SpriteId:148 FrameCount:5 TagCount:87 name:b
	*/b (149)
	*/f/b (40,149)
	*/f/b (40,149)
	pl0/f/b (40,149)
	pl0/f/b (40,149)
	*/pl0/f/b (50,40,149)
	*/pl0/f/b (50,40,149)
	p0/pl0/f/b (50,40,149)
	p0/pl0/f/b (50,40,149)
SpriteId:149 FrameCount:33 TagCount:68 name:f
	*/f (40)
	*/f (40)
	pl0/f (40)

MovieClip の内部ID(SpriteId), フレーム数、タグ数、名前と、可能性のあるターゲットパス名が表示されます。

アクション系

swflistaction.php

  • SWF に含まれる Actionバイトコードの一覧を表示します。
~/svn/IO_SWF% php sample/swflistaction.php CerberusQuest.swf
spriteId:0(root)  frame:1	=> instruction:323  length=1880
spriteId:30  frame:1	=> instruction:1  length=2
spriteId:33  frame:1	=> instruction:1  length=2
<略>
  • MovieClipの ID(spriteId), フレーム番号、バイトコードの命令数、バイト長の順で1行に表示します。

swfdeleteaction.php

  • SWF 中の ActionScript を消します。制御を無くして全フレーム表示させたい場合に便利です。

全消し (-a オプション)

~/svn/IO_SWF% php sample/swfdeleteaction.php -a -f CerberusQuest.swf > CerberusQuest-noaction.swf
~/svn/IO_SWF% php sample/swflistaction.php CerberusQuestnoaction.swf
~/svn/IO_SWF% 

  • 制御が全部消えるので、各々の MovieClip が無限ループで再生されました。
  • 条件を指定すると特定のアクションを削除します。-s でスプライト(MovieClip)指定、-l でバイトコード長指定が出来ます。
~/svn/IO_SWF% php sample/swflistaction.php CerberusQuest.swf
spriteId:0(root)  frame:1	=> instruction:323  length=1880
spriteId:30  frame:1	=> instruction:1  length=2
spriteId:33  frame:1	=> instruction:1  length=2
<略>
~/svn/IO_SWF% php sample/swfdeleteaction.php -s 0:1 -f CerberusQuest.swf > CerberusQuest-noaction0-1.swf
~/svn/IO_SWF% php sample/swflistaction.php CerberusQuest-noaction0-1.swf
spriteId:30  frame:1	=> instruction:1  length=2
spriteId:33  frame:1	=> instruction:1  length=2
<略>

swfdump.php

  • swfdump.php で Actionコードを逆アセンブルした結果が表示されます。又、-l を付けると Branch 命令に関連する場所にラベルがつきます。
~/svn/IO_SWF% php sample/swfdump.php -l -f CerberusQuest.swf
<略>
        [95] StringExtract(Code:0x15)
        [96] SetVariable(Code:0x1D)
        [97] Jump(Code:0x99) (Length:2) BranchOffset=-106 (LABEL: 75)
    (LABEL: 98):
        [98] Push(Code:0x96) (Length:5) (String)btn
        [99] Push(Code:0x96) (Length:3) (String)0
        [100] SetVariable(Code:0x1D)
    (LABEL: 101):

swfdecompile.php

  • ActionScript バイトコードを逆コンパイルします。但し、独自言語です。
~/svn/IO_SWF% php sample/swfdecompile.php -f CerberusQuest.swf
Signature: FWS
Version: 4
FileLength: 96523
FrameSize: Xmin: 0 Xmax: 240 Ymin: 0 Ymax: 240
FrameRate: 12
FrameCount: 101
Tags:
    Actions: (in frame 1)
1	NextURL = "http://labs.gree.jp/blog/"

Push Push SetVariable が = に変換されて表示されたり、他にもバイトコードのニーモニックそのものより見易い形式で表示されます。

SWF 解析が捗りますね!

最後に

この他にも便利なツールがありますので、是非お試し下さい。引数無しで実行するとヘルプが表示されるので、使い方はそれで何となく分かると思います。
特に、

  • swfdeformeshape.php (Shapeの線分を減らす)
  • swfcountshapeedges.php (Shapeの線分を数える)
  • swfwireframe.php (塗りを境界のみ表示する)

の3つは、Shape の構造を探る上で役に立つかもしれません。

尚、これらの解析ツールの半分は beketa 氏 の実装です。この場を借りて感謝します。

以上、Flash Player 作りでデバッグのお役に立てると幸いです。

P.S. ツールが欲しいと思った時に即興で作ったものばかりなので、ファイル指定で -f を付けたり付けなかったり、ファイル名も統一性がありませんが、どうかご容赦を。