系统调用

API函数的调用过程

Application Programming Interface,简称API函数。

Windows有多少个API:主要是存放在C:/WINDOWS/system32下的所有dll

几个重要的DLL

  • Kernel32.dll:最核心的功能模块,比如管理内存,进程,线程相关的函数等。
  • User32.dll:是Windows用户界面相关应用程序接口,如创建窗口和发送消息等。
  • GDI32.dll:全称是Graphical Device Interface(图形设备接口),包含用于画图和显示文本的函数。比如要显示一个程序窗口,就调用其中的函数来画这个窗口
  • Ntdll.dll:大多数API都会通过这个DLL进入内核(0环)

3环部分

分析ReadProcessMemory

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
.text:7C8021D0 ; Exported entry 682. ReadProcessMemory
.text:7C8021D0
.text:7C8021D0 ; =============== S U B R O U T I N E =======================================
.text:7C8021D0
.text:7C8021D0 ; Attributes: bp-based frame
.text:7C8021D0
.text:7C8021D0 ; BOOL __stdcall ReadProcessMemory(HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesRead)
.text:7C8021D0 public _ReadProcessMemory@20
.text:7C8021D0 _ReadProcessMemory@20 proc near ; CODE XREF: GetProcessVersion(x)+2F12F↓p
.text:7C8021D0 ; GetProcessVersion(x)+2F14E↓p ...
.text:7C8021D0
.text:7C8021D0 hProcess = dword ptr 8
.text:7C8021D0 lpBaseAddress = dword ptr 0Ch
.text:7C8021D0 lpBuffer = dword ptr 10h
.text:7C8021D0 nSize = dword ptr 14h
.text:7C8021D0 lpNumberOfBytesRead= dword ptr 18h
.text:7C8021D0
.text:7C8021D0 mov edi, edi
.text:7C8021D2 push ebp
.text:7C8021D3 mov ebp, esp
.text:7C8021D5 lea eax, [ebp+nSize]
.text:7C8021D8 push eax ; NumberOfBytesRead
.text:7C8021D9 push [ebp+nSize] ; NumberOfBytesToRead
.text:7C8021DC push [ebp+lpBuffer] ; Buffer
.text:7C8021DF push [ebp+lpBaseAddress] ; BaseAddress
.text:7C8021E2 push [ebp+hProcess] ; ProcessHandle
.text:7C8021E5 call ds:__imp__NtReadVirtualMemory@20 ; NtReadVirtualMemory(x,x,x,x,x)//跳到ntdll.dll中的NtReadVirtualMemory函数
.text:7C8021EB mov ecx, [ebp+lpNumberOfBytesRead]
.text:7C8021EE test ecx, ecx
.text:7C8021F0 jnz short loc_7C8021FD
.text:7C8021F2
.text:7C8021F2 loc_7C8021F2: ; CODE XREF: ReadProcessMemory(x,x,x,x,x)+32↓j
.text:7C8021F2 test eax, eax
.text:7C8021F4 jl short loc_7C802204
.text:7C8021F6 xor eax, eax
.text:7C8021F8 inc eax
.text:7C8021F9
.text:7C8021F9 loc_7C8021F9: ; CODE XREF: ReadProcessMemory(x,x,x,x,x)+3C↓j
.text:7C8021F9 pop ebp
.text:7C8021FA retn 14h
.text:7C8021FD ; ---------------------------------------------------------------------------
.text:7C8021FD
.text:7C8021FD loc_7C8021FD: ; CODE XREF: ReadProcessMemory(x,x,x,x,x)+20↑j
.text:7C8021FD mov edx, [ebp+nSize]
.text:7C802200 mov [ecx], edx
.text:7C802202 jmp short loc_7C8021F2
.text:7C802204 ; ---------------------------------------------------------------------------
.text:7C802204
.text:7C802204 loc_7C802204: ; CODE XREF: ReadProcessMemory(x,x,x,x,x)+24↑j
.text:7C802204 push eax ; Status
.text:7C802205 call _BaseSetLastNTError@4 ; BaseSetLastNTError(x)
.text:7C80220A xor eax, eax
.text:7C80220C jmp short loc_7C8021F9
.text:7C80220C _ReadProcessMemory@20 endp
//通篇流程基本上就是调用了NtReadVirtualMemory函数

ReadProcessMemory->NtReadVirtualMemory

ntdll.dll中的NtReadVirtualMemory函数

1
2
3
4
5
6
7
8
9
.text:7C92D9E0 ; __stdcall NtReadVirtualMemory(x, x, x, x, x)
.text:7C92D9E0 public _NtReadVirtualMemory@20
.text:7C92D9E0 _NtReadVirtualMemory@20 proc near ; CODE XREF: LdrFindCreateProcessManifest(x,x,x,x,x)+1CC↓p
.text:7C92D9E0 ; LdrCreateOutOfProcessImage(x,x,x,x)+7C↓p ...
.text:7C92D9E0 mov eax, 0BAh ; NtReadVirtualMemory//操作系统内核函数的编号
.text:7C92D9E5 mov edx, 7FFE0300h//存放函数地址的地址,这个值决定了我们用什么方式进入零环
.text:7C92D9EA call dword ptr [edx]
.text:7C92D9EC retn 14h
.text:7C92D9EC _NtReadVirtualMemory@20 endp

NtReadVirtualMemory就是提供一个编号,找到一个函数,通过这个函数进零环。

7FFE0300h这个地址存放着系统调用进零环的函数地址

课后练习:

自己编写WriteProcessMemory函数(不使用任何DLL,直接调用0环函数),并在代码中使用。

重写API的意义:自己实现API,可避免三环恶意挂钩

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
#include"stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

void __stdcall myWriteProcessMemory(
HANDLE hProcess,
LPVOID lpBaseAddress,
LPCVOID lpBuffer,
SIZE_T nSize,
SIZE_T *lpNumberOfBytesWritten
)
{
__asm
{
push lpNumberOfBytesWritten;
push nSize;
push lpBuffer;
push lpBaseAddress;
push hProcess;
mov eax,0x115;//写内存对应的系统服务号
lea edx,dword ptr [esp];
int 0x2E;
add esp,0x14;//平栈
}
}

void main()
{
int a=300;
SIZE_T haveRead;
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,NULL,1520);
myWriteProcessMemory(hProcess,(LPVOID)0x12ff7c,&a,4,&haveRead);
system("pause");
}

目标修改进程:image-20210902143222155

image-20210902143202632

3环进0环

_KUSER_SHARED_DATA结构

  1. 在User层和Kernel层分别定义了一个_KUSER_SHARED_DATA结构区域,用于User层和Kernel层共享某些数据。
  2. 它们使用固定的地址值映射,_KUSER_SHARED_DATA结构区域在User和Kernel层地址分别为:
    • User层地址为:0x7FFE0000
    • Kernel层地址为:0xFFDF0000

0x7FFE0000和0xFFDF0000指向的是同一个物理页,但User层只读,Kernel可写

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
ntdll!_KUSER_SHARED_DATA
+0x000 TickCountLow : Uint4B
+0x004 TickCountMultiplier : Uint4B
+0x008 InterruptTime : _KSYSTEM_TIME
+0x014 SystemTime : _KSYSTEM_TIME
+0x020 TimeZoneBias : _KSYSTEM_TIME
+0x02c ImageNumberLow : Uint2B
+0x02e ImageNumberHigh : Uint2B
+0x030 NtSystemRoot : [260] Uint2B
+0x238 MaxStackTraceDepth : Uint4B
+0x23c CryptoExponent : Uint4B
+0x240 TimeZoneId : Uint4B
+0x244 Reserved2 : [8] Uint4B
+0x264 NtProductType : _NT_PRODUCT_TYPE
+0x268 ProductTypeIsValid : UChar
+0x26c NtMajorVersion : Uint4B//NT内核的主版本号
+0x270 NtMinorVersion : Uint4B//NT内核的次版本号
+0x274 ProcessorFeatures : [64] UChar
+0x2b4 Reserved1 : Uint4B
+0x2b8 Reserved3 : Uint4B
+0x2bc TimeSlip : Uint4B
+0x2c0 AlternativeArchitecture : _ALTERNATIVE_ARCHITECTURE_TYPE
+0x2c8 SystemExpirationDate : _LARGE_INTEGER
+0x2d0 SuiteMask : Uint4B
+0x2d4 KdDebuggerEnabled : UChar
+0x2d5 NXSupportPolicy : UChar
+0x2d8 ActiveConsoleId : Uint4B
+0x2dc DismountCount : Uint4B
+0x2e0 ComPlusPackage : Uint4B
+0x2e4 LastSystemRITEventTickCount : Uint4B
+0x2e8 NumberOfPhysicalPages : Uint4B
+0x2ec SafeBootMode : UChar
+0x2f0 TraceLogging : Uint4B
+0x2f8 TestRetInstruction : Uint8B
+0x300 SystemCall : Uint4B//系统调用的方式,存放KiFastSystemCall或KiIntSystemCall地址
+0x304 SystemCallReturn : Uint4B//快速调用的返回地址
+0x308 SystemCallPad : [3] Uint8B
+0x320 TickCount : _KSYSTEM_TIME
+0x320 TickCountQuad : Uint8B
+0x330 Cookie : Uint4B

//实例
ntdll!_KUSER_SHARED_DATA
+0x000 TickCountLow : 0xb752
+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//NT内核的主版本号
+0x270 NtMinorVersion : 1//NT内核的次版本号
+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 : 0xadbe8
+0x2e8 NumberOfPhysicalPages : 0x1ff6a
+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 : 0xbde19661

0x7FFE0300存储的到底是什么?

两种情况:是否支持快速调用

