How To Decompile Actionscript Code In Swf

Author: xy7[80sec]
EMail: xy7#80sec.com/[email protected]
Site: http://www.80sec.com
Date: 2009-7-27

[ 目录 ]
0x00 前言
0x01 swf 文件头分析
0x02 swf 文件结构
0x03 swf tag类型
0x04 寻找Actionscript代码
0x05 还原带有变量的AS代码段
0x06 总结
0x07 代码示例

0x00 前言
随着Flash的应用越来越广泛,针对Flash的安全性问题也被人们所重视。通过提取Flash文件中的AS代码,可以进行恶意行为检测(网马分析),代码安全性分析(HP的SWFSCAN)或者针对AS代码中出现的URL进行进一步检测分析(APPSCAN)。本文只是一篇工作笔记,通过实现一个简单的Swf Decompiler来提取SWF文件中的AS代码。

0x01 swf 文件头分析
首先新建一个flash文件,默认大小,背景,无任何其他元素,只添加一个geturl函数,里面随便写上一个链接http://1.1.1.1。这个新创建的flash文件就作为我们的分析对象。
SWF文件头格式如下:
SWF File Header
Field Type Comment
Signature UI8 Signature byte:
“F” indicates uncompressed
“C” indicates compressed (SWF 6 and later only)
Signature UI8 Signature byte always “W”
Signature UI8 Signature byte always “S”
Version UI8 Single byte file version (for example, 0x06 for SWF 6)
FileLength UI32 Length of entire file in bytes
FrameSize RECT Frame size in twips
FrameRate UI16 Frame delay in 8.8 fixed number of frames per second
FrameCount UI16 Total number of frames in file
UE打开新建的flash文件,首先读前三个字节,46 57 53=FWS,说明该文件是未经过zlib压缩。
下一个字节是代表flash的版本,0a=10。
继续后四个字节代表flash文件的大小4E 05 00 00 ,这里需要转成大尾存储0000054e=1385bytes。
接下来是flash的尺寸,这里定义了一个RECT结构用来存储这些数据。
RECT结构如下:
RECT
Field Type Comment
Nbits UB[5] Bits in each rect value field
Xmin SB[Nbits] x minimum position for rect
Xmax SB[Nbits] x maximum position for rect
Ymin SB[Nbits] y minimum position for rect
Ymax SB[Nbits] y maximum position for rect
继续往后读1个字节78,转换成2进制补齐8位78=01111000,根据RECT的格式取前五位01111=15,表示剩下的数据要按每15位进行划分,由于还剩下4组数据要表示,所以需要60位,60位至少需要读8个字节,所以RECT结构需要的字节数为00000008h: 78 00 05 5F 00 00 0F A0 00,都转为2进制进行15位分割,最后不足15位的舍去,最后得出的结果为 twip(20twip=1像素),所以最后输入应该是这样:
01111
000000000000000
010101011111000
000000000000000
001111101000000
RECT binary value:0
RECT binary value:550
RECT binary value:0
RECT binary value:400
接下来2个字节代表帧速 00 18 =>18 00 =>24
最后2个字节代表帧数 01 00 =>00 01 => 1
至此,swf文件头就结束了。

0x02 swf 文件结构
Swf文件主体是由一连串的tag组成,最终以一个end tag为结尾。大概结构如下:
[文件头]—[ FileAttributes]—[tag]—[tag]…[end tag]
| |
[tag type] [tag type]
| |
[tag data] [tag data]

0x03 swf tag类型
根据tag头可以将tag分为短tag和长tag,具体格式如下:
RECORDHEADER (short)
Field Type Comment
TagCodeAndLength UI16 Upper 10 bits: tag type
Lower 6 bits: tag length
RECORDHEADER (long)
Field Type Comment
TagCodeAndLength UI16 Tag type and length of 0x3F
Packed together as in short header
Length SI32 Length of tag
也就是说高10位都代表着tag类型,后6位如果长度大于等于0x3f则为长tag,其余的则是短tag。
按tag类型来分,tag也可分做2类:Definition tags和Control tags。其中Definition tags主要包含了flash中的文本,声音,图像等资源,而Control tags则包含了一些文件的处理流程等控制元素。
既然已经分清楚了swf tag类型,那么就可以进一步分析tag以寻找我们需要的AS代码。
文件头已经分析完毕,接下来就来分析第一个tag:FileAttributes。继续上面的例子,到了分析tag的时候依次读2个字节:44 11 =>11 44 => 0001000101000100 => 0001000101(高10位) => 69,在swf文件格式中查找Tag type = 69,正是FileAttributes,接着低6位代表tag长度:000100 =>4,向后再读4个字节:10 00 00 00 => 00 00 00 10 =>16 => 00010000,这段数据就是FileAttributes tag的数据内容了,FileAttributes tag到此结束。数据可以对照着FileAttributes数据结构来看,结构比较长就不贴出来了,其中第4位是1,表明这个 flash中含有matadata,继续往后读2个字节 7F 13,按位取高10位=77。
Metadata
Field Type Comment
Header RECORDHEADER Tag type = 77
Metadata STRING XML Metadata
低6位全为1,所以说matadata是第一个长tag,长tag需要4个字节来表示数据长度,这些数据对实际分析没有什么作用,所以分析出数据长度就可以跳过了。

