查看原文
其他

Windows API调用详解

SnA1lGo 看雪学苑 2022-07-01


本文为看雪论坛优秀文章
看雪论坛作者ID:SnA1lGo


综述:

Windows内核中的执行体层暴露了大量执行体中的对象给Windows用户层的API来操作,那么用户层的API是怎么调用这些功能的呢,比如说创建一个文件,文件是一个内核对象必须得有内核层来处理,所以肯定有一个从用户层到内核层然后内核层解决再返回到用户层的一个流程。


API调用流程:

这里以CreateFile举例:(大致流程如下)



采用OllyDbg跟踪观察:

#include<iostream>#include<Windows.h>using namespace std;int main(){ cout << "Begin" << endl; auto hFile =CreateFileW( L"temp.txt", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); return NULL;}

 

可以看到在调用CreateFile时,是直接采用的Kernel32中的CreateFileW函数:

 

然后跟进Kernel32.CreateFileW函数中:

 

 

会跳转到KernelBase中的CreateFileW函数中,继续步入查看:

 

一直往下翻才能看到NtCreateFile函数,前面的我猜是给创建新文件所需进行初始化的一些代码。

正所谓前人栽树后人乘凉,书上的大牛写的是调用ntdll中的NtCreateFile我们就不要怀疑了,然后步入Ntdll.NtCreateFile查看:

 

 

这里的call esi就是调用NtCreateFile的地方,然后继续步入查看:

 

 

就成了这样子,这个我也不知道什么意思,就继续call dword ptr ds:[edx]查看:

最后的最后是由三个汇编指令为结尾:

mov edx,espsysenterretn


retn是返回指令,表示已经执行完成。

 

sysenter在od中无法看到,不管是步入还是步过只会直接执行完跳过。

 

所以目前而言的API调用过程是这样的:

现在不理解的就是NtCreateFile函数内部的代码逻辑:

mov eax,xxmov edx,xxxcall dword ptr ds:[edx]retn xxx... mov edx,espsysenter


这几行汇编代码是什么意思。


详解NtCreateFile:


首先是NtCreateFile中的第一层汇编代码:

mov eax,xxmov edx,xxcall dword ptr ds:[edx]retn xxx


这附近的汇编代码都是这样的样式:

 


mov eax,xx


这里的eax叫做系统服务号,它代表进入0环后,调用那一个API,就类似于数组和下标的关系,根据下标一对一对应内核中的某一个API,然后内核调用内核的对应的API。

 

也就是说由eax的值来决定内核API的调用。


mov edx,xxx

所有通过ntdll调用的函数,给edx所传递的值都是一样的,这里都是0x7FFE0300。

 

该值是_KUSER_SHARED_DATA结构体的一个成员,该结构体所在的内存空间是一个提供给User层和Kernel层共享的一个内存空间,该空间主要用来在User和Kernel之间快速传递信息。

 

该结构体所在的内存空间是固定的:User层下:0x7FFE0000,Kernel层下:0xFFDF0000。