当通过eax=1来执行cpuid指令时,处理器的特征信息被放在ecx和edx寄存器中,其中edx包含了一个SEP位(0开始第11位),该位指明了当前处理器是否支持systenter/sysexit指令

  • 1==支持——>ntdll.dll!KiFastSystemCall()
  • 0==不支持—>ntdll.dll!KiIntSystemCall()

0x7FFE0300存储的就是上诉两个函数地址之一

3环进0环需要更改哪些寄存器

  1. CS的权限由3变为0,意味着需要新的CS
  2. SS与CS的权限永远一致,因此需要新的SS
  3. 权限发生切换的时候,堆栈也一定会切换,需要新的ESP
  4. 进0环后代码的位置,因此需要EIP

p.s. windbg进入指定进程空间命令:

1
.process 0xXXXXXXXX(!process 0 0遍历出来的每个PROCESS的第一个数值)

KiFastSystemCall与KiIntSystemCall的区别

KiIntSystemCall

1
2
3
4
5
6
7
8
9
10
11
12
//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 //eax是内核函数的编号,系统调用号
.text:7C92E500 lea edx, [esp+arg_4]//edx说明参数在哪里
.text:7C92E504 int 2Eh ; DOS 2+ internal - EXECUTE COMMAND//中断门进内核
.text:7C92E504 ; DS:SI -> counted CR-terminated command string
.text:7C92E506 retn
.text:7C92E506 _KiIntSystemCall@0 endp

分析INT 0x2E进0环

  1. 在IDT表中找到0x2E

    image-20210831143046585

  2. 分析CS/SS/ESP/EIP

    ee表明是个中断门描述符,CS是0x0008,EIP为0x8053E481

    image-20210831155837942

  3. 分析EIP是什么

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //只取开头部分
    kd> u 8053e481
    nt!KiSystemService:
    8053e481 6a00 push 0
    8053e483 55 push ebp
    8053e484 53 push ebx
    8053e485 56 push esi
    8053e486 57 push edi
    8053e487 0fa0 push fs
    8053e489 bb30000000 mov ebx,30h
    8053e48e 668ee3 mov fs,bx

此时KiSystemService函数才真正是内核函数


KiFastSystemCall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//KiFastSystemCall
.text:7C92E4F0 ; _DWORD __stdcall KiFastSystemCall()
.text:7C92E4F0 public _KiFastSystemCall@0
.text:7C92E4F0 _KiFastSystemCall@0 proc near ; DATA XREF: .text:off_7C923428↑o
.text:7C92E4F0 mov edx, esp//3环栈顶esp放到edx中 //系统调用号在eax寄存器
.text:7C92E4F2 sysenter//快速调用进内核
.text:7C92E4F2 _KiFastSystemCall@0 endp
.text:7C92E4F2
.text:7C92E4F4 ; Exported entry 42. KiFastSystemCallRet
.text:7C92E4F4
.text:7C92E4F4 ; =============== S U B R O U T I N E =======================================
.text:7C92E4F4
.text:7C92E4F4
.text:7C92E4F4 ; _DWORD __stdcall KiFastSystemCallRet()
.text:7C92E4F4 public _KiFastSystemCallRet@0
.text:7C92E4F4 _KiFastSystemCallRet@0 proc near ; DATA XREF: .text:off_7C923428↑o
.text:7C92E4F4 retn
.text:7C92E4F4 _KiFastSystemCallRet@0 endp

什么叫快速调用?

  • 中断门进0环,需要的CS,EIP在IDT表中,需要查内存(SS与ESP由TSS提供)
  • 而CPU如果支持sysenter指令时,操作系统会提前将CS/SS/ESP/EIP的值存储在MSR寄存器中,sysenter指令执行时,CPU会将MSR寄存器中的值直接写入相关寄存器,没有读内存的过程,所以叫快速调用,但本质都是切换CS,SS,ESP,EIP。

分析sysenter进0环

执行sysenter指令之前,操作系统必须指定0环的CS段,SS段,EIP以及ESP。sysenter是由CPU设计规定并操作的

MSR 地址
IA32_SYSENTER_CS 174H
IA32_SYSENTER_ESP 175H
IA32_SYSENTER_EIP 176H

SS是通过【IA32_SYSENTER_CS+8】的段选择子来计算出来的。即CS是0x08指向的段,SS就是gdt中0x08指向的段描述符的下一个段描述符。

vt,MSR结构非常重要。

intel文档上说该指令是为了快速的系统调用(从ring3 到ring0),在执行sysenter之前了,必须要设置好ring0中的代码段、入口函数地址、ring0栈所在的段、栈指针,设置方式是写下列MSRs:

  • IA32_SYSENTER_CS:32bit,前16个字节为代码段选择符,其确定的索引+1为栈段的索引,所以在全局描述符表中这两个必须相连。
  • IA32_SYSENTER_EIP:32bit,入口指令地址。
  • IA32_SYSENTER_ESP:32bit,内核栈地址。

可以通过RDMSR/WRMST来进行读写(操作系统使用WRMST写该寄存器):

image-20210831145044898

参考:Intel白皮书第二卷(搜索sysenter)

当执行SYSENTER,处理器会做下面的动作:

  1. 从IA32_SYSENTER_CS从取出段选择子加载到CS中。

  2. 从IA32_SYSENTER_EIP取出指令指针放到EIP中

  3. 将IA32_SYSENTER_CS的值加上8,将其结果加载到SS中。

  4. 从IA32_SYSENTER_ESP取出堆栈指针放到ESP寄存器中

上述步骤相当于切换到0环的_KiFastCallEntry函数

EIP指向的代码:

1
2
3
4
5
6
7
8
9
10
11
//只取开头部分
kd> u 8053e540
nt!KiFastCallEntry:
8053e540 b923000000 mov ecx,23h
8053e545 6a30 push 30h
8053e547 0fa1 pop fs
8053e549 8ed9 mov ds,cx
8053e54b 8ec1 mov es,cx
8053e54d 8b0d40f0dfff mov ecx,dword ptr ds:[0FFDFF040h]
8053e553 8b6104 mov esp,dword ptr [ecx+4]
8053e556 6a23 push 23h

可见,进入了KiFastCallEntry内核函数。

API进零环的两种方式总结

  • API通过中断门进0环
    1. 固定中断号为0x2E
    2. CS/EIP由门描述符提供,ESP/SS由TSS提供
    3. 进入0环后执行的内核函数:NT!KiStstemService
  • API通过sysenter指令进入0环
    1. CS/ESP/EIP由MSR寄存器提供(SS是算出来的)(实际上逆向时ESP还是由TSS中来)
    2. 进入0环后执行的内核函数:NT!KiFastCallEntry

winXP内核模块:ntoskrnl.exe(10-10-12分页)/ntkrnlpa.exe(2-9-9-12分页)

image-20210902143436444

KiFastSystemCall方式三环进0环,TerminateThread API进入流程(未记录返回部分):

image-20211028194257701

思考

自己实现通过中断门直接调用内核函数

通过IDA找到KiStstemService和KiFastCallEntry函数并分析

  1. 进0环后,原来的寄存器存在哪里?
  2. 如何根据系统调用号(eax中存储)找到要执行的内核函数?
  3. 调用时参数时存储到3环的堆栈,如何传递给内核函数?
  4. 两种调用方式分别是如何返回到3环的

后续的章节会展开讲上面的点。

内核结构体介绍

  1. 储存3环寄存器信息结构体:_Ktrap_frame结构体
  2. 描述线程相关信息结构体:_ETHREAD和_KTHREAD
  3. 描述当前CPU状态的结构体:_KPCR

_Ktrap_frame结构体

陷阱帧

无论是通过sysenter还是通过中断门进入零环,三环所有寄存器都会存在这个结构中

该结构是零环结构体,由操作系统进行维护

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
kd> dt _Ktrap_frame
ntdll!_KTRAP_FRAME
+0x000 DbgEbp : Uint4B
+0x004 DbgEip : Uint4B
+0x008 DbgArgMark : Uint4B
+0x00c DbgArgPointer : Uint4B
+0x010 TempSegCs : Uint4B
+0x014 TempEsp : Uint4B
+0x018 Dr0 : Uint4B
+0x01c Dr1 : Uint4B
+0x020 Dr2 : Uint4B
+0x024 Dr3 : Uint4B
+0x028 Dr6 : Uint4B
+0x02c Dr7 : Uint4B
+0x030 SegGs : Uint4B
+0x034 SegEs : Uint4B
+0x038 SegDs : Uint4B
+0x03c Edx : Uint4B
+0x040 Ecx : Uint4B
+0x044 Eax : Uint4B
+0x048 PreviousPreviousMode : Uint4B
+0x04c ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x050 SegFs : Uint4B
+0x054 Edi : Uint4B
+0x058 Esi : Uint4B
+0x05c Ebx : Uint4B
+0x060 Ebp : Uint4B
+0x064 ErrCode : Uint4B
+0x068 Eip : Uint4B
+0x06c SegCs : Uint4B
+0x070 EFlags : Uint4B
+0x074 HardwareEsp : Uint4B
+0x078 HardwareSegSs : Uint4B
+0x07c V86Es : Uint4B
+0x080 V86Ds : Uint4B
+0x084 V86Fs : Uint4B
+0x088 V86Gs : Uint4B

image-20210831191749044

  • KiFastSystemCall由0x78开始往上压栈。(sysenter)
  • KiIntSystemCall由0x64开始往上压栈。(中断)

THREAD线程相关的结构体

image-20210831191949142

_ETHREAD结构体

