WindowsAPI调用探寻

moshui Lv2

API调用探寻

首先我们选择OpenProcess作为目标API,进而分析3环进0环的过程。
实验代码:

1
2
3
4
5
6
7
8
9
#include <Windows.h>
#include <stdio.h>
int main()
{
printf("%p", main);
getchar();
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
return 0;
}

笔者这里一开始代码有些问题后面修改了一下应该使用GetCurrentProcessId()函数,下文图片没更改,但不影响理解

我们用x64dbg来看一下。
image.png
可以看出调用了Kernel32.dll中的OpenProcess,我们继续步入。
image.png
可以发现Kernel32.dll中的OpenProcess只是个代理函数,真正的函数在KernelBase.dll中,我们继续步入。
image.png
可以看出OpenProcess只是处理了一些参数,然后调用了ntdll.dll中的ZwOpenProcess,我们转到IDA中分析一下,我们可以发现ZwOpenProcess只是NtOpenProcess的一个别名,其导出位置都是一个函数。
image.png
其中可以发现在进入函数后,通过检测7FFE0308地址的值,来判断是否使用syscallint 2E指令进入内核。

那么7FFE0308是什么呢?如果涉猎过x86就会发现,7FFE0308这个地址与x86下_KUSER_SHARED_DATA结构中的SystemCall地址很接近,我们合理猜测7FFE0308也是_KUSER_SHARED_DATA结构中的SystemCall

实验1——自写R3进内核

在看完R3下进内核的过程后,我们可以尝试编写一段代码来代替ntdll.dll完成从R3到R0的过程。

当我们完成这一切后,R3层的APIHOOK将对我们无效,且无法通过导入表的方式查看到我们引用的WindowsAPI。

要想完成这一切,我们就需要去KernelBase.dll中查看如何ntdll.dllNtOpenProcess函数,并完成复现。
image.png
我们只需要按照这个结构进行编写并完成上文中所示的NtOpenProcess函数即可。

代码如下:

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
#include <stdio.h>
#include <Windows.h>
typedef struct _CLIENT_ID
{
HANDLE UniqueProcess;
HANDLE UniqueThread;
}*PCLIENT_ID;
typedef struct _OBJECT_ATTRIBUTES
{
ULONG Length;
HANDLE RootDirectory;
PVOID ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
}*POBJECT_ATTRIBUTES;

