按关键词阅读:
图5 从32位到64位的过渡过程
用于实现CPU模式切换的跳转指令的目标是图6所示的64位代码:
文章插图
图6 KiFastSystemCall的目标代码
图6显示了到目前为止 , 我们在这个调用跟踪中看到执行的第一条64位指令 。 为了理解它 , 我们需要看看WOW64系统是如何初始化自己的 。 关于这方面的详细解释 , 请参考(wbenny) 。 现在 , 我们可以看看wow64cpu!RunSimulatedCode中的重要部分:
文章插图
图7 保存在RunSimulatedCode中的64位寄存器
图7描述了对64位TEB的检索 , 它用于访问槽位索引(slot index)1处的Thread Local Storage 。 然后 , 将一个函数指针表移入寄存器r15中 。 实际上 , 检索到的TLS数据是一个未公开的数据结构WOW64_CPURESERVED , 它包含了WOW64层用来设置和恢复32位和64位边界的寄存器数据和CPU状态信息 。 在这个结构体中 , 还有一个WOW64_CONTEXT结构体 , 微软网站上提供了部分文档 。 我在本篇文章的最后给出了这两个结构体 。 我们将在后面介绍如何使用这个上下文结构 , 但是为了理解前面的jmp指令 , 我们只需要知道r15存放的是一个函数指针表的地址即可 。
现在 , 我们还需要留意WOW64层的架构 。 从64位内核的角度来看 , 32位(Wow64)用户模式应用程序的运行过程本质上就是一个大的while循环 。 这个循环在处理器的32位执行模式下执行x86指令 , 并偶尔退出循环来运行系统调用 。 因为内核是64位的 , 所以这时处理器模式会暂时切换到64位 , 以提供系统调用服务 , 然后 , 处理器将切换回原来的模式 , 从暂停的地方继续循环 。 也就是说 , WOW64层的作用就像一个模拟器 , 但是其中的指令都是在物理CPU上执行的 。
回到图6中我们看到的jmp指令 , 现在我们知道发生了什么 。 指令jmp [r15 + 0xF8] 相当于C代码jmp TurboThunkDispatch[0xF8/sizeof(uint64_t)] 。 观察这个索引处的函数指针 , 我们可以找到函数wow64cpu!CpupReturnFromSimulatedCode(图8) 。
文章插图
图8 TurboThunk表的最后一个函数指针条目是一个exit例程
这个例程负责将32位寄存器的状态保存到我们之前提到的WOW64_CONTEXT结构体中 , 并获取syscall的参数 。 这里有一些棘手的问题 , 所以让我们来详细研究一下 。 首先 , 通过xchg将堆栈指针移到r14中 , 这个位置的值将是调用Wow64SystemServiceCall的syscall stub的返回地址 。 然后 , 堆栈指针r14递增4 , 以获得一个指针 , 该指针指向在恢复所有这些上下文值时堆栈应该重置的位置 。 然后 , 这两个值分别保存到上下文的EIP和ESP变量中 。 然后 , 让r14堆栈指针再递增一次 , 以得到__stdcall参数的位置(记住stdcall是通过堆栈传递所有参数的) 。 这个参数数组对以后很重要 , 所以要记住它 。 之后 , 将参数指针移到r11中 , 所以在C语言中 , 这意味着r11相当于一个堆栈槽的数组 , 每个槽是一个参数uint32_t r11[argCount] 。 最后 , 保存其余的寄存器和EFlags 。
一旦保存了32位上下文 , WOW64层就会通过获取syscall编号的高16位来计算要调用的TurboThunk , 并派发到该thunk 。 需要注意的是 , 在这个数组的开头是图9所示的函数TurboDispatchJumpAddressEnd , 它是为不支持TurboThunks的函数而调用的 。
文章插图
图9 TurboThunk表的第一个函数指针条目是一个entry例程
关于TurboThunks , 建议读者仔细阅读wbenny撰写的相关文章 。 为了照顾没有读过这篇文章的读者 , 我们总结一下这篇文章的大意:对于那些参数宽度<= sizeof(uint32_t)的简单函数 , WOW64层会直接通过零扩展或带符号的扩展将这些参数拓宽到64位 , 然后直接通过系统调用进入内核模式 。 这一切都发生在wow64cpu中 , 而不是执行如下所述的更复杂的路径 。 这就起到了优化的作用 。 对于不支持TurboThunks的复杂函数 , 可以使用TurboDispatchJumpAddressEnd存根函数 , 以派遣到wow64!SystemServiceEx执行系统调用 , 具体如图10所示 。
文章插图
图10 复杂的系统调用要经过Wow64SystemServiceEx例程
稍后 , 我们将介绍这个例程 , 因为它是本文的重点内容 , 但现在让我们跟踪调用流程 。 当Wow64SystemServiceEx运行系统调用并返回后 , eax寄存器中的返回值就会被移动到WOW64_CONTEXT结构体中 , 并恢复32位寄存器的状态 。 为此 , 有两种路径 , 一种是常见的情况 , 一种是似乎只存在于NtContinue和其他WOW64内部的情况 。 至于选择哪种恢复路径 , 要视从TLS槽中检索到的WOW64_CPURESERVED结构体开头的一个标志而定 , 具体如图11所示:
稿源:(未知)
【傻大方】网址:http://www.shadafang.com/c/111J2RB2020.html
标题:深入考察WOW64子系统运行机制及其Hooking技术(上)( 二 )