0x04寻找Actionscript代码
通过前面2个tag的分析,已经能够清楚的知道swf tag的固定格式以及取数据的方法了,那么我们定义的AS2中geturl函数到底存在哪里呢?继续读下2个字节:3f 03 => 03 3f ,取高10位0000001100 =>12,这个12是什么意思呢,查swf文档:
DoAction
Field Type Comment
Header RECORDHEADER Tag type = 12
Actions ACTIONRECORD [zero or more] List of actions to perform (see
following table, ActionRecord)
ActionEndFlag UI8 = 0 Always set to 0
注意ACTIONRECORD
Field Type Comment
ActionCode UI8 An action code
Length If code >= 0x80, UI16 The number of bytes in the
ACTIONRECORDHEADER, not
counting the ActionCode
现在可以确定ActionCode是包含在Actions ACTIONRECORD中,Actions ACTIONRECORD又是属于DoAction。判断低6位得知DoAction是长tag,继续读后面4个字节来确定DoAction的数据长度:1A 00 00 00 =>26。至此就可以判定我们之前建立的AS代码就包含在这26个字节中。接下来根据ACTIONRECORD的定义,1个字节代表ActionCode,继续读:83,查找swf文档:
Field Type Comment
ActionGetURL ACTIONRECORDHEADER ActionCode = 0x83
UrlString STRING Target URL string
TargetString STRING Target string
找到了我们定义的geturl函数,所以继续读下面2个字节16 00 =>00 16=>22,那么这22个字节就是geturl函数里的参数了,提取出来还原一下看看:
687474703a2f2f312e312e312e31005f626c616e6b00
http://1.1.1.1 _blank
最终通过对swf文件格式的分析,还原出了一段最简单的AS代码:

_getURL('http://1.1.1.1' '_blank')
_end

0x05 还原带有变量的AS代码段
假设有如下代码段:

on (release)
{
var host = "http://10.1.5.16";
var url = host + "/sql/index.php";
getURL(url, "_blank");
}

Geturl获取的是通过变量拼接组成的url。首先看一段数据:
88 32 00 05 00 68 6f 73 74 00 68 74 74 70 3a 2f 2f 31 30 2e 31 2e 35 2e 31 36 00 75 72 6c 00 2f 73 71 6c 2f 69 6e 64 65 78 2e 70 68 70 00 5f 62 6c 61 6e 6b 00
第一个字节:0x88
ActionConstantPool
ActionConstantPool creates a new constant pool, and replaces the old constant pool if one
already exists.
Field Type Comment
ActionConstantPool ACTIONRECORDHEADER ActionCode = 0x88
Count UI16 Number of constants to follow
ConstantPool STRING[Count] String constants
所有的变量会被预先存放在ConstantPool中,上面的数据还原后如下:
_constantPool “host” “http://10.1.5.16” “url” “/sql/index.php” “_blank”
接着往下读一个字节:96 = ActionPush,后两个字节04 00 =>00 04 一共要往后再读4个字节的数据,合并起来就是:
96 04 00 08 00 08 01
08代表ActionPush结构中的type 00代表ConstantPool中的第一个算素,01代表ConstantPool中第2个元素,还原就是:
_push “host” http://10.1.5.16
读下一个字节:3c
Field Type Comment
ActionDefineLocal ACTIONRECORDHEADER ActionCode = 0x3C
赋值操作:var host = http://10.1.5.16
继续读,push操作跟前面一样:
96 04 00 08 02 08 00
还原:
_push “url” “host”
继续读:1c
ActionGetVariable ACTIONRECORDHEADER ActionCode = 0x1C
url = host
96 02 00 08 03
_push “/sql/index.php”
47
ActionAdd2 ACTIONRECORDHEADER ActionCode = 0x47
url = host+”/sql/index.php”
3c
ActionDefineLocal
Var url = host+”/sql/index.php”
//96 02 00 08 02
_push “url”
//1c
_getVariable
//96 02 00 08 04
_push “_blank”
获取url _blank
//9a 01 00 00
_getURL2 flag 0
调用geturl2函数……
其中包含的一些actioncode具体可以查看swf文档,有很详细的操作说明,篇幅问题这里就不贴出来了。