每个线程在零环都有一个对应的_ETHREAD结构体

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
ntdll!_ETHREAD
+0x000 Tcb : _KTHREAD
+0x1c0 CreateTime : _LARGE_INTEGER
+0x1c0 NestedFaultCount : Pos 0, 2 Bits
+0x1c0 ApcNeeded : Pos 2, 1 Bit
+0x1c8 ExitTime : _LARGE_INTEGER
+0x1c8 LpcReplyChain : _LIST_ENTRY
+0x1c8 KeyedWaitChain : _LIST_ENTRY
+0x1d0 ExitStatus : Int4B
+0x1d0 OfsChain : Ptr32 Void
+0x1d4 PostBlockList : _LIST_ENTRY
+0x1dc TerminationPort : Ptr32 _TERMINATION_PORT
+0x1dc ReaperLink : Ptr32 _ETHREAD
+0x1dc KeyedWaitValue : Ptr32 Void
+0x1e0 ActiveTimerListLock : Uint4B
+0x1e4 ActiveTimerListHead : _LIST_ENTRY
+0x1ec Cid : _CLIENT_ID
+0x1f4 LpcReplySemaphore : _KSEMAPHORE
+0x1f4 KeyedWaitSemaphore : _KSEMAPHORE
+0x208 LpcReplyMessage : Ptr32 Void
+0x208 LpcWaitingOnPort : Ptr32 Void
+0x20c ImpersonationInfo : Ptr32 _PS_IMPERSONATION_INFORMATION
+0x210 IrpList : _LIST_ENTRY
+0x218 TopLevelIrp : Uint4B
+0x21c DeviceToVerify : Ptr32 _DEVICE_OBJECT
+0x220 ThreadsProcess : Ptr32 _EPROCESS
+0x224 StartAddress : Ptr32 Void
+0x228 Win32StartAddress : Ptr32 Void
+0x228 LpcReceivedMessageId : Uint4B
+0x22c ThreadListEntry : _LIST_ENTRY
+0x234 RundownProtect : _EX_RUNDOWN_REF
+0x238 ThreadLock : _EX_PUSH_LOCK
+0x23c LpcReplyMessageId : Uint4B
+0x240 ReadClusterSize : Uint4B
+0x244 GrantedAccess : Uint4B
+0x248 CrossThreadFlags : Uint4B
+0x248 Terminated : Pos 0, 1 Bit
+0x248 DeadThread : Pos 1, 1 Bit
+0x248 HideFromDebugger : Pos 2, 1 Bit
+0x248 ActiveImpersonationInfo : Pos 3, 1 Bit
+0x248 SystemThread : Pos 4, 1 Bit
+0x248 HardErrorsAreDisabled : Pos 5, 1 Bit
+0x248 BreakOnTermination : Pos 6, 1 Bit
+0x248 SkipCreationMsg : Pos 7, 1 Bit
+0x248 SkipTerminationMsg : Pos 8, 1 Bit
+0x24c SameThreadPassiveFlags : Uint4B
+0x24c ActiveExWorker : Pos 0, 1 Bit
+0x24c ExWorkerCanWaitUser : Pos 1, 1 Bit
+0x24c MemoryMaker : Pos 2, 1 Bit
+0x250 SameThreadApcFlags : Uint4B
+0x250 LpcReceivedMsgIdValid : Pos 0, 1 Bit
+0x250 LpcExitThreadCalled : Pos 1, 1 Bit
+0x250 AddressSpaceOwner : Pos 2, 1 Bit
+0x254 ForwardClusterOnly : UChar
+0x255 DisablePageFaultClustering : UChar

_KTHREAD结构体

_ETHRAD结构体中的子结构体,存储的都是相对比较重要的信息:

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
ntdll!_KTHREAD
+0x000 Header : _DISPATCHER_HEADER
+0x010 MutantListHead : _LIST_ENTRY
+0x018 InitialStack : Ptr32 Void
+0x01c StackLimit : Ptr32 Void
+0x020 Teb : Ptr32 Void
+0x024 TlsArray : Ptr32 Void
+0x028 KernelStack : Ptr32 Void
+0x02c DebugActive : UChar
+0x02d State : UChar
+0x02e Alerted : [2] UChar
+0x030 Iopl : UChar
+0x031 NpxState : UChar
+0x032 Saturation : Char
+0x033 Priority : Char
+0x034 ApcState : _KAPC_STATE
+0x04c ContextSwitches : Uint4B
+0x050 IdleSwapBlock : UChar
+0x051 Spare0 : [3] UChar
+0x054 WaitStatus : Int4B
+0x058 WaitIrql : UChar
+0x059 WaitMode : Char
+0x05a WaitNext : UChar
+0x05b WaitReason : UChar
+0x05c WaitBlockList : Ptr32 _KWAIT_BLOCK
+0x060 WaitListEntry : _LIST_ENTRY
+0x060 SwapListEntry : _SINGLE_LIST_ENTRY
+0x068 WaitTime : Uint4B
+0x06c BasePriority : Char
+0x06d DecrementCount : UChar
+0x06e PriorityDecrement : Char
+0x06f Quantum : Char
+0x070 WaitBlock : [4] _KWAIT_BLOCK
+0x0d0 LegoData : Ptr32 Void
+0x0d4 KernelApcDisable : Uint4B
+0x0d8 UserAffinity : Uint4B
+0x0dc SystemAffinityActive : UChar
+0x0dd PowerState : UChar
+0x0de NpxIrql : UChar
+0x0df InitialNode : UChar
+0x0e0 ServiceTable : Ptr32 Void
+0x0e4 Queue : Ptr32 _KQUEUE
+0x0e8 ApcQueueLock : Uint4B
+0x0f0 Timer : _KTIMER
+0x118 QueueListEntry : _LIST_ENTRY
+0x120 SoftAffinity : Uint4B
+0x124 Affinity : Uint4B
+0x128 Preempted : UChar
+0x129 ProcessReadyQueue : UChar
+0x12a KernelStackResident : UChar
+0x12b NextProcessor : UChar
+0x12c CallbackStack : Ptr32 Void
+0x130 Win32Thread : Ptr32 Void
+0x134 TrapFrame : Ptr32 _KTRAP_FRAME
+0x138 ApcStatePointer : [2] Ptr32 _KAPC_STATE
+0x140 PreviousMode : Char//先前模式,从用户模式调用Native API则previous mode是1,如果从内核模式调用Native API则previous mode是0
+0x141 EnableStackSwap : UChar
+0x142 LargeStack : UChar
+0x143 ResourceIndex : UChar
+0x144 KernelTime : Uint4B
+0x148 UserTime : Uint4B
+0x14c SavedApcState : _KAPC_STATE
+0x164 Alertable : UChar
+0x165 ApcStateIndex : UChar
+0x166 ApcQueueable : UChar
+0x167 AutoAlignment : UChar
+0x168 StackBase : Ptr32 Void
+0x16c SuspendApc : _KAPC
+0x19c SuspendSemaphore : _KSEMAPHORE
+0x1b0 ThreadListEntry : _LIST_ENTRY
+0x1b8 FreezeCount : Char
+0x1b9 SuspendCount : Char
+0x1ba IdealProcessor : UChar
+0x1bb DisableBoost : UChar

CPU相关结构体

CPU相关windbg指令

查看CPU数量
1
dd KeNumberProcessors

image-20210831151847441

表示一核

查看KPRCB存在哪里
1
dd KiProcessorBlock

image-20210831152107988

如果有两个核,那么就会出现两个地址,上图由于只有一个核所以还有一个地址。

120为kpcr中KPRCB的对应位置,0xffdff120-0x120就是_kpcr结构体的地址。

p.s. 在0环中,FS段描述符指向的就是KPCR结构首地址,写死在gdtr+0x30位置。

查看kpcr指令:

1
dt _kpcr 0xffdff120-0x120

_KPCR结构体

image-20210831191911557

KPCR 也叫CPU控制区(Processor Control Region)

每一个CPU核都有一个_KPCR结构体来描述当前CPU所处的状态。

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
kd> dt _kpcr 0xffdff120-0x120
nt!_KPCR
+0x000 NtTib : _NT_TIB
+0x01c SelfPcr : 0xffdff000 _KPCR
+0x020 Prcb : 0xffdff120 _KPRCB
+0x024 Irql : 0 ''
+0x028 IRR : 0
+0x02c IrrActive : 0
+0x030 IDR : 0xffffffff
+0x034 KdVersionBlock : 0x80546ab8 Void
+0x038 IDT : 0x8003f400 _KIDTENTRY//当前核的IDT
+0x03c GDT : 0x8003f000 _KGDTENTRY//当前核的GDT
+0x040 TSS : 0x80042000 _KTSS//当前核的TSS
+0x044 MajorVersion : 1
+0x046 MinorVersion : 1
+0x048 SetMember : 1
+0x04c StallScaleFactor : 0xd49
+0x050 DebugActive : 0 ''//调试激活位
+0x051 Number : 0 ''
+0x052 Spare0 : 0 ''
+0x053 SecondLevelCacheAssociativity : 0 ''
+0x054 VdmAlert : 0
+0x058 KernelReserved : [14] 0
+0x090 SecondLevelCacheSize : 0
+0x094 HalReserved : [16] 0
+0x0d4 InterruptMode : 0
+0x0d8 Spare1 : 0 ''
+0x0dc KernelReserved2 : [17] 0
+0x120 PrcbData : _KPRCB


ntdll!_NT_TIB
+0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD  //当前线程内核异常链表(SEH)
+0x004 StackBase : Ptr32 Void                  //当前线程堆栈的基址
+0x008 StackLimit : Ptr32 Void                  //当前线程堆栈的大小
+0x00c SubSystemTib : Ptr32 Void
+0x010 FiberData : Ptr32 Void
+0x010 Version : Uint4B
+0x014 ArbitraryUserPointer : Ptr32 Void
+0x018 Self : Ptr32 _NT_TIB                //指向自己头部,目的为了方便查找

