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. 用 Flask 来写个轻博客 (20) — 实现注册表单与应用 reCAPTCHA 来实现验证码

    Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 reCAPTCHA 应用 reCAPTCHA 前文列表 用 Flask ...

  2. 测开之路三十五:css引入

    CSS是一种定义样式结构,如字体.颜色.位置等的语言,被用于描述网页上的信息格式化和现实的方式.CSS样式可以直接存储于HTML网页或者单独的样式单文件.无论哪一种方式,样式单包含将样式应用到指定类型 ...

  3. java对象实例化 静态块,对象块,构造函数执行顺序

    public class TestA { public static void main(String []args) { new B(); } } class A { static { System ...

  4. 在Windows及Linux下获取毫秒级运行时间的方法

    在Windows下获取毫秒级运行时间的方法 头文件:<Windows.h> 函数原型: /*获取时钟频率,保存在结构LARGE_INTEGER中***/ WINBASEAPI BOOL W ...

  5. SQL语句:随机取3条不重复的记录

    随机取3条不重复的记录 [Access]select top 3 * from tablename order by rnd(id); [SqlServer]select top 3 * from t ...

  6. nginx 设置http访问ftp目录内文件

    设置 nginx “403 Forbidden” 错误的原因及解决办法 原文链接 https://www.cnblogs.com/chenzc/p/4461130.html nginx 的 403 F ...

  7. linux 部署系统通过SecureCRT启动tomcat 控制台中文乱码

    查资料又是查了半天 首先 查看linux 当前系统字符集命令 echo $LANG 查看linux 当前系统语言 locale 网上说的又是下中文包,又是改临时语言,这些不能一概而论,我也觉得我不是中 ...

  8. Apache Shiro 会话+缓存+记住我(三)

    1.会话管理SessionDao和SessionManager 1)安装Redis 2)依赖 <dependency> <groupId>redis.clients</g ...

  9. go module管理依赖包

    go mod 最大的好处就是摆脱了GOPATH这个限制,在除了GOPATH以外的目录下也能开展你的项目 go mod使用: 1,确保你的go版本是1.1以上 2,创建一个项目目录example,并添加 ...

  10. python学习笔记--类(一)

    # 类是为了代码重用 class First: #类 pass #实例 = 类名() obj = First() # 类是一种产生实例的工厂# 类和模块的差异在于,内存中特定的模块只有一个实例(所以我 ...