Windows内核:API调用-->系统调用

算是对之前实验的补充,依旧是xp系统

几个重要dll

kernel32.dll:管理内存,进程和线程相关函数

User32.dll:用户界面相关应用程序接口,创建窗口和发送消息等

GDI32.dll:画图,显示文本相关函数

Ntdll.dll:三环进零环相关

ReadProcessMemory

kernel32.dll里的ReadProcessMemory为例

调用了ntdll里的导出函数

.text:7C8021E5 FF 15 18 14 80 7C             call    ds:__imp__NtReadVirtualMemory@20 ; NtReadVirtualMemory(x,x,x,x,x)

ntdll里

.text:7C92D9E0 B8 BA 00 00 00                mov     eax, 0BAh                       ;BA编号对应内核函数NtReadVirtualMemory
.text:7C92D9E5 BA 00 03 FE 7F mov edx, 7FFE0300h
.text:7C92D9EA FF 12 call dword ptr [edx]
.text:7C92D9EC C2 14 00 retn 14h

7FFE0300h是啥

_KUSER_SHARED_DATA结构体:用户态的地址0x7FFE0000内核态的地址0xffdf0000共享一张物理页,用户态是只读的,内核态是可写的

kd> dt _KUSER_SHARED_DATA  0x7FFE0000
...
+0x300 SystemCall : 0x7c92e4f0

7FFE0300h处存在两种情况:KiFastSystemCall或者KiIntSystemCall(都是ntdll导出函数)

eax置0,执行cpuid指令会把处理器特征存在ecx edx,edx 第11为SEP为1则支持快速调用sysenter/sysexit指令为KiFastSystemCall

kd> u 0x7c92e4f0
7c92e4f0 8bd4 mov edx,esp//KiFastSystemCall
7c92e4f2 0f34 sysenter
7c92e4f4 c3 ret

否则为KiIntSystemCall

.text:7C92E500                               ; _DWORD __stdcall KiIntSystemCall()
.text:7C92E500 public _KiIntSystemCall@0
.text:7C92E500 _KiIntSystemCall@0 proc near ; DATA XREF: .text:off_7C923428↑o
.text:7C92E500
.text:7C92E500 arg_4= byte ptr 8
.text:7C92E500
.text:7C92E500 8D 54 24 08 lea edx, [esp+arg_4] ;32位用栈传参嘛,所以这里是系统调用参数地址
.text:7C92E504 CD 2E int 2Eh ; DOS 2+ internal - EXECUTE COMMAND
.text:7C92E504 ; DS:SI -> counted CR-terminated command string
.text:7C92E504
.text:7C92E506 C3 retn

int 0x2e和sysenter区别联系

相同点

从3环到0环都需要改变cs ss esp eip

不同点

int 2e

通过idtr寄存器,找到中断门描述符得到CS,EIP,再通过TR寄存器找到TSS取出ss和esp

image-20231103230557854

到0环执行内核函数KiSystemService

sysenter

通过MSR寄存器找到CS,EIP,SS,ESP(其中SS是用CS+8算出来的,这样只需要把他们的选择子相邻,减少寄存器数量),加载到寄存器是cpu自动完成的,和操作系统没关系,操作系统负责用rdmsr和wrmst来给msr寄存器赋正确值(手册第三卷5.87)

image-20231103230613180

kd> rdmsr 174   //查看CS

到0环执行内核函数KiFastCallEntry

KiSystemService

内核函数KiSystemService和KiFastCallEntry都在c:windows/system32下,非pae分页用的是ntoskrnl.exe,pae分页是ntkrnlpa.exe

保存现场

涉及到一些结构体

dt _Ktrap_frame 保存寄存器的结构体
dt _KPCR:KPCR中存储了CPU本身要用的一些重要数据:GDT、IDT以及线程相关的一些信息,一个cpu核一个
dd KeNumberProcessors 查看cpu核数
dd KiProcessorBlock 查看每个cpu的kpcr结构体中偏移0x120处的_KPRCB结构体的位置
dt _ETHREAD 线程相关结构体
.text:00466489 BB 30 00 00 00                mov     ebx, 30h ; '0'
.text:0046648E 66 8E E3 mov fs, bx

0x30按照段描述符拆分一下 00110 0 00,0环全局描述符表的第6个

image-20231103230641570