_KPRCB结构体

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
kd> dt _KPRCB ffdff000+0x120
ntdll!_KPRCB
+0x000 MinorVersion : 1
+0x002 MajorVersion : 1
+0x004 CurrentThread : 0x80553740 _KTHREAD
+0x008 NextThread : (null)
+0x00c IdleThread : 0x80553740 _KTHREAD
+0x010 Number : 0 ''
+0x011 Reserved : 0 ''
+0x012 BuildType : 2
+0x014 SetMember : 1
+0x018 CpuType : 6 ''
+0x019 CpuID : 1 ''
+0x01a CpuStep : 0x3c03
+0x01c ProcessorState : _KPROCESSOR_STATE
+0x33c KernelReserved : [16] 0
+0x37c HalReserved : [16] 0
+0x3bc PrcbPad0 : [92] ""
+0x418 LockQueue : [16] _KSPIN_LOCK_QUEUE
+0x498 PrcbPad1 : [8] ""
+0x4a0 NpxThread : 0x81d10af0 _KTHREAD
+0x4a4 InterruptCount : 0x34e1
+0x4a8 KernelTime : 0x1aaf
+0x4ac UserTime : 0xa1
+0x4b0 DpcTime : 0x16
+0x4b4 DebugDpcTime : 0
+0x4b8 InterruptTime : 0x79
+0x4bc AdjustDpcThreshold : 0x14
+0x4c0 PageColor : 0
+0x4c4 SkipTick : 0
+0x4c8 MultiThreadSetBusy : 0 ''
+0x4c9 Spare2 : [3] ""
+0x4cc ParentNode : 0x80553e00 _KNODE
+0x4d0 MultiThreadProcessorSet : 1
+0x4d4 MultiThreadSetMaster : (null)
+0x4d8 ThreadStartCount : [2] 0
+0x4e0 CcFastReadNoWait : 0
+0x4e4 CcFastReadWait : 0
+0x4e8 CcFastReadNotPossible : 0
+0x4ec CcCopyReadNoWait : 0
+0x4f0 CcCopyReadWait : 0
+0x4f4 CcCopyReadNoWaitMiss : 0
+0x4f8 KeAlignmentFixupCount : 0
+0x4fc KeContextSwitches : 0x1a44d
+0x500 KeDcacheFlushCount : 0
+0x504 KeExceptionDispatchCount : 0x290
+0x508 KeFirstLevelTbFills : 0
+0x50c KeFloatingEmulationCount : 0
+0x510 KeIcacheFlushCount : 0
+0x514 KeSecondLevelTbFills : 0
+0x518 KeSystemCalls : 0x24189b//系统调用次数
+0x51c SpareCounter0 : [1] 0
+0x520 PPLookasideList : [16] _PP_LOOKASIDE_LIST
+0x5a0 PPNPagedLookasideList : [32] _PP_LOOKASIDE_LIST
+0x6a0 PPPagedLookasideList : [32] _PP_LOOKASIDE_LIST
+0x7a0 PacketBarrier : 0
+0x7a4 ReverseStall : 0
+0x7a8 IpiFrame : (null)
+0x7ac PrcbPad2 : [52] ""
+0x7e0 CurrentPacket : [3] (null)
+0x7ec TargetSet : 0
+0x7f0 WorkerRoutine : (null)
+0x7f4 IpiFrozen : 0
+0x7f8 PrcbPad3 : [40] ""
+0x820 RequestSummary : 0
+0x824 SignalDone : (null)
+0x828 PrcbPad4 : [56] ""
+0x860 DpcListHead : _LIST_ENTRY [ 0x80553da4 - 0x80553da4 ]
+0x868 DpcStack : 0xf8ac2000 Void
+0x86c DpcCount : 0x2058
+0x870 DpcQueueDepth : 1
+0x874 DpcRoutineActive : 0
+0x878 DpcInterruptRequested : 0
+0x87c DpcLastCount : 0x2058
+0x880 DpcRequestRate : 0
+0x884 MaximumDpcQueueDepth : 1
+0x888 MinimumDpcRate : 3
+0x88c QuantumEnd : 0
+0x890 PrcbPad5 : [16] ""
+0x8a0 DpcLock : 0
+0x8a4 PrcbPad6 : [28] ""
+0x8c0 CallDpc : _KDPC
+0x8e0 ChainedInterruptList : (null)
+0x8e4 LookasideIrpFloat : 0n1438
+0x8e8 SpareFields0 : [6] 0
+0x900 VendorString : [13] "GenuineIntel"//CPU厂商名称
+0x90d InitialApicId : 0 ''
+0x90e LogicalProcessorsPerPhysicalProcessor : 0x1 ''
+0x910 MHz : 0xd47
+0x914 FeatureBits : 0xa0013fff
+0x918 UpdateSignature : _LARGE_INTEGER 0x00000025`00000000
+0x920 NpxSaveArea : _FX_SAVE_AREA
+0xb30 PowerState : _PROCESSOR_POWER_STATE

分析KiStstemService和KiFastCallEntry

KiStstemService和KiFastCallEntry从两个口进来,最终执行的代码是一样的。

开始的时候,这两函数都在填充_Ktrap_frame结构体以保存三环的信息:

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
kd> uf 8053e481
Flow analysis was incomplete, some code may be missing
nt!KiBBTUnexpectedRange:
8053e332 83f910 cmp ecx,10h
8053e335 7539 jne nt!KiBBTUnexpectedRange+0x3e (8053e370) Branch

nt!KiBBTUnexpectedRange+0x5:
8053e337 52 push edx
8053e338 53 push ebx
8053e339 e8e2530800 call nt!PsConvertToGuiThread (805c3720)
8053e33e 0bc0 or eax,eax
8053e340 58 pop eax
8053e341 5a pop edx
8053e342 8bec mov ebp,esp
8053e344 89ae34010000 mov dword ptr [esi+134h],ebp
8053e34a 0f847d020000 je nt!KiFastCallEntry+0x8d (8053e5cd) Branch

nt!KiBBTUnexpectedRange+0x1e:
8053e350 8d15703f5580 lea edx,[nt!KeServiceDescriptorTableShadow+0x10 (80553f70)]
8053e356 8b4a08 mov ecx,dword ptr [edx+8]
8053e359 8b12 mov edx,dword ptr [edx]
8053e35b 8d148a lea edx,[edx+ecx*4]
8053e35e 25ff0f0000 and eax,0FFFh
8053e363 03d0 add edx,eax
8053e365 0fbe02 movsx eax,byte ptr [edx]
8053e368 0bc0 or eax,eax
8053e36a 0f8eca020000 jle nt!KiFastCallEntry+0xfa (8053e63a) Branch

nt!KiBBTUnexpectedRange+0x3e:
8053e370 b81c0000c0 mov eax,0C000001Ch
8053e375 e9c0020000 jmp nt!KiFastCallEntry+0xfa (8053e63a) Branch

//==================================调试相关beign==============================
//就是将调试寄存器中的DR0到DR7放入_KTRAP_FRAME结构中的DR0到DR7
nt!Dr_kss_a:
8053e37c f7457000000200 test dword ptr [ebp+70h],20000h
8053e383 750d jne nt!Dr_kss_a+0x16 (8053e392) Branch

nt!Dr_kss_a+0x9:
8053e385 f7456c01000000 test dword ptr [ebp+6Ch],1
8053e38c 0f845d010000 je nt!KiSystemService+0x6e (8053e4ef) Branch

nt!Dr_kss_a+0x16:
8053e392 0f21c3 mov ebx,dr0
8053e395 0f21c9 mov ecx,dr1
8053e398 0f21d7 mov edi,dr2
8053e39b 895d18 mov dword ptr [ebp+18h],ebx
8053e39e 894d1c mov dword ptr [ebp+1Ch],ecx
8053e3a1 897d20 mov dword ptr [ebp+20h],edi
8053e3a4 0f21db mov ebx,dr3
8053e3a7 0f21f1 mov ecx,dr6
8053e3aa 0f21ff mov edi,dr7
8053e3ad 895d24 mov dword ptr [ebp+24h],ebx
8053e3b0 894d28 mov dword ptr [ebp+28h],ecx
8053e3b3 33db xor ebx,ebx
8053e3b5 897d2c mov dword ptr [ebp+2Ch],edi
8053e3b8 0f23fb mov dr7,ebx
8053e3bb 648b3d20000000 mov edi,dword ptr fs:[20h]
8053e3c2 8b9ff8020000 mov ebx,dword ptr [edi+2F8h]
8053e3c8 8b8ffc020000 mov ecx,dword ptr [edi+2FCh]
8053e3ce 0f23c3 mov dr0,ebx
8053e3d1 0f23c9 mov dr1,ecx
8053e3d4 8b9f00030000 mov ebx,dword ptr [edi+300h]
8053e3da 8b8f04030000 mov ecx,dword ptr [edi+304h]
8053e3e0 0f23d3 mov dr2,ebx
8053e3e3 0f23d9 mov dr3,ecx
8053e3e6 8b9f08030000 mov ebx,dword ptr [edi+308h]
8053e3ec 8b8f0c030000 mov ecx,dword ptr [edi+30Ch]
8053e3f2 0f23f3 mov dr6,ebx
8053e3f5 0f23f9 mov dr7,ecx
8053e3f8 e9f2000000 jmp nt!KiSystemService+0x6e (8053e4ef) Branch
//==================================调试相关end==============================

nt!KiSystemService://KiStstemService此函数开始(//系统调用号在eax寄存器,系统调用号//edx说明参数在哪里)
8053e481 6a00 push 0 //_KTRAP_FRAME +0x64 ErrCode
8053e483 55 push ebp//+0x060 ebp
8053e484 53 push ebx//+0x05c ebx
8053e485 56 push esi//+0x058 esi
8053e486 57 push edi//+0x054 edi
8053e487 0fa0 push fs//+0x050 SegFs
8053e489 bb30000000 mov ebx,30h//0x30的选择子指向ffc093df`f0000001描述符,其中base为ffdff000,刚好是KPCR结构体的地址
8053e48e 668ee3 mov fs,bx//FS在3环指向PEB,在零环FS指向KPCR结构体!!!!!!
8053e491 ff3500f0dfff push dword ptr ds:[0FFDFF000h]//_KTRAP_FRAME +0x4c保存老的ExceptionList
8053e497 c70500f0dfffffffffff mov dword ptr ds:[0FFDFF000h],0FFFFFFFFh//将新的ExceptionList置为-1
8053e4a1 8b3524f1dfff mov esi,dword ptr ds:[0FFDFF124h]//KPCR结构体0x124偏移的CurrentThread,esi为当前线程信息_ETHREAD结构体地址
8053e4a7 ffb640010000 push dword ptr [esi+140h]//当前线程_ETHREAD中0x140偏移的PreviousMode先前模式存入_KTRAP_FRAME +0x48
8053e4ad 83ec48 sub esp,48h//提升堆栈,esp也为_KTRAP_FRAME首地址
8053e4b0 8b5c246c mov ebx,dword ptr [esp+6Ch]//ebx为三环原来的CS值
8053e4b4 83e301 and ebx,1//取三环原来的CS值最后一位,0环为0,三环为1
8053e4b7 889e40010000 mov byte ptr [esi+140h],bl//当前线程_ETHREAD中0x140偏移的PreviousMode先前模式位置存入值,先前模式表示,提升权限前是三环就是1,提升权限前是0环就是0。有些内核代码在三环和零环都可以执行,但是执行不一样,就是通过该先前模式进行判断。
8053e4bd 8bec mov ebp,esp//栈底提升,ebp也为_KTRAP_FRAME首地址
8053e4bf 8b9e34010000 mov ebx,dword ptr [esi+134h]//ebx为_ETHREAD中的0x134偏移的旧_KTRAP_FRAME地址
8053e4c5 895d3c mov dword ptr [ebp+3Ch],ebx//将旧_KTRAP_FRAME地址临时存在这里_KTRAP_FRAME内的edx位置
8053e4c8 89ae34010000 mov dword ptr [esi+134h],ebp//将新的_KTRAP_FRAME地址存入_ETHREAD中的0x134偏移处
8053e4ce fc cld
8053e4cf 8b5d60 mov ebx,dword ptr [ebp+60h]//将原来的三环的ebp放进ebx中
8053e4d2 8b7d68 mov edi,dword ptr [ebp+68h]//将原来三环的eip放进edi
8053e4d5 89550c mov dword ptr [ebp+0Ch],edx//_Trap_Frame结构中的DbgArgPointer位置存【三环时往edx存入的参数指针】
8053e4d8 c74508000ddbba mov dword ptr [ebp+8],0BADB0D00h//将0BADB0D00h存入_Trap_Frame结构中的DbgArgMark位置
8053e4df 895d00 mov dword ptr [ebp],ebx//将原来的三环的ebp放进_Trap_Frame结构中的DbgEbp位置
8053e4e2 897d04 mov dword ptr [ebp+4],edi//将原来的三环的eip放进_Trap_Frame结构中的DbgEip位置
8053e4e5 f6462cff test byte ptr [esi+2Ch],0FFh//比较当前线程_ETHREAD中0x2C偏移的DebugActive的值和-1比较
8053e4e9 0f858dfeffff jne nt!Dr_kss_a (8053e37c) Branch//如果当前线程_ETHREAD中0x2C偏移的DebugActive的值不是-1,则说明处于调试状态,则跳转去:将调试寄存器中的DR0到DR7放入_KTRAP_FRAME结构中的DR0到DR7
//因此人为将当前线程_ETHREAD中0x2C偏移的DebugActive置为-1,则无法进行硬件断点了。


