Loading

学习自建调试体系(二)

这一部分的代码的作用主要是用于枚举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.

posted @ 2016-04-20 18:42  Lnju  阅读(2025)  评论(0编辑  收藏  举报