この記事のURL

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


FLASH tips 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)

[ FLASH ] [ tips ] 投稿者 siratama : 2011年01月15日 16:30

トラックバック

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

コメント

すみません。そして、ご指摘ありがとうございます。
こちらの図は先程修正しました。> 先頭箇所 2 byte には CharacterID が必要ですが、上記参考サイトの図には その説明が省略されています

投稿者 よや : 2011年02月08日 13:18

erroneous header について補足のコメントを書いている内に長文になってしまった為、はてなの方にエントリを作りました。参考までに。m(_ _)m

http://d.hatena.ne.jp/yoya/20110208/jpeg

尚、「version 8 未満ならば erroneous header 付与形式にすれば」の方針は、自分も賛成です。

投稿者 よや : 2011年02月08日 14:41

> よやさん

コメント、並びに もろもろの点ありがとうございます!
erroneous header について勉強になります。

投稿者 siratama : 2011年02月08日 19:30

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




[EDIT]