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

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


 

 

2.1内核与系统调用

上节讲到进入内核五种方式 其中一种就是 系统调用 syscall/sysenter或者int 2e( 64 位环境里统一使用 syscall/sysret 指令,在 32 位环境里统一使用 sysenter/sysexit compatibility 模式下必须切换到 64 位模式,然后使用 syscall/sysret 指令 注释:32cpux86模式 也叫legacy模式 再说清楚点 就是包含了实模式:可以执行以前的16位程序 也包含了保护模式:可以执行32位的程序 64cpulong模式:分为两种 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 从逻辑地址转换为线性地址 最后访问线性地址 会根据四级表转换为物理地址)和内核函数地址和内核堆栈地址 这样就省去了查找idtTSS段 获得内核函数地址和内核堆栈地址 节省了大量时间

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++以下是X64syscall 讲解 参考总结++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

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

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

 

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++以下是X86sysenter 讲解 参考总结++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

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//r0fs指向kpcr fscs一样都是段选择子 所以这六30hGDTkpcr的索引

:

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 
posted @ 2018-08-23 15:50  狂客  阅读(1778)  评论(0编辑  收藏  举报