kd> dq @gdtr+8*6 l1
8003f030 ffc093df`f0000001

再拆分一下段描述符ffc093df f0000001得到基址ffdf f000

ffdf f000指向的就是kpcr结构体,

fs在三环时指向TEB结构体

.text:00466481 arg_0= dword ptr  4
.text:00466481
.text:00466481 push 0 ;push errcode,在此之前中断时cpu已经自动帮我们把ss esp eflags cs ip压栈
.text:00466483 push ebp
.text:00466484 push ebx
.text:00466485 push esi
.text:00466486 push edi
.text:00466487 push fs ; 保存到_ktrap_frame对应位置
.text:00466489 mov ebx, 30h ; '0'
.text:0046648E mov fs, bx
.text:00466491 assume fs:nothing
.text:00466491 push dword ptr ds:0FFDFF000h ; 保存kpcr结构体第一项_EXCEPTION_REGISTRATION_RECORD异常链表到ktrap_frame结构体
.text:00466497 mov dword ptr ds:0FFDFF000h, 0FFFFFFFFh ; 把第一项_EXCEPTION_REGISTRATION_RECORD异常链表置零
.text:004664A1 mov esi, ds:0FFDFF124h ; KPCR结构体里KPCRB+4的位置是当前CPU所执行线程的_ETHREAD结构体
.text:004664A7 push dword ptr [esi+140h] ; 保存_ETHREAD第一个成员_KTHREAD+0x140处为PreviousMode先前模式到fram结构体,1代表先前模式为3环,0代表0环
.text:004664AD sub esp, 48h ; esp指向_kTrap_Frame的起始位置
.text:004664B0 mov ebx, [esp+68h+arg_0] ; _Ktrap_frame-->SegCs
.text:004664B4 and ebx, 1
.text:004664B7 mov [esi+140h], bl ; _KTHREAD的PreviousMode赋值为cs寄存器特权级,1代表3环来的,0代表0环来的
.text:004664BD mov ebp, esp ; ebp=_kTrap_Frame首地址
.text:004664BF mov ebx, [esi+134h] ; ebx=KTHREAD-->TrapFrame
.text:004664C5 mov [ebp+3Ch], ebx
.text:004664C8 mov [esi+134h], ebp ; 老的TrapFrame的值新TrapFram-->Edx,然后将新的TrapFrame放到EHTREAD+134h的trapfram处
.text:004664CE cld
.text:004664CF mov ebx, [ebp+60h]
.text:004664D2 mov edi, [ebp+68h] ; 原来三环的ebp放到ebx里,三环eip放到edi中
.text:004664D5 mov [ebp+0Ch], edx ; KiIntSystemCall中edx保存参数地址,存放到trapfram的DbgArgPointer
.text:004664D8 mov dword ptr [ebp+8], 0BADB0D00h
.text:004664DF mov [ebp+0], ebx ; 原来三环ebp放到trapfram的DbgEbp
.text:004664E2 mov [ebp+4], edi ; 原来三环eip放到trapfram的DbgEip
.text:004664E5 test byte ptr [esi+2Ch], 0FFh ; _ETHREAD._KTHREAD.DebugActive
.text:004664E9 jnz Dr_kss_a ; 处于调试状态,值不等于0xff,则跳转,跳转位置主要是保存trapfram中的调试寄存器
.text:004664E9
.text:004664EF
.text:004664EF loc_4664EF: ; CODE XREF: Dr_kss_a+10↑j
.text:004664EF ; Dr_kss_a+7C↑j
.text:004664EF sti
.text:004664F0 jmp loc_4665CD ; jmp到_KiFastCallEntry函数里某个的位置,此时和KifastCallentry一样流程

调用内核函数

_KPCR.PrcbData(_KPRCB).CurrentThread(_KTHREAD)+0x0e0 处为ServiceTable,有下面两个table

image-20231103230718438

ServiceTable指向的是函数地址数组,每个成员四个字节

Count表示调用次数

ServiceLimit表示这张表有几个函数;

ArgumentTable指向对应函数有几个字节的参数,每个成员一个字节

通过eax索引号找到相关函数

image-20231103230739865

分析一下

.text:004665CD loc_4665CD:                             ; CODE XREF: _KiBBTUnexpectedRange+18↑j
.text:004665CD ; _KiSystemService+6F↑j
.text:004665CD mov edi, eax ; edi=传入系统服务号
.text:004665CF shr edi, 8 ; 右移8位
.text:004665D2 and edi, 30h ; 基本系统调用号都是小于0x1000
.text:004665D2 ; 窗口界面操作调用号大于0x1000定义在win32k.sys
.text:004665D2 ; 如果12位为1,与完后edi=0x10,否则就为0
.text:004665D5 mov ecx, edi
.text:004665D7 add edi, [esi+0E0h] ; KPCR.PrcbData(_KPRCB).CurrentThread(_KTHREAD)+0x0e0=ServiceTable
.text:004665D7 ; 如果edi是0x10(ServiceTable大小0x10)正好寻址到win32k.sys的ServiceTabl
.text:004665DD mov ebx, eax
.text:004665DF and eax, 0FFFh ; 保留调用号低12位,作为函数地址表的下标
.text:004665E4 cmp eax, [edi+8] ; ServiceTable.count比较
.text:004665E7 jnb _KiBBTUnexpectedRange ; 大于等于跳转
.text:004665E7
.text:004665ED cmp ecx, 10h ; 判断是哪张服务表
.text:004665F0 jnz short loc_46660C ; 上图绿色那张表跳转
.text:004665F0
.text:004665F2 mov ecx, ds:0FFDFF018h
.text:004665F8 xor ebx, ebx
.text:004665F8
.text:004665FA
.text:004665FA loc_4665FA: ; DATA XREF: _KiTrap0E+113↓o
.text:004665FA or ebx, [ecx+0F70h]
.text:00466600 jz short loc_46660C ; _KPRCB -> +0x518 KeSystemCalls++;
.text:00466600
.text:00466602 push edx
.text:00466603 push eax
.text:00466604 call ds:_KeGdiFlushUserBatch
.text:00466604
.text:0046660A pop eax
.text:0046660B pop edx
.text:0046660B
.text:0046660C
.text:0046660C loc_46660C: ; CODE XREF: _KiFastCallEntry+B0↑j
.text:0046660C ; _KiFastCallEntry+C0↑j
.text:0046660C inc dword ptr ds:0FFDFF638h ; _KPRCB -> +0x518 KeSystemCalls++;
.text:00466612 mov esi, edx ; edx=三环栈上参数地址
.text:00466614 mov ebx, [edi+0Ch] ; ebx=ServiceTabl.ParamTableBase
.text:00466617 xor ecx, ecx
.text:00466619 mov cl, [eax+ebx] ; cl=参数总字节数
.text:0046661C mov edi, [edi] ; edi=ServiceTable.函数数组
.text:0046661E mov ebx, [edi+eax*4] ; ebx=目标函数
.text:00466621 sub esp, ecx ; 提升堆栈
.text:00466623 shr ecx, 2 ; ecx/4=一次拷贝四个字节的拷贝次数
.text:00466626 mov edi, esp ; 拷贝目标地址
.text:00466628 cmp esi, ds:_MmUserProbeAddress ; 和用户能访问的最大地址范围比较,判断是否越界
.text:0046662E jnb loc_4667DC
.text:0046662E
.text:00466634
.text:00466634 loc_466634: ; CODE XREF: _KiFastCallEntry+2A0↓j
.text:00466634 ; DATA XREF: _KiTrap0E+109↓o
.text:00466634 rep movsd ; copy参数
.text:00466636 call ebx ; 调用
.text:00466636
.text:00466638
.text:00466638 loc_466638: ; CODE XREF: _KiFastCallEntry+2AB↓j
.text:00466638 ; DATA XREF: _KiTrap0E+129↓o
.text:00466638 ; _KiTrap0E+149↓o
.text:00466638 mov esp, ebp
.text:00466638
.text:0046663A
.text:0046663A loc_46663A: ; CODE XREF: _KiBBTUnexpectedRange+38↑j
.text:0046663A ; _KiBBTUnexpectedRange+43↑j
.text:0046663A mov ecx, ds:0FFDFF124h
.text:00466640 mov edx, [ebp+3Ch]
.text:00466643 mov [ecx+134h], edx
.text:00466643
.text:00466643 _KiFastCallEntry endp

系统服务描述符表

System Services Descriptor Table(SSDT)

typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
KSYSTEM_SERVICE_TABLE ntoskrnl; // ntoskrnl.exe 的服务函数
KSYSTEM_SERVICE_TABLE win32k; // win32k.sys 的服务函数(GDI32.dll/User32.dll 的内核支持)
KSYSTEM_SERVICE_TABLE notUsed1;
KSYSTEM_SERVICE_TABLE notUsed2;
} KSERVICE_TABLE_DESCRIPTOR, * PKSERVICE_TABLE_DESCRIPTOR;

KeServiceDescriptorTable是ntkrnlpa导出符号

KeServiceDescriptorTable处只有系统服务ntkrnlpa.exe对应的SystemServiceTable

kd> dd KeServiceDescriptorTable
80553fa0 80502b8c 00000000 0000011c 80503000
80553fb0 00000000 00000000 00000000 00000000
80553fc0 00000000 00000000 00000000 00000000
80553fd0 00000000 00000000 00000000 00000000

win32k需要通过SSDT Shadow来访问到,这是一个未导出的结构,就可以获取到两表了

kd> dd  KeServiceDescriptorTableShadow
80553f60 80502b8c 00000000 0000011c 80503000
80553f70 bf999b80 00000000 0000029b bf99a890
80553f80 00000000 00000000 00000000 00000000
80553f90 00000000 00000000 00000000 0000000

Reference

https://www.braveenough.cn/2022/01/30/%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8/