Swf Decrypt详解
http://www.2cto.com/Article/201507/414477.html
攻击在持续,攻击的技术在演进。防御者需要持续的跟进研究和投入。最近Flash 0day频繁出现,将我们更多的目光集中到flash上。
Flash作为脚本语言,可以编译,不少的恶意flash文件通过各种加密、混淆、动态解密来对抗人工分析和引擎检测。通过动态执行,常见的动态解密的swf都可以解决。不过遇到很多条件判断、环境依赖的限制,往往又无法稳定dump出解密的swf文件和关键的数据(shellcode)。如果能在静态的方法对混淆、加密的swf文件直接进行反混淆、反编译,则将能有效提升检测效果。
市面上可以对swf进行加密的软件很多,如swfencrypt、doswf、secureswf、dcomsoft等,我们这次对doswf以及secureswf进行简单的分析,与各位小伙伴分享。
恶意的SWF常常通过doswf加密和secure swf混淆,真正利用的部分被加密、混淆,通过研究doswf的加密方式以及secureswf的混淆方式,可以直接通过静态的方法进行相应的解密和反混淆,直接检测最核心的恶意代码部分,有效提升引擎检测率。(用户可使用文件B超系统 http://b-chao.com 和云盾-星云APT检测产品享受到我们的技术成果)
doswf
doswf是国人开发的一款flash加密混淆软件,从官网可以看出该软件已经停止了开发,当前最新版本为5.4.3。
我们对比了版本4.9.7和最新版本之间加密效果的区别,发现两者相差不大,在介绍完整体的加密流程之后,再将它们之间的差异展示出来。
Crypt Flow
经过doswf加密的swf文件,都存在DefineBinaryData的tag,因为原始swf文件将会存放在这个二进制数据中,二进制数据经过解密后通过loadbytes进行加载。
这个二进制数据的头部结构如下(B标示字节):
原始swf文件从起始以offset为步长,每次对block_sz的块进行处理,块中的数据并不是逐一字节处理的,而是依据一个特定值跳跃处理,在整个文件处理完成后进行压缩,压缩后的数据再加上上面的头部信息就形成了完整的二进制数据了。
上面的描述简化了一些不重要的因素,如doswf加密时还会嵌入两个额外的swf文件,所以当解密二进制数据后,会发现解密后的数据中包含三个swf,这时只要根据硬编码特征”FWS”、”CWS”就可以将所有包含的swf文件完整dump出来。
关键解密代码:
版本4.9.7和最新版本解密代码的差异:
可以看到,差异在于头字节结构的几个值得获取、大小端以及跳跃值的不同。
我们拿CVE-2012-0779样本(md5:2b98d285c8b581855d59ac368956ee78)进行测试,这是一个doswf4.9.7版本加壳的样本:
检测dump出的数据,可以发现里面包含有三个swf文件。
Decrypt Code
目前放出针对旧版的解密代码(新版自行解决,根据前面的提示很容易更改),仅支持解压后的doswf解密
/*
* Decoder for DoSWF.
* This extracts the DefineBinary tag from argv[1] to argv[2]. To un-zlib that using python:
*
* python -u -c \
* "import sys, zlib; sys.stdout.write(zlib.decompress(sys.stdin.read()))" \
* < definebinary.z > definebinary.txt
*
* Then trim off the first 6 bytes to get to the SWF header.
*/
#include
#include
#include
#include
#include
#pragma warning(disable:4996)
#ifdef _DEBUG
#define DBGBREAK() __debugbreak()
#else
#define DBGBREAK()
#endif
#define LOG(...) fprintf(stderr, __VA_ARGS__)
//#define DIE(...) (LOG(__VA_ARGS__), DBGBREAK(), exit(1), 0)
#define DIE(...) (LOG(__VA_ARGS__), exit(1),1)
typedef unsigned char byte;
void decompress_string(byte* data, int length, byte *out, int *outlength, int avaout);
void handle_DefineBinary(FILE *fi, FILE *fo, int taglen, byte *buffer)
{
LOG("* dumping DefineBinary section\n");
byte *end = buffer + taglen;
byte *ptr = buffer, *old;
#define GET(type) ( \
((ptr+sizeof(type) >= end) ? DIE("eof\n") : 0), \
old = ptr, \
ptr += sizeof(type), \
*(type *)old)
short tag = GET(short);
int reserved = GET(int);
LOG("2* dumping DefineBinary section,tag=%u,reserved=%d\n", tag, reserved);
byte block_size = GET(byte) - 1;
byte key = GET(byte) - 1;
int offset = __bswap_32 (GET(int)) - 2;
int length = __bswap_32 (GET(int)) - 2;
byte *data = buffer+taglen-length;
LOG("3* dumping DefineBinary section, block_size=%d,offset=%d,data[0]=%x\n", block_size, offset,data[0]);
for (int count = 0; count < length;) {
for (int i = 0; i < block_size; i += 5) {
data[count] = data[count] ^ key;
++count;
if (count >= length)
break;
}
count = count + offset;
}
LOG("4* dumping DefineBinary section\n");
//fwrite(data, 1, length, fo);
LOG("5* dumping DefineBinary section\n");
int outl = length*5;
byte *outd = new byte[outl];
decompress_string(data, length, outd, &outl, outl);
fwrite(outd, 1, outl, fo);
}
void decompress_string(byte* data, int length, byte *out, int *outlength, int avaout)
{
z_stream zs; // z_stream is zlib's control structure
memset(&zs, 0, sizeof(zs));
if (inflateInit(&zs) != Z_OK)
LOG("init error!\n");
zs.next_in = data;
zs.avail_in = length;
int ret;
zs.next_out = out;
zs.avail_out = avaout;
ret = inflate(&zs, 0);
inflateEnd(&zs);
if (ret != Z_STREAM_END) { // an error occurred that was not EOF
LOG("inflate end error,ret=%d!\n", ret);
}
*outlength = zs.total_out;
return;
}
int main(int argc, char *argv[])
{
if (argc != 3)
DIE("syntax: UndoSWF \n");
FILE *fi = fopen(argv[1], "rb");
if (!fi)
DIE("can't open %s\n", argv[1]);
FILE *fo = fopen(argv[2], "wb");
if (!fo)
DIE("can't create %s\n", argv[2]);
char header[9];
if (fread(header, 1, 9, fi) != 9)
DIE("can't read header\n");
if (memcmp(header, "FWS", 3))
DIE("invalid header\n");
int rectbits = header[8] >> 3;
int hdrbits = 8*8 + 5+rectbits*4;
int hdrbytes = (hdrbits+7)/8 + 4;
LOG("header size: %d\n", hdrbytes);
if (fseek(fi, hdrbytes-9, SEEK_CUR) < 0)
DIE("can't skip %d bytes\n", hdrbytes-9);
for (;;) {
unsigned short hdr;
if (fread(&hdr, 1, 2, fi) != 2)
DIE("can't read tag hdr\n");
if (hdr == 0) break;
int taglen = hdr & 0x3f;
int tagtype = hdr >> 6;
bool longtag = taglen == 0x3f;
if (longtag) {
if (fread(&taglen, 1, 4, fi) != 4)
DIE("can't read tag len\n");
}
LOG("tag type=0x%02x len=0x%x %s\n", tagtype, taglen, longtag ? "long" : "short");
byte *buffer = new byte[taglen];
if (fread(buffer, 1, taglen, fi) != taglen)
DIE("can't read tag data\n");
if (tagtype == 0x57) handle_DefineBinary(fi, fo, taglen, buffer);
delete buffer;
}
fclose(fi);
fclose(fo);
return 0;
}
测试的样本由MD5: 2b98d285c8b581855d59ac368956ee78 解压之后的样本。
SecureSwf
Secureswf加密软件,号称break掉所有的flash反编译软件,支持对资源加密、 支持对as code级别做混淆,通过添加各种jump 、反复跳转指令、垃圾指令填充 阻止反编译软件反编译 增加分析人员分析出具体的actionscript原理难度。
之前拿到CVE-2015-3105 样本就是用secure swf混淆过的,刚好可以分析下
MD5: 58d1022923950ad1452c72f46b1ee3d0。
DUS function
以这个dus函数为例做反混淆:
对应的abc code:
code
getlocal_0
pushscope
pushbyte 0
newfunction 30
pop
jump ofs0017
convert_i
declocal 4
urshift
rshift
declocal_i 2
declocal 2
astypelate
newactivation
increment
ofs0017:getlocal 4
iffalse ofs0025
decrement_i
increment_i
increment_i
increment_i
pushbyte 7
multiply_i
decrement_i
ofs0025:setlocal_3
getlocal 5
iftrue ofs0065
getlocal_1
getlocal 5
iftrue ofs00ac
pushbyte 4
getlocal 5
iffalse ofs005a
increment_i
pushbyte 52
subtract_i
pushbyte 114
add_i
pushbyte 49
jump ofs0055
nextvalue
setlocal_2
setlocal_3
kill 3
declocal 4
setlocal 4
declocal 4
getlocal_3
istypelate
ofs0055:add_i
decrement_i
pushbyte 15
subtract_i
ofs005a:modulo
getlocal 4
iftrue ofs00ab
iffalse ofs0065
ofs0065:getlocal_1
getlocal 4
iftrue ofs00ac
getlex Qname(PrivateNamespace("dphgjxukp"),"var_5")
pushbyte 8
getlocal 4
iffalse ofs0092
pushbyte 39
multiply_i
jump ofs008b
inclocal 2
inclocal_i 2
declocal 3
getlocal_3
declocal_i 2
convert_i
multiply_i
inclocal_i 4
increment_i
ofs008b:negate_i
negate_i
pushbyte 115
subtract_i
increment_i
decrement_i
ofs0092:add
subtract
getlocal 5
not
iffalse ofs00ab
pushbyte 4
getlocal 4
iffalse ofs00aa
increment_i
pushbyte 35
multiply_i
pushbyte 29
subtract_i
ofs00aa:divide
ofs00ab:convert_u
ofs00ac:setlocal_3
getlocal 4
iftrue ofs00b9
getlex Qname(PrivateNamespace("dphgjxukp"),"var_1")
getlocal_3
getlocal_2
setproperty MultinameL([PrivateNamespace("dphgjxukp"),ProtectedNamespace("dphgjxukp"),StaticProtectedNs("dphgjxukp"),StaticProtectedNs("flash.display:Sprite"),StaticProtectedNs("flash.display:DisplayObjectContainer"),StaticProtectedNs("flash.display:InteractiveObject"),StaticProtectedNs("flash.display:DisplayObject"),StaticProtectedNs("flash.events:EventDispatcher"),StaticProtectedNs("Object"),PackageNamespace("flash.display"),PackageNamespace("flash.text"),PackageNamespace("flash.utils"),PackageNamespace("flash.net"),PackageNamespace("flash.system"),PackageNamespace(""),PackageInternalNs(""),PrivateNamespace("FilePrivateNS:dphgjxukp"),Namespace("http://adobe.com/AS3/2006/builtin")])
ofs00b9:returnvoid
returnvoid
Type1 jump
getlocal_0
pushscope
pushbyte 0
newfunction 30
pop
jump ofs0017
convert_i
declocal 4
urshift
rshift
declocal_i 2
declocal 2
astypelate
newactivation
increment
ofs0017:getlocal 4
jump之间都是垃圾指令 可以直接改成
getlocal_0
pushscope
pushbyte 0
newfunction 30
pop
getlocal 4
把这段abccode中所有jump之间的直接去掉。
Type2 getlocal n
混淆指令里面充斥着getlocal n指令 获取寄存器的value 并PUSH STACK,紧接着跟着判断,如:
getlocal 4 // reg4.value push stack
iffalse ofs0013 // pop reg4.value judge this value if is false
pushbyte 0
newfunction 30
pop
getlocal 4
iffalse ofs0013
increment_i
increment_i
pushbyte 7
multiply_i
decrement_i
ofs0013:setlocal_3
local4 初始化值应该为0 ,则可以直接简化成
pushbyte 0
newfunction 30
pop
setlocal_3
后面以此类似:
getlocal 5
iftrue ofs0053
local5 为0,则不会跳转,直接简化这两条指令。
所以没有经过setlocal n的n对应的getlocal n获取的值应该都是0,带着这样的设定来精简指令就很容易了。
getlocal 13
not
iffalse ofs00d9 //条件不成立 直接去掉这样的三条指令
getlocal 13
iftrue ofs00ed //条件不成立 直接去掉这样的2条指令
getlocal 12
iffalse ofs0159//条件成立 直接去掉当前2条指令到ofs0159 直接的所有指令(中间代码没有其他地方跳转过来)
Type3 garbage ins
getlocal_1
pushbyte 4
modulo
iffalse ofs0053
ofs0053:
local1 为0,modulo之后 0/4 为0 ,push stack,iffalse pop stack,条件成立,无意义片段。
还有一些:
decrement_i
increment_i
decrypt dus function
Rxgpittdkc function
在以这个函数为例,按照上面的步骤,先去除jump 垃圾指令,在去除getloca n之类的垃圾指令:
Dphgjxukp function
Type4 kill ins
getlocal_1 // reg1.value push stack
getlocal_0 // reg0.value push stack
getlocal_2 // reg2.value push stack
kill 1 //kill reg1.value
kill 0 //kill reg0.value
kill 2 //kill reg2.value
setlocal_2 //pop stack,set value to reg2.value
setlocal_0 //pop stack,set value to reg0.value
setlocal_1 //pop stack,set value to reg1.value
这样的指令集也是垃圾指令。
Type5 as function flow change
Secure Swf会对简单的as 函数调用从abc 层面流程进行混乱。比如这个dphgjxukp 函数 还原之后真正的代码如下:
niraodwx();
init();
return;
解混淆之后:
可以看到jpexs对这样的反编译是混乱的,可以把相应的跳转换成jump。比如getlocal_2 iffalse 换成jump ofs0044 ,以此类似,最终得到的反编译结果。
可以看到这个swf混淆的还是很容易解密出来的,更多的人工体力活可以通过编写自动化的脚本来实现。(有钱的主可以购买avs actionscript view的149USD的插件 直接反编译secureswf)。
Swf Decrypt详解的更多相关文章
- HTML加载FLASH(*.swf文件)详解
引言 在web项目中经常会遇到在线浏览word文档,通常解决方法将word转换成pdf,然后在线浏览,但是在实际实现过程中,由于阅读器的原因,用户可以直接下载该pdf,这显然不是我们想要的,通过网络搜 ...
- Fiddler界面详解
Statistics 页签 完整页签如下图: Statistics 页签显示当前用户选择的 Sessions 的汇总信息,包括:选择的 Sessions 总数.发送字节数.接收字节数.响应类型的汇总表 ...
- (转)Fiddler菜单栏详解
原文作者:子信风蓝蓝 传送门:http://www.cnblogs.com/chengchengla1990/p/5681775.html Statistics 页签 完整页签如下图: Statist ...
- [转载]Fiddler界面详解
转载地址:http://www.cnblogs.com/chengchengla1990/p/5681775.html Statistics 页签 完整页签如下图: Statistics 页签显示当前 ...
- 猫哥网络编程系列:详解 BAT 面试题
从产品上线前的接口开发和调试,到上线后的 bug 定位.性能优化,网络编程知识贯穿着一个互联网产品的整个生命周期.不论你是前后端的开发岗位,还是 SQA.运维等其他技术岗位,掌握网络编程知识均是岗位的 ...
- Nginx配置文件nginx.conf中文详解(转)
######Nginx配置文件nginx.conf中文详解##### #定义Nginx运行的用户和用户组 user www www; #nginx进程数,建议设置为等于CPU总核心数. worker_ ...
- 3.awk数组详解及企业实战案例
awk数组详解及企业实战案例 3.打印数组: [root@nfs-server test]# awk 'BEGIN{array[1]="zhurui";array[2]=" ...
- 服务器.htaccess 详解以及 .htaccess 参数说明(转载)
htaccess文件(或者”分布式配置文件”)提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录.作为用户,所能使用的命令受到限 ...
- Nginx配置文件详解
Nginx是一款面向性能设计的HTTP服务器,相较于Apache.lighttpd具有占有内存少,稳定性高等优势. ######Nginx配置文件nginx.conf中文详解##### #定义Ngin ...
随机推荐
- Android-Native-Server 启动和注册详细分析
Android-Native-Server 启动和注册详细分析 以mediaService为实例来讲解: mediaService的启动入口 是一个 传统的 main()函数 源码位置E:\ ...
- MapReduce 开发环境搭建(Eclipse\MyEclipse + Maven)
写在前面的话 可详细参考,一定得去看 HBase 开发环境搭建(Eclipse\MyEclipse + Maven) Zookeeper项目开发环境搭建(Eclipse\MyEclipse + Mav ...
- 打造无DLL版穿透防火墙Downloader
这份代码的思路来自于国外EES组织的Aphex.基本上所有的无DLL Download都是利用的这种方法.其实也就是用烂了的远程注入法.不过注入的对象不是一个DLL,而是本身的一个过程.下面是代码,由 ...
- BNUOJ 26475 Cookie Selection
LINK:BNUOJ 26475 Cookie Selection 题意: 你在不停的输入数字a1,a2,a3,......,ak,当你输入#时,就把已输入数字中的第k/2+1删除,然后剩下的数字又组 ...
- How To Use Logstash and Kibana To Centralize Logs On CentOS 6
原文链接:https://www.digitalocean.com/community/tutorials/how-to-use-logstash-and-kibana-to-centralize-l ...
- 友盟分享 -QQAPI- QQApi.m:250 param error: url is nil
有一个项目 需要用到友盟分享,点击分享内容,需要跳转到指定的url,不带参数的url非常好跳,也没什么问题,但是 带了参数之后:比如http://121.43.121.8:8080/tj/photo/ ...
- 作为平台的Windows PowerShell(二)
在此系列文章的前一篇,我们看到了怎样使用System.Management.Automation.PowerShell 类来在c#应用程序中运行PowerShell 命令.在那些例子中,我们创建的都是 ...
- 创建性能监视器(logman)
在本地计算机上抓取性能信息 Logman.exe create counter Perf-1Second -f bincirc -max 500 -c "\Processor(*)\% Pr ...
- 【转】C++及java在内存分配上的区别
转自:http://blog.csdn.net/qinghezhen/article/details/9116053 C++内存分配由五个部分组成:栈.堆.全局代码区.常量区.程序代码区.如下图所示: ...
- VS2005工程迁移到Eclipse CDT
原工程在VS2005下创建,后迁移到Eclipse CDT 3.3.2 + MingGW下,并增加makefile文件. 原VS2005下工程Sample,实现了对类SampleClass封装,生成S ...