windows7内核分析之x86&x64第二章系统调用

windows7内核分析之x86&x64第二章系统调用


2.1内核与系统调用

上节讲到进入内核五种方式 其中一种就是 系统调用 syscall/sysenter或者int 2e(在 64 位环境里统一使用 syscall/sysret 指令,在 32 位环境里统一使用 sysenter/sysexit 在 compatibility 模式下必须切换到 64 位模式,然后使用 syscall/sysret 指令 注释:32位cpu是x86模式 也叫legacy模式 再说清楚点 就是包含了实模式:可以执行以前的16位程序 也包含了保护模式:可以执行32位的程序 64位cpu是long模式:分为两种 64位模式:只执行64位的程序和compatibility模式:可以执行x86模式的程序 老式的cpu不支持 不提供sysenter指令,只能由int 2e模拟中断方式进入内核,调用系统服务)这两者什么区别呢?

1,Int 2e速度慢 首先从TSS中加载内核堆栈的ss esp->保存5个寄存器的现场(ss esp eip eflags cs)->然后还要去IDT中查找isr,这个过程消耗的时间太多

2,sysenter 提供了三个MSR寄存器 分别是SYSENTER_CS_MSR SYSENTER_EIP_MSR SYSENTER_ESP_MSR 分别指示了内核对应处理例程的cs选择子(函数所在段的选择子 通过选择子找到GDT 从逻辑地址转换为线性地址 最后访问线性地址 会根据四级表转换为物理地址)和内核函数地址和内核堆栈地址 这样就省去了查找idt表 TSS段 获得内核函数地址和内核堆栈地址 节省了大量时间

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++以下是X64的syscall 讲解 参考总结++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

syscall 的内核入口点是 KiSystemCall64() ,系统在 KiInitializeBootStructures() 里对 syscall/sysret 执行环境进行了设置:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

nt!KiInitializeBootStructures+0x233:

fffff800`03f12f63 498b442408      mov     rax,qword ptr [r12+8]

fffff800`03f12f68 b968000000      mov     ecx,68h

fffff800`03f12f6d 66894866        mov     word ptr [rax+66h],cx

fffff800`03f12f71 48b80000000010002300 mov rax,23001000000000h

fffff800`03f12f7b b9810000c0      mov     ecx,0C0000081h                         ; MSR_STAR

fffff800`03f12f80 488bd0          mov     rdx,rax

fffff800`03f12f83 48c1ea20        shr     rdx,20h

fffff800`03f12f87 0f30            wrmsr

fffff800`03f12f89 488d05701cdbff  lea     rax,[nt!KiSystemCall32 (fffff800`03cc4c00)]

fffff800`03f12f90 b9830000c0      mov     ecx,0C0000083h                         ; MSR_CSTAR

fffff800`03f12f95 488bd0          mov     rdx,rax

fffff800`03f12f98 48c1ea20        shr     rdx,20h

fffff800`03f12f9c 0f30            wrmsr

fffff800`03f12f9e 488d051b1fdbff  lea     rax,[nt!KiSystemCall64 (fffff800`03cc4ec0)]

fffff800`03f12fa5 b9820000c0      mov     ecx,0C0000082h                         ; MSR_LSTAR

fffff800`03f12faa 488bd0          mov     rdx,rax

fffff800`03f12fad 48c1ea20        shr     rdx,20h

fffff800`03f12fb1 0f30            wrmsr

fffff800`03f12fb3 b800470000      mov     eax,4700h

fffff800`03f12fb8 b9840000c0      mov     ecx,0C0000084h                         ; MSR_SFMASK

fffff800`03f12fbd 488bd0          mov     rdx,rax

fffff800`03f12fc0 48c1ea20        shr     rdx,20h

fffff800`03f12fc4 0f30            wrmsr

fffff800`03f12fc6 85ed            test    ebp,ebp

fffff800`03f12fc8 750a            jne     nt!KiInitializeBootStructures+0x2a4 (fffff800`03f12fd4)

MSR_STAR 寄存器里的值被设为 23001000000000h,它意味着:

SYSCALL_EIP 为 0

SYSCALL_CS 为 0x10

SYSRET_CS 为 0x23

在 SYSRET_CS 中,SYSRET_CS.RPL = 3 返回的权限级别是 3 级(用户代码)。

MSR_CSTAR 寄存器被设为 nt!KiSystemCall32 (fffff800`03cc4c00) 地址值,这是为了 compaitibility 模式代码调用而设置的。

MSR_LSTAR 寄存器被设为 nt!KiSystemCall64 (fffff800`03cc4ec0) 地址值,是为 64-bit 模式而准备的。CSTAR 寄存器为 compatibility 模式下的代码提供 rip 值,当 processor 在 comatibility 模式下运行时,执行了 syscall 指令,此时 rip 值将从 MSR_STAR 寄存器中加载。请记住:只能在 AMD 的 processor 使用 compaitibility 模式下的调用。照顾通用性,为了在 Intel 和 AMD 的 processor 上都能够使用 fast call 功能,操作系统的设计者应该要避免在 comaptibility 模式下使用 syscall 指令。前面提到过,建议在 compatibility 模式下先切换到 64-bit 模式后,再执行 syscall 指令