EXTERN_C NTSTATUS OpenProcessNT(PHANDLE pH, ACCESS_MASK da, POBJECT_ATTRIBUTES oba, PCLIENT_ID cid);
HANDLE OpenProc(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId) {
_OBJECT_ATTRIBUTES obj = { 0 };
_CLIENT_ID cid = { 0 };
HANDLE retHandle = 0;

obj.Length = 48;
obj.RootDirectory = 0;
obj.Attributes = bInheritHandle ? 2 : 0;
obj.ObjectName = 0;
obj.SecurityDescriptor =(PVOID) 0x000EAB000000000;
cid.UniqueProcess = (HANDLE)dwProcessId;

NTSTATUS sta = OpenProcessNT(&retHandle, dwDesiredAccess, &obj, &cid);
return retHandle;
}
int main() {
HANDLE h = OpenProc(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
printf("Handle is %p", h);
CloseHandle(h);
return 0;
}
1
2
3
4
5
6
OpenProcessNT PROC
mov r10, rcx
mov eax, 26h
syscall ; or int 2Eh
ret
OpenProcessNT ENDP

编译运行后发现其确实可以正常完成OpenProcess的功能。
image.png

KUSER_SHARED_DATA

使用windbg查询一下该结构的信息:

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
0: kd> dt _KUSER_SHARED_DATA
nt!_KUSER_SHARED_DATA
+0x000 TickCountLowDeprecated : 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] Wchar
+0x238 MaxStackTraceDepth : Uint4B
+0x23c CryptoExponent : Uint4B
+0x240 TimeZoneId : Uint4B
+0x244 LargePageMinimum : Uint4B
+0x248 AitSamplingValue : Uint4B
+0x24c AppCompatFlag : Uint4B
+0x250 RNGSeedVersion : Uint8B
+0x258 GlobalValidationRunlevel : Uint4B
+0x25c TimeZoneBiasStamp : Int4B
+0x260 NtBuildNumber : Uint4B
+0x264 NtProductType : _NT_PRODUCT_TYPE
+0x268 ProductTypeIsValid : UChar
+0x269 Reserved0 : [1] UChar
+0x26a NativeProcessorArchitecture : Uint2B
+0x26c NtMajorVersion : Uint4B
+0x270 NtMinorVersion : Uint4B
+0x274 ProcessorFeatures : [64] UChar
+0x2b4 Reserved1 : Uint4B
+0x2b8 Reserved3 : Uint4B
+0x2bc TimeSlip : Uint4B
+0x2c0 AlternativeArchitecture : _ALTERNATIVE_ARCHITECTURE_TYPE
+0x2c4 BootId : Uint4B
+0x2c8 SystemExpirationDate : _LARGE_INTEGER
+0x2d0 SuiteMask : Uint4B
+0x2d4 KdDebuggerEnabled : UChar
+0x2d5 MitigationPolicies : UChar
+0x2d5 NXSupportPolicy : Pos 0, 2 Bits
+0x2d5 SEHValidationPolicy : Pos 2, 2 Bits
+0x2d5 CurDirDevicesSkippedForDlls : Pos 4, 2 Bits
+0x2d5 Reserved : Pos 6, 2 Bits
+0x2d6 CyclesPerYield : Uint2B
+0x2d8 ActiveConsoleId : Uint4B
+0x2dc DismountCount : Uint4B
+0x2e0 ComPlusPackage : Uint4B
+0x2e4 LastSystemRITEventTickCount : Uint4B
+0x2e8 NumberOfPhysicalPages : Uint4B
+0x2ec SafeBootMode : UChar
+0x2ed VirtualizationFlags : UChar
+0x2ee Reserved12 : [2] UChar
+0x2f0 SharedDataFlags : Uint4B
+0x2f0 DbgErrorPortPresent : Pos 0, 1 Bit
+0x2f0 DbgElevationEnabled : Pos 1, 1 Bit
+0x2f0 DbgVirtEnabled : Pos 2, 1 Bit
+0x2f0 DbgInstallerDetectEnabled : Pos 3, 1 Bit
+0x2f0 DbgLkgEnabled : Pos 4, 1 Bit
+0x2f0 DbgDynProcessorEnabled : Pos 5, 1 Bit
+0x2f0 DbgConsoleBrokerEnabled : Pos 6, 1 Bit
+0x2f0 DbgSecureBootEnabled : Pos 7, 1 Bit
+0x2f0 DbgMultiSessionSku : Pos 8, 1 Bit
+0x2f0 DbgMultiUsersInSessionSku : Pos 9, 1 Bit
+0x2f0 DbgStateSeparationEnabled : Pos 10, 1 Bit
+0x2f0 SpareBits : Pos 11, 21 Bits
+0x2f4 DataFlagsPad : [1] Uint4B
+0x2f8 TestRetInstruction : Uint8B
+0x300 QpcFrequency : Int8B
+0x308 SystemCall : Uint4B
+0x30c Reserved2 : Uint4B
+0x310 SystemCallPad : [2] Uint8B
+0x320 TickCount : _KSYSTEM_TIME
+0x320 TickCountQuad : Uint8B
+0x320 ReservedTickCountOverlay : [3] Uint4B
+0x32c TickCountPad : [1] Uint4B
+0x330 Cookie : Uint4B
+0x334 CookiePad : [1] Uint4B
+0x338 ConsoleSessionForegroundProcessId : Int8B
+0x340 TimeUpdateLock : Uint8B
+0x348 BaselineSystemTimeQpc : Uint8B
+0x350 BaselineInterruptTimeQpc : Uint8B
+0x358 QpcSystemTimeIncrement : Uint8B
+0x360 QpcInterruptTimeIncrement : Uint8B
+0x368 QpcSystemTimeIncrementShift : UChar
+0x369 QpcInterruptTimeIncrementShift : UChar
+0x36a UnparkedProcessorCount : Uint2B
+0x36c EnclaveFeatureMask : [4] Uint4B
+0x37c TelemetryCoverageRound : Uint4B
+0x380 UserModeGlobalLogger : [16] Uint2B
+0x3a0 ImageFileExecutionOptions : Uint4B
+0x3a4 LangGenerationCount : Uint4B
+0x3a8 Reserved4 : Uint8B
+0x3b0 InterruptTimeBias : Uint8B
+0x3b8 QpcBias : Uint8B
+0x3c0 ActiveProcessorCount : Uint4B
+0x3c4 ActiveGroupCount : UChar
+0x3c5 Reserved9 : UChar
+0x3c6 QpcData : Uint2B
+0x3c6 QpcBypassEnabled : UChar
+0x3c7 QpcShift : UChar
+0x3c8 TimeZoneBiasEffectiveStart : _LARGE_INTEGER
+0x3d0 TimeZoneBiasEffectiveEnd : _LARGE_INTEGER
+0x3d8 XState : _XSTATE_CONFIGURATION
+0x710 FeatureConfigurationChangeStamp : _KSYSTEM_TIME
+0x71c Spare : Uint4B