nt!KiSystemService+0x6e:
8053e4ef fb sti//允许中断
8053e4f0 e9d8000000 jmp nt!KiFastCallEntry+0x8d (8053e5cd) Branch


nt!KiFastCallEntry2+0x24://不知道啥意思
8053e519 8b0d40f0dfff mov ecx,dword ptr ds:[0FFDFF040h]//TSS地址放入ecx
8053e51f 8b6104 mov esp,dword ptr [ecx+4]//esp0放入esp
8053e522 6a00 push 0//填的是啥
8053e524 6a00 push 0
8053e526 6a00 push 0
8053e528 6a00 push 0
8053e52a 6a23 push 23h
8053e52c 6a00 push 0
8053e52e 6802020200 push 20202h
8053e533 6a1b push 1Bh
8053e535 6a00 push 0
8053e537 e9f8150000 jmp nt!KiTrap06 (8053fb34) Branch

nt!KiFastCallEntry2+0x47:
8053e53c ebdb jmp nt!KiFastCallEntry2+0x24 (8053e519) Branch

//KiFastCallEntry此函数开始(//3环栈顶放到edx中 //系统调用号在eax寄存器)
nt!KiFastCallEntry:
8053e540 b923000000 mov ecx,23h
8053e545 6a30 push 30h
8053e547 0fa1 pop fs//fs指向的段描述符base指向KPCR结构体的地址
8053e549 8ed9 mov ds,cx//ds置为0x23段选择子对应的段
8053e54b 8ec1 mov es,cx//es置为0x23段选择子对应的段
8053e54d 8b0d40f0dfff mov ecx,dword ptr ds:[0FFDFF040h]//KPCR地址+0x40偏移存的TSS地址放入ecx中,ecx为当前的TSS内存地址
8053e553 8b6104 mov esp,dword ptr [ecx+4]//堆栈切为内核栈!!!,将TSS内存中的esp0放入esp中
8053e556 6a23 push 23h//填入ss
8053e558 52 push edx//push 3环栈顶esp
8053e559 9c pushfd
8053e55a 6a02 push 2
8053e55c 83c208 add edx,8//edx=三环esp+8,其实就是edx现在为参数起始地址
8053e55f 9d popfd //eflags置为2,因为eflags第二位默认为1
8053e560 804c240102 or byte ptr [esp+1],2//将在陷阱帧中的老的eflags的第10位IF位置1,设置可屏蔽中断使能,但是只是在内存中(不是寄存器中),所以无用
8053e565 6a1b push 1Bh//CS,写死的R3的CS
8053e567 ff350403dfff push dword ptr ds:[0FFDF0304h]//_KUSER_SHARED_DATA中的SystemCallReturn作为eip填入陷阱帧。调用结束后返回就返回到这里。
8053e56d 6a00 push 0//errCode
8053e56f 55 push ebp
8053e570 53 push ebx
8053e571 56 push esi
8053e572 57 push edi
8053e573 8b1d1cf0dfff mov ebx,dword ptr ds:[0FFDFF01Ch]//ebx为SelfPcr自己的PCR,自己的成员指向自己
8053e579 6a3b push 3Bh//三环的fs,写死的fs
8053e57b 8bb324010000 mov esi,dword ptr [ebx+124h]//esi为currentThread,_ETHREAD结构体地址
8053e581 ff33 push dword ptr [ebx]//保存到陷阱帧的三环的exceptionList
8053e583 c703ffffffff mov dword ptr [ebx],0FFFFFFFFh//零环的exceptionList置为-1
8053e589 8b6e18 mov ebp,dword ptr [esi+18h]//_ethread中InitialStack放入ebp
8053e58c 6a01 push 1//先前模式存为1,表示三环升零环。
8053e58e 83ec48 sub esp,48h//之后esp指向_Trap_Frame首地址
8053e591 81ed9c020000 sub ebp,29Ch//ebp也升,栈底到陷阱帧之间还存了很多结构,ebp提升后正常都会和esp相等。
8053e597 c6864001000001 mov byte ptr [esi+140h],1//_ethread先前模式设为1
8053e59e 3bec cmp ebp,esp
8053e5a0 759a jne nt!KiFastCallEntry2+0x47 (8053e53c) Branch//esp和ebp不相等跳到某个异常处理。

nt!KiFastCallEntry+0x62://ebp和esp相等则继续执行
8053e5a2 83652c00 and dword ptr [ebp+2Ch],0//陷阱帧中DR7清零
8053e5a6 f6462cff test byte ptr [esi+2Ch],0FFh//判断当前线程调试状态是否激活
8053e5aa 89ae34010000 mov dword ptr [esi+134h],ebp//记录_Ktrap_Framed地址到_ethread中的trapFrame位置,环境和线程相关了
8053e5b0 0f854afeffff jne nt!Dr_FastCallDrSave (8053e400) Branch//跳到保存调试相关部分

nt!KiFastCallEntry+0x76:
8053e5b6 8b5d60 mov ebx,dword ptr [ebp+60h]//ebx为ebp
8053e5b9 8b7d68 mov edi,dword ptr [ebp+68h]//edi为eip
8053e5bc 89550c mov dword ptr [ebp+0Ch],edx//将三环esp+8的地址存入_Trap_Frame中的DbgArgPointer位置
8053e5bf c74508000ddbba mov dword ptr [ebp+8],0BADB0D00h//将0BADB0D00h存入_Trap_Frame中的DbgArgMark位置
8053e5c6 895d00 mov dword ptr [ebp],ebx//ebx存到_Trap_Frame中的DbgEbp
8053e5c9 897d04 mov dword ptr [ebp+4],edi////eip存到_Trap_Frame中的DbgEip
8053e5cc fb sti//允许中断

