驱动下Wow64栈回溯和进程模块枚举

很久没写驱动代码,最近又摸了一下。

在驱动中回溯调用栈,找到特定模块,获取模块地址、大小、路径等信息,然后…。

堆栈回溯

驱动中通常使用RtlWalkFrameChain来获取调用栈信息,接口如下:

ULONG
RtlWalkFrameChain(OUT PVOID *Callers, IN ULONG Count, IN ULONG Flags);
//Callers一个PVOID数组,保存栈中retaddr值
//Count表示数组大小
//Flags=0获取内核层栈信息,=1获取应用层栈信息
//返回值表示栈的层数

还有其他函数,未使用:

VOID
RtlGetCallersAddress(
    OUT PVOID *CallersAddress, //address to save the first caller.
    OUT PVOID *CallersCaller   //address to save the second caller.
)
RtlCaptureStackBackTrace

其实对于单纯的x86(ring3)->x86(ring0),和x64(ring3)->x64(ring0)没甚么好说的,就是普通的栈信息。

我这里要着重提的是x86(ring3)->x64(ring0),也就是64位系统的32位程序在进行系统调用时的堆栈(称为Wow64)。

先看看CreateFile的栈信息。Windbg并不能直接通过k显示wow64到内核的所有栈信息,wow64部分需要通过扩展指令切换,具体如下:

1: kd> kvn
 # Child-SP          RetAddr           : Args to Child                                                           : Call Site
05 fffff880`026cf940 fffff800`041dc574 : 00000000`0027e0d8 fffff960`c0100080 00000000`0027e9a0 00000000`0027e0f0 : nt!IopCreateFile+0x2bc
06 fffff880`026cf9e0 fffff800`03ecf693 : fffffa80`19646060 fffff880`026cfb60 00000000`0027e028 00000000`fffffffc : nt!NtCreateFile+0x78
07 fffff880`026cfa70 00000000`7796c08a : 00000000`73c8c1ff 00000000`001deeb0 00000000`001deec8 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x13 (TrapFrame @ fffff880`026cfae0)
08 00000000`0027e068 00000000`73c8c1ff : 00000000`001deeb0 00000000`001deec8 00000000`00000000 00000000`00000000 : ntdll!ZwCreateFile+0xa
09 00000000`0027e070 00000000`73c7d18f : 00000000`001deeb0 00000000`00000000 00000000`00000000 00000000`00000060 : wow64!whNtCreateFile+0x10f
0a 00000000`0027e140 00000000`73c02776 : 00000000`76c572b9 00000000`73c70023 00000000`00000246 00000000`001dee38 : wow64!Wow64SystemServiceEx+0xd7
0b 00000000`0027ea00 00000000`73c7d286 : 00000000`00000000 00000000`73c01920 00000000`77a303c8 00000000`7794ca81 : wow64cpu!ServiceNoTurbo+0x2d
0c 00000000`0027eac0 00000000`73c7c69e : 00000000`00000000 00000000`00000000 00000000`73c74b10 00000000`7ffe0030 : wow64!RunCpuSimulation+0xa
0d 00000000`0027eb10 00000000`7795f9b6 : 00000000`00332db0 00000000`00000000 00000000`77a4d670 00000000`77a20910 : wow64!Wow64LdrpInitialize+0x42a
0e 00000000`0027f060 00000000`779bbb89 : 00000000`00000000 00000000`7795f1d1 00000000`0027f610 00000000`00000000 : ntdll!LdrpInitializeProcess+0x17e3
0f 00000000`0027f550 00000000`7794a0ee : 00000000`0027f610 00000000`00000000 00000000`7efdf000 00000000`00000000 : ntdll! ?? ::FNODOBFM::`string'+0x22a30
10 00000000`0027f5c0 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!LdrInitializeThunk+0xe
1: kd> !wow64exts.sw
Switched to Guest (WoW) mode
1: kd:x86> kvn
The context is partially valid. Only x86 user-mode context is available.
 # ChildEBP          RetAddr           Args to Child                                         