//0x5f0 bytes (sizeof)struct _KUSER_SHARED_DATA{ ULONG TickCountLowDeprecated; //0x0 ULONG TickCountMultiplier; //0x4 volatile struct _KSYSTEM_TIME InterruptTime; //0x8 volatile struct _KSYSTEM_TIME SystemTime; //0x14 volatile struct _KSYSTEM_TIME TimeZoneBias; //0x20 USHORT ImageNumberLow; //0x2c USHORT ImageNumberHigh; //0x2e WCHAR NtSystemRoot[260]; //0x30 ULONG MaxStackTraceDepth; //0x238 ULONG CryptoExponent; //0x23c ULONG TimeZoneId; //0x240 ULONG LargePageMinimum; //0x244 ULONG Reserved2[7]; //0x248 enum _NT_PRODUCT_TYPE NtProductType; //0x264 UCHAR ProductTypeIsValid; //0x268 ULONG NtMajorVersion; //0x26c ULONG NtMinorVersion; //0x270 UCHAR ProcessorFeatures[64]; //0x274 ULONG Reserved1; //0x2b4 ULONG Reserved3; //0x2b8 volatile ULONG TimeSlip; //0x2bc enum _ALTERNATIVE_ARCHITECTURE_TYPE AlternativeArchitecture; //0x2c0 ULONG AltArchitecturePad[1]; //0x2c4 union _LARGE_INTEGER SystemExpirationDate; //0x2c8 ULONG SuiteMask; //0x2d0 UCHAR KdDebuggerEnabled; //0x2d4 UCHAR NXSupportPolicy; //0x2d5 volatile ULONG ActiveConsoleId; //0x2d8 volatile ULONG DismountCount; //0x2dc ULONG ComPlusPackage; //0x2e0 ULONG LastSystemRITEventTickCount; //0x2e4 ULONG NumberOfPhysicalPages; //0x2e8 UCHAR SafeBootMode; //0x2ec union { UCHAR TscQpcData; //0x2ed struct { UCHAR TscQpcEnabled:1; //0x2ed UCHAR TscQpcSpareFlag:1; //0x2ed UCHAR TscQpcShift:6; //0x2ed }; }; UCHAR TscQpcPad[2]; //0x2ee union { ULONG SharedDataFlags; //0x2f0 struct { ULONG DbgErrorPortPresent:1; //0x2f0 ULONG DbgElevationEnabled:1; //0x2f0 ULONG DbgVirtEnabled:1; //0x2f0 ULONG DbgInstallerDetectEnabled:1; //0x2f0 ULONG DbgSystemDllRelocated:1; //0x2f0 ULONG DbgDynProcessorEnabled:1; //0x2f0 ULONG DbgSEHValidationEnabled:1; //0x2f0 ULONG SpareBits:25; //0x2f0 }; }; ULONG DataFlagsPad[1]; //0x2f4 ULONGLONG TestRetInstruction; //0x2f8 ULONG SystemCall; //0x300 ULONG SystemCallReturn; //0x304 ULONGLONG SystemCallPad[3]; //0x308 union { volatile struct _KSYSTEM_TIME TickCount; //0x320 volatile ULONGLONG TickCountQuad; //0x320 ULONG ReservedTickCountOverlay[3]; //0x320 }; ULONG TickCountPad[1]; //0x32c ULONG Cookie; //0x330 ULONG CookiePad[1]; //0x334 LONGLONG ConsoleSessionForegroundProcessId; //0x338 ULONG Wow64SharedInformation[16]; //0x340 USHORT UserModeGlobalLogger[16]; //0x380 ULONG ImageFileExecutionOptions; //0x3a0 ULONG LangGenerationCount; //0x3a4 ULONGLONG Reserved5; //0x3a8 volatile ULONGLONG InterruptTimeBias; //0x3b0 volatile ULONGLONG TscQpcBias; //0x3b8 volatile ULONG ActiveProcessorCount; //0x3c0 volatile USHORT ActiveGroupCount; //0x3c4 USHORT Reserved4; //0x3c6 volatile ULONG AitSamplingValue; //0x3c8 volatile ULONG AppCompatFlag; //0x3cc ULONGLONG SystemDllNativeRelocation; //0x3d0 ULONG SystemDllWowRelocation; //0x3d8 ULONG XStatePad[1]; //0x3dc struct _XSTATE_CONFIGURATION XState; //0x3e0};


edx是该结构体中的SystemCall成员,该结构体成员指定了从User层到Kernel层的调用方式,这里我们可以通过WinDbg来查看一下:

kd> dt nt!_KUSER_SHARED_DATA 0xFFDF0000 +0x000 TickCountLowDeprecated : 0 +0x004 TickCountMultiplier : 0xf99a027 +0x008 InterruptTime : _KSYSTEM_TIME +0x014 SystemTime : _KSYSTEM_TIME +0x020 TimeZoneBias : _KSYSTEM_TIME +0x02c ImageNumberLow : 0x14c +0x02e ImageNumberHigh : 0x14c +0x030 NtSystemRoot : [260] "C:\Windows" +0x238 MaxStackTraceDepth : 0 +0x23c CryptoExponent : 0 +0x240 TimeZoneId : 0 +0x244 LargePageMinimum : 0x200000 +0x248 Reserved2 : [7] 0 +0x264 NtProductType : 1 ( NtProductWinNt ) +0x268 ProductTypeIsValid : 0x1 '' +0x26c NtMajorVersion : 6 +0x270 NtMinorVersion : 1 +0x274 ProcessorFeatures : [64] "" +0x2b4 Reserved1 : 0x7ffeffff +0x2b8 Reserved3 : 0x80000000 +0x2bc TimeSlip : 0 +0x2c0 AlternativeArchitecture : 0 ( StandardDesign ) +0x2c4 AltArchitecturePad : [1] 0 +0x2c8 SystemExpirationDate : _LARGE_INTEGER 0x0 +0x2d0 SuiteMask : 0x110 +0x2d4 KdDebuggerEnabled : 0x3 '' +0x2d5 NXSupportPolicy : 0x2 '' +0x2d8 ActiveConsoleId : 1 +0x2dc DismountCount : 0 +0x2e0 ComPlusPackage : 0xffffffff +0x2e4 LastSystemRITEventTickCount : 0 +0x2e8 NumberOfPhysicalPages : 0x3ff7e +0x2ec SafeBootMode : 0 '' +0x2ed TscQpcData : 0 '' +0x2ed TscQpcEnabled : 0y0 +0x2ed TscQpcSpareFlag : 0y0 +0x2ed TscQpcShift : 0y000000 (0) +0x2ee TscQpcPad : [2] "" +0x2f0 SharedDataFlags : 0xe +0x2f0 DbgErrorPortPresent : 0y0 +0x2f0 DbgElevationEnabled : 0y1 +0x2f0 DbgVirtEnabled : 0y1 +0x2f0 DbgInstallerDetectEnabled : 0y1 +0x2f0 DbgSystemDllRelocated : 0y0 +0x2f0 DbgDynProcessorEnabled : 0y0 +0x2f0 DbgSEHValidationEnabled : 0y0 +0x2f0 SpareBits : 0y0000000000000000000000000 (0) +0x2f4 DataFlagsPad : [1] 0 +0x2f8 TestRetInstruction : 0xc3 +0x300 SystemCall : 0x76f46c00 +0x304 SystemCallReturn : 0x76f46c04 +0x308 SystemCallPad : [3] 0 +0x320 TickCount : _KSYSTEM_TIME +0x320 TickCountQuad : 0x9806 +0x320 ReservedTickCountOverlay : [3] 0x9806 +0x32c TickCountPad : [1] 0 +0x330 Cookie : 0xccc8c182 +0x334 CookiePad : [1] 0 +0x338 ConsoleSessionForegroundProcessId : 0n1504 +0x340 DEPRECATED_Wow64SharedInformation : [16] 0 +0x380 UserModeGlobalLogger : [16] 0 +0x3a0 ImageFileExecutionOptions : 0 +0x3a4 LangGenerationCount : 1 +0x3a8 Reserved5 : 0 +0x3b0 InterruptTimeBias : 0 +0x3b8 TscQpcBias : 0 +0x3c0 ActiveProcessorCount : 1 +0x3c4 ActiveGroupCount : 1 +0x3c6 Reserved4 : 0 +0x3c8 AitSamplingValue : 0 +0x3cc AppCompatFlag : 1 +0x3d0 DEPRECATED_SystemDllNativeRelocation : 0 +0x3d8 DEPRECATED_SystemDllWowRelocation : 0 +0x3dc XStatePad : [1] 0 +0x3e0 XState : _XSTATE_CONFIGURATION
+0x300 SystemCall : 0x76f46c00,该字段存放着函数的调用地址,结合前面的内容可以看到确实是该地址:

//然后反汇编查看一下该函数的内容:kd> u 0x76f46c00ntdll!KiFastSystemCall:76f46c00 ?? ??? ^ Memory access error in 'u 0x76f46c00' 这个函数的名字叫做KiFastSystemCall 那么这一段汇编代码:mov eax,xxmov edx,xxcall dword ptr ds:[edx]retn xxx 就可以抽象成一个函数:KiFastSystemCall()//当然里面肯定有参数


那么图就可以变成这样子:

 

然后重点就来到了剩下的两个。


mov edx,esp:


这个其实就是函数传参,参数在栈里面,然后把栈的首地址交给edx。


sysenter


sysenter才是整个调用的关键。

 

整个调用方式最关键的就是通过sysenter从User层到达Kernel层,可以说前面的都是在给这一步做铺垫。

 

sysenter叫做快速系统调用,叫快速是因为之前的系统调用不快,在Pentium II(奔腾2代CPU)之后才有的sysenter,在其之前是采用的 KiIntSystemCall函数来处理的。

 

在IDA下查看:该函数:

; Exported entry 109. KiIntSystemCall ; _DWORD __stdcall KiIntSystemCall()public _KiIntSystemCall@0_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 stringretn_KiIntSystemCall@0 endp


这里的int 2Eh,采用的就是一种CPU机制,叫做中断门:

 

Windows内核中的CPU架构-6-中断门(32-Bit Interrupt Gate) - Sna1lGo - 博客园 (cnblogs.com)(https://www.cnblogs.com/Sna1lGo/p/15515746.html

 

这样调用会用到内存,比较麻烦,所以就引入了快速系统调用:

 

sysenter/sysexit 两个函数和三个MSR寄存器。

 

MSR寄存器比较类似内存,直接根据序号来命名,没有像通用寄存器EAX,EBX一样,单独命名。

 

和sysenter连用的是MSR174和,MSR175,MSR176三个寄存器。


sysenter内部逻辑为:

//1:设置寄存器CS = IA32_SYSENTER_CSSS = IA32_SYSENTER_CS+8eip = IA32_SYSENTER_EIPesp = IA32_SYSENTER_ESP //2:切换特权级切换到0环特权级,(其实设置了寄存器就是切换了) //3:切换CPU模式清楚eflags寄存器中的虚拟8086模式(VM标志) //4:执行执行系统例程调用





看雪ID:SnA1lGo

https://bbs.pediy.com/user-home-948248.htm

*本文由看雪论坛 SnA1lGo 原创,转载请注明来自看雪社区



# 往期推荐

1.漏洞考古:MS08-067详细分析

2.多项式MBA原理及其在代码混淆中的应用

3.逆向角度看C++部分特性

4.Android netlink&svc 获取 Mac方法深入分析

5.CVE-2014-4113提权漏洞学习笔记

6.Go语言模糊测试工具:Go-Fuzz






球分享

球点赞

球在看



点击“阅读原文”,了解更多!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存