[SWF-convert] SWF内に存在する画像の取得(jpeg)

2016年5月5日

PHP swf テクノロジー プログラミング 日記

SWF研究の第2弾として、内部構造が大体理解できたので、比較的簡単そうな「jpeg」データの取得を行なってみたいと思います。 SWFで使用できる画像は主にjpeg、GIF,PNGの3つ(※BMPも使えたっけ?)、まあ、WEBで使う画像としてもこの3つなので、 これをコントロールできる事が、実践の第一歩であると考えてます。 前回のSWFファイルの情報取得のソースを流用して、内部に格納されているJPEGデータを吸い出してJPEGファイルを作成してみたいと思います。 ちなみに、yoyaさんをリスペクトしてPHPコマンドで行なってみたいと思います。 ※他言語へのローカライズは別の機会に行いたいと思います。

ソース

getImage.php

ソースの冒頭に簡単な仕様記述をしておきました。 <?php date_default_timezone_set('Asia/Tokyo'); /** * Images of swf-file to pick-up. * write : yugeta * date : 2016/4/29 * * [sample] * getImage.php f=../swf/hoge.swf d=true * * [specification] * param @ f : target-file (*must) * param @ d : view dump [true / false(*default)] **/ //CLIの場合argvをREQUESTに変換する。 if(isset($argv)){ for($i=1,$c=count($argv);$i<$c;$i++){ if(!$argv[$i]){continue;} //各クエリの分解 $q = explode("=",$argv[$i]); if(count($q)<2){continue;} if($q[0]!=''){ //requestに格納 $key = $q[0]; $val = join("=",array_slice($q,1)); $_REQUEST[$key]=$val; } } } // File-check if(!$_REQUEST["f"] || !is_file($_REQUEST["f"])){ echo "Error ! : not-file".PHP_EOL; exit(0); } // Init require_once "BitReader.php"; $swfdata = file_get_contents($_REQUEST["f"]); $reader = new BitReader; $reader->input($swfdata); $outputPath = "output/".date("YmdHis").".".explode(" ",microtime())[1]; mkdir($outputPath,0777,true); file_put_contents($outputPath."/".basename($_REQUEST["f"]) , $swfdata); // Get header $signature = $reader->getData(3); $version = $reader->getUI8(); $fileLength = $reader->getUI32LE(); $NBits = $reader->getUIBits(5); $xmin = ($reader->getUIBits($NBits) / 20); $xmax = ($reader->getUIBits($NBits) / 20); $ymin = ($reader->getUIBits($NBits) / 20); $ymax = ($reader->getUIBits($NBits) / 20); $reader->byteAlign(); $frameRate = ($reader->getUI16LE() / 0x100); $frameCount = $reader->getUI16LE(); // SWF Tags $imgCnt=0; $tagsView = array(); array_push($tagsView, 'Tag:'.PHP_EOL); while (true) { $startOffset = $reader->_byte_offset; $tagAndLength = $reader->getUI16LE(); $type = $tagAndLength >> 6; $length = $tagAndLength & 0x3f; if ($length == 0x3f) { $length = $reader->getUI32LE(); } $contents = $reader->getData($length); array_push($tagsView, "\t"."type: $type length: $length tagAndLength: $tagAndLength"); array_push($tagsView, "\t(".$startOffset." / ".$reader->_byte_offset." / ".($reader->_byte_offset-$startOffset).")"); array_push($tagsView, PHP_EOL); // END Tag if ($type == 0) {break;} // JPEG ----- // getImage (jpeg) else if($type == 21){ $img = LIB::data2jpeg($contents); file_put_contents($outputPath."/img_".$imgCnt."_type_".$type.".jpg", $img); $imgCnt++; } } if($_REQUEST["d"]=="true"){ echo 'Signature: '.$signature.PHP_EOL; echo 'Version: '.$version.PHP_EOL; echo 'FileLength: '.$fileLength.PHP_EOL; echo 'FrameSize: '.PHP_EOL; echo "\tXmin: ".$xmin.PHP_EOL; echo "\tXmax: ".$xmax.PHP_EOL; echo "\tYmin: ".$ymin.PHP_EOL; echo "\tYmax: ".$ymax.PHP_EOL; echo 'FrameRate: '.$frameRate.PHP_EOL; echo 'FrameCount: '.$frameCount.PHP_EOL; echo join("",$tagsView); } // finish echo "Finished !!".PHP_EOL; exit(0); // class-library class LIB{ // type:-- function data2img($contents){ return substr($contents, 4, strlen($contents)-4); } // type:20 gif function data2gif_rgb($contents){ $image_id = ord(substr($contents, 0, 2)); $compossed = ord(substr($contents, 2, 1)); $width = substr($contents, 3, 2); $height = substr($contents, 5, 2); //$uncomp = gzuncompress(substr($contents, 8, strlen($contents)-8)); //color-table $colorTable = ""; $offset = ""; //data Bitmap-Format(compossed)==3 only if($compossed==3){ $colortable_num = ord(substr($contents, 7, 1)); $cnt = (($colortable_num+1)*3); for($i=0;$i<=255;$i++){ if($i<=$colortable_num){ $colorTable.= substr($contents, 8+($i*3), 3); } else{ $colorTable.= pack("h",0).pack("h",0).pack("h",0); } } //$colorTable = substr($contents, 8, $cnt); $cnt+=9; $pixeldata = substr($contents, $cnt, strlen($contents) - $cnt); } else{ $colortable_num = 0; $cnt = 7; //$pixeldata = gzuncompress(substr($contents, $cnt, strlen($contents) - $cnt)); $pixeldata = substr($contents, $cnt, strlen($contents) - $cnt); } // Header -- $data = "GIF"."87a"; $data.= pack("v",ord($width)).pack("v",ord($height)); $data.= pack("h","1");//Global Color Table Flag(1 Bit) $data.= pack("h","7").pack("h","7").pack("h","7");//Color Resolution(3 Bits) $data.= pack("h","0");//Sort Flag(1 Bit) $data.= pack("h","7").pack("h","7").pack("h","7");//Size of Global Color Table(3 Bits) $data.= pack("h",0);//Background Color Index(1 Byte) $data.= pack("h",0);//Pixel Aspect Ratio(1 Byte) $data.= $colorTable; // Image -- $data.= pack("n",0x2C);//Image Separator(1B) $data.= pack("v",0).pack("v",0);//Image Left,Top Position(2B,2B) $data.= pack("v",ord($width)).pack("v",ord($height));//Image Width,Height(2B,2B) $data.= pack("h",0);//Local Color Table Flag(1b) $data.= pack("h",0);//Interlace Flag((1b) $data.= pack("h",0);//Sort Flag(1b) $data.= pack("v",0);//Reserved(2b) //$data.= pack("h",0).pack("h",0).pack("h",0);//Size of Local Color Table(3b)-- $data.= pack("h",0);//LZW Minimum Code Side(1 Byte) //$data.= pack("h",strlen($pixeldata));//Block Size(1 Byte) $data.= pack("h",0);//Block Size(1 Byte) $data.= $pixeldata; //$data.= pack("n",0x00);//image-end //Tailer $data.= pack("n",0x3B);//end return $data; } // type:20 gif function data2gif_rgba($contents){ return substr($contents, 6, strlen($contents)-6); } // type:21 jpeg2 function data2jpeg($contents){ $offset = 4; return substr($contents, $offset, strlen($contents)-$offset); } //type 20 -> png function data2png($contents){ $image_id = ord(substr($contents, 0, 2)); $compossed = ord(substr($contents, 2, 1)); $width = substr($contents, 3, 2); $height = substr($contents, 5, 2); $colortable_num = ord(substr($contents, 7, 1)); $cnt = (($colortable_num+1)*3); $colorTable = substr($contents, 8, $cnt); $cnt+=9; $pixeldata = substr($contents, $cnt, strlen($contents) - $cnt); //-- $data = pack("n",0x89).pack("n",0x50).pack("n",0x4E).pack("n",0x47); $data.= pack("n",0x0D).pack("n",0x0A).pack("n",0x1A).pack("n",0x0A); $dataPart = substr($contents, 8, strlen($contents)-8); $data.= pack("h",strlen($dataPart)); $data.= pack("h","aaaa"); $data.= $dataPart; $data.= ""; return $data; } }