00 001dee2c 76f1c76b 001deec8 c0100080 001dee6c ntdll_77b00000!NtCreateFile+0x12 (FPO: [11,0,0])
01 001deed0 75583f66 00000060 c0100080 00000003 KERNELBASE!CreateFileW+0x35e (FPO: [Non-Fpo])
02 001deefc 755853c4 004b4d90 c0000000 00000003 kernel32!CreateFileWImplementation+0x69 (FPO: [Non-Fpo])
03 001def2c 013259e2 001def78 c0000000 00000003 kernel32!CreateFileA+0x37 (FPO: [Non-Fpo])

而RtlWalkFrameChain(x, n, 1)是可以完整获取到wow64到nt之前的所有应用层栈信息。如下:

00000000`7796c08a //ntdll!ZwCreateFile+0xa
00000000`73c8c1ff
00000000`73c7d18f
00000000`73c02776
00000000`73c7d286
00000000`73c7c69e
00000000`7795f9b6
00000000`779bbb89
00000000`7794a0ee
//00000000`00000000 没有这层
76f1c76b //ntdll_77b00000!NtCreateFile+0x12
75583f66
755853c4
013259e2

需要注意的是,两个ntdll并不一样(ntdll_77b00000是32位dll),并且ntdll中间出现了wow64和wow64cpu两个模块,这就涉及到具体x86调用(wow64)如何切换到x64了,这里不展开。

进程模块枚举

可能大家都知道驱动中枚举模块的一种方法(非ZwQuerySystemInformation、SystemModuleInformation ),使用进程Peb->Ldr链表枚举,可以获取到模块的路径、基址、大小等信息。

下面是通常驱动下获取模块信息的代码,适用于x64内核获取x64进程模块信息以及x86内核获取x86进程模块信息。

