ZwQueryVirtualMemory枚举进程模块
01.用ZwQueryVirtualMemory枚举进程模块
02.枚举进程模块通常可以使用诸如:CreateToolhelp32Snapshot,Module32First,Module32Next 等"Tool Help Functions"接口来实现, 并且这也是最通用的方法(从Win95就开始支持了), 但是今天我们要介绍的是ntdll.dll导出的未文档化接口ZwQueryVirtualMemory,。相比前面所介绍的方法,该方法可以检测出隐藏的模块(类似IceSword)。
03.我们先来看下这个接口的原型:
04.
05.//-------------------------------------------------------------------------------------------------
06.
07.NTSTATUS
08.NTAPI
09.ZwQueryVirtualMemory(
10. IN HANDLE ProcessHandle,
11. IN PVOID BaseAddress,
12. IN MEMORY_INFORMATION_CLASS MemoryInformationClass,
13. OUT PVOID MemoryInformation,
14. IN ULONG MemoryInformationLength,
15. OUT PULONG ReturnLength OPTIONAL );
16.
17.typedef enum _MEMORY_INFORMATION_CLASS {
18. MemoryBasicInformation,
19. MemoryWorkingSetList,
20. MemorySectionName,
21. MemoryBasicVlmInformation
22.} MEMORY_INFORMATION_CLASS;
23.
24.参数说明:
25. ProcessHandle - 目标进程句柄
26. BaseAddress - 要查询的虚拟内存基址
27. MemoryInformationClass - 要查询的内存信息类
28. MemoryInformation - 用户提供的缓冲区,返回内存相关信息
29. MemoryInformationLength - 缓冲区长度(字节为单位)
30. ReturnLength - 实际返回的内存信息长度(字节为单位)
31.返回值:
32. NTSTATUS - 返回STATUS_SUCCESS或一个错误状态码
33.
34.//-------------------------------------------------------------------------------------------------
35.
36.我们要枚举进程模块信息, 需要用到两类内存信息MemoryBasicInformation和MemorySectionName,
37.前者返回内存的基本信息, 比如: 内存区的基址,大小以及页面的各种属性等等, 而后者则返回内存段的名字,
38.也就是我们所要找的模块名. 利用前者我们可以过滤出类型为MEM_IMAGE的内存段并得到内存段的基址和属性, 利用后者我们可以得到模块名.
39.另外,需要注意的是该方法找出来的设备名是诸如\Device\HarddiskVolume1之类的名称,所以我们需要把它转换为我们习惯的DOS设备名,如C:\,D:\等。不能自以为是的认为\Device\HarddiskVolume1对应C盘\Device\HarddiskVolume2对应D盘。该转换是通过调用QueryDosDevice来实现的。
40.具体看代码:
41.// CheckDll.cpp : 定义控制台应用程序的入口点。
42.//
43.
44.#include "stdafx.h"
45.
46.#include <windows.h>
47.#include <winternl.h>
48.
49.#include <string>
50.#include <map>
51.using namespace std;
52.
53.#pragma warning(disable:4312)
54.
55.typedef enum _MEMORY_INFORMATION_CLASS
56.{
57. MemoryBasicInformation,
58. MemoryWorkingSetList,
59. MemorySectionName
60.}MEMORY_INFORMATION_CLASS;
61.
62.typedef
63.NTSTATUS
64.(WINAPI *ZWQUERYVIRTUALMEMORY) (
65. IN HANDLE ProcessHandle,
66. IN PVOID BaseAddress,
67. IN MEMORY_INFORMATION_CLASS MemoryInformationClass,
68. OUT PVOID MemoryInformation,
69. IN ULONG MemoryInformationLength,
70. OUT PULONG ReturnLength OPTIONAL
71. );
72.
73.map<wstring, wstring> g_mapDevice2Path;
74.
75.void ConvertVolumePaths(
76. IN PWCHAR DeviceName,
77. IN PWCHAR VolumeName
78. )
79.{
80. DWORD CharCount = MAX_PATH + 1;
81. PWCHAR Names = NULL;
82. PWCHAR NameIdx = NULL;
83. BOOL Success = FALSE;
84.
85. for (;;)
86. {
87. //
88. // Allocate a buffer to hold the paths.
89. Names = (PWCHAR) new BYTE [CharCount * sizeof(WCHAR)];
90.
91. if ( !Names )
92. {
93. //
94. // If memory can't be allocated, return.
95. return;
96. }
97.
98. //
99. // Obtain all of the paths
100. // for this volume.
101. Success = GetVolumePathNamesForVolumeNameW(
102. VolumeName, Names, CharCount, &CharCount
103. );
104.
105. if ( Success )
106. {
107. break;
108. }
109.
110. if ( GetLastError() != ERROR_MORE_DATA )
111. {
112. break;
113. }
114.
115. //
116. // Try again with the
117. // new suggested size.
118. delete [] Names;
119. Names = NULL;
120. }
121.
122. if ( Success )
123. {
124. //
125. // Display the various paths.
126. for ( NameIdx = Names;
127. NameIdx[0] != L'\0';
128. NameIdx += wcslen(NameIdx) + 1 )
129. {
130. g_mapDevice2Path[DeviceName] = NameIdx;
131. }
132. }
133.
134. if ( Names != NULL )
135. {
136. delete [] Names;
137. Names = NULL;
138. }
139.
140. return;
141.}
142.
143.BOOL InitDevice2Path()
144.{
145. BOOL bRet = FALSE;
146. DWORD CharCount = 0;
147. WCHAR DeviceName[MAX_PATH] = L"";
148. DWORD Error = ERROR_SUCCESS;
149. HANDLE FindHandle = INVALID_HANDLE_VALUE;
150. BOOL Found = FALSE;
151. size_t Index = 0;
152. BOOL Success = FALSE;
153. WCHAR VolumeName[MAX_PATH] = L"";
154.
155. //
156. // Enumerate all volumes in the system.
157. FindHandle = FindFirstVolumeW(VolumeName, ARRAYSIZE(VolumeName));
158.
159. if (FindHandle == INVALID_HANDLE_VALUE)
160. {
161. Error = GetLastError();
162. wprintf(L"FindFirstVolumeW failed with error code %d\n", Error);
163. return bRet;
164. }
165.
166. for (;;)
167. {
168. //
169. // Skip the \\?\ prefix and remove the trailing backslash.
170. Index = wcslen(VolumeName) - 1;
171.
172. if (VolumeName[0] != L'\\' ||
173. VolumeName[1] != L'\\' ||
174. VolumeName[2] != L'?' ||
175. VolumeName[3] != L'\\' ||
176. VolumeName[Index] != L'\\')
177. {
178. Error = ERROR_BAD_PATHNAME;
179. wprintf(L"FindFirstVolumeW/FindNextVolumeW returned a bad path: %s\n", VolumeName);
180. break;
181. }
182.
183. //
184. // QueryDosDeviceW doesn't allow a trailing backslash,
185. // so temporarily remove it.
186. VolumeName[Index] = L'\0';
187.
188. CharCount = QueryDosDeviceW(&VolumeName[4], DeviceName, ARRAYSIZE(DeviceName));
189.
190. VolumeName[Index] = L'\\';
191.
192. if ( CharCount == 0 )
193. {
194. Error = GetLastError();
195. wprintf(L"QueryDosDeviceW failed with error code %d\n", Error);
196. break;
197. }
198.
199. ConvertVolumePaths(DeviceName, VolumeName);
200.
201. //
202. // Move on to the next volume.
203. Success = FindNextVolumeW(FindHandle, VolumeName, ARRAYSIZE(VolumeName));
204.
205. if ( !Success )
206. {
207. Error = GetLastError();
208.
209. if (Error != ERROR_NO_MORE_FILES)
210. {
211. wprintf(L"FindNextVolumeW failed with error code %d\n", Error);
212. break;
213. }
214.
215. //
216. // Finished iterating
217. // through all the volumes.
218. Error = ERROR_SUCCESS;
219. break;
220. }
221. }
222.
223. FindVolumeClose(FindHandle);
224. FindHandle = INVALID_HANDLE_VALUE;
225.
226. return bRet;
227.}
228.
229.void DeviceName2PathName(OUT WCHAR* szPathName, IN const WCHAR* szDeviceName)
230.{
231. memset(szPathName, 0, MAX_PATH * 2);
232. wstring strDeviceName = szDeviceName;
233. size_t pos = strDeviceName.find(L'\\', 9);
234. wstring strTemp1 = strDeviceName.substr(0, pos);
235. wstring strTemp2 = strDeviceName.substr(pos + 1);
236. wstring strDriverLetter = g_mapDevice2Path[strTemp1];
237. wstring strPathName = strDriverLetter + strTemp2;
238.
239. wcscpy_s(szPathName, MAX_PATH, strPathName.c_str());
240.}
241.
242./**
243.* 枚举指定进程加载的模块
244.* @param dwProcessId 进程Id
245.* @return void
246.*/
247.void EnumProcessModules(IN DWORD dwProcessId)
248.{
249. DWORD dwStartAddr = 0x00000000;
250. BYTE szBuffer[MAX_PATH * 2 + 4] = {0};
251. WCHAR szModuleName[MAX_PATH] = {0};
252. WCHAR szPathName[MAX_PATH] = {0};
253. MEMORY_BASIC_INFORMATION mbi;
254. PUNICODE_STRING usSectionName;
255. ZWQUERYVIRTUALMEMORY fnZwQueryVirtualMemory;
256. HANDLE hProcess =NULL;
257.
258. hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, dwProcessId);
259.
260. if (hProcess == NULL)
261. {
262. wprintf(L"Open Process %d Error\n", dwProcessId);
263. return;
264. }
265.
266. dwStartAddr = 0x00000000;
267.
268. fnZwQueryVirtualMemory = (ZWQUERYVIRTUALMEMORY)
269. ::GetProcAddress(GetModuleHandleA("ntdll.dll"),
270. "ZwQueryVirtualMemory" );
271.
272. if(fnZwQueryVirtualMemory)
273. {
274. do
275. {
276. if (fnZwQueryVirtualMemory(
277. hProcess,
278. (PVOID)dwStartAddr,
279. MemoryBasicInformation,
280. &mbi,
281. sizeof(mbi),
282. 0) >= 0 )
283. {
284. if(mbi.Type == MEM_IMAGE)
285. {
286. if (fnZwQueryVirtualMemory(
287. hProcess,
288. (PVOID)dwStartAddr,
289. MemorySectionName,
290. szBuffer,
291. sizeof(szBuffer),
292. 0) >= 0 )
293. {
294. usSectionName = (PUNICODE_STRING)szBuffer;
295. if( _wcsnicmp(szModuleName, usSectionName->Buffer, usSectionName->Length / sizeof(WCHAR)) )
296. {
297. wcsncpy_s(szModuleName, usSectionName->Buffer, usSectionName->Length / sizeof(WCHAR) );
298. szModuleName[usSectionName->Length / sizeof(WCHAR)] = UNICODE_NULL;
299. DeviceName2PathName(szPathName, szModuleName);
300. wprintf(L"[0x%.8x]\t%s\n", dwStartAddr, szPathName);
301. }
302. }
303. }
304.
305. }
306. // 递增基址,开始下一轮查询!
307. dwStartAddr += 0x1000;
308. }while( dwStartAddr < 0x80000000 );
309. }
310.
311. CloseHandle(hProcess);
312.}
313.
314./**
315.* 提升当前进程权限函数("SeDebugPrivilege"读、写控制权限)
316.* @param void
317.* @return TRUE-成功;FALSE-失败
318.*/
319.BOOL EnableDebugPriv()
320.{
321. HANDLE hToken;
322. TOKEN_PRIVILEGES tkp;
323. LUID Luid;
324.
325. if (!OpenProcessToken(GetCurrentProcess(),
326. TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
327. {
328. return FALSE;
329. }
330.
331. if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &Luid ))
332. {
333. CloseHandle(hToken);
334. return FALSE;
335. }
336.
337. tkp.PrivilegeCount = 1;
338. tkp.Privileges[0].Luid = Luid;
339. tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
340.
341. if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL))
342. {
343. CloseHandle(hToken);
344. return FALSE;
345. }
346.
347. return TRUE;
348.}
349.
350.int _tmain(int argc, _TCHAR* argv[])
351.{
352. DWORD dwProcessId = 4;
353.
354. if (argc != 2)
355. {
356. wprintf(L"usage:CheckDll ProcessId");
357. return 1;
358. }
359.
360. dwProcessId = _ttoi(argv[1]);
361.
362. InitDevice2Path();
363.
364. // 首先提示权限
365. if (EnableDebugPriv())
366. {
367. EnumProcessModules(dwProcessId);
368. }
369.
370. g_mapDevice2Path.clear();
371.
372. return 0;
373.}