1

2

3

4

5

6

; NtReadFile

.text:7DE8F905                 mov     ecx, 1Ah

.text:7DE8F90A                 lea     edx, [esp+FileHandle]

.text:7DE8F90E                 call    large dword ptr fs:0C0h //注意在win7x64 long模式下的 运行32位程序 就会进入兼容模式下 兼容模式下调用NtReadFile 这里就是上面说的必须切换到64位模式 这个函数里面 就是切换模式的 完后调用syscall

.text:7DE8F915                 add     esp, 4

.text:7DE8F918                 retn    24h

MSR_SFMASK 寄存器设为 4700h,意味着:

NT DF IF TF

这些 rflags 寄存器中的标志位在进入 KiSystemCall64() 后会被清 0

在 long mode(win7 x64 ) 下,当执行 syscall 指令时,当前的 rflags 寄存器值被保存在 r11 寄存器,processor 在执行 syscall 时,准备的目标执行环境中,rflags 将会根据 SFMASK 寄存器的值进行设置:如果 SFMASK 寄存器的某一位置为 1,那么 rflags 寄存器中相应的位将会被清 0,置为 0 时,rflags 寄存器中相应位不变

它的逻辑 C 描述为:

rflags = rflags & (~sfmask);

你应该在系统服务例程先保存 r11 值(原来的 rflags 寄存器值),以便 sysret 执行返回时可以恢复原来的 rflags 值

那么,对于要进入系统调用的代码来说:


1

2

3

4

5

ntdll!NtReadFile:

00000000`773ad410 4c8bd1           mov     r10,rcx //保存原来的rcx

00000000`773ad413 b803000000       mov     eax,3 //系统调用号 这里NtReadFile的内核号码是3

00000000`773ad418 0f05              Syscall

00000000`773ad41a c3                ret

执行 syscall 后,rcx 会保存返回值,因此应该要保存 rcx 原来的值。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

KiSystemCall64  proc near

     swapgs//将gs的基址与MSR[c0000102]内容互换,设置过后gs:0指向内核处理器控制域_KPCR

      mov     gs:10h, rsp//将用户态堆栈指针保存到_KPCR+0x010的成员UserRsp里

      mov     rsp, gs:1A8h//使用_KPCR+0x1A8的成员RspBase设置当前rsp,这个成员存储了当前线程核心态的堆栈指针

          push    2Bh

          push    qword ptr gs:10h

          push    r11//r11里保存的是rflags

          push    33h

          push    rcx// rcx里保存的是用户态syscall的下一条指令地址

          mov     rcx, r10// 把系统调用的第一个参数重新赋给rcx

          sub     rsp,8

          push    rbp

          sub     rsp,158h// rsp与设置伊始相比共减少了0x190字节。期间恢复了rcx,在堆栈上保存了一些值。使用下面的命令,我们可以看出,0x190正是_KTRAP_FRAME的大小:

          lea     rbp, [rsp+190h+var_110]//现在rsp指向_KTRAP_FRAME的起始地址,rbp指向_KTRAP_FRAME+0x80的位置

(4)      mov     [rbp+0C0h],rbx

          mov     [rbp+0C8h], rdi

          mov     [rbp+0D0h], rsi

          mov     byte ptr [rbp-55h], 2

(5)      mov     rbx, gs:188h

          prefetchw byte ptr [rbx+1D8h]

          stmxcsr dword ptr [rbp-54h]

          ldmxcsr dword ptr gs:180h

(6)      cmp     byte ptr [rbx+3],0//是否在被调试 调试就保存寄存器

(7)           mov     word ptr [rbp+80h],0

          jz      loc_140071B50

          mov     [rbp-50h], rax

(7)      mov     [rbp-48h], rcx//以下若干条指令将rcx、rdx、r8、r9存入了_KTRAP_FRAME当中,其中rbp-48h相当于rsp+80g-48h即rsp+38h

          mov     [rbp-40h], rdx

          test    byte ptr [rbx+3],3

          mov     [rbp-38h], r8

          mov     [rbp-30h], r9

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++以下是X86的sysenter 讲解 参考总结++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

ntdll!ZwReadFile:

776262dc b811010000      mov     eax,111h//系统调用号

776262e1 ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub (7ffe0300)//这个7ffe03000地址固定的 系统初始化 查看是否支持快速系统调用 支持 这个地址里面存的就是KiFastSystemCall 否则就是KiInitSystemCall(int 0x2e模拟中断)的地址 这些函数都存在于用户空间ntdll.dll(和内核ntdll不一样)中 这个dll 对于每个进程 地址不变 一直在内存中

7ffe03000 和0x7fe0000 一个是r3的地址 一个是内核的地址 两个64kb地址映射到同一个物理内存

