Python + SWFバイナリ解析 その2 : jpeg バイナリ解析
Python による jpeg バイナリ解析と swf 内データ差し替えのサンプルを作成しました。
・参考
GREE Engineers' Blog : SWFバイナリ編集のススメ第三回 (JPEG)
http://labs.gree.jp/blog/2010/09/782/
SWF version 8 より前のバージョンならば erroneous header を jpeg に付与するだけで jpeg 差し替えが可能との事です。swf のバージョンを判別して、version 8 以降ならば jpeg バイナリ解析を行い、version 8 未満ならば erroneous header 付与形式にすれば、サーバ側での処理負荷を減らすことができそうです。
一応 試しに erroneous header 付与形式の jpeg を version 10 の swf に埋め込んでみましたが、今回作成したサンプルの処理範囲内では問題なく動作していました。
つまずいた点
Tag DefineBitsJPEG2 の Content 内 先頭箇所 2 byte には CharacterID が必要ですが、上記参考サイトの図には その説明が省略されています。swf 内にある jpeg データが、元 jpeg データより 6 byte 容量が大きくなっており、そのうち 4 byte は erroneous header だとして 残り 2 byte は何のデータなんだろうと悩みました。
サンプル
以下 jpeg と ActionScript(以下AS)埋め込み(差し替え)を行ったサンプル swf となります。AS は「test="おはよう"」「test2="こんばんは"」という二行を埋め込み、テキストフィールドに表示するようにしています。
差し替え前の jpeg
差し替え後の jpeg
データ差し替え前の swf
データ差し替え後の swf
差し替え後の swf を見ると、jpeg 画像が全て表示しきれていません。jpeg の表示領域設定が、差し替え前の jpeg のもののままになっているからです。
差し替えた jpeg の表示領域を変更したい場合、DefineBitsJPEG タグの後に登場する DefineShape タグのデータを変更する必要あります。jpeg 表示領域変更処理は今後必要時に作成する予定です。
・参考
Elementary Note : FLASH解析(JPEG差し替え編)
http://reindrop.wordpress.com/2008/08/28/flash解析jpeg差し替え編/
以下は今回作成した JPEG バイナリ解析処理用クラスです。ご参考程度にどうぞ。
from struct import * class JpegChunkSet: def __init__(self): self.__SWF_VERSION_THAT_USES_ERRONEOUS_HEADER = 7 self.__BINARY_OF_SOI = "\xff" + "\xd8" self.__BINARY_OF_EOI = "\xff" + "\xd9" self.__jpegChunkSet = [] def parse(self, binarySet): markerType = MarkerType() encodingTablesBinarySet = "" imageDataBinarySet = "" binaryLength = len(binarySet) index = 0 while index < binaryLength: jpegChunk = JpegChunk() index = jpegChunk.create(binarySet, index, markerType, binaryLength) self.__jpegChunkSet.append(jpegChunk) if markerType.isEncodingTables(jpegChunk.getType()): encodingTablesBinarySet = encodingTablesBinarySet + jpegChunk.pack() else: imageDataBinarySet = imageDataBinarySet + jpegChunk.pack() encodingTablesBinarySet = self.__BINARY_OF_SOI + encodingTablesBinarySet + self.__BINARY_OF_EOI self.__contentForDefineBitsJPEG2 = encodingTablesBinarySet + imageDataBinarySet def checkWhetherToUseErroneousHeader(self, swfVersion): return swfVersion <= self.__SWF_VERSION_THAT_USES_ERRONEOUS_HEADER def getErroneousHeader(self): return self.__BINARY_OF_EOI + self.__BINARY_OF_SOI def getContentForDefineBitsJPEG2(self): return self.__contentForDefineBitsJPEG2 def toString(self): print "--- jpeg ---" for jpegChunk in self.__jpegChunkSet: jpegChunk.toString() class JpegChunk: def __init__(self): self.__MARKER_FIELD_BYTE = 2 self.__LENGTH_FIELD_BYTE = 2 self.__lengthFieldBinary = "" self.__appDataBinary = "" def create(self, binarySet, index, markerType, binaryLength): n = index + self.__MARKER_FIELD_BYTE markerFieldBinary = binarySet[index:n] if ord(markerFieldBinary[0]) != 0xff: raise Exception("error", ord(markerFieldBinary[0])) self.__markerFieldBinary = markerFieldBinary self.__type = ord(markerFieldBinary[1]) index = n if markerType.isSOI(self.__type) or markerType.isEOI(self.__type): return index elif markerType.isSOS(self.__type) or markerType.isRST(self.__type): n = index while n < binaryLength: if ord(binarySet[n]) == 0xff and ord(binarySet[n + 1]) != 0x00: break else: n = n + 1 if n >= binaryLength: raise Exception("error2") self.__appDataBinary = binarySet[index:n] return n else: n = index + self.__LENGTH_FIELD_BYTE self.__lengthFieldBinary = binarySet[index:n] length = unpack(">H", self.__lengthFieldBinary)[0] index = n n = index + length - self.__LENGTH_FIELD_BYTE self.__appDataBinary = binarySet[index:n] return n def pack(self): return self.__markerFieldBinary + self.__lengthFieldBinary + self.__appDataBinary def getType(self): return self.__type def toString(self): print "0x%x" % self.__type if self.__lengthFieldBinary != "": print " length", unpack(">H", self.__lengthFieldBinary)[0] if self.__appDataBinary != "": print " appDataLength", len(self.__appDataBinary) print " chunk length", len(self.pack()) class MarkerType: def isSOI(self, type): return type == 0xd8 def isEOI(self, type): return type == 0xd9 def isSOS(self, type): return type == 0xda def isRST(self, type): i = 0xd0 while i <= 0xd7: if type == i: return True i = i + 1 return False def isDQT(self, type): return type == 0xdb def isDHT(self, type): return type == 0xc4 def isEncodingTables(self, type): return self.isDQT(type) or self.isDHT(type)
すみません。そして、ご指摘ありがとうございます。
こちらの図は先程修正しました。> 先頭箇所 2 byte には CharacterID が必要ですが、上記参考サイトの図には その説明が省略されています
投稿者 よや : 2011年02月08日 13:18