其中不难发现 在0x308处发现了SystemCall,那么7FFE0000则是KUSER_SHARED_DATA的地址。

在x86下,ntdll会直接使用SystemCall中的地址来执行KiFastSystemCallKiIntSystemCall,其中分别对应着sysenter/ syscall(快速调用指令)或int中断指令进行调用。

我们继续看x64下,其中就判断了SystemCall中是否为1,来选择使用是syscallint中断指令调用(为1使用中断,为0使用快速调用),相当于是把原来KiFastSystemCallKiIntSystemCall函数中的指令直接展开到了此处。

我猜测这里是防止该结构变化而权衡的一种方法,毕竟4字节塞不下x64的地址

地址

KUSER_SHARED_DATA结构的地址是固定的,在r3下是0x7FFE0000,在r0下是0xFFFFF78000000000。正如其名字中的SHARED,他是一个r0,r3的共享结构

我们可以在r0下查看KUSER_SHARED_DATA的物理内存。
image.png
以及附加到一个程序查看KUSER_SHARED_DATA的物理内存。
image.png
其中可以看到除了内存属性不一样以外,物理地址是一样的,均为0x10B5000(保护模式-页中有讲怎么转换)。

我们可以发现R3下只有读权限,而R0下则有写权限

快速系统调用

在x86下KiFastSystemCall函数对应着快速系统调用,而在x64下则没有这个函数转而直接把函数内部的指令syscall展开到了每个函数处

快速系统调用,顾名思义就是快,快速系统调用指令是由CPU进行直接处理的。

CPU的操作

SYSCALL

当我们执行了syscall指令后CPU帮我们做了许多操作,其中概括如下:

  1. RFLAGS保存到R11,将下一条指令的RIP保存到RCX
  2. CS段寄存器的选择子设置为MSR寄存器中的IA32_STAR[47:32]
  3. RIP变为MSR寄存器中的IA32_LSTAR
  4. SS段寄存器的选择子设置为MSR寄存器中的IA32_STAR[47:32]
  5. RFLAGS设置为其当前值与MSR寄存器中的IA32_FMASK值的补码的逻辑与值。

image.png

SYSRET

当我们执行了sysret指令后CPU帮我们做了许多操作,其中概括如下:

  1. CS段寄存器的选择子设置为MSR寄存器中的IA32_STAR[63:43]+ 16
  2. RIP变为RCX寄存器中存储的值
  3. SS段寄存器的选择子设置为MSR寄存器中的IA32_STAR[63:43]+ 8
  4. EFLAGS变为R11寄存器中存储的值

我们可以发现CPU对于syscall指令来说不保存栈指针,sysret指令不恢复栈指针。

分析

image.png
翻阅手册可以得知IA32_LSTAR寄存器的地址为C0000082
我们使用windbg进行分析。

1
2
3
4
5
6
7
8
9
10
11
12
0: kd> rdmsr 0xC0000082
msr[c0000082] = fffff807`08414000
0: kd> u fffff807`08414000
nt!KiSystemCall64:
fffff807`08414000 0f01f8 swapgs
fffff807`08414003 654889242510000000 mov qword ptr gs:[10h],rsp
fffff807`0841400c 65488b2425a8010000 mov rsp,qword ptr gs:[1A8h]
fffff807`08414015 6a2b push 2Bh
fffff807`08414017 65ff342510000000 push qword ptr gs:[10h]
fffff807`0841401f 4153 push r11
fffff807`08414021 6a33 push 33h
fffff807`08414023 51 push rcx

可以发现其中调用了KiSystemCall64函数进入R0层。
此处可以看出笔者并没有开启KPTI(页表隔离)

援引看雪博客 的内容,此处分析ntoskrnl.exe时注意,为了下载合适的pdb可以将其按如下改名:

  1. ntoskrnl - 单处理器,不支持PAE
  2. ntkrnlpa - 单处理器,支持PAE
  3. ntkrnlmp - 多处理器,不支持PAE
  4. ntkrpamp - 多处理器,支持PAE