776262e6 ff12            call    dword ptr [edx]

776262e8 c22400          ret     24h

776262eb 90              nop

 

ntdll!KiFastSystemCall:

776270d0 8bd4            mov     edx,esp//进入KiFastSystemCall之前 已经被调用方压入了各种参数 最后压入的是返回地址 当前esp指向它 在书中 被压入堆栈的参数区域叫参数块  处理完后 调用sysexit 出栈ret 返回

776270d2 0f34            sysenter

Sysenter进入内核后 的总入口是KiFastCallEntry

和int2e的区别:快速系统调用的sysenter 堆栈地址保存在edx 返回地址保存在SharedUserData->SystemCallReturn指向KiFastSystemCallRet地址

 

nt!KiFastCallEntry:

83e888e0 b923000000      mov     ecx,23h

83e888e5 6a30            push    30h

83e888e7 0fa1            pop     fs//fs指向kpcr

83e888e9 8ed9            mov     ds,cx//指向用户空间数据段

83e888eb 8ec1            mov     es,cx

83e888ed 648b0d40000000  mov     ecx,dword ptr fs:[40h]//从kpcr获取TSS段的起点

83e888f4 8b6104          mov     esp,dword ptr [ecx+4]//从TSS获取系统空间段的指针

83e888f7 6a23            push    23h//压栈r3的数据段选择子(模仿中断自陷 异常进入内核的指令 会自动堆栈上创建一个框架 这个框架结构一样 所以这里虽然是快速系统调用 但是也是进入内核 模仿自陷 中断 异常进入内核的函数入口样子 照猫画虎 但是注意int2e的内核入口函数没有照猫画虎 因为int 2e本身就是中断 所以cpu进入内核后 堆栈上已经画出老虎了

)

83e888f9 52              push    edx//压栈r3的esp

83e888fa 9c              pushfd//压栈r3的eflags

83e888fb 6a02            push    2

83e888fd 83c208          add     edx,8//跳过用户堆栈的参数块

83e88900 9d              popfd//r0的eflags 所有标志都为0 中断关闭

83e88901 804c240102      or      byte ptr [esp+1],2

83e88906 6a1b            push    1Bh//模仿自陷中断异常的push cs eip83e88908 ff350403dfff    push    dword ptr ds:[0FFDF0304h]//KiFastSystemCallRet地址

83e8890e 6a00            push    0//这里以下见①注释

83e88910 55              push    ebp

83e88911 53              push    ebx

83e88912 56              push    esi

83e88913 57              push    edi

83e88914 648b1d1c000000  mov     ebx,dword ptr fs:[1Ch]

83e8891b 6a3b            push    3Bh

83e8891d 8bb324010000    mov     esi,dword ptr [ebx+124h]

83e88923 ff33            push    dword ptr [ebx]

83e88925 c703ffffffff    mov     dword ptr [ebx],0FFFFFFFFh

83e8892b 8b6e28          mov     ebp,dword ptr [esi+28h]

83e8892e 6a01            push    1

83e88930 83ec48          sub     esp,48h

83e88933 81ed9c020000    sub     ebp,29Ch

83e88939 c6863a01000001  mov     byte ptr [esi+13Ah],1

83e88940 3bec            cmp     ebp,esp

83e88942 7597            jne     nt!KiFastCallEntry2+0x49 (83e888db)

83e88944 83652c00        and     dword ptr [ebp+2Ch],0

83e88948 f64603df        test    byte ptr [esi+3],0DFh

83e8894c 89ae28010000    mov     dword ptr [esi+128h],ebp

83e88952 0f8538feffff    jne     nt!Dr_FastCallDrSave (83e88790)

83e88958 8b5d60          mov     ebx,dword ptr [ebp+60h]

83e8895b 8b7d68          mov     edi,dword ptr [ebp+68h]

83e8895e 89550c          mov     dword ptr [ebp+0Ch],edx

83e88961 c74508000ddbba  mov     dword ptr [ebp+8],0BADB0D00h

83e88968 895d00          mov     dword ptr [ebp],ebx

83e8896b 897d04          mov     dword ptr [ebp+4],edi

83e8896e fb              sti

83e8896f 8bf8            mov     edi,eax

83e88971 c1ef08          shr     edi,8

83e88974 83e710          and     edi,10h

83e88977 8bcf            mov     ecx,edi

83e88979 03bebc000000    add     edi,dword ptr [esi+0BCh]

83e8897f 8bd8            mov     ebx,eax

83e88981 25ff0f0000      and     eax,0FFFh

83e88986 3b4708          cmp     eax,dword ptr [edi+8]

83e88989 0f8333fdffff    jae     nt!KiBBTUnexpectedRange (83e886c2)

83e8898f 83f910          cmp     ecx,10h

83e88992 751a            jne     nt!KiSystemServiceAccessTeb+0x12 (83e889ae)

83e88994 8b8e88000000    mov     ecx,dword ptr [esi+88h]

