学习自建调试体系(二)

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

转载于:https://www.cnblogs.com/Lnju/p/5413868.html

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值