此处我们分析代码更多的KiSystemCall64Shadow函数来解析。
image.png
image.png
继续往下看,后面有一大堆修栈的指令,我们直接看最底下
image.png
其直接跳跃到了KiSystemServiceUser函数,我们跟过去看一下
image.png
可以发现往下走则是处理线程处在调试状态时的情况,我们简化一下,直接去看正常状态下的调用。
image.png
我们只关注一下非GDI线程的情况,也就是走SSDT的情况。
image.png
继续跟。
image.png
其中我们看一下KiSystemServiceCopyStart,该块是用来复制参数的,其中由上图的jmp r11指令来控制复制多少个参数。
image.png
复制完参数后,便进入真正调用的地方。
image.png
在调用后,便进入KiSystemServiceExit返回给R3。

KiSystemCall64函数就是KiSystemCall64Shadow在不开页表隔离机制的情况下实现,代码大同小异

总结

  1. 通过API调用进入ntdll.dll或者其他,调用syscall(快速调用)指令。
  2. 快速调用进入KiSystemCall64或者KiSystemCall64Shadow函数。
  3. 切换内核栈,使用_KTRAP_FRAME结构保存寄存器信息
  4. 找到SSDT表,并调用

SSDT转换的过程中:

  1. 读取KeServiceDescriptorTable存的值,获取SSDT基址。
  2. 使用公式Offset = SSDTbase + 4 * 调用号获取4字节的偏移。
  3. 使用公式(Offset ÷ 16) + SSDTBase直接获取函数地址。

INT 2E 中断调用

既然是中断调用,那么其信息应该在IDT中,直接转到IDT可以查看到其对应的函数为KiSystemServiceShadow(开启页表隔离)或KiSystemService

KiSystemServiceShadow

image.png
根据上图可以发现其结构与KiSystemCall64Shadow函数一开始的处理过程十分相似,在设置好内核Cr3与一些必要操作后便进入了KiSystemService函数。

KiSystemService

该函数与KiSystemCall64的大致流程极为相似,几乎都是对那些东西进行操作。故此不做细致分析。
image.png
由图可知,其最后是调用了KiSystemServiceUser函数,与快速系统调用的最后结果殊途同归。

实验2——手动解析SSDT

在上文中,我们针对OpenProcessAPI进行了分析,我们在实验中将手动解析SSDT表并找到内核所对应的函数。

首先我们在上文中知道了调用ZwOpenProcess的函数的调用号(Eax)为0x26,结合上述分析,我们需要先获取到SSDT基地址。

1
2
3
1: kd> dq KeServiceDescriptorTable
fffff802`3360b8c0 fffff802`328d19f0 00000000`00000000
....

我们可以发现其基地址为fffff802`328d19f0

然后我们使用公式Offset = SSDTbase + 4 * 调用号

1
2
3
1: kd> dd fffff802`328d19f0 + 0x26 * 4
fffff802`328d1a88 05f1e600 01484a01 0559ba06 051bf707
....

请注意这里是取4字节作为偏移进行下一步处理(movsxd的含义),也就是这一步Offset = 0x5f1e600

下一步需要将(Offset ÷ 16) + SSDTBase算出最终的函数地址。

1
2
3
4
5
6
7
8
9
10
1: kd> u fffff802`328d19f0 + 5f1e600 / 0x10
nt!NtOpenProcess:
fffff802`32ec3850 4883ec38 sub rsp,38h
fffff802`32ec3854 65488b042588010000 mov rax,qword ptr gs:[188h]
fffff802`32ec385d 448a9032020000 mov r10b,byte ptr [rax+232h]
fffff802`32ec3864 4488542428 mov byte ptr [rsp+28h],r10b
fffff802`32ec3869 4488542420 mov byte ptr [rsp+20h],r10b
fffff802`32ec386e e83d8bf4ff call nt!PsOpenProcess (fffff802`32e0c3b0)
fffff802`32ec3873 4883c438 add rsp,38h
fffff802`32ec3877 c3 ret

可以发现我们也是成功的获取到了SSDT中的函数。

  • 标题: WindowsAPI调用探寻
  • 作者: moshui
  • 创建于 : 2024-09-20 09:00:00
  • 更新于 : 2024-09-20 18:56:17
  • 链接: https://www.moshui.eu.org/2024/09/20/WinAPI-Call/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论