Python + SWFバイナリ解析
Google App Engine に試用に伴う Python の勉強として、Python による swf バイナリ解析処理を作成してみることに。
以下の URL の解説の順序通り解析処理まで作成しました。
GREE Engineers' Blog : SWFバイナリ編集のススメ第一回
http://labs.gree.jp/blog/2010/08/631/
・少々つまずいた点
パブリッシュ設定の「XMP メタデータを含める」にチェックがあると、このデータが Header の後に追加されるようなので、ひとまずチェックを外した swf を利用しました。
Google App Engine の Python の現バージョンは 2.5.2 で、数値を二進数文字列に変換する処理がデフォルトでサポートしていない?ため、以下のサイトにあった処理を利用させていただきました。
PythonRecipe : 2進、8進、10進、16進の各表現を相互に変換
・その他参考サイト等
Flash SWF バイナリ
http://pwiki.awm.jp/~yoya/?Flash/SWF
Python 3.x で Flash Lite 1.1 のパラメータ埋め込み
http://nakagami.blog.so-net.ne.jp/2010-02-02
以下、作成した Python サンプルソースコードと実行結果を掲載。初めての Python につき、誤っていたり無駄な処理がある可能性があります。
sample.py
# coding: UTF-8 from urllib import urlopen from struct import * from MathUtil import * from StringUtil import * import zlib class HeaderParser: def __init__(self, binarySet, util): self.binarySet = binarySet self.util = util def run(self): self.__parseSignature() self.__parseSwfViersion() self.__parseFileSize() self.__parseRect() self.__parseFrames() def __parseSignature(self): self.signatureFirstByte = self.binarySet[0] def __parseSwfViersion(self): self.swfVersion = ord(self.binarySet[3]) def __parseFileSize(self): self.fileLength = unpack("<L", self.binarySet[4:8])[0] def __parseRect(self): self.nbits = ord(self.binarySet[8]) >> 3 rectbitsStr = "" for i in range(8, 16): rectbitsStr = rectbitsStr + self.util.changeFromBinaryToBinStr(self.binarySet[i]) self.twipsSet = [] for i in range(0, 4): pos = 5 + (i * self.nbits) binStr = rectbitsStr[pos:pos + self.nbits] self.twipsSet.append(int(binStr, 2)) def __parseFrames(self): self.frameRate = unpack("<H", self.binarySet[16:18])[0] / 256.0 self.rootTotalFrames = unpack("<H", self.binarySet[18:20])[0] def toString(self): print "--- header ---" print "signature first byte :", self.signatureFirstByte print "version", self.swfVersion print "file length", self.fileLength print "Nbits", self.nbits print "Xmin", self.twipsSet[0] print "Xmax", self.twipsSet[1] print "Ymin", self.twipsSet[2] print "Ymax", self.twipsSet[3] print "frame rate", self.frameRate print "root totalframes", self.rootTotalFrames class TagParser: def __init__(self, binarySet, util): self.util = util self.binarySet = binarySet self.tagSet = [] def run(self): binaryIndex = 20 binaryLength = len(self.binarySet) while binaryIndex < binaryLength: tag = Tag() tag.create(self.binarySet, binaryIndex, self.util) self.tagSet.append(tag) binaryIndex = tag.dataLastBinaryIndex def toString(self): print "--- tag ---" for tag in self.tagSet: tag.toString() class Tag: def __init__(self): self.TAG_INFO_BYTE = 2 self.LENGTH_BYTE_FOR_LONG_TYPE = 4 def create(self, binarySet, binaryIndex, util): binStr = "" n = self.TAG_INFO_BYTE - 1 while n >= 0: binStr = binStr + util.changeFromBinaryToBinStr(binarySet[binaryIndex + n]) n = n - 1 self.type = int(binStr[0:10], 2) self.dataLength = int(binStr[10:], 2) if self.dataLength == 0x3f: n = binaryIndex + self.TAG_INFO_BYTE self.dataLength = unpack("<L", binarySet[n:n + self.LENGTH_BYTE_FOR_LONG_TYPE])[0] self.dataStartIndex = binaryIndex + self.TAG_INFO_BYTE + self.LENGTH_BYTE_FOR_LONG_TYPE else: self.dataStartIndex = binaryIndex + self.TAG_INFO_BYTE self.dataLastBinaryIndex = self.dataStartIndex + self.dataLength self.data = binarySet[self.dataStartIndex:self.dataLastBinaryIndex] def toString(self): print "type", self.type, "dataLength", self.dataLength, "dataStartIndex", self.dataStartIndex, "dataLastBinaryIndex", self.dataLastBinaryIndex class Util: def __init__(self): self.MAX_PLACE_OF_HEX = 2 self.MAX_PLACE_OF_BINARY = 4 self.mathUtil = MathUtil() self.stringUtil = StringUtil() def changeFromBinaryToBinStr(self, binary): binStr = "" hexStr = self.changeFromBinaryToHexStr(binary) for i in range(self.MAX_PLACE_OF_HEX): binStr = binStr + self.changeFromHexStrToBinStr(hexStr[i]) return binStr def changeFromBinaryToHexStr(self, binary): hexStr = self.mathUtil.changeFromIntToStr(ord(binary), 16) hexStr = self.stringUtil.addZeroToHeadUntilSpecifiedPlace(hexStr, self.MAX_PLACE_OF_HEX) return hexStr def changeFromHexStrToBinStr(self, hexStr): binStr = self.mathUtil.changeFromIntToStr(int(hexStr, 16), 2) binStr = self.stringUtil.addZeroToHeadUntilSpecifiedPlace(binStr, self.MAX_PLACE_OF_BINARY) return binStr # if __name__ == '__main__': binarySet = urlopen("trunk/deploy/dango-itimi/sample.swf").read() if ord(binarySet[0]) == 0x43: binarySet = binarySet[:8] + zlib.decompress(binarySet[8:]) util = Util() headerParser = HeaderParser(binarySet, util) headerParser.run() headerParser.toString() tagParser = TagParser(binarySet, util); tagParser.run() tagParser.toString()
MathUtil.py : 参考サイトの PythonRecipe 内にあった処理 ほぼそのまま
class MathUtil: def __init__(self): self.int2str_table = '0123456789abcdefghijklmnopqrstuvwxyz' def changeFromIntToStr(self, i, base): if not 2 <= base <= 36: raise ValueError('base must be 2 <= base < 36') result = [] temp = abs(i) if temp == 0: result.append('0') else: while temp > 0: result.append(self.int2str_table[temp % base]) temp /= base if i < 0: result.append('-') return ''.join(reversed(result))
StringUtil.py : ◯進数文字列表現時、指定桁数になるまで「0」を文頭に付与する用
class StringUtil: def addZeroToHeadUntilSpecifiedPlace(self, originalStr, place): execTotal = place - len(originalStr) str = originalStr for i in range(execTotal): str = "0" + str return str
sample.py 実行結果
Flash Lite 1.1 用にパブリッシュした swf を利用
--- header --- signature first byte : F version 4 file length 741 Nbits 14 Xmin 0 Xmax 4800 Ymin 0 Ymax 4800 frame rate 16.0 root totalframes 1 --- tag --- type 9 dataLength 3 dataStartIndex 22 dataLastBinaryIndex 25 type 48 dataLength 566 dataStartIndex 31 dataLastBinaryIndex 597 type 88 dataLength 76 dataStartIndex 603 dataLastBinaryIndex 679 type 11 dataLength 41 dataStartIndex 685 dataLastBinaryIndex 726 type 26 dataLength 9 dataStartIndex 728 dataLastBinaryIndex 737 type 1 dataLength 0 dataStartIndex 739 dataLastBinaryIndex 739 type 0 dataLength 0 dataStartIndex 741 dataLastBinaryIndex 741
Flash Player 10 用等にパブリッシュした swf も解析可能
追記:2011/1/11)
frameRate 計算箇所の誤りを修正
追記: 2011/3/24)
上記ソースは swf 表示領域 240x240 ピクセルの解析には対応していますが、それ以外の表示領域の場合エラーが発生する可能性があります。Nbits の値に応じて処理内容を変更するようにする必要があります。
すみません。pwiki.awm.jp のドキュメントに誤りがありまして、
frameRate は整数部小数部合わせて 2byte の8.8固定小数点なので、
frameRate1,2 を 2byte まとめて読んで、使う時に 256.0 で割るか、とりあえず表示を合わせるのであれば、
print "frame rate", (frameRate1 + frameRate2 / 256.0)
が正しいです。
小数部は大抵 0 なので実際に問題になる事は無いと思いますが、訂正させて下さい。(*'-')ノ
投稿者 よや : 2011年01月11日 08:40