枚举进程模块通常可以使用诸如:CreateToolhelp32Snapshot,Module32First,Module32Next 等"Tool Help Functions"接口来实现, 并且这也是最通用的方法(从Win95就开始支持了), 但是今天我们要介绍的是ntdll.dll导出的未文档化接口ZwQueryVirtualMemory,。相比前面所介绍的方法,该方法可以检测出隐藏的模块(类似IceSword)。
我们先来看下这个接口的原型:
//-------------------------------------------------------------------------------------------------
NTSTATUS
NTAPI
ZwQueryVirtualMemory(
IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
IN MEMORY_INFORMATION_CLASS MemoryInformationClass,
OUT PVOID MemoryInformation,
IN ULONG MemoryInformationLength,
OUT PULONG ReturnLength OPTIONAL );
typedef enum _MEMORY_INFORMATION_CLASS {
MemoryBasicInformation,
MemoryWorkingSetList,
MemorySectionName,
MemoryBasicVlmInformation
} MEMORY_INFORMATION_CLASS;
参数说明:
ProcessHandle - 目标进程句柄
BaseAddress - 要查询的虚拟内存基址
MemoryInformationClass - 要查询的内存信息类
MemoryInformation - 用户提供的缓冲区,返回内存相关信息
MemoryInformationLength - 缓冲区长度(字节为单位)
ReturnLength - 实际返回的内存信息长度(字节为单位)
返回值:
NTSTATUS - 返回STATUS_SUCCESS或一个错误状态码
//-------------------------------------------------------------------------------------------------
我们要枚举进程模块信息, 需要用到两类内存信息MemoryBasicInformation和MemorySectionName,
前者返回内存的基本信息, 比如: 内存区的基址,大小以及页面的各种属性等等, 而后者则返回内存段的名字,
也就是我们所要找的模块名. 利用前者我们可以过滤出类型为MEM_IMAGE的内存段并得到内存段的基址和属性, 利用后者我们可以得到模块名.
另外,需要注意的是该方法找出来的设备名是诸如\Device\HarddiskVolume1之类的名称,所以我们需要把它转换为我们习惯的DOS设备名,如C:\,D:\等。不能自以为是的认为\Device\HarddiskVolume1对应C盘\Device\HarddiskVolume2对应D盘。该转换是通过调用QueryDosDevice来实现的。
具体看代码:
// CheckDll.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <windows.h>
#include <winternl.h>
#include <string>
#include <map>
using namespace std;
#pragma warning(disable:4312)
typedef enum _MEMORY_INFORMATION_CLASS
{
MemoryBasicInformation,
MemoryWorkingSetList,
MemorySectionName
}MEMORY_INFORMATION_CLASS;
typedef
NTSTATUS
(WINAPI *ZWQUERYVIRTUALMEMORY) (
IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
IN MEMORY_INFORMATION_CLASS MemoryInformationClass,
OUT PVOID MemoryInformation,
IN ULONG MemoryInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
map<wstring, wstring> g_mapDevice2Path;
void ConvertVolumePaths(
IN PWCHAR DeviceName,
IN PWCHAR VolumeName
)
{
DWORD CharCount = MAX_PATH + 1;
PWCHAR Names = NULL;
PWCHAR NameIdx = NULL;
BOOL Success = FALSE;
for (;;)
{
//
// Allocate a buffer to hold the paths.
Names = (PWCHAR) new BYTE [CharCount * sizeof(WCHAR)];
if ( !Names )
{
//
// If memory can't be allocated, return.
return;
}
//
// Obtain all of the paths
// for this volume.
Success = GetVolumePathNamesForVolumeNameW(
VolumeName, Names, CharCount, &CharCount
);
if ( Success )
{
break;
}
if ( GetLastError() != ERROR_MORE_DATA )
{
break;
}
//
// Try again with the
// new suggested size.
delete [] Names;
Names = NULL;
}
if ( Success )
{
//
// Display the various paths.
for ( NameIdx = Names;
NameIdx[0] != L'\0';
NameIdx += wcslen(NameIdx) + 1 )
{
g_mapDevice2Path[DeviceName] = NameIdx;
}
}
if ( Names != NULL )
{
delete [] Names;
Names = NULL;
}
return;
}
BOOL InitDevice2Path()
{
BOOL bRet = FALSE;
DWORD CharCount = 0;
WCHAR DeviceName[MAX_PATH] = L"";
DWORD Error = ERROR_SUCCESS;
HANDLE FindHandle = INVALID_HANDLE_VALUE;
BOOL Found = FALSE;
size_t Index = 0;
BOOL Success = FALSE;
WCHAR VolumeName[MAX_PATH] = L"";
//
// Enumerate all volumes in the system.
FindHandle = FindFirstVolumeW(VolumeName, ARRAYSIZE(VolumeName));
if (FindHandle == INVALID_HANDLE_VALUE)
{
Error = GetLastError();
wprintf(L"FindFirstVolumeW failed with error code %d\n", Error);
return bRet;
}
for (;;)
{
//
// Skip the \\?\ prefix and remove the trailing backslash.
Index = wcslen(VolumeName) - 1;
if (VolumeName[0] != L'\\' ||
VolumeName[1] != L'\\' ||
VolumeName[2] != L'?' ||
VolumeName[3] != L'\\' ||
VolumeName[Index] != L'\\')
{
Error = ERROR_BAD_PATHNAME;
wprintf(L"FindFirstVolumeW/FindNextVolumeW returned a bad path: %s\n", VolumeName);
break;
}
//
// QueryDosDeviceW doesn't allow a trailing backslash,
// so temporarily remove it.
VolumeName[Index] = L'\0';
CharCount = QueryDosDeviceW(&VolumeName[4], DeviceName, ARRAYSIZE(DeviceName));
VolumeName[Index] = L'\\';
if ( CharCount == 0 )
{
Error = GetLastError();
wprintf(L"QueryDosDeviceW failed with error code %d\n", Error);
break;
}
ConvertVolumePaths(DeviceName, VolumeName);
//
// Move on to the next volume.
Success = FindNextVolumeW(FindHandle, VolumeName, ARRAYSIZE(VolumeName));
if ( !Success )
{
Error = GetLastError();
if (Error != ERROR_NO_MORE_FILES)
{
wprintf(L"FindNextVolumeW failed with error code %d\n", Error);
break;
}
//
// Finished iterating
// through all the volumes.
Error = ERROR_SUCCESS;
break;
}
}
FindVolumeClose(FindHandle);
FindHandle = INVALID_HANDLE_VALUE;
return bRet;
}
void DeviceName2PathName(OUT WCHAR* szPathName, IN const WCHAR* szDeviceName)
{
memset(szPathName, 0, MAX_PATH * 2);
wstring strDeviceName = szDeviceName;
size_t pos = strDeviceName.find(L'\\', 9);
wstring strTemp1 = strDeviceName.substr(0, pos);
wstring strTemp2 = strDeviceName.substr(pos + 1);
wstring strDriverLetter = g_mapDevice2Path[strTemp1];
wstring strPathName = strDriverLetter + strTemp2;
wcscpy_s(szPathName, MAX_PATH, strPathName.c_str());
}
/**
* 枚举指定进程加载的模块
* @param dwProcessId 进程Id
* @return void
*/
void EnumProcessModules(IN DWORD dwProcessId)
{
DWORD dwStartAddr = 0x00000000;
BYTE szBuffer[MAX_PATH * 2 + 4] = {0};
WCHAR szModuleName[MAX_PATH] = {0};
WCHAR szPathName[MAX_PATH] = {0};
MEMORY_BASIC_INFORMATION mbi;
PUNICODE_STRING usSectionName;
ZWQUERYVIRTUALMEMORY fnZwQueryVirtualMemory;
HANDLE hProcess =NULL;
hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, dwProcessId);
if (hProcess == NULL)
{
wprintf(L"Open Process %d Error\n", dwProcessId);
return;
}
dwStartAddr = 0x00000000;
fnZwQueryVirtualMemory = (ZWQUERYVIRTUALMEMORY)
::GetProcAddress(GetModuleHandleA("ntdll.dll"),
"ZwQueryVirtualMemory" );
if(fnZwQueryVirtualMemory)
{
do
{
if (fnZwQueryVirtualMemory(
hProcess,
(PVOID)dwStartAddr,
MemoryBasicInformation,
&mbi,
sizeof(mbi),
0) >= 0 )
{
if(mbi.Type == MEM_IMAGE)
{
if (fnZwQueryVirtualMemory(
hProcess,
(PVOID)dwStartAddr,
MemorySectionName,
szBuffer,
sizeof(szBuffer),
0) >= 0 )
{
usSectionName = (PUNICODE_STRING)szBuffer;
if( _wcsnicmp(szModuleName, usSectionName->Buffer, usSectionName->Length / sizeof(WCHAR)) )
{
wcsncpy_s(szModuleName, usSectionName->Buffer, usSectionName->Length / sizeof(WCHAR) );
szModuleName[usSectionName->Length / sizeof(WCHAR)] = UNICODE_NULL;
DeviceName2PathName(szPathName, szModuleName);
wprintf(L"[0x%.8x]\t%s\n", dwStartAddr, szPathName);
}
}
}
}
// 递增基址,开始下一轮查询!
dwStartAddr += 0x1000;
}while( dwStartAddr < 0x80000000 );
}
CloseHandle(hProcess);
}
/**
* 提升当前进程权限函数("SeDebugPrivilege"读、写控制权限)
* @param void
* @return TRUE-成功;FALSE-失败
*/
BOOL EnableDebugPriv()
{
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
LUID Luid;
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
return FALSE;
}
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &Luid ))
{
CloseHandle(hToken);
return FALSE;
}
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = Luid;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL))
{
CloseHandle(hToken);
return FALSE;
}
return TRUE;
}
int _tmain(int argc, _TCHAR* argv[])
{
DWORD dwProcessId = 4;
if (argc != 2)
{
wprintf(L"usage:CheckDll ProcessId");
return 1;
}
dwProcessId = _ttoi(argv[1]);
InitDevice2Path();
// 首先提示权限
if (EnableDebugPriv())
{
EnumProcessModules(dwProcessId);
}
g_mapDevice2Path.clear();
return 0;
}