0:000> u ntdll!KiFastSystemCall
ntdll!KiFastSystemCall:
7c92eb8b 8bd4 mov edx,esp
7c92eb8d 0f34 sysenter
ntdll!KiFastSystemCallRet:
7c92eb8f 90 nop
7c92eb90 90 nop
7c92eb91 90 nop
7c92eb92 90 nop
7c92eb93 90 nop
ntdll!KiFastSystemCallRet:
7c92eb94 c3 ret

而我们通过dump disasm得到的结果

0x77d1a146 :             FF 15 24 14 D1 77 		call dword ptr [0x77d11424]
【ntdll.dll.[.text].NtCallbackReturn】
0x7c92d51d : B8 14 00 00 00 mov eax, 0x14
0x7c92d522 : BA 00 03 FE 7F mov edx, 0x7ffe0300
0x7c92d527 : FF 12 call dword ptr [edx]
  【ntdll.dll.[.text].KiFastSystemCall】
  0x7c92eb8b : 8B D4 mov edx, esp
  0x7c92eb8d : 0F 34 sysenter
  0x7c92eb94 : C3 ret
0x77ef67d4 : C3 ret

根据上面的结果显示,KiFastSystemCall在执行结束本来应该返回到0x7c92d527的下一条指令即0x7c92d529处执行,但是为什么会返回到0x77ef67d4处呢?

通过查看调用栈

        call [0x0012f43c]     :     0x7c92d527 --> 0x7c92eb8b
    return [0x0012f458]     :     0x77ef67d4 <-- 0x7c92eb94