83e8899a 33f6            xor     esi,esi

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++以下是int 2e 讲解  参考总结+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


1

2

3

4

5

6

7

8

ntdll!ZwReadFile:

    push ebp

mov ebp,esp

Mov eax,111h//系统调用号

Lea edx,[8+ebp]//指向参数块

Int 2e//不支持syscall/sysenter的cpu 只能用这个了

Pop ebp

    ret     9h

2.2系统调用的内核入口KiSystemService()

上面讲到老的cpu是通过int 2e 模拟进入内核 而不是新cpu 直接支持快速系统调用

那么KiSystemService()就是int 2e的处理函数 进入这个函数之前 cpu会自动读取TR寄存器 找到TSS段 读取里面的ss esp 就是内核堆栈地址了 完后往这个地址保存用户空间的堆栈 eflags cs eip 具体开头已经说了 这里就不废话了

nt!KiSystemService:

83e8880e 6a00            push    0   //①是一种类似TrapFrame 进入这个函数之前 cpu会自动压入各种ss esp eip等等 你可以理解为一种上下文 这里push 0 是因为函数最后要把他的值存入eax当做状态码返回 另外就是操作系统 把这个Frame 定义了一个结构 然而异常发生后 cpu会自动压入一个错误码 中断和自陷都没有 所以为了通用 这个结构里面不管是异常还是中断进入内核 结构第一个元素都是0 占个位置的意思

83e88810 55              push    ebp//保存栈帧

83e88811 53              push    ebx//函数下面要用到ebx 所以这里先保存一下

83e88812 56              push    esi//函数下面 要用到esi保存kthread 所以这先保存

83e88813 57              push    edi//函数下面 要用edi引用调用号 所以这也要保存

83e88814 0fa0            push    fs//内核fs指向kpcr 用户层指向TEB 所以这里提前保存 因为进了内核 fs要改变了

83e88816 bb30000000      mov     ebx,30h//r0的fs指向kpcr fs和cs一样都是段选择子 所以这六30h是GDT的kpcr的索引

表②:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

83e8881b 668ee3          mov     fs,bx

83e8881e bb23000000      mov     ebx,23h//数据段的选择子 见上图 内核中都规定好

83e88823 8edb            mov     ds,bx

83e88825 8ec3            mov     es,bx

83e88827 648b3524010000  mov     esi,dword ptr fs:[124h]//使esi指向当前的ethread结构

83e8882e 64ff3500000000  push    dword ptr fs:[0]//保存老的exceptionList

83e88835 64c70500000000ffffffff mov dword ptr fs:[0],0FFFFFFFFh新的exceptionlist为空白

83e88840 ffb63a010000    push    dword ptr [esi+13Ah]//保存老的先前模式

83e88846 83ec48          sub     esp,48h//为之后要保存的调试寄存器 留下空间

83e88849 8b5c246c        mov     ebx,dword ptr [esp+6Ch]//系统调用前夕的cs印象

83e8884d 83e301          and     ebx,1//0环最低位为0  3环最低位为1

83e88850 889e3a010000    mov     byte ptr [esi+13Ah],bl//新的先前模式

83e88856 8bec            mov     ebp,esp

83e88858 8b9e28010000    mov     ebx,dword ptr [esi+128h]//kthread 结构里的TrapFrame 因为KiSystemService里面可能调用其他api 也会再次进入KiSystemService 梯归调用 那么肯定要梯归返回 所以需要提前保存好上下文 但是驱动可以搜索特征码 直接定位函数地址 绕过KiSystemService

83e8885e 895d3c          mov     dword ptr [ebp+3Ch],ebx//暂时保存在这里

83e88861 83652c00        and     dword ptr [ebp+2Ch],0//dr7 设置为0

83e88865 f64603df        test    byte ptr [esi+3],0DFh//判断当前是否被调试

83e88869 89ae28010000    mov     dword ptr [esi+128h],ebp//新的TrapFrame是此时堆栈上的框架Frame

83e8886f fc              cld //屏蔽中断

83e88870 0f859afeffff    jne     nt!Dr_kss_a (83e88710)//如果被调试 那么要保存好调试寄存器

83e88876 8b5d60          mov     ebx,dword ptr [ebp+60h]//这里书上也没讲清楚 知道的告诉我一下

83e88879 8b7d68          mov     edi,dword ptr [ebp+68h]

83e8887c 89550c          mov     dword ptr [ebp+0Ch],edx

83e8887f c74508000ddbba  mov     dword ptr [ebp+8],0BADB0D00h

83e88886 895d00          mov     dword ptr [ebp],ebx

83e88889 897d04          mov     dword ptr [ebp+4],edi

83e8888c fb              sti//开启中断

83e8888d e9dd000000      jmp     nt!KiFastCallEntry+0x8f (83e8896f)

 

nt!KiFastCallEntry+0x8f:

83e8896f 8bf8            mov     edi,eax//系统调用号

83e88971 c1ef08          shr     edi,8//除以256

83e88974 83e710          and     edi,10h//调用号是否大于1000