//下面含义是:取出系统调用号,3环传进来的eax(共同部分,无论是KiFastCallEntry还是KiSystemService都会走的部分)后面的部分,后续有分析:
nt!KiFastCallEntry+0x8d:
8053e5cd 8bf8 mov edi,eax
8053e5cf c1ef08 shr edi,8
8053e5d2 83e730 and edi,30h
8053e5d5 8bcf mov ecx,edi
8053e5d7 03bee0000000 add edi,dword ptr [esi+0E0h]
8053e5dd 8bd8 mov ebx,eax
8053e5df 25ff0f0000 and eax,0FFFh
8053e5e4 3b4708 cmp eax,dword ptr [edi+8]
8053e5e7 0f8345fdffff jae nt!KiBBTUnexpectedRange (8053e332) Branch

KiFastCallEntry单独分析:

image-20211106145549920

系统调用目标内核函数后续跳转

image-20211107153842092

系统服务表

上一章是关于进入0环后,3环的各种寄存器都会保留到_Trap_Frame结构体中

这一章是关于

  1. 如何根据系统服务号(eax中存储)找到要执行的内核函数
  2. 调用时参数时存储的3环的堆栈,如何传递给内核函数

系统服务号盘点

系统服务表(SystemServiceTable)

image-20210831192744340

  • ServiceTable指向的是函数地址表
  • COUNT当前函数地址表中的地址被调用了多少次
  • ServiceLimit当前函数地址表中一共有多少个函数
  • ArgmentTable指向的是函数参数表,函数参数表中存的是相对应每个函数的参数的【字节数】,函数参数表的每个成员只有1个字节

函数地址1就对应参数个数1,一一对应。

winXP只有两张系统服务表

  1. 第一张是Ntoskrl.exe中的常用函数
  2. 第二张是Win32k.sys中的常用函数(User32.dll的底层都是使用GDI32.dll,GDI32.dll底层又是Win32k.sys)

两张系统服务表挨着放到一起,每张系统服务表占16个字节,4个4字节成员。

SystemServiceTable系统服务表的地址在_KTHREAD中0xE0偏移处存着

系统服务号如何索引

image-20210831205053088

  • 系统服务号下标为12的位:如果是0,就在第一张系统服务表,为1就在第二张系统服务表。

  • 系统服务号的低12位:就是函数地址表和函数参数表中的索引。

    目标函数地址=dword ptr ds:[函数地址表地址+(0~11位索引)*4]

    目标函数参数总字节数=byte ptr ds:[函数参数表地址+(0~11位索引)*1]

系统是怎么通过系统调用号查找函数的:

继续分析KiFastCallEntry和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
249
250
251
252
253
//KiFastCallEntry和KiSystemService汇聚于此:
nt!KiFastCallEntry+0x8d:
8053e5cd 8bf8 mov edi,eax//edi为系统调用号
8053e5cf c1ef08 shr edi,8//系统调用号右移8位
8053e5d2 83e730 and edi,30h//edi要么是0,要么是0x10,目的在于判断下标12的位是否为1,为1则edi为0x10
8053e5d5 8bcf mov ecx,edi
8053e5d7 03bee0000000 add edi,dword ptr [esi+0E0h]//esi+0xE0是_KTHREAD结构的ServiceTable(替换这个,突破口之一),系统服务表加edi的值,系统服务表地址+0正好是第一张系统服务表,系统服务表+0x10则是第二个表,因为系统服务表占的字节大小正好是0x10。之后edi为两种系统服务表之一的首地址
8053e5dd 8bd8 mov ebx,eax//ebx为系统调用号
8053e5df 25ff0f0000 and eax,0FFFh//eax取后12位,即函数地址表的索引
8053e5e4 3b4708 cmp eax,dword ptr [edi+8]//比较后12位有没有超过函数个数
8053e5e7 0f8345fdffff jae nt!KiBBTUnexpectedRange (8053e332) Branch//eax>[edi+8]则跳,即超过的话表示越界,则跳转

nt!KiFastCallEntry+0xad:
8053e5ed 83f910 cmp ecx,10h//判断ecx是否0x10
8053e5f0 751a jne nt!KiFastCallEntry+0xcc (8053e60c) Branch//ecx不是0x10,即表示要查的是第一张表则跳转

nt!KiFastCallEntry+0xb2://第二张表涉及到图形函数是动态加载相关的,中间多调用了一个函数,略
8053e5f2 8b0d18f0dfff mov ecx,dword ptr ds:[0FFDFF018h]
8053e5f8 33db xor ebx,ebx
8053e5fa 0b99700f0000 or ebx,dword ptr [ecx+0F70h]
8053e600 740a je nt!KiFastCallEntry+0xcc (8053e60c) Branch

nt!KiFastCallEntry+0xc2:
8053e602 52 push edx
8053e603 50 push eax
8053e604 ff15e43f5580 call dword ptr [nt!KeGdiFlushUserBatch (80553fe4)]//第二章表动态加载相关的函数,GDI批量刷新用户界面
8053e60a 58 pop eax
8053e60b 5a pop edx

nt!KiFastCallEntry+0xcc:
8053e60c ff0538f6dfff inc dword ptr ds:[0FFDFF638h]//_KPRCB地址+0x518的KeSystemCalls系统调用次数加1
8053e612 8bf2 mov esi,edx//esi为三环的参数指针
8053e614 8b5f0c mov ebx,dword ptr [edi+0Ch]//ebx为SST函数参数表的地址
8053e617 33c9 xor ecx,ecx//ecx清零
8053e619 8a0c18 mov cl,byte ptr [eax+ebx]//SST函数参数表的地址+系统服务号后12位索引中的值就是参数总字节数,cl为参数总字节数
8053e61c 8b3f mov edi,dword ptr [edi]//edi为SSDT函数地址表地址
8053e61e 8b1c87 mov ebx,dword ptr [edi+eax*4]//ebx为要调用的目标函数地址
8053e621 2be1 sub esp,ecx//esp开辟参数总字节数的大小
8053e623 c1e902 shr ecx,2//ecx为多少个4字节参数,参数个数,也是拷贝时拷贝4字节的次数
8053e626 8bfc mov edi,esp//设置edi为要拷贝的目的地,目的是要把三环的参数拷贝到零环堆栈里来
8053e628 3b35d4995580 cmp esi,dword ptr [nt!MmUserProbeAddress (805599d4)]//和一个全局变量(0x7FFFFFFF用户程序能访问的最大内存范围)比较,三环的参数地址有没有越界
8053e62e 0f83a8010000 jae nt!KiSystemCallExit2+0x9f (8053e7dc) Branch//越界的话跳转,实际上就是跳转后判断cs是不是内核权限,是的话正常拷贝,不是的话返回C0000005错误。

nt!KiFastCallEntry+0xf4:
8053e634 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]//开始拷贝,rep是重复的意思,拷贝的次数取决于ecx
8053e636 ffd3 call ebx//终于调用目标函数!!!!!!

nt!KiFastCallEntry+0xf8:
8053e638 8be5 mov esp,ebp//零环堆栈平栈,esp又指向了陷阱帧首地址

nt!KiFastCallEntry+0xfa:
8053e63a 8b0d24f1dfff mov ecx,dword ptr ds:[0FFDFF124h]//ecx为_ehtread地址
8053e640 8b553c mov edx,dword ptr [ebp+3Ch]//将前面临时放在edx的旧_Trap_Frame地址放到edx
8053e643 899134010000 mov dword ptr [ecx+134h],edx//将_ethread中TrapFrame设置为前面临时放在edx的旧_Trap_Frame地址
8053e649 fa cli//禁止中断发生
8053e64a f7457000000200 test dword ptr [ebp+70h],20000h//判断陷阱帧中eflags的VM位,就是判断是不是虚拟8086模式。
8053e651 7506 jne nt!KiServiceExit+0x10 (8053e659) Branch//是虚拟8086跳转

nt!KiServiceExit+0xa:
8053e653 f6456c01 test byte ptr [ebp+6Ch],1//判断陷阱帧中cs是R0权限还是R3权限
8053e657 7457 je nt!KiServiceExit+0x67 (8053e6b0) Branch//R0权限则跳转

nt!KiServiceExit+0x10:
8053e659 8b1d24f1dfff mov ebx,dword ptr ds:[0FFDFF124h]//ecx为_ehtread地址
//后面都是APC的,后续再分析。
8053e65f c6432e00 mov byte ptr [ebx+2Eh],0//线程唤醒标志置0
8053e663 807b4a00 cmp byte ptr [ebx+4Ah],0
8053e667 7447 je nt!KiServiceExit+0x67 (8053e6b0) Branch

nt!KiServiceExit+0x20:
8053e669 8bdd mov ebx,ebp
8053e66b 894344 mov dword ptr [ebx+44h],eax
8053e66e c743503b000000 mov dword ptr [ebx+50h],3Bh
8053e675 c7433823000000 mov dword ptr [ebx+38h],23h
8053e67c c7433423000000 mov dword ptr [ebx+34h],23h
8053e683 c7433000000000 mov dword ptr [ebx+30h],0
8053e68a b901000000 mov ecx,1
8053e68f ff15f4864d80 call dword ptr [nt!_imp_KfRaiseIrql (804d86f4)]
8053e695 50 push eax
8053e696 fb sti
8053e697 53 push ebx
8053e698 6a00 push 0
8053e69a 6a01 push 1
8053e69c e88d03fcff call nt!KiDeliverApc (804fea2e)//APC执行函数
8053e6a1 59 pop ecx
8053e6a2 ff151c874d80 call dword ptr [nt!_imp_KfLowerIrql (804d871c)]
8053e6a8 8b4344 mov eax,dword ptr [ebx+44h]
8053e6ab fa cli
8053e6ac ebab jmp nt!KiServiceExit+0x10 (8053e659) Branch