0x06 总结
终于到总结了……呃,这篇文章只是比较简单的描述如何通过静态分析swf文件格式来提取出AS代码,举的例子也都比较简单,但实际分析过程中程序还需要考虑很多因素,包括载入swf tag字典,判断哪些tag是包含AS代码的,那些是Definition tags可以跳过的,当然,如果是想开发一个完整的SWFDecompiler,那么所有的tag都需要逐个分析。工作笔记难免有没说清楚和理解错误的地方,还望多多包涵。
0x07 代码示例

#!/usr/bin/php
<?php
/*--------------------------
Author: xy7[80sec]
EMail: xy7#80sec.com/[email protected]
Site: http://www.80sec.com
Date: 2009-5-31
---------------------------*/
if ($argc < 2) {
print_r('
Usage: php '.$argv[0].' filename
Example:
php '.$argv[0].' hello.swf
');
}
error_reporting(7);
ini_set('max_execution_time', 0);
$swffile = $argv[1];
function SingleHexToDec($hex)
{
$v=Ord($hex);
if(47<$v&&$v<58)
return $v-48;
if(64<$v&&$v<71)
return $v-65+10;
if(96<$v&&$v<103)
return $v-97+10;
}
function UnsetFromHexString($str)
{
if(!$str)
return false;
$code="";
for($i=0;$i<strlen($str);$i+=2)
{
$code.=chr(SingleHexToDec(substr($str,$i,1))*16+SingleHexToDec(substr($str,$i+1,1)));
}
return $code;
}
function openswfflie(){
global $swffile;
[email protected]($swffile,"r");
if($fp){
echo "OUTPUT:".$swffile."------------OPENED \r\n\r\n";
$sign=fread($fp,3);
echo "Output:".bin2hex($sign)."\r\n";
if($sign!="FWS" && $sign!="CWS"){
echo "OUTPUT:".$swffile."is not a SWF file!\r\n";
}
if (substr($sign,0,1)=="F")
echo "Indicates uncompressed\r\n";
else
echo "Indicates compressed";
$version=bin2hex(fread($fp,1));
echo "OUTPUT:".$version."\r\n";
echo "version is ".hexdec($version)."\r\n";
//读文件大小
for ($i=0;$i<4;$i++){
$size = fread($fp,1);
echo "Output:File size read 1 byte:".bin2hex($size)."\r\n";
$nsize = ord($size);
//echo "Output:Partial size is".($nsize<<(8*$i))."\r\n";
$totalsize +=($nsize<<(8*$i));
}
echo "Swf size is:".$totalsize."bytes\r\n";
//RECT
$firstbyte = fread($fp,1);
$newfirstbyte = bin2hex($firstbyte);
$change_newfirstbyte = decbin(hexdec($newfirstbyte));
if (strlen($change_newfirstbyte)!=8){
$new_change_newfirstbyte = str_pad($change_newfirstbyte,8,"0",STR_PAD_LEFT);
}
//echo $new_change_newfirstbyte;
$fieldsize = ord($firstbyte)>>3;
echo "RECT field size:".$fieldsize."\r\n";
$needbytes = ceil($fieldsize*4/8)+1;
//echo $needbytes;
for($i=0;$i<8;$i++){
$nextbytes = bin2hex(fread($fp,1));
echo $nextbytes."\r\n";
$change_bin = decbin(hexdec($nextbytes));
//echo $change_bin."<br>";
if (strlen($change_bin)<=8)
$new_change_bin = str_pad($change_bin,8,"0",STR_PAD_LEFT);
//echo $new_change_bin."<br>";
$bytes .=$new_change_bin;
}
//echo $bytes;
$allbytes = $new_change_newfirstbyte.$bytes;
//echo $allbytes."<br>";
$thefirstfivebits = substr($allbytes,0,5);
echo $thefirstfivebits."\r\n";
$next1 = substr($allbytes,6,$fieldsize);
echo $next1."\r\n";
$next2 = substr($allbytes,20,$fieldsize);
echo $next2."\r\n";
$next3 = substr($allbytes,35,$fieldsize);
echo $next3."\r\n";
$next4 = substr($allbytes,50,$fieldsize);
echo $next4."\r\n";
echo "RECT binary value:".(bindec($next1)/20)."\r\n";
echo "RECT binary value:".(bindec($next2)/20)."\r\n";
echo "RECT binary value:".(bindec($next3)/20)."\r\n";
echo "RECT binary value:".(bindec($next4)/20)."\r\n";
//Frame rate
for($i=0;$i<2;$i++){
$framerate = ord(fread($fp,1));
echo $framerate."\r\n";
}
//Frame
$newframe = 0;
for($i=0;$i<2;$i++){
$frame = ord(fread($fp,1));
$newframe += ($frame<<(8*$i));
//echo $newframe;
}
echo "Frames:".$newframe."\r\n";
//---------------------FileAttribute-----------------------//
for($i=0;$i<2;$i++){
$file = ord(fread($fp,1));
$newfile += ($file<<(8*$i));
}
echo $newfile."\r\n";
echo $high10 = $newfile>>6;
$low6 = substr(decbin($newfile),-6);
//echo decbin($low6)."\r\n";
if ($high10 == 69){
echo "Found FileAttribute Tag!\r\n";
}
echo "FileAttributeDataBytes is:".bindec($low6)."\r\n";
//---------------------FileAttribute-----------------------//
//---------------------FileAttributeData-------------------//
for($i=0;$i<4;$i++){
$data = ord(fread($fp,1));
$newdata += ($data<<(8*$i));
}
$newdata = str_pad(decbin($newdata),8,"0",STR_PAD_LEFT);
echo $newdata."\r\n";
if (substr($newdata ,3,1) == 1){
echo "Found Metadata!\r\n";
}else{
echo "Not found Metadata!\r\n";
}
//---------------------FileAttributeData-------------------//
//---------------------HasMetadata-------------------//
for($i=0;$i<2;$i++){
$metatag = ord(fread($fp,1));
$newmetatag += ($metatag<<(8*$i));
}
$high10 = $newmetatag>>6;
if ($high10 == 77){
echo "Metadata Type:".$high10."\r\n";
}
$bin = decbin($newmetatag);
$low6 = substr($bin,-6);
if ($low6 == 111111){
echo "Long Tag - Need 4 bytes\r\n";
}else{
echo "Tag type wrong\r\n";
}
for ($i=0;$i<4;$i++){
$metalength = ord(fread($fp,1));
$newmetalength += ($metalength<<(8*$i));
}
echo "Damn Metadata Length is ".$newmetalength."\r\n";
//---------------------HasMetadata-------------------//
//----------------------SetBackgroundColor------------------//
fseek($fp,$newmetalength,SEEK_CUR);
for ($i=0;$i<2;$i++){
$tag = ord(fread($fp,1));
$newtag += ($tag<<(8*$i));
}
echo $high10 = $newtag>>6;
if ($high10 == 9){
echo "Found SetBackgroundColor!\r\n";
}
for ($i=0;$i<3;$i++){
$color = bin2hex(fread($fp,1));
echo "".$color."\r\n";
}
//----------------------SetBackgroundColor------------------//
//----------------------DoAction------------------//
for ($i=0;$i<2;$i++){
$nexttag = bin2hex(fread($fp,1));
echo $nexttag;
$newnexttag += ($nexttag<<(8*$i));
}
echo $high10 = $newnexttag>>6;
if ($high10 == 12){
echo "Found First DoAction!\r\n";
}else{
for ($i=0;$i<2;$i++){
$nexttag = ord(fread($fp,1));
$newnexttag += ($nexttag<<(8*$i));
}
echo ($high10 = $newnexttag>>6)."\r\n";
}
for ($i=0;$i<4;$i++){
$actionlength = ord(fread($fp,1));
$length += ($actionlength<<(8*$i));
}
echo "First DoAction Length is ".$length."\r\n";
$actioncode = bin2hex(fread($fp,1));
echo "0x".$actioncode."\r\n";
if ($actioncode == 83){
echo "_geturl\r\n";
}elseif($actioncode == "9a"){
echo "_geturl2\r\n";
}else{
echo "NULL";
}
for ($i=0;$i<2;$i++){
$q = ord(fread($fp,1));
$len += ($q<<(8*$i));
}
for ($i=0;$i<$len;$i++){
$c = fread($fp,1);
$hexcontent .= bin2hex($c);
//$hexcontent.=$hexcontent;
}
echo "[Action in Frame 1]\r\n";
echo $hexcontent."\r\n";
echo UnsetFromHexString($hexcontent);
}
}
openswfflie();
//FindGoOn();
?>

相关日志

抢楼还有机会... 抢座Rss 2.0或者 Trackback

发表评论