この記事のURL

http://www.dango-itimi.com/blog/archives/2011/001039.html


FLASH tips 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 の値に応じて処理内容を変更するようにする必要があります。

[ FLASH ] [ tips ] 投稿者 siratama : 2011年01月10日 02:43

トラックバック

http://www.dango-itimi.com/blog/mt-tb.cgi/999

コメント

すみません。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

> よやさん

ご指摘ありがとうございます!
フレームレート表示箇所は、得られた数値から「これであってるだろう」と特に何も調べず適当に記述してしまっていました。
よやさんのご指摘内容を理解した後、修正したいと思います。

投稿者 siratama : 2011年01月11日 11:42

以下コメントを書き込むだけでは、管理人には通知が行われません。通知を行いたい場合、管理人の書き込みに「返信」を押してコメントをしていただくか、あるいは Google+, Twitter へご連絡ください。




[EDIT]