83e88977 8bcf            mov     ecx,edi//00 或者10 nt4.0之前小于1000 那么ecx就是00 否则10

 

83e88979 03bebc000000    add     edi,dword ptr [esi+0BCh]//kthread 结构里有一个ServiceTable指针 其实默认他不是指向KeServiceDescriptorTableShadow(win32k.sys) 就是指向KeServiceDescriptorTable(基本系统调用)也就是说每个线程 可以指向不同的表 脑洞大开 即使tp对KeServiceDescriptorTable做了手脚 你也可以提前备份一个表 完后让线程的ServiceTable指向它 KeServiceDescriptorTable[0]是1000以下的系统调用 [1]是1000以上的系统调用(KeServiceDescriptorTableShadow[1])  里面每一项 都是一个结构 KService_Table_Descriptor 第一个元素就是MainSSDT,MainSSDT={{NtOpenFile},{NtCloseFile},{}......}

83e8897f 8bd8            mov     ebx,eax

83e88981 25ff0f0000      and     eax,0FFFh//获取调用号的低12位

83e88986 3b4708        cmp     eax,dword ptr [edi+8]//和上面KService_Table_Descriptor结构的Limit比较

83e88989 0f8333fdffff    jae     nt!KiBBTUnexpectedRange (83e886c2)//如果超了 肯定是大于1000那么就是shadow表里的 就跳到错误处理的地方 完后会加载win32k.sys 使当前线程指向KeServiceDescriptorTableShadow[1]

83e8898f 83f910          cmp     ecx,10h//如果是10 那么就是win32k调用

83e88992 751a            jne     nt!KiSystemServiceAccessTeb+0x12 (83e889ae)

83e88994 8b8e88000000    mov     ecx,dword ptr [esi+88h]//使用win32k系统调用表 获取表项里面的地址 完后跳过去执行

83e8899a 33f6            xor     esi,esi

 

83e889ae 64ff05b0060000  inc     dword ptr fs:[6B0h]

83e889b5 8bf2            mov     esi,edx//使esi指向 用户空间堆栈上的参数块

83e889b7 33c9            xor     ecx,ecx

83e889b9 8b570c          mov     edx,dword ptr [edi+0Ch]

83e889bc 8b3f            mov     edi,dword ptr [edi]//使edi指向具体的系统调用表

83e889be 8a0c10          mov     cl,byte ptr [eax+edx]//函数指针

83e889c1 8b1487          mov     edx,dword ptr [edi+eax*4]

83e889c4 2be1            sub     esp,ecx//在系统空间上留出空间

83e889c6 c1e902          shr     ecx,2

83e889c9 8bfc            mov     edi,esp

83e889cb f6457202        test    byte ptr [ebp+72h],2

83e889cf 7506            jne     nt!KiSystemServiceAccessTeb+0x3b (83e889d7)

83e889d1 f6456c01        test    byte ptr [ebp+6Ch],1

83e889d5 740c            je      nt!KiSystemServiceCopyArguments (83e889e3)

83e889d7 3b355078fb83    cmp     esi,dword ptr [nt!MmUserProbeAddress (83fb7850)]//参数块的位置 不得高于MmUserProbeAddress 这个定义了用户空间最大地址

83e889dd 0f832e020000    jae     nt!KiSystemCallExit2+0xa5 (83e88c11)

nt!KiSystemServiceCopyArguments:

83e889e3 f3a5            rep movs dword ptr es:[edi],dword ptr [esi]//复制用户空间参数到内核堆栈上

83e889e5 f6456c01        test    byte ptr [ebp+6Ch],1

83e889e9 7416            je      nt!KiSystemServiceCopyArguments+0x1e (83e88a01)

83e889eb 648b0d24010000  mov     ecx,dword ptr fs:[124h]

83e889f2 8b3c24          mov     edi,dword ptr [esp]

83e889f5 89993c010000    mov     dword ptr [ecx+13Ch],ebx

83e889fb 89b92c010000    mov     dword ptr [ecx+12Ch],edi

83e88a01 8bda            mov     ebx,edx

83e88a03 f6058837f88340  test    byte ptr [nt!PerfGlobalGroupMask+0x8 (83f83788)],40h

83e88a0a 0f954512        setne   byte ptr [ebp+12h]

83e88a0e 0f8580030000    jne     nt!KiServiceExit2+0x179 (83e88d94)

83e88a14 ffd3            call    ebx//调用内核目标函数

 

nt!KiSystemServicePostCall:

83e88a16 f6456c01        test    byte ptr [ebp+6Ch],1

83e88a1a 7434            je      nt!KiSystemServicePostCall+0x3a (83e88a50)

83e88a1c 8bf0            mov     esi,eax

83e88a1e ff1568c1e483    call    dword ptr [nt!_imp__KeGetCurrentIrql (83e4c168)]

83e88a24 0ac0            or      al,al

83e88a26 0f852f030000    jne     nt!KiServiceExit2+0x140 (83e88d5b)