//通过模块名获取模块基址、大小、全路径等信息
Peb = GetProcessPeb(Process);
Ldr = Peb->Ldr;
if (Ldr && Ldr->Initialized)
{
	if (!IsListEmpty(&Ldr->InLoadOrderModuleList))
	{
		ListPtr = ListHead = Ldr->InLoadOrderModuleList.Flink;
		do
		{
			pLdrDataEntry = CONTAINING_RECORD(ListPtr, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
			if (RtlEqualUnicodeString(&pLdrDataEntry->BaseDllName, Target, TRUE))
			{
				if (Base) {
					*Base = (ULONG_PTR)pLdrDataEntry->DllBase;
				}
				if (Size) {
					*Size = pLdrDataEntry->SizeOfImage;
				}
				Status = STATUS_SUCCESS;
				break;
			}
			ListPtr = ListPtr->Flink;
		} while (ListPtr->Flink != ListHead);
	}

那特殊的Wow64又有什么不同呢?(x64内核获取x86进程模块信息)。

对于wow64进程来说,EPROCESS结构中有个特殊字段保存wow64的peb结构。

struct _EPROCESS
{
	PVOID Wow64Process;//
}

Win7之前Wow64Process是_WOW64_PROCESS结构,内部包含字段位wow64的peb,win7后Wow64Process直接就是wow64的peb。

直接就可以通过PsGetProcessWow64Process(未文档化函数)来获取到该字段。

Peb = PsGetProcessWow64Process(Process); //Process->Wow64Process

wow64的peb结构不再是_PEB,而是使用于wow64的_PEB32,一大特点就是所有的地址都是32位的,为了在x64下定义这种字段,只好使用ULONG。

#pragma pack(push, 1)
typedef struct _PEB32 {
	BOOLEAN InheritedAddressSpace;      // These four fields cannot change unless the
	BOOLEAN ReadImageFileExecOptions;   //
	BOOLEAN BeingDebugged;              //
	BOOLEAN SpareBool;                  //
	ULONG Mutant;                      // INITIAL_PEB structure is also updated.

	ULONG ImageBaseAddress;
	ULONG Ldr;//PPEB_LDR_DATA32
}PEB32, *PPEB32;
#pragma pack(pop)

结构中Ldr位32位地址指针,使用ULONG定义,Ldr也是特殊的_PEB_LDR_DATA32结构,字段和普通的_PEB_LDR_DATA完全一致,只是地址全为ULONG(32位,x64地址为64位)。

typedef struct _PEB_LDR_DATA32 {
	ULONG Length;
	ULONG Initialized;//bool
	ULONG SsHandle;
	LIST_ENTRY32 InLoadOrderModuleList;
	LIST_ENTRY32 InMemoryOrderModuleList;
	LIST_ENTRY32 InInitializationOrderModuleList;
	ULONG EntryInProgress;//pvoid
} PEB_LDR_DATA32, *PPEB_LDR_DATA32;

如此wow64进程模块信息获取的方法也出来了。

#define UlongToPtr(ul) ULongToPtr(ul)
#define ULongToPtr( ul ) ((VOID *)(ULONG_PTR)((unsigned long)ul))

Peb = PsGetProcessWow64Process(Process);
Ldr = UlongToPtr(Peb->Ldr);
if (Ldr && Ldr->Initialized)
{
	if (UlongToPtr(Ldr->InLoadOrderModuleList.Flink) != &Ldr->InLoadOrderModuleList)
	{
		ListPtr = ListHead = UlongToPtr(Ldr->InLoadOrderModuleList.Flink);
		do
		{
			pLdrDataEntry = CONTAINING_RECORD(ListPtr, LDR_DATA_TABLE_ENTRY32, InLoadOrderLinks);
			RtlZeroMemory(ModuleName, MAX_PATH*sizeof(WCHAR));
			RtlCopyMemory(ModuleName, UlongToPtr(pLdrDataEntry->BaseDllName.Buffer),
						pLdrDataEntry->BaseDllName.Length > MAX_PATH*sizeof(WCHAR) ? 
						MAX_PATH*sizeof(WCHAR):pLdrDataEntry->BaseDllName.Length);
			RtlInitUnicodeString(&Name, ModuleName);
			if (RtlEqualUnicodeString(&Name, Target, TRUE))
			{
				if (Base) {
					*Base = (ULONG_PTR)pLdrDataEntry->DllBase;
				}
				if (Size) {
					*Size = pLdrDataEntry->SizeOfImage;
				}
				Status = STATUS_SUCCESS;				
				break;
			}
			ListPtr = UlongToPtr(ListPtr->Flink);
		} while (ListPtr->Flink != (ULONG)ListHead);
	}
}

搞定。

参考:

1.http://www.cnblogs.com/welfear/archive/2010/11/16/1878503.html
2.http://www.kernelmode.info/forum/viewtopic.php?t=2516
3.http://rce.co/category/wow64/

PEB(Process Environment Block)是Windows操作系统中的一个重要结构体,它包含了当前进程的环境信息。通过PEB,我们可以获取当前进程模块信息,包括模块的基地址、模块的名称等。 在x64和wow64下,PEB结构体的定义并没有变化,但是由于x64和wow64的指针长度不同,所以在使用PEB时需要注意指针长度的问题。 以下是通过PEB遍历进程模块的代码示例: ```c++ #include <windows.h> #include <winternl.h> #include <iostream> typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING, *PUNICODE_STRING; typedef struct _PEB_LDR_DATA { ULONG Length; BOOLEAN Initialized; PVOID SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; } PEB_LDR_DATA, *PPEB_LDR_DATA; typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; USHORT LoadCount; USHORT TlsIndex; union { LIST_ENTRY HashLinks; struct { PVOID SectionPointer; ULONG CheckSum; }; }; union { ULONG TimeDateStamp; PVOID LoadedImports; }; PVOID EntryPointActivationContext; PVOID PatchInformation; LIST_ENTRY ForwarderLinks; LIST_ENTRY ServiceTagLinks; LIST_ENTRY StaticLinks; } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; typedef struct _PEB { BOOLEAN InheritedAddressSpace; BOOLEAN ReadImageFileExecOptions; BOOLEAN BeingDebugged; BOOLEAN Spare; HANDLE Mutant; PVOID ImageBaseAddress; PPEB_LDR_DATA LoaderData; PVOID ProcessParameters; PVOID SubSystemData; PVOID ProcessHeap; PVOID FastPebLock; PVOID AtlThunkSListPtr; PVOID IFEOKey; PVOID CrossProcessFlags; PVOID UserSharedInfoPtr; ULONG SystemReserved; ULONG AtlThunkSListPtr32; PVOID ApiSetMap; ULONG TlsExpansionCounter; PVOID TlsBitmap; ULONG TlsBitmapBits[2]; PVOID ReadOnlySharedMemoryBase; PVOID HotpatchInformation; PVOID ReadOnlyStaticServerData; PVOID AnsiCodePageData; PVOID OemCodePageData; PVOID UnicodeCaseTableData; ULONG NumberOfProcessors; ULONG NtGlobalFlag; LARGE_INTEGER CriticalSectionTimeout; ULONG_PTR HeapSegmentReserve; ULONG_PTR HeapSegmentCommit; ULONG_PTR HeapDeCommitTotalFreeThreshold; ULONG_PTR HeapDeCommitFreeBlockThreshold; ULONG_PTR NumberOfHeaps; ULONG_PTR MaximumNumberOfHeaps; PVOID ProcessHeaps; PVOID GdiSharedHandleTable; PVOID ProcessStarterHelper; PVOID GdiDCAttributeList; PVOID LoaderLock; ULONG OSMajorVersion; ULONG OSMinorVersion; USHORT OSBuildNumber; USHORT OSCSDVersion; ULONG OSPlatformId; ULONG ImageSubsystem; ULONG ImageSubsystemMajorVersion; ULONG ImageSubsystemMinorVersion; ULONG_PTR ImageProcessAffinityMask; ULONG_PTR GdiHandleBuffer[34]; PVOID PostProcessInitRoutine; PVOID TlsExpansionBitmap; ULONG TlsExpansionBitmapBits[32]; ULONG SessionId; ULARGE_INTEGER AppCompatFlags; ULARGE_INTEGER AppCompatFlagsUser; PVOID pShimData; PVOID AppCompatInfo; UNICODE_STRING CSDVersion; PVOID ActivationContextData; PVOID ProcessAssemblyStorageMap; PVOID SystemDefaultActivationContextData; PVOID SystemAssemblyStorageMap; ULONG_PTR MinimumStackCommit; } PEB, *PPEB; void EnumerateProcessModules(HANDLE hProcess) { PEB peb; ZeroMemory(&peb, sizeof(PEB)); // 获取PEB地址 BOOL bRet = ReadProcessMemory(hProcess, &NtCurrentTeb()->ProcessEnvironmentBlock, &peb, sizeof(PEB), NULL); if (!bRet) { std::cout << "ReadProcessMemory failed!" << std::endl; return; } // 遍历模块列表 PPEB_LDR_DATA pLdrData = peb.LoaderData; if (pLdrData == NULL) { std::cout << "LoaderData is NULL!" << std::endl; return; } PLIST_ENTRY pListHead = &pLdrData->InMemoryOrderModuleList; PLIST_ENTRY pListEntry = pListHead->Flink; while (pListEntry != pListHead) { PLDR_DATA_TABLE_ENTRY pLdrEntry = CONTAINING_RECORD(pListEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); if (pLdrEntry->DllBase != NULL) { // 获取模块基地址和名称 wchar_t szModule[MAX_PATH] = { 0 }; bRet = ReadProcessMemory(hProcess, pLdrEntry->FullDllName.Buffer, szModule, pLdrEntry->FullDllName.Length, NULL); if (bRet) { std::wcout << L"Module Base: " << pLdrEntry->DllBase << L", Module Name: " << szModule << std::endl; } } pListEntry = pListEntry->Flink; } } int main(int argc, char* argv[]) { DWORD dwProcessId = 0; if (argc > 1) { dwProcessId = atoi(argv[1]); } HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId); if (hProcess == NULL) { std::cout << "OpenProcess failed!" << std::endl; return 0; } EnumerateProcessModules(hProcess); CloseHandle(hProcess); return 0; } ``` 需要注意的是,以上代码中使用了一些Windows内部的结构体和函数,如`UNICODE_STRING`、`LIST_ENTRY`、`NtCurrentTeb()`等,需要包含相应的头文件,并且这些结构体和函数都不是官方公开的API,可能会在未来的操作系统版本中发生变化。因此,开发者在使用这些结构体和函数时需要特别注意。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值