nt!KiServiceExit+0x67://现场还原
8053e6b0 8b54244c mov edx,dword ptr [esp+4Ch]//edx为陷阱帧中存的异常链表
8053e6b4 648b1d50000000 mov ebx,dword ptr fs:[50h]//ebx为_KPCR调试激活位
8053e6bb 64891500000000 mov dword ptr fs:[0],edx//陷阱帧中存的异常链表放回_KPCR中的异常链表位置(即返回三环异常链表)
8053e6c2 8b4c2448 mov ecx,dword ptr [esp+48h]//ecx为陷阱帧中先前模式
8053e6c6 648b3524010000 mov esi,dword ptr fs:[124h]//esi为_ethread结构地址
8053e6cd 888e40010000 mov byte ptr [esi+140h],cl//陷阱帧中原来的先前模式放回_ethread结构地址的先前模式位置(返回原来的先前模式)
8053e6d3 f7c3ff000000 test ebx,0FFh//判断_KPCR调试激活位是否-1
8053e6d9 7579 jne nt!KiSystemCallExit2+0x17 (8053e754) Branch//调试激活位是-1的时候跳转

nt!KiServiceExit+0x92:
8053e6db f744247000000200 test dword ptr [esp+70h],20000h
8053e6e3 0f85eb080000 jne nt!Kei386EoiHelper+0x12c (8053efd4) Branch

nt!KiServiceExit+0xa0:
8053e6e9 66f744246cf8ff test word ptr [esp+6Ch],0FFF8h
8053e6f0 0f84b4000000 je nt!KiSystemCallExit2+0x6d (8053e7aa) Branch

nt!KiServiceExit+0xad:
8053e6f6 66837c246c1b cmp word ptr [esp+6Ch],1Bh //判断一下是否三环
8053e6fc 660fba64246c00 bt word ptr [esp+6Ch],0
8053e703 f5 cmc
8053e704 0f878e000000 ja nt!KiSystemCallExit2+0x5b (8053e798) Branch

nt!KiServiceExit+0xc1:
8053e70a 66837d6c08 cmp word ptr [ebp+6Ch],8
8053e70f 7405 je nt!KiServiceExit+0xcd (8053e716) Branch

nt!KiServiceExit+0xc8:
8053e711 8d6550 lea esp,[ebp+50h]
8053e714 0fa1 pop fs

nt!KiServiceExit+0xcd:
8053e716 8d6554 lea esp,[ebp+54h]
8053e719 5f pop edi
8053e71a 5e pop esi
8053e71b 5b pop ebx
8053e71c 5d pop ebp
8053e71d 66817c24088000 cmp word ptr [esp+8],80h
8053e724 0f87c6080000 ja nt!Kei386EoiHelper+0x148 (8053eff0) Branch

nt!KiServiceExit+0xe1:
8053e72a 83c404 add esp,4
8053e72d f744240401000000 test dword ptr [esp+4],1
8053e735 7506 jne nt!KiSystemCallExit2 (8053e73d) Branch

nt!KiSystemCallExitBranch+0x2:
8053e737 5a pop edx
8053e738 59 pop ecx
8053e739 9d popfd
8053e73a ffe2 jmp edx

nt!KiSystemCallExit:
8053e73c cf iretd

nt!KiSystemCallExit2:
8053e73d f644240901 test byte ptr [esp+9],1
8053e742 75f8 jne nt!KiSystemCallExit (8053e73c) Branch

nt!KiSystemCallExit2+0x7:
8053e744 5a pop edx
8053e745 83c404 add esp,4
8053e748 80642401fd and byte ptr [esp+1],0FDh
8053e74d 9d popfd
8053e74e 59 pop ecx
8053e74f fb sti
8053e750 0f35 sysexit//sysenter进去的会用sysexit返回
8053e752 cf iretd

nt!KiSystemCallExit2+0x17:
8053e754 f7457000000200 test dword ptr [ebp+70h],20000h
8053e75b 750d jne nt!KiSystemCallExit2+0x2d (8053e76a) Branch

nt!KiSystemCallExit2+0x20:
8053e75d f7456c01000000 test dword ptr [ebp+6Ch],1
8053e764 0f8471ffffff je nt!KiServiceExit+0x92 (8053e6db) Branch

nt!KiSystemCallExit2+0x2d:
8053e76a 33db xor ebx,ebx
8053e76c 8b7518 mov esi,dword ptr [ebp+18h]
8053e76f 8b7d1c mov edi,dword ptr [ebp+1Ch]
8053e772 0f23fb mov dr7,ebx
8053e775 0f23c6 mov dr0,esi
8053e778 8b5d20 mov ebx,dword ptr [ebp+20h]
8053e77b 0f23cf mov dr1,edi
8053e77e 0f23d3 mov dr2,ebx
8053e781 8b7524 mov esi,dword ptr [ebp+24h]
8053e784 8b7d28 mov edi,dword ptr [ebp+28h]
8053e787 8b5d2c mov ebx,dword ptr [ebp+2Ch]
8053e78a 0f23de mov dr3,esi
8053e78d 0f23f7 mov dr6,edi
8053e790 0f23fb mov dr7,ebx
8053e793 e943ffffff jmp nt!KiServiceExit+0x92 (8053e6db) Branch

nt!KiSystemCallExit2+0x5b:
8053e798 8b442444 mov eax,dword ptr [esp+44h]
8053e79c 83c430 add esp,30h
8053e79f 0fa9 pop gs
8053e7a1 07 pop es
8053e7a2 1f pop ds
8053e7a3 5a pop edx
8053e7a4 59 pop ecx
8053e7a5 e967ffffff jmp nt!KiServiceExit+0xc8 (8053e711) Branch

nt!KiSystemCallExit2+0x6d:
8053e7aa 8b5c2410 mov ebx,dword ptr [esp+10h]
8053e7ae 895c246c mov dword ptr [esp+6Ch],ebx
8053e7b2 8b5c2414 mov ebx,dword ptr [esp+14h]
8053e7b6 83eb0c sub ebx,0Ch
8053e7b9 895c2464 mov dword ptr [esp+64h],ebx
8053e7bd 8b742470 mov esi,dword ptr [esp+70h]
8053e7c1 897308 mov dword ptr [ebx+8],esi
8053e7c4 8b74246c mov esi,dword ptr [esp+6Ch]
8053e7c8 897304 mov dword ptr [ebx+4],esi
8053e7cb 8b742468 mov esi,dword ptr [esp+68h]
8053e7cf 8933 mov dword ptr [ebx],esi
8053e7d1 83c454 add esp,54h
8053e7d4 5f pop edi
8053e7d5 5e pop esi
8053e7d6 5b pop ebx
8053e7d7 5d pop ebp
8053e7d8 8b2424 mov esp,dword ptr [esp]
8053e7db cf iretd

nt!KiSystemCallExit2+0x9f:
8053e7dc f6456c01 test byte ptr [ebp+6Ch],1
8053e7e0 0f844efeffff je nt!KiFastCallEntry+0xf4 (8053e634) Branch

nt!KiSystemCallExit2+0xa9:
8053e7e6 b8050000c0 mov eax,0C0000005h
8053e7eb e948feffff jmp nt!KiFastCallEntry+0xf8 (8053e638) Branch

nt!Kei386EoiHelper+0x12c:
8053efd4 83c43c add esp,3Ch
8053efd7 5a pop edx
8053efd8 59 pop ecx
8053efd9 58 pop eax
8053efda 8d6554 lea esp,[ebp+54h]
8053efdd 5f pop edi
8053efde 5e pop esi
8053efdf 5b pop ebx
8053efe0 5d pop ebp
8053efe1 66817c24088000 cmp word ptr [esp+8],80h
8053efe8 7706 ja nt!Kei386EoiHelper+0x148 (8053eff0) Branch

nt!Kei386EoiHelper+0x142:
8053efea 83c404 add esp,4
8053efed cf iretd

nt!Kei386EoiHelper+0x148:
8053eff0 66837c240200 cmp word ptr [esp+2],0
8053eff6 74f2 je nt!Kei386EoiHelper+0x142 (8053efea) Branch

nt!Kei386EoiHelper+0x150:
8053eff8 66833c2400 cmp word ptr [esp],0
8053effd 75eb jne nt!Kei386EoiHelper+0x142 (8053efea) Branch

nt!Kei386EoiHelper+0x157:
8053efff c12c2410 shr dword ptr [esp],10h
8053f003 66c7442402f800 mov word ptr [esp+2],0F8h
8053f00a 660fb22424 lss sp,dword ptr [esp]
8053f00f 0fb7e4 movzx esp,sp
8053f012 cf iretd

系统调用号使用流程图

image-20211107202221078

SSDT

上一章通过_kthread的0xE0偏移找到SST

其实还可以通过其他方式来访问系统服务表

就是SSDT

SSDT:System Services Descriptor Table,系统服务描述符表

其他两种方式查看SST

第一种方式

这种方式只能看第一张SST

1
dd KeServiceDescriptorTable//(简称:SSDT)

ntkrnlpa.exe或ntoskrnl.exe导出的,代码中声明一下就可以使用

KeServiceDescriptorTable是ntkrnlpa.exe或ntoskrnl.exe导出的全局变量

第二种方式

这种方式两张SST都有

1
dd KeServiceDescriptorTableShadow//(简称:SSDT Shadow)

KeServiceDescriptorTableShadow是Win32k.sys导出的

代码中要自己通过内存搜索的方式到Win32k.sys的导出表中找到他的地址。

课后练习