83e88a2c 8bc6            mov     eax,esi

83e88a2e 648b0d24010000  mov     ecx,dword ptr fs:[124h]

83e88a35 f68134010000ff  test    byte ptr [ecx+134h],0FFh

83e88a3c 0f8537030000    jne     nt!KiServiceExit2+0x15e (83e88d79)

83e88a42 8b9184000000    mov     edx,dword ptr [ecx+84h]

83e88a48 0bd2            or      edx,edx

83e88a4a 0f8529030000    jne     nt!KiServiceExit2+0x15e (83e88d79)

83e88a50 8be5            mov     esp,ebp //回到自陷框架

83e88a52 807d1200        cmp     byte ptr [ebp+12h],0

83e88a56 0f8544030000    jne     nt!KiServiceExit2+0x185 (83e88da0)

83e88a5c 648b0d24010000  mov     ecx,dword ptr fs:[124h]//使ecx指向当前线程的kthread

83e88a63 8b553c          mov     edx,dword ptr [ebp+3Ch]//取出保存的TrapFrame框架

83e88a66 899128010000    mov     dword ptr [ecx+128h],edx//恢复kthread里的Frame

nt!KiServiceExit:

83e88a6c fa              cli//关闭中断

83e88a6d f6457202        test    byte ptr [ebp+72h],2

83e88a71 7506            jne     nt!KiServiceExit+0xd (83e88a79)

83e88a73 f6456c01        test    byte ptr [ebp+6Ch],1//执行APC的时机是在(系统调用、中断、或异常处理之后)从内核返回用户空间的途中 我们这里是系统调用返回的时候 如果先前模式是用户层 有用户APC请求正在等待执行(KTHREAD_PENDING_USER_APC是ApcState.KernelApcPending在KTHREAD数据结构中的位移)。 那么要提交apc请求(类似内核发送给用户层的”中断信号” 例如 用户层要异步读写文件 那么ReadFile调用完毕 继续执行别的去了 内核设备驱动程序收到io请求执行读写 读写完毕 就会通知用户程序 我已经读写完毕了 你需要暂时停止其他工作 先处理我读写后的数据 怎么通知用户层呢 就是APC回调了 每个线程2个队列 一个是内核apc队列(其中的回调函数是在内核) 一个是用户层apc队列(其中的回调函数在用户层)  在这里读写文件 是用户层APC 执行用户apc前(每次只执行队列的第一个) 必须先把内核apc队列里的所有函数都执行完毕后再执行用户apc队列的函数  如果是内核模式的apc 那么就只执行线程内核apc队列所有函数)

83e88a77 7467            je      nt!KiServiceExit+0x74 (83e88ae0)

83e88a79 648b1d24010000  mov     ebx,dword ptr fs:[124h]

83e88a80 f6430202        test    byte ptr [ebx+2],2

83e88a84 7408            je      nt!KiServiceExit+0x22 (83e88a8e)

83e88a86 50              push    eax

83e88a87 53              push    ebx

83e88a88 e8ce660a00      call    nt!KiCopyCounters (83f2f15b)

83e88a8d 58              pop     eax

83e88a8e c6433a00        mov     byte ptr [ebx+3Ah],0

83e88a92 807b5600        cmp     byte ptr [ebx+56h],0

83e88a96 7448            je      nt!KiServiceExit+0x74 (83e88ae0)

83e88a98 8bdd            mov     ebx,ebp

83e88a9a 894344          mov     dword ptr [ebx+44h],eax

83e88a9d c743503b000000  mov     dword ptr [ebx+50h],3Bh

83e88aa4 c7433823000000  mov     dword ptr [ebx+38h],23h

83e88aab c7433423000000  mov     dword ptr [ebx+34h],23h

83e88ab2 c7433000000000  mov     dword ptr [ebx+30h],0

83e88ab9 b901000000      mov     ecx,1//APC Level

83e88abe ff155cc1e483    call    dword ptr [nt!_imp_KfRaiseIrql (83e4c15c)]//提升irql 每个调用来自用户空间的内核函数执行完毕 都会提交APC(类似linux信号 发送给用户层 让用户层”中断” )

83e88ac4 50              push    eax

83e88ac5 fb              sti

83e88ac6 53              push    ebx

83e88ac7 6a00            push    0

83e88ac9 6a01            push    1

83e88acb e8e53d0700      call    nt!KiDeliverApc (83efc8b5)

83e88ad0 59              pop     ecx

83e88ad1 ff1558c1e483    call    dword ptr [nt!_imp_KfLowerIrql (83e4c158)]//不能主动降低irql 只能是升高irql后 再降低到原始irql 降低后 可能会发生线程切换 这个函数里面可能会执行DPC(调用KiDispatchInterrupt())最后会看看 KPCR得字段QuantumEnd是否为null 如果是 那么要切换线程了

83e88ad7 8b4344          mov     eax,dword ptr [ebx+44h]

83e88ada fa              cli

