我正在寻找一个从开发人员/工程师的角度解释问题的答案。究竟是什么被利用了,为什么它会起作用?
请注意,我不是在寻找有关如何利用的说明,也没有兴趣以任何方式复制它;我只是好奇它背后的技术背景。
一些相关链接:
Microsoft 安全公告 2757760
CVE-20012-4969,MITRE
CVE-20012-4969,NVD
CVE-20012-4969,CVE 详细信息
我正在寻找一个从开发人员/工程师的角度解释问题的答案。究竟是什么被利用了,为什么它会起作用?
请注意,我不是在寻找有关如何利用的说明,也没有兴趣以任何方式复制它;我只是好奇它背后的技术背景。
一些相关链接:
Microsoft 安全公告 2757760
CVE-20012-4969,MITRE
CVE-20012-4969,NVD
CVE-20012-4969,CVE 详细信息
CVE-2012-4969,又名最新的 IE 0-day,是一个基于 IE 渲染引擎中的 use-after-free 漏洞。当动态分配的内存块在被释放(即释放)后被使用时,就会发生释放后使用。可以通过创建内部结构包含指向敏感内存位置(例如堆栈或可执行堆块)的指针的情况来利用此类错误,这种情况会导致程序将 shellcode 复制到可执行区域中。
在这种情况下,问题出CMshtmlEd::Exec
在mshtml.dll
. CMshtmlEd
对象被意外释放,然后在释放操作后Exec
对其调用方法。
首先,我想介绍一些理论。如果您已经知道 use-after-free 的工作原理,请随意跳过。
在低层次上,一个类可以等同于一个包含其状态(例如字段、属性、内部变量等)和一组对其进行操作的函数的内存区域。这些函数实际上带有一个“隐藏”参数,它指向包含实例状态的内存区域。
例如(请原谅我可怕的伪 C++):
class Account
{
int balance = 0;
int transactionCount = 0;
void Account::AddBalance(int amount)
{
balance += amount;
transactionCount++;
}
void Account::SubtractBalance(int amount)
{
balance -= amount;
transactionCount++;
}
}
上面的内容实际上可以表示为:
private struct Account
{
int balance = 0;
int transactionCount = 0;
}
public void* Account_Create()
{
Account* account = (Account*)malloc(sizeof(Account));
account->balance = 0;
account->transactionCount = 0;
return (void*)account;
}
public void Account_Destroy(void* instance)
{
free(instance);
}
public void Account_AddBalance(void* instance, int amount)
{
((Account*)instance)->balance += amount;
((Account*)Account)->transactionCount++;
}
public void Account_SubtractBalance(void* instance, int amount)
{
((Account*)instance)->balance -= amount;
((Account*)instance)->transactionCount++;
}
public int Account_GetBalance(void* instance)
{
return ((Account*)instance)->balance;
}
public int Account_GetTransactionCount(void* instance)
{
return ((Account*)instance)->transactionCount;
}
我void*
用来演示参考的不透明性质,但这并不重要。关键是我们不希望任何人能够Account
手动更改结构,否则他们可以任意添加资金,或者在不增加交易计数器的情况下修改余额。
现在,想象我们做这样的事情:
void* myAccount = Account_Create();
Account_AddBalance(myAccount, 100);
Account_SubtractBalance(myAccount, 75);
// ...
Account_Destroy(myAccount);
// ...
if(Account_GetBalance(myAccount) > 1000) // <-- !!! use after free !!!
ApproveLoan();
现在,当我们到达Account_GetBalance
时,in 中的指针值myAccount
实际上指向了处于不确定状态的内存。现在,假设我们可以执行以下操作:
Account_Destroy
。Account_Destroy
但之前 执行任何操作Account_GetBalance
允许我们分配合理数量的内存,以及我们选择的内容。通常,这些调用是在不同的地方触发的,因此实现这一点并不难。现在,会发生以下情况:
Account_Create
分配一个 8 字节的内存块(每个字段 4 个字节)并返回一个指向它的指针。该指针现在存储在myAccount
变量中。Account_Destroy
释放内存。该myAccount
变量仍然指向相同的内存地址。39 05 00 00 01 00 00 00
. 此模式与balance = 1337
和相关transactionCount = 1
。由于旧内存块现在被标记为空闲,内存管理器很可能会将我们的新内存写入旧内存块。Account_GetBalance
被调用,期望指向一个Account
结构。实际上,它指向我们被覆盖的内存块,导致我们的余额实际上是1337,所以贷款被批准了!当然,这都是一种简化,真正的类会创建更迟钝和复杂的代码。关键是类实例实际上只是一个指向数据块的指针,类方法与任何其他函数一样,但它们“默默地”接受指向实例的指针作为参数。
这个原则可以扩展到控制堆栈上的值,这反过来会导致程序执行被修改。通常,目标是将 shellcode 放入堆栈,然后覆盖返回地址,使其现在指向一条jmp esp
指令,然后该指令运行 shellcode。
这个技巧适用于非 DEP 机器,但是当 DEP 启用时,它会阻止堆栈的执行。相反,必须使用面向返回的编程 (ROP)来设计 shellcode ,它使用来自应用程序及其模块的小块合法代码来执行 API 调用,以绕过 DEP。
无论如何,我有点跑题了,所以让我们来看看 CVE-2012-4969 的有趣细节吧!
在野外,有效载荷是通过一个打包的 Flash 文件丢弃的,旨在一次性利用 Java 漏洞和新的 IE 错误。AlienVault也对它进行了一些有趣的分析。
metasploit模块说明如下:
此模块利用 Microsoft Internet Explorer (MSIE) 中发现的漏洞。渲染 HTML 页面时,CMshtmlEd 对象以意外方式被删除,但稍后在 CMshtmlEd::Exec() 函数中再次重用相同的内存,导致释放后使用情况。
还有一篇关于这个错误的有趣博客文章,虽然英文很差——我相信作者是中国人。无论如何,博客文章会详细介绍:
当
execCommand
IE的函数执行命令事件时,会CMshtmlEd
通过AddCommandTarget
函数分配对应的对象,然后调用mshtml@CMshtmlEd::Exec()
函数执行。但是,在execCommand
函数添加了相应的事件之后,会立即触发并调用相应的事件函数。通过document.write("L")
函数重写html中相应的事件函数被调用。从而导致IE调用CHTMLEditor::DeleteCommandTarget
释放原来的应用对象CMshtmlEd
,然后在后面执行该msheml!CMshtmlEd::Exec()
函数时触发了used-after-free漏洞。
让我们看看我们是否可以将其解析为更具可读性的内容:
execCommand
它CMshtmlEd
通过AddCommandTarget
函数分配一个对象。document.write
修改页面。CMshtmlEd
对象通过CHTMLEditor::DeleteCommandTarget
.execCommand
在它被释放之后调用CMshtmlEd::Exec()
该对象。崩溃现场的部分代码如下所示:
637d464e 8b07 mov eax,dword ptr [edi]
637d4650 57 push edi
637d4651 ff5008 call dword ptr [eax+8]
use-after-free 允许攻击者控制 的值edi
,可以将其修改为指向攻击者控制的内存。假设我们可以01234f00
通过内存分配将任意代码插入到内存中。我们按如下方式填充数据:
01234f00: 01234f08
01234f04: 41414141
01234f08: 01234f0a
01234f0a: cccccccc // int3 breakpoint
edi
为。01234f00
mov eax,dword ptr [edi]
导致在eax
地址中填充内存edi
,即01234f00
。push edi
推01234f00
入堆栈。call dword ptr [eax+8]
取eax
(即01234f00
)并在其上加 8,得到01234f08
. 然后它取消引用该内存地址,给我们01234f0a
. 最后,它调用01234f0a
.01234f0a
被视为指令。cc
转换为int3
,这会导致调试器引发断点。我们已经执行了代码!这使我们可以控制eip
,因此我们可以将程序流修改为我们自己的 shellcode,或者修改为 ROP 链。
请记住,以上只是一个示例,实际上利用此漏洞还有许多其他挑战。这是一个非常标准的 use-after-free,但是 JavaScript 的性质产生了一些有趣的计时和堆喷射技巧,DEP 迫使我们使用 ROP 来获得一个可执行的内存块。
无论如何,这对研究很有趣,我希望它有所帮助。
如果您查看 technet 文章,您会看到对CVE-2012-4969的引用。CVE 页面有几个参考资料,其中最有帮助的(恕我直言)是这个。英文不是很好,但很好读。
另请参阅El Reg以获得更通俗易懂的语言解释。
我不打算在这里发布一个巨大的解释,因为它只是在抄袭其他人的工作,但总结一下,这是一个关于处理死对象的编码错误,导致任意代码执行。