API函数的调用过程(SystemServiceTable)
在上中,讲到进0环后,3环的各种寄存器都会保留到
_Trap_Frame结构体中,这篇我讲解
如何根据系统服务号(eax中存储)找到要执行的内核函数?
调用时参数是存储到3环的堆栈,如何传递给内核函数?
SystemServiceTable 系统服务表
ServiceTable存了系统服务表里函数地址表(4字节)
Count是当前系统服务表被调用了几次
serviceLimit存了有多少个函数
ArgmentTable存了有多少个参数(以字节为单位,比如穿了2个参数,每个参数4字节,所以这里存8,一个单元1字节)
绿色的是导出的函数
黄色结构是一样的,区别就是绿色的是Ntoskrl.exe的,黄色的是Win32k.sys(图形显示之类的模块)的。
那么程序执行的时候去哪找系统服务表,是_KTHREAD的偏移0xE0处。
下面系统服务号eax怎么找这个对应的函数呢,虽然eax32位,真正使用的只有13位,如下
判断要调用的函数在哪个表
低12位找对应偏移的函数,相同维序的参数表就是这个函数的参数。
下面来分析代码,2种调用除了开始寄存器保存不一样(KiSystemServer多做了一些读取内存寄存器的操作),后来都执行相同的代码所以直接开始分析KiSystemServer执行完跳转到KiFastCallEntry里的代码
下面先列一下不同于KiSystemServer,KiFastCallEntry多用到的结构体
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 |
kd> dt _KTSS nt!_KTSS +0x000 Backlink : Uint2B +0x002 Reserved0 : Uint2B +0x004 Esp0 : Uint4B +0x008 Ss0 : Uint2B +0x00a Reserved1 : Uint2B +0x00c NotUsed1 : [4] Uint4B +0x01c CR3 : Uint4B +0x020 Eip : Uint4B +0x024 EFlags : Uint4B +0x028 Eax : Uint4B +0x02c Ecx : Uint4B +0x030 Edx : Uint4B +0x034 Ebx : Uint4B +0x038 Esp : Uint4B +0x03c Ebp : Uint4B +0x040 Esi : Uint4B +0x044 Edi : Uint4B +0x048 Es : Uint2B +0x04a Reserved2 : Uint2B +0x04c Cs : Uint2B +0x04e Reserved3 : Uint2B +0x050 Ss : Uint2B +0x052 Reserved4 : Uint2B +0x054 Ds : Uint2B +0x056 Reserved5 : Uint2B +0x058 Fs : Uint2B +0x05a Reserved6 : Uint2B +0x05c Gs : Uint2B +0x05e Reserved7 : Uint2B +0x060 LDT : Uint2B +0x062 Reserved8 : Uint2B +0x064 Flags : Uint2B +0x066 IoMapBase : Uint2B +0x068 IoMaps : [1] _KiIoAccessMap +0x208c IntDirectionMap : [32] UChar |
FS寄存器,在用户层的时候,指向的是TEB,在内核层的时候,指向的是KPC
下面是通过IDA观察的_KiSystemService函数
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 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
.text:0043567E _KiSystemService proc near ; CODE XREF: ZwAcceptConnectPort(x,x,x,x,x,x)+Cp .text:0043567E ; ZwAccessCheck(x,x,x,x,x,x,x,x)+Cp ... .text:0043567E .text:0043567E arg_0 = dword ptr 4 .text:0043567E .text:0043567E push 0 ; _KTRAP_FRAME +0x064 ErrCode .text:00435680 push ebp ; 0x060 Ebp,3环寄存器入栈 .text:00435681 push ebx ; 0x05C,ebx .text:00435682 push esi ; 0x058 Esi .text:00435683 push edi ; 0x054 Edi .text:00435684 push fs ; 0x050 SegFs .text:00435686 mov ebx, 30h ; 为FS寄存器赋值,30就是段选择子,在GDTR找到相应的段描述符,加载到,FS,指向KPCR结构体 .text:0043568B mov fs, bx ; Windows内核有个特殊的基本要求,就是只要CPU在内核运行,就得使 .text:0043568B ; mov ebx,30 //0011 0000 所以就是0环GDT索引6 .text:0043568B ; mov fs,bx .text:0043568B ; .text:0043568B ; 0环的FS.Base指向CPU自己的KPCR,不是指向当前线程 .text:0043568B ; 选择码,0x30的结构分析如下: .text:0043568B ; 1.bit0~bit1:RPL,Requested Privilege Level,要求运行的级别,这里是0 .text:0043568B ; 2.bit2:找GDT还是IDT,这里是0,GDT .text:0043568B ; 3.bit3!bit15,是在GDT或者IDT的下标 .text:0043568B ; windbg查看段描述符:834093f2 dc003748 .text:0043568B ; Base:83f2dc00 指向当前的_KPCR .text:0043568E mov ebx, 23h .text:00435693 mov ds, ebx .text:00435695 mov es, ebx .text:00435697 mov esi, large fs:124h ; 查看下KPCR偏移124h是什么,查训发现是当前CPU所执行线程的_ETHREAD .text:0043569E push large dword ptr fs:0 ; 保存老的ExceptionList .text:0043569E ; _KPCR偏移+0x00->NT_TIB->ExceptionList .text:004356A5 mov large dword ptr fs:0, 0FFFFFFFFh ; 新的ExceptonList为空白,因为3环的异常链表,不能用,要进0环了 .text:004356B0 push dword ptr [esi+13Ah] ; 因为Esi存的_KTHREAD,他的偏移13A存的PreviousMode, .text:004356B0 ; 就是保存老的先前模式到堆栈 .text:004356B0 ; 先前模式就是当调用这些代码时候,原来是几环的数就是几,比如原来0环,先前模式就是0,原来3环就是1 .text:004356B0 ; 因为有些内核代码可以从0和3调用,但是执行内容不一样,通过这个知道执行什么。 .text:004356B6 sub esp, 48h ; ESP 提升到_KTRAP_FRAME结构体第一个成员,也就是这个结构体指针 .text:004356B9 mov ebx, [esp+68h+arg_0] ; 查了下这个位置是3环CS .text:004356B9 ; 所以这句是取出3环压入的参数CS _KTRAP_FRAME + 0x6C .text:004356BD and ebx, 1 ; 上面的CS跟1与运算 .text:004356BD ; 0环最低位是0,3环最低位为1 .text:004356C0 mov [esi+13Ah], bl ; 上面的运算结果存到esi+0x13Ah这个位置的偏移,就是新的"先前模式" .text:004356C6 mov ebp, esp ; 抬高栈针,ebp=esp=_KTRAP_FRAME指针 .text:004356C8 mov ebx, [esi+128h] ; _KTHTEAD中的TrapFrame给ebx .text:004356CE mov [ebp+3Ch], ebx ; 将_KTHREAD中的Trap_Frame暂时存在这个位置后面 .text:004356CE ; //会将这个值取出来,重新恢复给_KTHREAD的Trap_Frame .text:004356CE ; .text:004356CE ; 零时存在这 .text:004356D1 and dword ptr [ebp+2Ch], 0 ; Dr7清0 .text:004356D5 test byte ptr [esi+3], 0DFh ; 查看当前线程是否处于调试状态 .text:004356D5 ; 看看是不是-1, .text:004356D9 mov [esi+128h], ebp ; 因为有改变堆栈中的_KTRAP_FRAME,将其重新赋值给_KTHREAD中的TRAPFRAME .text:004356DF cld .text:004356E0 jnz Dr_kss_a ; 处于调试的话跳转,跳转那边的代码是讲调试寄存器都存到Trap_Frame里 .text:004356E6 .text:004356E6 loc_4356E6: ; CODE XREF: Dr_kss_a+Dj .text:004356E6 ; Dr_kss_a+79j .text:004356E6 mov ebx, [ebp+60h] ; 3环的EPB给ebx .text:004356E9 mov edi, [ebp+68h] ; 3环的Eip .text:004356EC mov [ebp+0Ch], edx ; edx存的3环参数指针: .text:004356EC ; .text:004356EC ; _kiFastSystemCall函数 .text:004356EC ; .text:004356EC ; mov edx,esp .text:004356EC ; .text:004356EC ; sysenter .text:004356EF mov dword ptr [ebp+8], 0BADB0D00h ; 这个是操作系统的标志 .text:004356F6 mov [ebp+0], ebx ; 3环的ebp存储到KTRAP_FRAME+0x000 DbgEbp的位置 .text:004356F9 mov [ebp+4], edi ; 3环的ebp存储到KTRAP_FRAME+0x004 DbgEip的位置 .text:004356FC sti .text:004356FD jmp loc_4357DF ; 跳到KiFastCallEntry .text:004356FD _KiSystemService endp .text:004356FD .text:00435702 .text:00435702 ; =============== S U B R O U T I N E ======================================= .text:00435702 .text:00435702 .text:00435702 _KiFastCallEntry2 proc near ; DATA XREF: _KiTrap01:loc_436724o .text:00435702 mov ecx, 30h .text:00435707 mov fs, ecx .text:00435709 mov ecx, 23h .text:0043570E mov ds, ecx .text:00435710 mov es, ecx .text:00435712 mov ecx, large fs:40h .text:00435719 mov esp, [ecx+4] .text:0043571C push 23h .text:0043571E push edx .text:0043571F pushf .text:00435720 or byte ptr [esp+1], 1 .text:00435725 jmp short loc_43576B ; Sanitize eflags, clear direction, NT etc;设置eflags为2,,表示所有标志位都是0(中断被关闭),就是清除所有标志位 .text:00435725 _KiFastCallEntry2 endp .text:00435725 .text:00435727 ; --------------------------------------------------------------------------- .text:00435727 ; START OF FUNCTION CHUNK FOR _KiFastCallEntry .text:00435727 .text:00435727 loc_435727: ; CODE XREF: _KiFastCallEntry:loc_43574Bj .text:00435727 mov ecx, large fs:40h .text:0043572E mov esp, [ecx+4] .text:00435731 push 0 .text:00435733 push 0 .text:00435735 push 0 .text:00435737 push 0 .text:00435739 push 23h .text:0043573B push 0 .text:0043573D push 20202h .text:00435742 push 1Bh .text:00435744 push 0 .text:00435746 jmp _KiTrap06 .text:0043574B ; --------------------------------------------------------------------------- .text:0043574B .text:0043574B loc_43574B: ; CODE XREF: _KiFastCallEntry+62j .text:0043574B jmp short loc_435727 .text:0043574B ; END OF FUNCTION CHUNK FOR _KiFastCallEntry .text:0043574B ; --------------------------------------------------------------------------- .text:0043574D align 10h .text:00435750 .text:00435750 ; =============== S U B R O U T I N E ======================================= .text:00435750 .text:00435750 ; ;此时: .text:00435750 ; ;eax指向服务编号 .text:00435750 ; ;edx指向当前用户栈,edx+8为参数列表 .text:00435750 ; .text:00435750 ; KGDT_R3_DATA OR RPL_MASK 给ecx .text:00435750 .text:00435750 _KiFastCallEntry proc near ; DATA XREF: KiLoadFastSyscallMachineSpecificRegisters(x)+21o .text:00435750 ; _KiTrap01+71o .text:00435750 .text:00435750 var_7C = dword ptr -7Ch .text:00435750 var_B = byte ptr -0Bh .text:00435750 .text:00435750 ; FUNCTION CHUNK AT .text:00435727 SIZE 00000026 BYTES .text:00435750 ; FUNCTION CHUNK AT .text:00435A75 SIZE 00000014 BYTES .text:00435750 ; FUNCTION CHUNK AT .text:00435BCB SIZE 00000038 BYTES .text:00435750 ; FUNCTION CHUNK AT .text:00435C04 SIZE 0000001A BYTES .text:00435750 .text:00435750 mov ecx, 23h .text:00435755 push 30h ; 这里的30是KGDT_R0_PCR,也就是0环的GDT的PCR,要给fs,使fs指向KPCR .text:00435757 pop fs ; 为FS寄存器赋值,30就是段选择子,在GDTR找到相应的段描述符,加载到,FS,指向KPCR结构体 .text:00435759 mov ds, ecx ; ds指向用户数据段 .text:0043575B mov es, ecx ; es指向用户数据段 .text:0043575D mov ecx, large fs:40h ; 传说中的TSS,保存了一大堆寄存器的值 .text:00435764 mov esp, [ecx+4] ; tts中获得当前线程堆栈的esp,0环ESP给ESP .text:00435767 push 23h ; 23是 KGDT_R3_DATA OR RPL_MASK ; Push user SS(压入用户态线程的堆栈段寄存器ss) .text:00435769 push edx ; edx,参数入栈, .text:00435769 ; edx保存的是用户层的堆栈,也就是Push ESP(压入esp) .text:0043576A pushf ; 压入标志寄存器 .text:0043576B .text:0043576B loc_43576B: ; CODE XREF: _KiFastCallEntry2+23j .text:0043576B push 2 ; Sanitize eflags, clear direction, NT etc;设置eflags为2,,表示所有标志位都是0(中断被关闭),就是清除所有标志位 .text:0043576D add edx, 8 ; edx+8就是dex移动到了参数位置 .text:00435770 popf ; .errnz(EFLAGS_INTERRUPT_MASK AND 0FFFF00FFh) .text:00435771 or [esp+0Ch+var_B], 2 ; or byte ptr [esp+1], EFLAGS_INTERRUPT_MASK/0100h ; Enable interrupts in eflags(打开用户态eflags的中断) .text:00435776 push 1Bh ; Push user CS .text:00435778 push dword ptr ds:0FFDF0304h ; push return address(使其返回时指向用户空间 .text:00435778 ; push dword ptr ds:[USER_SHARED_DATA+UsSystemCallReturn] ; .text:0043577E push 0 ; put pad dword for error on stack .text:00435780 push ebp ; save the non-volatile registers .text:00435781 push ebx .text:00435782 push esi .text:00435783 push edi ; 保存现场 .text:00435784 mov ebx, large fs:1Ch ; Ptr32 _KPCR 指向KPCR自己 .text:0043578B push 3Bh ; Push user mode FS .text:0043578B ; push KGDT_R3_TEB OR RPL_MASK .text:0043578D mov esi, [ebx+124h] ; esi为指向当前线程的结构体KTHREAD .text:00435793 push dword ptr [ebx] ; 吧KPCR入栈 .text:00435795 mov dword ptr [ebx], 0FFFFFFFFh ; ebx等于-1 .text:0043579B mov ebp, [esi+28h] ; 这个位置是InitialStack .text:0043579B ; 使ebp指向当前线程堆栈的栈顶 .text:0043579E push 1 ; Save previous mode as user(保存先前模式) .text:004357A0 sub esp, 48h ; 给ESP分配48h字节, 也就是DbgEbp到Eax所占据的空间 .text:004357A0 ; allocate remainder of trap frame .text:004357A3 sub ebp, 29Ch ; sub ebp, NPX_FRAME_LENGTH + KTRAP_FRAME_LENGTH .text:004357A3 ; 就是ebp指向了 _Ktrap_frame .text:004357A9 mov byte ptr [esi+13Ah], 1 ; esi+0x13Ah这个位置的偏移,就是新的"先前模式"为1,就是之前是从3环调用的这个代码 .text:004357B0 cmp ebp, esp ; 判断堆栈内压入的值是否正确(这些堆栈内压入的东西,就好比一个trap框架) .text:004357B2 jnz short loc_43574B ; 不正确重新来一遍 .text:004357B4 and dword ptr [ebp+2Ch], 0 ; sDr7置0 .text:004357B8 test byte ptr [esi+3], 0DFh ; 检查是否被调试 .text:004357BC mov [esi+128h], ebp ; 重新把修改过的TrapFrame设置回_KTHREAD的相应参数位置 .text:004357C2 jnz Dr_FastCallDrSave ; 处于调试则保存那些寄存器 .text:004357C8 .text:004357C8 loc_4357C8: ; CODE XREF: Dr_FastCallDrSave+Dj .text:004357C8 ; Dr_FastCallDrSave+79j .text:004357C8 mov ebx, [ebp+60h] ; Ebp给ebx .text:004357CB mov edi, [ebp+68h] ; eip给Edi .text:004357CE mov [ebp+0Ch], edx ; 参数地址给DbgArgPointer .text:004357D1 mov dword ptr [ebp+8], 0BADB0D00h .text:004357D8 mov [ebp+0], ebx ; 3环ebp给调试DbgEbp .text:004357DB mov [ebp+4], edi ; 3环Eip给DbgEip .text:004357DE sti ; 开中断,允许硬件中断 .text:004357DF .text:004357DF loc_4357DF: ; CODE XREF: _KiBBTUnexpectedRange+18j .text:004357DF ; _KiSystemService+7Fj .text:004357DF mov edi, eax ; 取出系统调用号,给edi .text:004357E1 shr edi, 8 ; 系统调用号右移8位 .text:004357E4 and edi, 10h ; 将第12位;与1与,得到的数,看看是哪个的系统服务表,是Ntoskrl的还是Win32k.sys的 .text:004357E7 mov ecx, edi ; 调用号给ecx .text:004357E9 add edi, [esi+0BCh] ; edi的值加上ServiceTable,如果0就是第一个表,1就是第二个表 .text:004357E9 ; 就是SSDT表 .text:004357EF mov ebx, eax ; 调用序号给ebx .text:004357F1 and eax, 0FFFh ; 系统调用号,只要后面12位 .text:004357F6 cmp eax, [edi+8] ; 调用号跟系统表函数总数作比较 .text:004357F9 jnb _KiBBTUnexpectedRange ; 大于等于说明越界,到处理越界的地方去 .text:004357FF cmp ecx, 10h ; 看看是1还是0,就是说看看是哪一张表 .text:00435802 jnz short loc_43581E ; 是0,第一张表往上面跳转 .text:00435804 mov ecx, [esi+88h] ; Teb给ecx .text:0043580A xor esi, esi ; esi清0 .text:0043580C .text:0043580C loc_43580C: ; DATA XREF: _KiTrap0E+156o .text:0043580C or esi, [ecx+0F70h] .text:00435812 jz short loc_43581E .text:00435814 push edx .text:00435815 push eax .text:00435816 call ds:_KeGdiFlushUserBatch ; 是第二张表跳转到的函数,一般都是图像相关,动态加载的 .text:0043581C pop eax .text:0043581D pop edx .text:0043581E .text:0043581E loc_43581E: ; CODE XREF: _KiFastCallEntry+B2j .text:0043581E ; _KiFastCallEntry+C2j .text:0043581E inc large dword ptr fs:6B0h .text:00435825 mov esi, edx ; edx存储着3环传入函数的指针 .text:00435827 xor ecx, ecx .text:00435829 mov edx, [edi+0Ch] ; edi存的SSDT表,所以这里+0x0Ch是SSDT参数表起始的的地址 .text:0043582C mov edi, [edi] ; 指向函数表的地址 .text:0043582E mov cl, [eax+edx] ; eax系统调用号 参数表+调用号得到参数个数 .text:00435831 mov edx, [edi+eax*4] ; 0环函数的地址,函数地址表参数4字节 .text:00435834 sub esp, ecx ; 提升堆栈,提高为Cl,因为参数是3环的,要把3环参数存到0环,所以是抬高这么高的栈针 .text:00435836 shr ecx, 2 ; 为了下面rep movsd,一次复制4字节,复制ecx次,ecx保存参数个数单位是一字节,所以这里要除以4 .text:00435839 mov edi, esp ; 设置要复制的地址 .text:0043583B cmp esi, ds:_MmUserProbeAddress ; 判断用户函数地址范围有没有越界 .text:00435841 jnb loc_435A75 .text:00435847 .text:00435847 loc_435847: ; CODE XREF: _KiFastCallEntry+329j .text:00435847 ; DATA XREF: _KiTrap0E:loc_438AD8o .text:00435847 rep movsd ; 开始复制参数 .text:00435849 test byte ptr [ebp+6Ch], 1 ; SegCs .text:0043584D jz short loc_435865 .text:0043584F mov ecx, large fs:124h .text:00435856 mov edi, [esp+7Ch+var_7C] .text:00435859 mov [ecx+13Ch], ebx .text:0043585F mov [ecx+12Ch], edi .text:00435865 .text:00435865 loc_435865: ; CODE XREF: _KiFastCallEntry+FDj .text:00435865 mov ebx, edx .text:00435867 test byte ptr ds:dword_52E0C8, 40h .text:0043586E setnz byte ptr [ebp+12h] .text:00435872 jnz loc_435C04 .text:00435878 .text:00435878 loc_435878: ; CODE XREF: _KiFastCallEntry+4BBj .text:00435878 call ebx ; 调用函数 |
API函数的调用过程(SSDT)
在上一节课中,我们讲到系统服务表的结构,以及如何找到系统服务
表(KTHREAD 0xbc偏移).
也可以通过SSDT访问
SSDT 的全称是 System Services Descriptor Table,系统服务描述符表
kd> dd KeServiceDescriptorTable(SSDT)ntosKrl是内核导出的
导出的 声明一下就可以使用了
kd> dd KeServiceDescriptorTableShadow(SSDT Shadow)
未导出 需要用其他的方式来查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
kd> dd KeServiceDescriptorTable 83f789c0 83e8cd9c 00000000 00000191 83e8d3e4 83f789d0 00000000 00000000 00000000 00000000 83f789e0 83eeb6af 00000000 025355a9 000000bb 83f789f0 00000011 00000100 5385d2ba d717548f 83f78a00 83e8cd9c 00000000 00000191 83e8d3e4 83f78a10 95d46000 00000000 00000339 95d4702c 83f78a20 00000000 00000000 83f78a24 00000340 83f78a30 00000340 865fab00 00000007 00000000 kd> dd KeServiceDescriptorTableShadow 83f78a00 83e8cd9c 00000000 00000191 83e8d3e4 83f78a10 95d46000 00000000 00000339 95d4702c 83f78a20 00000000 00000000 83f78a24 00000340 83f78a30 00000340 865fab00 00000007 00000000 83f78a40 865faa38 865fa7e0 865fa970 865fa8a8 83f78a50 00000000 865fa718 00000000 00000000 83f78a60 83e86809 83e93eed 83ea23a5 00000003 83f78a70 80783000 80784000 00000120 ffffffff |
因为KeServiceDescriptorTableShadow没有导出,所以一般采用内存搜索获取这张表
参考资料:
[1]:毛德操,《Windows内核情景分析》
[2]:滴水视频
[3]: 一大堆的别人的博客,重点博客https://blog.csdn.net/Sunny_wwc/article/details/5939848