83e88adb eb9c            jmp     nt!KiServiceExit+0xd (83e88a79)

83e88add 8d4900          lea     ecx,[ecx]

83e88ae0 8b54244c        mov     edx,dword ptr [esp+4Ch]

83e88ae4 64891500000000  mov     dword ptr fs:[0],edx

83e88aeb 8b4c2448        mov     ecx,dword ptr [esp+48h]

83e88aef 648b3524010000  mov     esi,dword ptr fs:[124h]

83e88af6 888e3a010000    mov     byte ptr [esi+13Ah],cl

83e88afc f744242cff23ffff test    dword ptr [esp+2Ch],0FFFF23FFh

83e88b04 0f857e000000    jne     nt!KiSystemCallExit2+0x1c (83e88b88)

83e88b0a f744247000000200 test    dword ptr [esp+70h],20000h

83e88b12 0f85340a0000    jne     nt!KiExceptionExit+0x134 (83e8954c)

83e88b18 66f744246cf9ff  test    word ptr [esp+6Ch],0FFF9h

83e88b1f 0f84b9000000    je      nt!KiSystemCallExit2+0x72 (83e88bde)

83e88b25 66837c246c1b    cmp     word ptr [esp+6Ch],1Bh

83e88b2b 660fba64246c00  bt      word ptr [esp+6Ch],0

83e88b32 f5              cmc

83e88b33 0f8793000000    ja      nt!KiSystemCallExit2+0x60 (83e88bcc)

83e88b39 66837d6c08      cmp     word ptr [ebp+6Ch],8

83e88b3e 7405            je      nt!KiServiceExit+0xd9 (83e88b45)

83e88b40 8d6550          lea     esp,[ebp+50h]

83e88b43 0fa1            pop     fs

83e88b45 8d6554          lea     esp,[ebp+54h]

83e88b48 5f              pop     edi

83e88b49 5e              pop     esi

83e88b4a 5b              pop     ebx

83e88b4b 5d              pop     ebp

83e88b4c 66817c24088000  cmp     word ptr [esp+8],80h

83e88b53 0f870f0a0000    ja      nt!KiExceptionExit+0x150 (83e89568)

83e88b59 83c404          add     esp,4

83e88b5c f744240401000000 test    dword ptr [esp+4],1

nt!KiSystemCallExitBranch:

83e88b64 7506            jne     nt!KiSystemCallExit2 (83e88b6c)

83e88b66 5a              pop     edx

83e88b67 59              pop     ecx/

83e88b68 9d              popfd//出栈r3的eflags

83e88b69 ffe2            jmp     edx//跳到edx返回地址

nt!KiSystemCallExit:

83e88b6b cf              iretd

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

为什么内核中不能直接调用NtReadFile()呢?

因为一方面NtReadFile不是导出函数 另外就是 调用这些函数 需要系统堆栈上有框架 但是内核中直接调用的话 肯定没有为本次调用的框架存在 可能是其他框架 比如自陷框架 中断异常框架 所以导致直接调用NtReadFile出错 可以通过调用ZwReadFile()调用

以下是x86的


1

2

3

4

5

6

7

nt!ZwReadFile:

83e874cc b811010000      mov     eax,111h//因为是内核直接调用 所以无须保存各个寄存器

83e874d1 8d542404        lea     edx,[esp+4]//使edx指向堆栈上的参数快

83e874d5 9c              pushfd   // eflags

83e874d6 6a08            push    8//cs

83e874d8 e831130000      call    nt!KiSystemService (83e8880e)//通过调用它来形成框架

83e874dd c22400          ret     24h

以下是x64的


1

2

3

4

5

6

7

8

9

10

11

12

nt!ZwReadFile:

fffff800`03c6f6e0 488bc4          mov     rax,rsp

fffff800`03c6f6e3 fa              cli

fffff800`03c6f6e4 4883ec10        sub     rsp,10h

fffff800`03c6f6e8 50              push    rax

fffff800`03c6f6e9 9c              pushfq

fffff800`03c6f6ea 6a10            push    10h

fffff800`03c6f6ec 488d05dd310000  lea     rax,[nt!KiServiceLinkage (fffff800`03c728d0)]

fffff800`03c6f6f3 50              push    rax

fffff800`03c6f6f4 b803000000      mov     eax,3

fffff800`03c6f6f9 e902690000 jmp  nt!KiServiceInternal (fffff800`03c76000)//KiServiceLinkage 和KiServiceInternal 都是初始化系统服务的里面会调用KiSystemServiceStart->KiSystemServiceRepeat(选择ssdt 还是shadow ssdt执行调用)->KiSystemServiceExit(退出调用)