Thread 38:


   [#       0] [0x0012fb20]:[0x7c941739 - 0x7c94173e] ==> [0x7c93c9e4] ntdll.dll.[.text].LdrFindResourceDirectory_U_0x000000af
[# 1] [0x0012fa18]:[0x7c93cba6 - 0x7c93cbab] ==> [0x7c921193] ntdll.dll.[.text].LdrInitializeThunk_0x00000015
[# 2] [0x0012f9f8]:[0x7c9211a4 - 0x7c9211a7] ==> [0x77d1f518] USER32.dll.[.text].UserClientDllInitialize
[# 3] [0x0012f46c]:[0x77d1f76f - 0x77d1f774] ==> [0x77d1f791] USER32.dll.[.text].UserClientDllInitialize_0x00000279
[# 4] [0x0012f460]:[0x77ef655e - 0x77ef6563] ==> [0x77ef67c8] GDI32.dll.[.text].GdiProcessSetup_0x000001ac
[# 5] [0x0012f45c]:[0x77ef67d2 - 0x77ef67d4] ==> [0x7c92eb8b] ntdll.dll.[.text].KiFastSystemCall
[# 6] [0x0012f450]:[0x7c92eae0 - 0x7c92eae3] ==> [0x77d1a12e] USER32.dll.[.text].ClientThreadSetup_0x00000134
[# 7] [0x0012f440]:[0x77d1a146 - 0x77d1a14c] ==> [0x7c92d51d] ntdll.dll.[.text].NtCallbackReturn
--> [# 8] [0x0012f43c]:[0x7c92d527 - 0x7c92d529] ==> [0x7c92eb8b] ntdll.dll.[.text].KiFastSystemCall

可见,0x77ef67d4是在第一次调用KiFastSystemCall的时候,保存在栈上的返回地址,当时的栈的位置是0x0012f45c(还没有将返回地址压到栈上时);

而在返回时,栈的位置是0x0012f458,因此正好是返回到了第一次调用KiFastSystemCall的位置。

如果是这样,那么我们的影子调用栈出现了什么问题呢?

我们知道,当用户态程序进入内核态执行后(通过sysenter),内核可能会调度到用户态的APC等等回调函数执行,这些回调函数的执行发起者是内核态代码,因此它们需要将执行流程返回给内核代码;

Windows提供了一种机制,当用户态的回调函数没有提供显式的返回到内核的代码时,Windows会自动执行默认的返回到内核态的代码,而这一段代码恰好也是通过KiFastSystemCall机制完成的(因为KiFastSystemCall是用户态通向内核态的唯一入口),这一机制被称为NtCallbackReturn。

因此,我们怀疑,NtCallbackReturn机制会使用户态的代码孤立起来看,是不符合栈平衡的,原因就是NtCallbackReturn的内核服务程序中会对用户态的栈做调整,以掩盖异步回调函数被执行过的痕迹。

因此,我们相信,NtCallbackReturn的存在,使得我们单纯地依据用户态的call/ret指令建立起来的影子调用栈出现了不平衡的现象。

解决方法,就是在调用过NtCallbackReturn之后,添加三次等效ret的效果,可以解决影子栈的不平衡问题。


事情到了这里,还没有结束,因为还不知道有哪些系统调用会破坏用户态栈的平衡。

比如NtContinue就很可疑(根据实现结果),那么用户态的影子调用栈要通过什么方式来保证其正确性呢?

更普遍的做法,可以根据KiFastSystemCallRet来判断,它可以与KiFastSystemCall配对,从而消除中间的多余项目。

NtCallbackReturn是否导致了用户态栈的不平衡的更多相关文章

  1. Linux中的栈:用户态栈/内核栈/中断栈

    http://blog.chinaunix.net/uid-14528823-id-4136760.html Linux中有多种栈,很容易弄晕,简单说明一下: 1.用户态栈:在进程用户态地址空间底部, ...

  2. v79.01 鸿蒙内核源码分析(用户态锁篇) | 如何使用快锁Futex(上) | 百篇博客分析OpenHarmony源码

    百篇博客分析|本篇为:(用户态锁篇) | 如何使用快锁Futex(上) 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) ...

  3. 总在用户态调试 C# 程序,终还是搭了一个内核态环境

    一:背景 一直在用 WinDbg 调试用户态程序,并没有用它调试过 内核态,毕竟不是做驱动开发,也没有在分析 dump 中需要接触用内核态的需求,但未知的事情总觉得很酷,加上最近在看 <深入解析 ...

  4. 130行C语言实现个用户态线程库——ezthread

    准确的说是除掉头文件,测试代码和非关键的纯算法代码(只有双向环形链表的ADT),核心代码只有130行左右,已经是蝇量级的用户态线程库了.把这个库取名为ezthread,意思是,这太easy了,人人都可 ...

  5. NFV、DPDK以及部分用户态协议研究

    本文为作者原创,转载请注明出处(http://www.cnblogs.com/mar-q/)by 负赑屃 对我而言,这是一个新的领域,很有意思. 一.解释名词: NFV(Network Functio ...

  6. 用户态使用 glibc/backtrace 追踪函数调用堆栈定位段错误【转】

    转自:https://blog.csdn.net/gatieme/article/details/84189280 版权声明:本文为博主原创文章 && 转载请著名出处 @ http:/ ...

  7. [中英对照]Device Drivers in User Space: A Case for Network Device Driver | 用户态设备驱动: 以网卡驱动为例

    前文初步介绍了Linux用户态设备驱动,本文将介绍一个典型的案例.Again, 如对Linux用户态设备驱动程序开发感兴趣,请阅读本文,否则请飘过. Device Drivers in User Sp ...

  8. linux 用户态和内核态以及进程上下文、中断上下文 内核空间用户空间理解

    1.特权级         Intel x86架构的cpu一共有0-4四个特权级,0级最高,3级最低,ARM架构也有不同的特权级,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查.硬件已经提 ...

  9. Linux内核笔记--内存管理之用户态进程内存分配

    内核版本:linux-2.6.11 Linux在加载一个可执行程序的时候做了种种复杂的工作,内存分配是其中非常重要的一环,作为一个linux程序员必然会想要知道这个过程到底是怎么样的,内核源码会告诉你 ...

随机推荐

  1. 同时连接gitlab和github

    ---恢复内容开始--- 原文地址:https://juejin.im/post/5ac0cf356fb9a028df22c246 1. 分别生成gitlab和github的ssh key 生成第一个 ...

  2. Java 反射简介(转载)

    反射机制是什么 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为java ...

  3. datastudion 资源导入python包,编写模块

    学习文档,不懂再问. https://help.aliyun.com/document_detail/74423.html?spm=a2c4g.11186623.6.688.72635debHqgkV ...

  4. Hibernate 异常org.hibernate.LazyInitializationException: could not ini...

    错误页面提示 could not initialize proxy - no Session 控制台 org.hibernate.LazyInitializationException: could ...

  5. 服务器oracle数据库定时备份

    首先要先建立一个.bat的文件  然后执行这个bat文件 测试是否能得到这个收据库的打包文件. bat文件内容: @echo off@color bdel /f /s /q D:\oracle\bac ...

  6. C中整数的溢出

    /** * 整数的溢出 */ #include <stdio.h> int main(int argc, char *argv[]) { short i = -24; // 将-24以无符 ...

  7. 获取年月日格式为yyyy-m-d简单写法

    方法:通过日期函数toLocaleString()获取.new Date().toLocaleString( );  //"2019/9/1 上午11:20:23" 获取年月日方法 ...

  8. Django版本更新(升级)到指定版本的命令

  9. 力扣 -- 寻找两个有序数组的中位数 Median of Two Sorted Arrays python实现

    题目描述: 中文: 给定两个大小为 m 和 n 的有序数组 nums1 和 nums2. 请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n)). 你可以假设 nums ...

  10. Charles IOS https抓包

    步骤 1.下载charles: https://www.charlesproxy.com/download/ 只有一个30天试用版,每次打开只能30分钟,如果想时间长点,就找破解版或者买个licenc ...