标签:
最近一直在学习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的地址。
标签:
原文地址:http://www.cnblogs.com/Windogs/p/4626911.html