fffff800`03c6f6fe 6690            xchg    ax,ax

最近身体出问题了 所以进度很慢  希望大家多多支持

      jpg 改 rar

原文地址:https://www.cnblogs.com/kuangke/p/9524228.html

时间: 2024-10-10 16:48:27

windows7内核分析之x86&x64第二章系统调用的相关文章

《linux内核设计与实现》第二章

第二章 从内核出发 一.获取内核源码 1.使用Git(linux创造的系统) 使用git来获取最新提交到linux版本树的一个副本: $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git 下载代码后,更新分支到Linux的最新分支: $ git pull 这两个命令可以获取并随时保持与内核官方的代码树一致. 2.安装内核源代码 压缩形式是bzip2,则运行: $ tar xvjf linu

图像处理分析与机器视觉【第二章】

第二章是数字图像及其性质,开篇介绍了一些基本概念和数学工具. 首先提出了一个问题,那就是透视投影,在将3D的物体经过透视投影和转化为2D图像时,损失了大量的信息,所以通过一幅图像重构3D场景就成为了一个病态的问题. 在数学知识部分,涉及到了很多接触过但未深入了解的和从未接触过的知识点.首先是理想冲激,狄拉克分布,可以进行图像的采样.从网上找了一个相关的笔记 http://www2.math.uu.se/~maciej/courses/PDE_for_Finance/SP2013.pdf 随后是对

20135201李辰希 《Linux内核分析》第五周 扒开系统调用的“三层皮”(下)

李辰希  原创作品转载请注明出处 <Linux内核分析> MOOC课程http://mooc.study.163.com/course/USTC-100002900 一.给MenusOS增加time和time-asm命令 1.操作步骤 进入实验楼 首先,强制删除当前的menu 克隆一个新的menu 进入menu之后,输入make rootfs,就可以自动编译 输入help,可以发现系统支持更多的命令: help version quit time time-asm 那么,time和time-a

《Linux内核分析》 第五节 扒开系统调用的三层皮(下)

摘要:范闻泽 原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 1.实验环境是使用本课程配置的实验楼虚拟机环境,打开命令行客户端,cd LinuxKernel目录,使用命令rm -rf menu 删除原来的代码,使用git clone https://github.com/mengning/menu.git获取menu的最新代码,然后cd menu进入menu子文件夹,使用vi test.c

《Linux内核分析》之第三章读书笔记

进程管理 进程是处于执行期的程序以及相关的资源的总称,也称作任务.执行线程,简称线程,是在进程中活动的对象. 可以两个或两个以上的进程执行同一个程序 也可以两个或两个以上并存的进程共享许多资源 内核调度的对象是线程,而不是进程. 进程描述符及任务结构 内核把进程的列表存放在任务列表(task list)的双向循环链表中. 链表中每一项都是类型为task_struct的进程描述符的结构. 进程描述符中包含的数据能完整地描述一个正在执行的程序: 打开的文件 进程的地址空间 挂起的信号 进程的状态 m

《Linux内核分析》之第四章读书笔记

4.1多任务 多任务操作系统:同时并发地交互执行多个进程的操作系统 多任务操作系统会使多个进程处于堵塞或者睡眠状态.这些任务尽管位于内存,但是并不处于可运行状态.这些进程利用内核堵塞自己,直到某一事件发生. 多任务系统可以划分为两类:非抢占式和抢占式. 抢占:强制挂起. 时间片:分配给每个可运行进程的处理器时间段. 4.2 linux的进程调度 4.3策略 策略决定调度程序在何时让什么程序运行. 4.3.1I/O消耗型和处理器消耗型的进程 I/O消耗型:进程的大部分时间用来提交I/O请求或是等待

《Linux内核分析》第四周笔记 扒开系统调用的三层皮(上)

扒开系统调用的三层皮(上) 一.用户态.内核态和中断 库函数将系统调用封装起来. 1.什么是用户态和内核态 一般现代CPU都有几种不同的指令执行级别. 在高执行级别下,代码可以执行特权指令,访问任意的物理地址,这种CPU执行级别就对应着内核态. 而在相应的低级别执行状态下(用户态),代码的掌控范围会受到限制.只能在对应级别允许的范围内活动.系统容易崩溃. 在intel X86CPU有四种不同的执行级别0,1,2,3,linux只使用了0级和3级分别来表示内核态和用户态. 2.在linux内核代码

《Linux内核分析》 期中总结

<Linux内核分析> 第一节 计算机是如何工作的:http://www.cnblogs.com/20132109HKK/p/5225027.html <Linux内核分析> 第二节 操作系统是如何工作的:http://www.cnblogs.com/20132109HKK/p/5248108.html <Linux内核分析> 第三节 构造一个简单的Linux系统MenuOS:http://www.cnblogs.com/20132109HKK/p/5272649.ht

LINUX内核分析第二周学习总结:操作系统是如何工作的?

马启扬 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.函数调用堆栈 1. 小结:计算机是怎样工作的 三个法宝:存储程序计算机.函数调用堆栈.中断机制. 存储程序计算机工作模型,计算机系统最最基础性的逻辑结构. 函数调用堆栈,高级语言得以运行的基础,只有机器语言和汇编语言的时候堆栈机制对于计算机来说并不那么重要,但有了高级语言及函数,堆栈成为了计算机的基础功能.(函数参数传递