这篇文章我们将研究CreateFile是如何从Ring3到Ring0,其中经过了哪些模块,而这些模块又是通过什么技术关联起来的。通过这一篇文章的学习,我们可以更好的了解操作系统内部的调用原理,更能帮助我们深入理解操作系统的各组件的相互调用关系。
为什么学?
如果你想在操作系统上达成以下事情,那么学习这篇文章就是你最好的选择。或者你有下列想法,那么通过举一反三,也是可以做到的。
1.做Hook。Hook可以做的事情非常多,账密拦截,HTTPS拦截。
2.研究和挖掘系统级别漏洞。
3.Rootkit研究。
4.夯实操作系统基础知识。
环境配置
1.在vmware安装win7 sp1 32位。
2.在win7 sp1 32位Windbg设置Symbol File Path为 SRV*c:\symbols*http://msdl.microsoft.com/download/symbols
(需要翻墙),勾选Reload后会下载符号。
3.下载符号完毕后将下载好的c:\symbols拷贝到物理机器。
4.使用VirtualKD进行双机调试。
实战开始
示例代码
我们要知道函数调用了哪个组件的API,就需要用到IDA来分析整个程序,获得程序的汇编代码还有其导入表。将示例代码使用VS2019生成后即可用IDA导入。在生成时记得要设置
Configuration Properties->Configuration Properties->Linker->Debugging的Generate Debug Info为Yes (/DEBUG)
这样生成后的EXE会伴随着一个PDB文件,PDB存放了程序的代码对应的行号等等调试相关的信息。在IDA打开时也会显示出程序的函数名,而不是一堆函数地址。
// 示例代码
#include "stdafx.h"
#include "windows.h"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE h = CreateFile(
L"bar.txt",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_TEMPORARY,
NULL);
if (h == INVALID_HANDLE_VALUE) {
DWORD err = GetLastError();
printf("err %d\n", err);
return 1;
}
printf("%p\n", h);
return 0;
}
IDA分析示例PE文件
首先我们使用IDA加载我们生成好的PE文件,查看main函数。
可以发现call ds:impCreateFileW@28的函数调用,而_imp开头的函数都不是本exe所实现的。都是外部调用。我们需要在IDA中打开导入表一栏,如下图所示
我们通过查看导入表得知。该函数导入自kernel32.dll。
IDA分析kernel32.dll
可以从IDA分析得知,Kernel32.dll又会依赖外部的函数_imp_CreateFile。
我们分析导入表,得知该函数导入自API-MS-Win-Core-File-L1-1-0.dll(这个DLL系统不存在,是一个虚拟DLL,会被重定向到kernelbase.dll,具体技术请参考MinWin Dll重定向集
在此我们可以利用一个工具DependenceWalker,在Ring3级别查看Dll的依赖,可以从下图看到,dll依赖路径是kernel32.dll->kernelbase.dll->ntdll.dll
IDA分析Kernelbase.dll
从IDA分析可以看到,kernelbase中的CreateFileW最终会调用_imp_NtCreateFile,该函数依旧是个外部引用函数。我们分析一下导入表。
经过导入表分析,发现NtCreateFile是来自ntdll.dll。
IDA分析ntdll.dll
将ntdll.dll导入IDA,在函数栏搜索NtCreateFile,可以看到NtCreateFile汇编代码如下图所示。
可以看到,在ntdll中仅有简单的如下几句代码:
mov eax,42h;
mov edx,7FFE0300h
call dword ptr [edx]
retn 2Ch
这里有两个很关键的值,42h和7FFE0300h,为了我们理解更加扎实,我们需要知道这个值不是随便选择的,而是来自一个数据结构SharedUserData。而7FFE0000则是这个结构的内存地址。300h则是偏移量。下面我们将研究这个结构的组成。
我们使用dt _KUSER_SHARED_DATA来查看这个数据结构,可以看到如下成员,还可以参考msdtc来查看此结构:
+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 Reserved2 : [7] Uint4B
+0x264 NtProductType : _NT_PRODUCT_TYPE
+0x268 ProductTypeIsValid : UChar
+0x26c NtMajorVersion : Uint4B
+0x270 NtMinorVersion : Uint4B
+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
+0x2d8 ActiveConsoleId : Uint4B
+0x2dc DismountCount : Uint4B
+0x2e0 ComPlusPackage : Uint4B
+0x2e4 LastSystemRITEventTickCount : Uint4B
+0x2e8 NumberOfPhysicalPages : Uint4B
+0x2ec SafeBootMode : UChar
+0x2f0 TraceLogging : Uint4B
+0x2f8 Fill0 : Uint8B
+0x300 SystemCall : [4] Uint8B
+0x320 TickCount : _KSYSTEM_TIME
+0x320 TickCountQuad : Uint8B
通过Windbg查询得知SystemCall是一个Uint8B[4],也就是一个八位无符号整形的数组,大小为4,我们使用dd addr来查看这个地址。
0:000> dd 7FFE0300
7ffe0300 77c470b0 77c470b4 00000000 00000000
7ffe0310 00000000 00000000 00000000 00000000
7ffe0320 001dd655 00000000 00000000 00000000
7ffe0330 23066a39 00000000 00000dec 00000000
7ffe0340 00000000 00000000 00000000 00000000
7ffe0350 00000000 00000000 00000000 00000000
7ffe0360 00000000 00000000 00000000 00000000
7ffe0370 00000000 00000000 00000000 00000000
查看可以看到两个值77c470b0,77c470b4。这两个值又是做什么的呢?我们通过uf命令查看
0:000> uf 77c470b0
ntdll!KiFastSystemCall:
77c470b0 8bd4 mov edx,esp //保存3环栈顶
77c470b2 0f34 sysenter //快速调用指令
77c470b4 c3 ret
0:000> u 77c470b4
ntdll!KiFastSystemCallRet:
77c470b4 c3 ret
ntdll!KiFastSystemCall会调用到systenter,而ntdll!KiFastSystemCallRet则是使用ret指令返回调用。
通过sysenter指令进0环主要有以下步骤:
CS/ESP/EIP由MSR寄存器提供(SS为msr[174]),msr[174]是cs,msr[175]为esp,msr[176]为eip
进入0环后执行的内核函数: NT!KiFastCallEntry
通过sysexit指令返回用户模式
在Windbg中,我们可以使用rdmsr命令来查看msr的值。通过读取到msr,我们再使用u addr(eip)来查看eip的指向是哪个函数。
1: kd> uf 77c470b0
ntdll!KiFastSystemCall:
77c470b0 8bd4 mov edx,esp //保存3环栈顶
77c470b2 0f34 sysenter //快速调用指令
1: kd> rdmsr 174
msr[174] = 00000000`00000008
1: kd> rdmsr 175
msr[175] = 00000000`807eb000
1: kd> rdmsr 176
msr[176] = 00000000`828490c0
1: kd> u 828490c0
nt!KiFastCallEntry:
828490c0 b923000000 mov ecx,23h
828490c5 6a30 push 30h
828490c7 0fa1 pop fs
828490c9 8ed9 mov ds,cx
828490cb 8ec1 mov es,cx
828490cd 648b0d40000000 mov ecx,dword ptr fs:[40h]
828490d4 8b6104 mov esp,dword ptr [ecx+4]
828490d7 6a23 push 23h
可以看到,我们通过断点到sysenter前。使用rdmsr指令读取msr[176]的值,读取出来的EIP为828490c0,使用u addr命令得知,在我们执行sysenter这句话后,会直接跳转到nt内核模块的KiFastCallEntry函数。
总结
有上述可以得知,在Ring3环节整个调用路径和其模块为
CreateFile(kernel32.dll) -> CreateFile(kernelbase.dll) -> NTCreateFile(ntdll.dll) -> KiFastSystemCall(ntdll.dll) -> sysenter(进入Ring0) -> KiFastCallEntry(nt模块)
发表评论
您还未登录,请先登录。
登录