内核提供的服务都通过NTDLL模块被应用程序使用,使用方法为通过调用一组系统dll中的API,间接的通过ntdll的存根函数来调用内核提供的系统服务
系统的服务例程运行在内核模式下,但是应用程序调用是在用户模式,如果想要使用则需要进行模式切换,这个切换工作在系统模块NTDLL中已经实现,依赖于硬件体系结构
进入R0
1.通过中断门进入R0
中断号为0x2e,通过“ int 0x2e”进入内核,在IDT 中查找2e 表项,IDTEntry 包含了一个段选择符和中断例程的段内偏移,所以,处理器还需要在GDT 中再查找一次表项,得到段选择符指定的段的虚拟基地址。段基地址加上中断例程偏移,最终得到中断例程的虚拟地址。
kd> dq idtr l40
kd> dq idtr l40
...
8003f540 00000000`00080000 00000000`00080000
8003f550 8053ee00`0008e9de 8053ee00`0008eae0
8003f560 8053ee00`0008ec80 8053ee00`0008f5c0
8003f570 8053ee00`0008e481 80548e00`00081780
//!idt -a 可以直接查看idt各项的地址
//8003f560 2e ,选择子为8,偏移为8053e481
kd> dq gdtr l20
8003f000 00000000`00000000 00cf9b00`0000ffff //base 0
uf 8053e481
通过图中可以形象的观察出通过中断进入内核的工作过程,尽管只需要一条指令,但是模式切换的过程中涉及多次的内存访问和权限检查以及查表,导致开销很大,从Pentium II 处理器开始,引入了新的切换方式,sysenter/syseixt
2.通过sysenter进入:
sysenter通过使用三个MSR寄存器指定跳转目标和栈位置
//查看
kd> rdmsr 174 //msr地址
msr[174] = 00000000`00000008
sysenter内部逻辑
- IA32_SYSENTER_CS和IA32_SYSENTER_EIP装载cs和eip寄存器中
- IA32_SYSENTER_CS+8和IA32_SYSENTER_ESP装载cs和esp寄存器中
- 切换特权级0
- 清除EFLAGS的VM标志
- 执行eip
注意: 代码段和栈段在GDT中需要相邻。即:(cs)GDTR+8x,(ss)GDTR+8x+8;代码段描述符需要指定基地址为0、段范围达4GB的特权级0的段,有可执行可读权限;栈描述符不同点是需要具有读写访问和向上拓展的权限。
sysexit内部逻辑 - IA32_SYSENTER_CS+16装载到CS寄存器
- edx寄存器里的指针装载到eip寄存器
- IA32_SYSENTER_CS+24装载ss寄存器
- 将ecx 寄存器中的指针装载到esp 寄存器中
- 切换特权级3
- 执行eip
注意: sysexit要求IA32_SYSENTER_CS+16和IA32_SYSENTER_CS+24在GDT中的位置紧跟在sysenter中使用的项后
_KUSER_SHARED_DATA
ReadVirtualMemory 逆向
...
push [ebp+lpBaseAddress] ; BaseAddress
push [ebp+hProcess] ; ProcessHandle
call ds:__imp__NtReadVirtualMemory@20 ; NtReadVirtualMemory(x,x,x,x,x)
...
//在导入表查看NtReadVirtualMemory,可以看到是在NTDLL导入的NtReadVirtualMemory
7C801418 NtReadVirtualMemory ntdll
//在NTDLL查找
mov eax, 0BAh ; NtReadVirtualMemory
mov edx, 7FFE0300h
call dword ptr [edx]
retn 14h
//eax放入一个编号,edx放入一个地址,然后call这个地址 (SystemCall地址)
用户空间和内核空间有一块共享区域,_KUSER_SHARED_DATA,R0和R3的线性地址映射在同一物理页上。大小为 4 KB
内核起始地址 | 内核结束地址 | 用户起始地址 | 用户结束地址 | |
---|---|---|---|---|
32 系统 | 0xFFDF00000 | xFFDF0FFF | 0x7FFE00000 | x7FFE0FFF |
64 系统 | 0xFFFFF780`000000000 | xFFFFF780`00000FFF | 0x7FFE00000 | x7FFE0FFF |
通过挂载指定进程查看
.process 861e45e0
kd> dt _KUSER_SHARED_DATA 7FFE0000
ntdll!_KUSER_SHARED_DATA
+0x000 TickCountLow : 0x612c
+0x004 TickCountMultiplier : 0xfa00000
+0x008 InterruptTime : _KSYSTEM_TIME
+0x014 SystemTime : _KSYSTEM_TIME
+0x020 TimeZoneBias : _KSYSTEM_TIME
+0x02c ImageNumberLow : 0x14c
+0x02e ImageNumberHigh : 0x14c
+0x030 NtSystemRoot : [260] 0x43
+0x238 MaxStackTraceDepth : 0
+0x23c CryptoExponent : 0
+0x240 TimeZoneId : 0
+0x244 Reserved2 : [8] 0
+0x264 NtProductType : 1 ( NtProductWinNt )
+0x268 ProductTypeIsValid : 0x1 ''
+0x26c NtMajorVersion : 5
+0x270 NtMinorVersion : 1
+0x274 ProcessorFeatures : [64] ""
+0x2b4 Reserved1 : 0x7ffeffff
+0x2b8 Reserved3 : 0x80000000
+0x2bc TimeSlip : 0
+0x2c0 AlternativeArchitecture : 0 ( StandardDesign )
+0x2c8 SystemExpirationDate : _LARGE_INTEGER 0x0
+0x2d0 SuiteMask : 0x110
+0x2d4 KdDebuggerEnabled : 0x3 ''
+0x2d5 NXSupportPolicy : 0x2 ''
+0x2d8 ActiveConsoleId : 0
+0x2dc DismountCount : 0
+0x2e0 ComPlusPackage : 0xffffffff
+0x2e4 LastSystemRITEventTickCount : 0x1ed87
+0x2e8 NumberOfPhysicalPages : 0x3ff6c
+0x2ec SafeBootMode : 0 ''
+0x2f0 TraceLogging : 0
+0x2f8 TestRetInstruction : 0xc3
+0x300 SystemCall : 0x7c92e4f0 //调用地址
+0x304 SystemCallReturn : 0x7c92e4f4
+0x308 SystemCallPad : [3] 0
+0x320 TickCount : _KSYSTEM_TIME
+0x320 TickCountQuad : 0
+0x330 Cookie : 0x31c5ba42
//当前支持sysenter所以里面存的是kifastsystemcall地址,查看systemcall内容
kd> uf 0x7c92e4f0
ntdll!KiFastSystemCall:
7c92e4f0 8bd4 mov edx,esp
7c92e4f2 0f34 sysenter
7c92e4f4 c3 ret
如果当前CPU不支持sysenter,则里面存放的是KiIntSystemCall函数的地址
_KiIntSystemCall@0 proc near
arg_4= byte ptr 8
lea edx, [esp+arg_4]
int 2Eh ; DOS 2+ internal - EXECUTE COMMAND
; DS:SI -> counted CR-terminated command string
retn
_KiIntSystemCall@0 endp
总结一下,应用程序通过系统dll调用时,会通过ntdll找到系统存根函数,然后判断是否支持快速调用,通过KiIntSystemCall/KiFastSystemCall进入,如果是由int指令进入,则KiSystemService获得控制权(IDT中描述符指定);如果是由sysenter进入,则KiFastCallEntry获得控制权(IA32_SYSENTER_EIP指定)。
返回R3
从R0返回R3,则是由_KiSystemCallExit返回,在_KiSystemCallExit中,会判断是否支持快速调用,通过不同的方式进行返回
_KiSystemCallExit:
iretd ; 中断返回
_KiSystemCallExit2:
test dword ptr [esp+8], EFLAGS_TF ; 测试陷阱标志
jne short _KiSystemCallExit ; 若是陷阱,则转到_KiSystemCallExit
pop edx ; 弹出eip,此EIP是_KiFastCallEntry填充的
add esp, 4 ; 移除cs
and dword ptr [esp], NOT EFLAGS_INTERRUPT_MASK ; 禁止eflags中的中断标志
popfd
pop ecx ; 弹出esp
sti ; 恢复中断,因为sysexit指令不会恢复中断标志
sysexit ; 返回用户模式
判断是否支持快速调用可以通过指令 cupid来查看,当通过eax=1 来执行cpuid 指令时,处理器的特征信息被存放在ecx 和edx 寄存器中,其中edx 包含了一个SEP 位(SysEnter/SysExit Present,第11位),该位指明了当前处理器是否支持sysenter/sysexit 指令。