BitReader.php

今回もyoyaさんのソースを流用してバイナリ読み込みを行なってます。 <?php class BitReader { var $_data; // input_data var $_byte_offset; var $_bit_offset; function input($data) { $this->_data = $data; $this->_byte_offset = 0; $this->_bit_offset = 0; } function byteAlign() { if ($this->_bit_offset > 0) { $this->_byte_offset ++; $this->_bit_offset = 0; } } function getData($length=1) { $this->byteAlign(); $data = substr($this->_data, $this->_byte_offset, $length); $data_len = strlen($data); $this->_byte_offset += $data_len; return $data; } function getUI8() { $this->byteAlign(); $value = @ord($this->_data{$this->_byte_offset}); $this->_byte_offset += 1; return $value; } function getUI16LE() { $this->byteAlign(); $ret = @unpack('v', substr($this->_data, $this->_byte_offset, 2)); $this->_byte_offset += 2; return $ret[1]; } function getUI32LE() { $this->byteAlign(); $ret = @unpack('V', substr($this->_data, $this->_byte_offset, 4)); $this->_byte_offset += 4; return $ret[1]; } function getUIBit() { $value = @ord($this->_data{$this->_byte_offset}); $value = 1 & ($value >> (7 - $this->_bit_offset)); $this->_bit_offset ++; if (8 <= $this->_bit_offset) { $this->_byte_offset++; $this->_bit_offset = 0; } return $value; } function getUIBits($width) { $value = 0; for ($i = 0 ; $i < $width ; $i++) { $value <<= 1; $value |= $this->getUIBit(); } return $value; } function getImage() { $this->byteAlign(); $ret = @unpack('v', substr($this->_data, $this->_byte_offset, 2)); //$this->_byte_offset += 2; //return $this->_data; //return join(",", $ret); return $this->_byte_offset; //return $ret[1]; } }

実行

jpegデータの格納されているSWFファイルを利用してください。 ※GIF、PNGは無視されます。 $ php getImage.php sample.swf 上記を実行すると「output」というフォルダを自動で作成して、実行IDフォルダ、対象SWFファイルのコピー そして内部に格納されているJPEGデータが存在する分だけ作成されます。 output/ 20160505000000/ sample.swf img_1.jpg img_2.jpg とりあえず、Jpegは簡単だということはわかりましたが、GIF、とPNGはかなりの難題であることも同時に理解できました。 しばらくは画像フォーマットのSDKとにらめっこしなければいけないようです・・・orz

参考リンク

SWFバイナリ編集のススメ第三回 (JPEG)

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。