首页 > 技术文章 > windows的系统调用

Windogs 2015-07-12 16:38 原文

最近一直在学习windows内核相关的知识,写一写博客用于备忘。

windows系统调用的具体流程在潘爱民老师的《WINDOWS内核原理与实现》中的第8章已经写得很清楚了,先看书中给出的这幅图。

以CreateFile为例,在ring3的CreateFile进行了一些参数检查后最终调用的是Ntdll中的NtCreateFile。同时也有ZwCreateFile,不过它们的地址指向同一区域,所以本质上来说是同一个函数。

可以再ntdll的导出表中看到:

之后通过sysenter或者0x2e中断进入ring0层,并将服务号放入eax中。

ntoskrnl.exe 中的 zwcreatefile:

kd> u nt!zwcreatefile
nt!ZwCreateFile:
80501010 b825000000   mov eax,25h    ;服务号
80501015 8d542404       lea edx,[esp+4]
80501019 9c                  pushfd
8050101a 6a08               push 8
8050101c e830140400    call nt!KiSystemService (80542451)
80501021 c22c00            ret 2Ch

 

使用int 0x2e进入ring0

 使用windbg可以直接查看idt的0x2e号中断:

kd>!idt 2e

Dumping IDT:

2e: 80542451 nt!KiSystemService  ;可以看到指向的是KiSystemService这个例程

 

当然,也可以通过查看idt表计算出0x2e中断所指向的地址,首先找到idt表中0x2e项的内容:

kd> !pcr
KPCR for Processor 0 at ffdff000:
Major 1 Minor 1
NtTib.ExceptionList: 80551cb0
NtTib.StackBase: 805524f0
NtTib.StackLimit: 8054f700
NtTib.SubSystemTib: 00000000
NtTib.Version: 00000000
NtTib.UserPointer: 00000000
NtTib.SelfTib: 00000000

SelfPcr: ffdff000
Prcb: ffdff120
Irql: 00000000
IRR: 00000000
IDR: ffffffff
InterruptMode: 00000000
IDT: 8003f400          ;IDT表的地址
GDT: 8003f000
TSS: 80042000

CurrentThread: 8055ce60
NextThread: 00000000
IdleThread: 8055ce60

 

计算一下  8003f400 + 8*0x2e = 8003F570

查看:

kd> db 8003F570
8003f570    51 24 08 00 00 ee 54 80-e0 57 08 00 00 8e 54 80    Q$....T..W....T.
8003f580    10 1b 08 00 00 8e 54 80-1a 1b 08 00 00 8e 54 80    ......T.......T.
8003f590    24 1b 08 00 00 8e 54 80-2e 1b 08 00 00 8e 54 80    $.....T.......T.
8003f5a0    38 1b 08 00 00 8e 54 80-42 1b 08 00 00 8e 54 80    8.....T.B.....T.

根据IDT中断描述符的格式,可以知道该例程偏移为 80542451  ,段选择符为0x8  (IDT结构的内容在《WINDOWS内核原理与实现》中的第5章有讲解)

知道了偏移地址,还需要知道段地址才可以计算出实际的地址。

已经知道了段选择符为0x8,可以查看相应的段描述符:

其中,TI为0表示改索引指向GDT,RPL为0表示当前特权级是0,索引代表它在GDT中的位置是第一项。

(这一部分的知识在书中第4章有介绍,但不是十分详细,具体内容可以查阅一些保护模式的资料)

查看GDT:

kd> r gdtr
gdtr=8003f000   ;也可以使用!pcr指令找到GDT地址

kd> db 8003f000
8003f000    00 00 00 00 00 00 00 00-ff ff 00 00 00 9b cf 00         ................
8003f010    ff   ff  00 00 00 93 cf  00-ff ff 00 00 00 fb  cf 00        ................

由段描述格式获得基地址为 0x0000  

计算出中断例程的地址为 0000 : 80542451  与windbg直接获得的地址一样。

 

通过sysenter进入ring0

先看sysenter指令的调用位置:

kd> u ntdll!KiFastSystemCall
ntdll!KiFastSystemCall:
770801d0 8bd4    mov edx,esp
770801d2 0f34     sysenter

ntdll!KiFastSystemCallRet:
770801d4 c3            ret
770801d5 8da42400000000    lea esp,[esp]
770801dc 8d642400        lea esp,[esp]

 

sysenter指令的工作原理是读取MSR寄存器,加载RING0层的CS,EIP,ESP,清楚Eflags中的VM标示.

IA32_SYSENTER_CS   0x174

IA32_SYSENTER_ESP   0x175

IA32_SYSENTER_EIP  0x176

使用windbg查看:

kd> rdmsr 174
msr[174] = 00000000`00000008
kd> rdmsr 175
msr[175] = 00000000`f8ac2000
kd> rdmsr 176
msr[176] = 00000000`80542520

 

CS段选择符为0x00000008与之前的一样,所以目标地址为  0000:80542520

反汇编此地址:

kd> u 80542520
nt!KiFastCallEntry:
80542520 b923000000    mov ecx,23h
80542525 6a30        push 30h
80542527 0fa1         pop fs
80542529 8ed9        mov ds,cx
8054252b 8ec1        mov es,cx
8054252d 648b0d40000000 mov ecx,dword ptr fs:[40h]
80542534 8b6104       mov esp,dword ptr [ecx+4]

 

KiFastCallEntry就是进入RING0的调用例程。

 

其实对KiSystemService的反汇编进行查看会发现,KiSystemService最终还是调用了KiFastCallEntry的例程:

nt!KiSystemService+0x5a:
805424ab c74508000ddbba   mov dword ptr [ebp+8],0BADB0D00h
805424b2 895d00         mov dword ptr [ebp],ebx
805424b5 897d04         mov dword ptr [ebp+4],edi
805424b8 f6462cff        test byte ptr [esi+2Ch],0FFh
805424bc 0f858afeffff       jne nt!Dr_kss_a (8054234c)
805424c2 fb           sti
805424c3 e9e7000000     jmp nt!KiFastCallEntry+0x8f (805425af)  ;跳转到KiFastCallEntry中

然后看一下KiFastCallEntry中被跳转的内容:

nt!KiFastCallEntry+0x8f:
805425af 8bf8        mov edi,eax      ;KiSystemService会跳转到这里
805425b1 c1ef08        shr edi,8
805425b4 83e730      and edi,30h
805425b7 8bcf       mov ecx,edi
805425b9 03bee0000000  add edi,dword ptr [esi+0E0h]
805425bf 8bd8        mov ebx,eax
805425c1 25ff0f0000     and eax,0FFFh
805425c6 3b4708      cmp eax,dword ptr [edi+8]

 

通过MSR寄存器获得KiFastCallEntry地址


如同在windbg中一样,使用rdmsr指令获得MSR寄存器的内容,然后通过GDT获得段地址,计算出KiFastCallEntry的实际地址。

ULONG GetAddressOfKiFastCallEntry()
{
	ULONG Address = 0;
	_asm
	{
	jmp func_main  
vgdtr:            //开辟一段控件用于保存gdtr寄存器中的内容
        _emit 0x00  
        _emit 0x00  
        _emit 0x00  
        _emit 0x00  
        _emit 0x00  
        _emit 0x00  
        _emit 0x00  
        _emit 0x00  
func_main:  
	pushad
	mov ecx,0x174
	rdmsr
	mov ebx, eax        //获得段选择子
		
	sgdt vgdtr  	    //读取gdtr寄存器保存至vgdtr
        mov edx, vgdtr  
        add edx, 0x02  
        mov eax, [edx]      //从gdtr寄存器中获取GDT地址,之后按照GDT的格式计算出段地址
        add ebx, eax       
  
        mov edx, ebx  
        add edx, 0x07  
        mov eax, [edx]  
        shl eax, 24;  		//循环左移留下后2个字节
        mov edx, ebx  
        add edx, 0x02  
        mov ecx, [edx]  
        and ecx, 0x00FFFFFF   
        add eax, ecx        //计算得段地址
        mov ebx, eax  
  
        mov ecx, 0x176    //获得EIP
        rdmsr  
        add eax, ebx  
  
        mov Address, eax  
     popad
	}
	return Address;
}

 

 

栈回溯法获得KifastCallEntry地址  

这种方法是360开始使用的,原理是kifastcallentry通过call ebx调用目标例程,那么例程栈的ebp+4里面所存放的就是返回到KiFastCallEntry下一条指令的地址。

看一下在XP中kifastcallentry调用的反汇编:

;eax存放调用号,edi存放SSDT或者ShadowSSDT的地址
805425fd   8a0c18     mov cl,byte ptr [eax+ebx] 80542600   8b3f    mov edi,dword ptr [edi]   ;获得KeServiceDescriptorTable.ServiceTableBase地址 80542602   8b1c87   mov ebx,dword ptr [edi+eax*4]  ;根据服务号计算出在表中的调用地址, 80542605   2be1    sub esp,ecx 80542607   c1e902    shr ecx,2 8054260a   8bfc    mov edi,esp 8054260c   3b3534315680    cmp esi,dword ptr [nt!MmUserProbeAddress (80563134)] 80542612   0f83a8010000    jae nt!KiSystemCallExit2+0x9f (805427c0) 80542618   f3a5          rep movs dword ptr es:[edi],dword ptr [esi] 8054261a   ffd3          call ebx              ;调用例程 8054261c   8be5      mov esp,ebp            ;这里就是返回的地址

  

这里我们学习一下360的方法,获得ZwSetEvent的服务号,安装它的SSDT HOOK。再自己调用它

 

HANDLE g_FakeEventHandle = (HANDLE)0×288C58F1;  //这个我们自己定义的句柄用于辨别是不是需要的功能。

然后调用

ZwSetEvent(g_FakeEventHandle, NULL);

在hook例程中判断

if ( EventHandle != g_FakeEventHandle || ExGetPreviousMode()==UserMode )// 不是我们自己调用,或者调用来自UserMode,直接调用原函数

{

return ((MYZwSetEvent)O_NtSetEvent)(EventHandle,PreviousState);

}

在hook例程中获得KiFastCallEntry地址

_asm
{
push eax
xor eax,eax
lea eax, [ebp+4]    //回溯栈的地址
mov eax, [eax]
mov Address, eax
pop eax
}

可以看到,获得的地址是0x8054261c

我们用winbdg查看这个地址

kd> u 0x8054261c
nt!KiFastCallEntry+0xfc:
8054261c 8be5         mov esp,ebp
8054261e 648b0d24010000  mov ecx,dword ptr fs:[124h]
80542625 8b553c       mov edx,dword ptr [ebp+3Ch]
80542628 899134010000   mov dword ptr [ecx+134h],edx
nt!KiServiceExit:
8054262e fa cli
8054262f f7457000000200  test dword ptr [ebp+70h],20000h
80542636 7506        jne nt!KiServiceExit+0x10 (8054263e)
80542638 f6456c01      test byte ptr [ebp+6Ch],1

可以看到就是上面call ebx之后的内容,这样就找到了kifatcallentry的地址。

推荐阅读