在SSDT表中追加一个函数地址(NtReadVirtualMemory),自己编写API的3环部分调用这个新增的函数(注意使用2-9-9-12分页,因为10-10-12分页会有蓝屏现象(调用返回还没涉及)

R0代码
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
#include <ntddk.h>

//卸载函数
VOID DriverUnload(PDRIVER_OBJECT driver)
{
DbgPrint("停止运行了\n");
}

typedef struct _SST
{
ULONG ServiceTable;
ULONG Count;
ULONG ServiceLimit;
ULONG ArgmentTable;
}SST;

extern ULONG KeServiceDescriptorTable;//声明系统内核导出的全局变量
//入口函数,相当于main函数
NTSTATUS DriverEntry(PDRIVER_OBJECT pdriver, PUNICODE_STRING pReg)
{
//设置一个卸载函数,用于退出
pdriver->DriverUnload = DriverUnload;
//修改SST表中的数据。
SST* sst1 = (SST*)KeServiceDescriptorTable;
ULONG num = sst1->ServiceLimit;
ULONG ftAddr = sst1->ServiceTable;
ULONG ftArgAddr = sst1->ArgmentTable;
DbgPrint("原来共有%d个函数\n", num);
DbgPrint("修改的函数地址位置:%p\n", (*(ULONG*)sst1) + num * 4);
DbgPrint("修改的函数参数位置:%p\n", (*(ULONG*)((ULONG)sst1 + 0xC)) + num);
__asm
{
pushad;
mov eax, dword ptr ds : [ftArgAddr] ;
mov ecx, dword ptr ds : [num] ;
mov edx, 0xBA;//NtReadVirtualMemory系统服务号
mov bl, byte ptr ds : [eax + edx] ;
lea edi, dword ptr ds : [eax + ecx] ;
mov byte ptr ds : [edi] , bl;//更换函数参数
mov eax, dword ptr ds : [ftAddr] ;
shl edx, 2;
mov ebx, dword ptr ds : [eax + edx] ;
shl ecx, 2;
lea edi, dword ptr ds : [eax + ecx] ;
mov dword ptr ds : [edi] , ebx;//更换函数参数总字节数
//函数个数+1
mov eax, dword ptr ds : [sst1] ;
add eax, 0x8;
add dword ptr ds : [eax] , 1;
popad;
}
return STATUS_SUCCESS;
}
R3代码
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
#include"stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

void __stdcall myReadProcessMemory(
HANDLE hProcess,
LPCVOID lpBaseAddress,
LPVOID lpBuffer,
SIZE_T nSize,
SIZE_T *lpNumberOfBytesRead
)
{
__asm
{
push lpNumberOfBytesRead;
push nSize;
push lpBuffer;
push lpBaseAddress;
push hProcess;
mov eax,0x11c;//放在这个位置上
lea edx,dword ptr [esp];
int 0x2E;
add esp,0x14;
}
}

void main()
{
int a=300;
SIZE_T haveRead;
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,NULL,1552);
myReadProcessMemory(hProcess,(LPVOID)0x12ff7c,&a,4,&haveRead);
printf("old a:300 new a after readMemory:%d",a);
system("pause");
}

image-20210904123123962

image-20210904123102688

修改后函数个数:image-20210904123203383

image-20210904121940924

关于API调用返回的问题:学完APC后再分析

SSDT HOOK

  • 优点:挂起来很容易,并且非常稳定
  • 缺点:容易被发现,容易被绕过,只能HOOK系统服务表里的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//下面两个结构的名字都可以随便起。
typedef struct _KSYSTEM_SERVICE_TABLE //对应SST
{
PULONG ServiceTableBase; // 服务函数地址表基址
PULONG ServiceCounterTableBase;
ULONG NumberOfService; // 服务函数的个数
PULONG ParamTableBase; // 服务函数参数表基址
} KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;

typedef struct _KSERVICE_TABLE_DESCRIPTOR //对应SSDT
{
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;


//导出由 ntoskrnl所导出的 SSDT
extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;

通过页表基址修改页属性

SSDT所在物理页是只读的,如果要修改别的进程(修改自己进程无所谓),先要修改页属性为可写的,两种方法

第一种办法

用我们学过的知识,通过页表基址直接修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//通过CR4判断是什么分页
if(RCR4 & 0x00000020)
{//说明是2-9-9-12分页
KdPrint(("2-9-9-12分页 %p\n",RCR4));
KdPrint(("PTE1 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 9) & 0x007FFFF8))));
*(DWORD64*)(0xC0000000 + ((HookFunAddr >> 9) & 0x007FFFF8)) |= 0x02;
KdPrint(("PTE1 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 9) & 0x007FFFF8))));
}
else
{//说明是10-10-12分页
KdPrint(("10-10-12分页\n"));
KdPrint(("PTE1 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 10) & 0x003FFFFC))));
*(DWORD*)(0xC0000000 + ((HookFunAddr >> 10) & 0x003FFFFC)) |= 0x02;
KdPrint(("PTE2 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 10) & 0x003FFFFC))));
}

这种方式的好处是不用关心是单核还是多核

第二种方法

通过修改CR0寄存器的WP位

由于修改的CR0寄存器每个核都有一个,这个修改的过程中,如果发生核的切换(cli只是线程无法切换了)多核还是可能切换,导致WP位并未修改。

image-20210905142808963

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
VOID PageProtectOn()
{
__asm{
mov eax,cr0
or eax,10000h
mov cr0,eax
sti
}
}

VOID PageProtectOff()
{
__asm{
cli
mov eax,cr0
and eax,not 10000h
mov cr0,eax
}
}

PageProtectOff();

//开始修改。。。

PageProtectOn();

改代码的一瞬间如果并未有核的切换就没问题。不然就会出问题。

SSDT HOOK实验

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
#include <ntddk.h>

//下面两个结构的名字都可以随便起。
typedef struct _KSYSTEM_SERVICE_TABLE //对应SST
{
PULONG ServiceTableBase; // 服务函数地址表基址
PULONG ServiceCounterTableBase;
ULONG NumberOfService; // 服务函数的个数
PULONG ParamTableBase; // 服务函数参数表基址
} KSYSTEM_SERVICE_TABLE, * PKSYSTEM_SERVICE_TABLE;

typedef struct _KSERVICE_TABLE_DESCRIPTOR //对应SSDT
{
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;

//导出由 ntoskrnl所导出的 SSDT
extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;

VOID PageProtectOn()
{
__asm {
mov eax, cr0
or eax, 10000h
mov cr0, eax
sti
}
}

VOID PageProtectOff()
{
__asm {
cli
mov eax, cr0
and eax, not 10000h
mov cr0, eax
}
}

ULONG oldNtReadVirtualMemoryAdd = NULL;//原来的地址

NTSTATUS __stdcall MYNtReadVirtualMemory(
HANDLE ProcessHandle,
PVOID BaseAddress,
PVOID Buffer,
ULONG BufferLength,
PULONG ReturnLength)
{
//执行想执行的
DbgPrint("someOne use NtReadVirtualMemory---SSDT HOOK\n");

//执行原函数
NTSTATUS result = 0;
__asm
{
push ReturnLength;
push BufferLength;
push Buffer;
push BaseAddress;
push ProcessHandle;
call oldNtReadVirtualMemoryAdd;
mov result, eax;
}
return result;
}

//卸载函数
VOID DriverUnload(PDRIVER_OBJECT driver)
{
//恢复SSDT HOOK
if (oldNtReadVirtualMemoryAdd)
{
PageProtectOff();
KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0xBA] = oldNtReadVirtualMemoryAdd;
PageProtectOn();
oldNtReadVirtualMemoryAdd = NULL;
}

DbgPrint("停止运行了\n");
}

//入口函数,相当于main函数
NTSTATUS DriverEntry(PDRIVER_OBJECT pdriver, PUNICODE_STRING pReg)
{
//设置一个卸载函数,用于退出
pdriver->DriverUnload = DriverUnload;
//保存原地址
oldNtReadVirtualMemoryAdd = KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0xBA];
DbgPrint("修改位置为:%p\n", &(KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0xBA]));
DbgPrint("原函数地址为:%p\n", oldNtReadVirtualMemoryAdd);
PageProtectOff();
//修改SST对应的0xBA服务号的函数
KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0xBA] = MYNtReadVirtualMemory;
PageProtectOn();
DbgPrint("修改后的函数地址为:%p\n", KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0xBA]);
return STATUS_SUCCESS;
}

image-20210906134948152

image-20210906135151739

如图,SSDT HOOK成功实现,但也被pc hunter轻易发现。

SSDT HOOK隐藏

先做自己的进程,非全局

  1. 首先找到自己的进程,进程遍历
  2. 遍历线程
  3. 替换_KTHREAD的0xE0存的SSDT表
  4. 定时器

后续回头做

0环回3环

系统调用第二天1小时28分逆向调用返回。(未完就绪)

_KiFastCallEntry由三部分组成

  1. 创建_KTRAP_FRAME结构备份三环信息
  2. 根据系统调用号调用内核中的服务过程
  3. 通过1步骤备份的_KTRAP_FRAME结构体返回3环(_KiServiceExit)

ZW系列内核函数和NT系列内核函数的区别

在ring3层,这两组函数确实是同一个函数,用u命令对NtReadFile以及ZwReadFile反汇编发现,他们的起始地址是一样的。调用ntdll.dll这个动态库的函数时,声明成这两种形式都可以,不过按照微软的说法,声明成Nt函数更恰当些,Zw开头的函数表示的是内核函数。熟悉ntdll.dll这个动态库的人都知道,其实这个库里面的函数都是stub函数,并不做实际功能,只是系统调用进入内核的入口。

​ 到了ring0层情况就不同了,两种形式的函数是不同的函数,Nt开头的函数是真正执行功能的函数。应用层所用的系统API都会通过int 2e或者sysenter指令切换到内核,然后调用内核导出的Nt系列函数。分析SSDT表即可以得出这个结论。

​ 而对于内核态的Zw开头的函数,通过汇编也可以发现,它的结构与ntdll.dll里面的函数类似,最后都会调用对应的内核nt函数,只不过已经在内核态,就没有int 2e这种切换状态的指令,也没有陷阱帧,但调用形式差不多,都是通过调用号来从SSDT中找到实现功能的Nt函数。微软建议内核开发者使用Zw系列的函数,因为这些函数多了一些参数检查的代码。