学习自建调试体系(二)
目录
这一部分的代码的作用主要是用于枚举NT内核模块的符号文件,很多都是常规写法(同一个套路),来做个总结,以后肯定是会再见到的。
获取模块信息
/*
获取模块信息到SysModInfo结构
*/
BOOL DbgGetSysModuleInfo(PSYSTEM_MODULE SysModInfo)
{
ULONG ulInfoLen = 0;
NTSTATUS Status;
SYSTEM_MODULE_INFORMATION* pStLoaderInfo = NULL;
if (SysModInfo == NULL)
{
return FALSE;
}
// 套路1
// 2次调用NtQuerySystemInforamtion获取系统模块信息。
// 首次调用获取模块信息长度,作为第二次调用的参数
// 再次调用才是真正的获取信息
// NtQuerySystemInformation下文有详细解释
NtQuerySystemInformation(SystemModuleInformation, NULL, 0, &ulInfoLen);
if (!ulInfoLen)
{
return STATUS_UNSUCCESSFUL;
}
pStLoaderInfo = (SYSTEM_MODULE_INFORMATION*)malloc(ulInfoLen + sizeof(ULONG));
//真正获取系统模块信息
Status = NtQuerySystemInformation(SystemModuleInformation, (PVOID)pStLoaderInfo,
ulInfoLen, &ulInfoLen);
if (!NT_SUCCESS(Status))
{
if (pStLoaderInfo)
{
free(pStLoaderInfo);
}
return Status;
}
/*
typedef struct _SYSTEM_MODULE_INFORMATION
{
ULONG uCount; //模块个数
SYSTEM_MODULE aSM[]; //模块数组
}
*/
if (pStLoaderInfo->aSM == NULL)
{
return STATUS_UNSUCCESSFUL;
}
//pStLoaderInfo->aSM[0] 第一个模块就是NT内核模块
//驱动级的内存复制
RtlMoveMemory(SysModInfo, &pStLoaderInfo->aSM[0], sizeof(SYSTEM_MODULE));
if (pStLoaderInfo)
{
free(pStLoaderInfo);
}
return TRUE;
}
NtQuerySystemInformation
NtQuerySystemInformation用于获取多种系统信息中的其中之一。
NTSTATUS
NtQuerySystemInformation(
IN SYSTEMINFOCLASS SystemInformationClass, //指定获取哪种系统信息,枚举值
OUT PVOID pSystemInformation, //buffer指针,用于接收请求的信息
IN ULONG uSystemInformationLength, //buffer的长度
OUT PULONG puReturnLength OPTIONAL //函数实际返回的系统信息占用的长度
);
RtlMoveMemory
//内存复制
VOID RtlMoveMemory(
_Out_ VOID UNALIGNED *Destination,
_In_ const VOID UNALIGNED *Source,
_In_ SIZE_T Length
);
枚举符号
枚举符号的过程感觉像是实现windbg从上游符号服务器获得符号文件的过程。很多地方能用到,IDA有时候也要符号文件,感觉也是很实用的套路。
DbgObjEnumSymbols(VOID)
{
CHAR FilePath[512] = { 0 };
CHAR Ntpath[512] = { 0 };
CHAR pSymPath[512] = { 0 };
CHAR SymFileName[512] = { 0 };
wchar_t str[256] = { 0 };
wchar_t SzOldEnvVar[512] = { 0 };
SYSTEM_MODULE SysModInfo;
HANDLE hProcess;
HANDLE hFile;
BOOL bRet = TRUE;
DWORD dwFileSize = 0;//文件大小
//
//------------------------------------获得NT模块的信息
//
if (!DbgGetSysModuleInfo(&SysModInfo))
{
return FALSE;
}
BaseOfDll = SysModInfo.Base;
//
//----------------------------------获取系统内核文件路径
//
GetSystemDirectoryA(Ntpath, sizeof(Ntpath));
strcat_s(Ntpath, sizeof(Ntpath), "\\");
strcat_s(Ntpath, sizeof(Ntpath), SysModInfo.ImageName + SysModInfo.ModuleNameOffset);
OutputDebugStringA(Ntpath);
//
//-----------------------------------获取当前程序绝对路径
//
DbgGetModuleFilePathA(FilePath, sizeof(FilePath));
sprintf_s(pSymPath, sizeof(pSymPath), "srv*%ssymbols*http://msdl.microsoft.com/download/symbols", FilePath);
OutputDebugStringA(pSymPath);
//
//------------------------------------ 设置符号选项
//
SymSetOptions(SYMOPT_CASE_INSENSITIVE | SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME);
//
//------------------------------------初始化符号库
//
hProcess = GetCurrentProcess();
bRet = SymInitialize(hProcess, pSymPath, TRUE); //说明函数1
if (!bRet)
{
swprintf_s(str, SizeOf(str), _T("SymInitialize bRet:%d"), bRet);
OutputDebugString(str);
return FALSE;
}
OutputDebugString(_T("初始化符号库成功!"));
//
//---------------------------------得到内核文件大小dwFileSize
//
hFile = CreateFileA(
Ntpath,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL
);
if (INVALID_HANDLE_VALUE == hFile)
{
swprintf_s(str, SizeOf(str), _T("CreateFile hFile:%d"), hFile);
OutputDebugString(str);
return FALSE;
}
if (INVALID_FILE_SIZE == (dwFileSize = GetFileSize(hFile, NULL)))
{
swprintf_s(str, SizeOf(str), _T("GetFileSize dwFileSize:%d"), dwFileSize);
OutputDebugString(str);
return FALSE;
}
CloseHandle(hFile);
__try
{
//
//调用SymLoadModule函数载入对应符号库-----------------------------------------------
//
DWORD64 dw64ModAddress = SymLoadModule64( //说明函数2
hProcess,
NULL,
Ntpath,
NULL,
(DWORD64)SysModInfo.Base,
dwFileSize
);
if (dw64ModAddress == 0)
{
swprintf_s(str, SizeOf(str), _T("SymLoadModule64 dw64ModAddress:%ld"), dw64ModAddress);
OutputDebugString(str);
return FALSE;
}
//
//使用SymEnumSymbols函数枚举模块中的符号
//
//学习点4
/*
枚举符号,它既可以枚举全局的符号,也可以枚举当前作用域内的符号
BOOL WINAPI SymEnumSymbols(
_In_ HANDLE hProcess, // 符号处理器的标识符,之前传递给SymInitialize函数
_In_ ULONG64 BaseOfDll, // 指定模块的基地址
_In_opt_ PCTSTR Mask, // 字符串,只有名称与该字符串匹配的符号才会被枚举,在字符串中允许使用通配符*和?
_In_ PSYM_ENUMERATESYMBOLS_CALLBACK EnumSymbolsCallback, // 对于每个被枚举的符号都会调用该函数
_In_opt_ const PVOID UserContext //用于传递给EnumSymbolsCallback函数的UserContext参数
);
*/
OutputDebugString(_T("使用SymEnumSymbols函数枚举模块中的符号!"));
if (!SymEnumSymbolsW( //说明函数3
hProcess,
dw64ModAddress,
NULL,
EnumSymCallBack, //说明函数4
NULL))
{
return FALSE;
}
SymUnloadModule64(hProcess, dw64ModAddress);
SymCleanup(hProcess);
bRet = TRUE;
}
__except (EXCEPTION_EXECUTE_HANDLER) {
bRet = FALSE;
}
return bRet;
}
SymInitialize
初始化进程所对应的符号处理程序
BOOL
IMAGEAPI
SymInitialize(
__in HANDLE hProcess, //标识调用者的句柄,这个值必须是唯一且非0的数字,但是不需要是一个真正的进程句柄。如果用了进程句柄,那么一定保证句柄值的正确性。如果程序是一个调试器,传递进程句柄要传被调试进程的句柄,而不要传GetCurrentProcess返回值。
__in_opt PCSTR UserSearchPath, //用来搜索符号文件的路径,用;分隔
__in BOOL fInvadeProcess //如果为真,枚举进程被加载的模块,对每个模块调用SymLoadModule64函数
);
对于第三个参数如果为False,那么SymInitialize只是创建一个符号处理器,不加载任何模块的调试符号,此时需要我们自己调用SymLoadModule64函数来加载模块;如果为TRUE,SymInitialize会遍历进程的所有模块,并加载其调试符号,所以在这种情况下hProcess必须是一个有效的进程句柄。
SymLoadModule64
装载指定模块的符号表
DWORD64
IMAGEAPI
SymLoadModule64(
__in HANDLE hProcess, //保证和SymInitialize参数1相同
__in_opt HANDLE hFile,
__in_opt PCSTR ImageName, //文件路径
__in_opt PCSTR ModuleName, //可选参数
__in DWORD64 BaseOfDll, //模块基地址
__in DWORD SizeOfDll //文件大小
);
参数二 传递NULL说明不使用文件句柄。一个文件句柄。MSDN说第二个参数多被调试器程序使用,调试器可以传递从debugging event中获取到的句柄值。
SymEnumSymbolsW
BOOL
IMAGEAPI
SymEnumSymbolsW(
__in HANDLE hProcess, //和之前一样,不多说
__in ULONG64 BaseOfDll, //SymLoadModule64的返回值
__in_opt PCWSTR Mask, //字符串,只有名称与该字符串匹配的符号才会被枚举,在字符串中允许使用通配符*和?,我们代码这里传NULL
__in PSYM_ENUMERATESYMBOLS_CALLBACKW EnumSymbolsCallback,//对于每个被枚举的符号都会调用该函数
__in_opt PVOID UserContext //用于EnumSymbolsCallback的参数,我们代码里传递的NULL
);
回调函数EnumSymbolsCallback
typedef BOOL
(CALLBACK *PSYM_ENUMERATESYMBOLS_CALLBACKW)(
__in PSYMBOL_INFOW pSymInfo, //指向PSYMBOL_INFO结构的指针,提供关于符号的信息
__in ULONG SymbolSize, //符号大小
__in_opt PVOID UserContext //可选参数
);
返回值:
If the function returns TRUE, the enumeration will continue.
If the function returns FALSE, the enumeration will stop.