首页 > 系统相关 >WinDBG详解进程初始化dll是如何加载的

WinDBG详解进程初始化dll是如何加载的

时间:2022-11-14 11:34:53浏览次数:39  
标签:初始化 WinDBG 0x400000 IMAGE 0x000 dll DIRECTORY DATA

一:背景

1.讲故事

有朋友咨询个问题,他每次在调试 WinDbg 的时候,进程初始化断点之前都会有一些 dll 加载到进程中,比如下面这样:

Microsoft (R) Windows Debugger Version 10.0.25200.1003 X86
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: D:\net6\ConsoleApp1\Debug\ConsoleApplication3.exe

************* Path validation summary **************
Response Time (ms) Location
Deferred srv*c:\mysymbols*https://msdl.microsoft.com/download/symbols
Symbol search path is: srv*c:\mysymbols*https://msdl.microsoft.com/download/symbols
Executable search path is:
ModLoad: 00400000 0041f000 ConsoleApplication3.exe
ModLoad: 774b0000 77653000 ntdll.dll
ModLoad: 753a0000 75490000 C:\Windows\SysWOW64\KERNEL32.DLL
ModLoad: 75900000 75b14000 C:\Windows\SysWOW64\KERNELBASE.dll
ModLoad: 79bc0000 79d36000 C:\Windows\SysWOW64\ucrtbased.dll
ModLoad: 79ba0000 79bbe000 C:\Windows\SysWOW64\VCRUNTIME140D.dll
(44c.4b0c): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=afe00000 edx=00000000 esi=774c1ff4 edi=774c25bc
eip=77561a42 esp=0019fa20 ebp=0019fa4c iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
ntdll!LdrpDoDebuggerBreak+0x2b:
77561a42 cc int 3

问是否可以用 WinDbg 解读下内部运作原理,哈哈,其实要了解运作原理,一定要熟知 PE 头,那这篇就安排上。

二:理解 PE 头结构

1. 测试代码

为了方便讲述,先上一段测试代码,这里故意加载 ​​combase.dll​​ 是为了提取 PE 中的某些数据结构,代码如下:

#include <iostream>
#include <Windows.h>

int main(int argc, char* argv[]) {

LoadLibrary(L"combase.dll");

getchar();
}

其实你仔细想一想也能知道,既然能做到初始化加载,必然在 PE 头上藏了什么东西,这些东西让 Windows 加载器可以顺利加载诸如 ​​ntdll.dll​​​, ​​KERNEL32.dll​​ 等等,接下来一起观察下。

2. 可视化观察 PE 头

要想可视化观察 PE 头,工具有很多,这里使用 ​​PPEE​​ 工具,截图如下:

WinDBG详解进程初始化dll是如何加载的_初始化

从图中可以看到,其实初始化加载什么,由可选头中的 ​​DIRECTORY_ENTRY_IMPORT​​​ 数据目录项决定,哪这里包含了哪些初始化 dll 呢? 可以选中右边的 ​​DIRECTORY_ENTRY_IMPORT​​ 项即可,如下图所示:

WinDBG详解进程初始化dll是如何加载的_初始化_02

肯定有朋友说,WinDbg 上显示的是 5 个,你这里才 3 个,还有 2 个为什么没有? 很简单,多余的 ​​ntdll.dll​​​ 和 ​​KERNELBASE.dll​​ 必然是依赖项哈。

3. 用 WinDbg 深入探究

玩 WinDbg 都喜欢刨根问底,拿可视化 PPEE 肯定忽悠不过去,那好吧,我们用 C 中的结构体去解剖它。

  1. DOS Header 节

这一块信息在源码中是用 ​​ntdll!_IMAGE_DOS_HEADER​​​ 结构来承载的,可以用 dt 输出,起始点就是我们的 ​​ConsoleApplication3.dll​​​ 在进程的首位置,即: ​​0x400000​​。

0:000> dt 0x400000 _IMAGE_DOS_HEADER 
ConsoleApplication3!_IMAGE_DOS_HEADER
+0x000 e_magic : 0x5a4d
+0x002 e_cblp : 0x90
+0x004 e_cp : 3
...
+0x024 e_oemid : 0
+0x026 e_oeminfo : 0
+0x028 e_res2 : [10] 0
+0x03c e_lfanew : 0n232
  1. NT Header 节

接下来就是 NT Header 节,它在源码中是由 ​​_IMAGE_NT_HEADERS​​​ 结构来承载的,起始位置的偏移已经保存在上面的 ​​e_lfanew​​​ 字段中,即 ​​0n232​​。

0:000> dt 0x400000+0n232 _IMAGE_NT_HEADERS
ConsoleApplication3!_IMAGE_NT_HEADERS
+0x000 Signature : 0x4550
+0x004 FileHeader : _IMAGE_FILE_HEADER
+0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER
  1. _IMAGE_DATA_DIRECTORY

在 PPEE 的第一张截图中,我们查看的是 ​​Data Directorys​​​ 数组中的第二项 ​​DIRECTORY_ENTRY_IMPORT​​​ 内容,它里面定义了我们需要初始化导入的 dll,我们可以用 ​​dt r3​​ 展开一下,然后一直点点点就好了,简化后如下:

0:000> dt -r3 0x400000+0n232 _IMAGE_NT_HEADERS
ConsoleApplication3!_IMAGE_NT_HEADERS
+0x000 Signature : 0x4550
+0x004 FileHeader : _IMAGE_FILE_HEADER
+0x000 Machine : 0x14c
...
+0x012 Characteristics : 0x103
+0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER
+0x000 Magic : 0x10b
+0x002 MajorLinkerVersion : 0xe ''
...
+0x05c NumberOfRvaAndSizes : 0x10
+0x060 DataDirectory : [16] _IMAGE_DATA_DIRECTORY
+0x000 VirtualAddress : 0
+0x004 Size : 0
0:000> dx -r1 (*((ConsoleApplication3!_IMAGE_DATA_DIRECTORY (*)[16])0x400160))
(*((ConsoleApplication3!_IMAGE_DATA_DIRECTORY (*)[16])0x400160)) [Type: _IMAGE_DATA_DIRECTORY [16]]
[0] [Type: _IMAGE_DATA_DIRECTORY]
[1] [Type: _IMAGE_DATA_DIRECTORY]
[2] [Type: _IMAGE_DATA_DIRECTORY]
...
[15] [Type: _IMAGE_DATA_DIRECTORY]

0:000> dx -r1 (*((ConsoleApplication3!_IMAGE_DATA_DIRECTORY *)0x400168))
(*((ConsoleApplication3!_IMAGE_DATA_DIRECTORY *)0x400168)) [Type: _IMAGE_DATA_DIRECTORY]
[+0x000] VirtualAddress : 0x1b1cc [Type: unsigned long]
[+0x004] Size : 0x50 [Type: unsigned long]

从输出的 ​​VirtualAddress=0x1b1cc​​​ 中可以看到,我们 PPEE 截图二中的 ​​DIRECTORY_ENTRY_IMPORT​​​ 真实内容是在偏移 ​​0x1b1cc​​​ 处,它是一个 ​​combase!_IMAGE_IMPORT_DESCRIPTOR​​ 结构体,输出如下:

0:005> dt 0x400000+0x1b1cc combase!_IMAGE_IMPORT_DESCRIPTOR
+0x000 Characteristics : 0x1b21c
+0x000 OriginalFirstThunk : 0x1b21c
+0x004 TimeDateStamp : 0
+0x008 ForwarderChain : 0
+0x00c Name : 0x1b40c
+0x010 FirstThunk : 0x1b000

到这里就很关键了,涉及到如下几点信息:

  • 加载的 dll 名字是什么?

可以从 ​​Name​​ 字段提取,参考如下代码:

0:005> da 0x400000+0x1b40c
0041b40c "KERNEL32.dll"
  • 加载的 方法名 是什么?

这需要提取 ​​OriginalFirstThunk​​​ 字段,这里是一个 ​​_IMAGE_IMPORT_BY_NAME​​类型的指针数组,代码如下:

0:005> dp 0x400000+0x1b21c
0041b21c 0001b3e8 0001b3fc 0001b954 0001b942
0041b22c 0001b934 0001b924 0001b912 0001b906
0041b23c 0001b8fa 0001b8ea 0001b8d6 0001b8ba
...

0:005> dt 0x400000+0x0001b3e8 combase!_IMAGE_IMPORT_BY_NAME
+0x000 Hint : 0x382
+0x002 Name : [1] "I"

0:005> da 0x400000+0x0001b3e8+0x2
0041b3ea "IsDebuggerPresent"

结合上面的输出,我们知道 ​​IsDebuggerPresent()​​​ 是属于 ​​KERNEL32.dll​​​ 下的,有了这两点信息,Windows 加载器就可以用 ​​LoadLibrary​​​ 和 ​​GetProcAddress​​ 方法将其加载到进程中了,转化为 C 代码大概是这样的。

typedef BOOL(CALLBACK* DeubbgerFunc)();

int main(int argc, char* argv[])
{
HMODULE hModule = LoadLibrary(L"KERNEL32.dll");

DeubbgerFunc func = (DeubbgerFunc)GetProcAddress(hModule, "IsDebuggerPresent");

BOOL b= func();
}
  • func 函数地址会保存吗?

当然会保存了,会放在 ​​_IMAGE_IMPORT_DESCRIPTOR​​​ 结构下的 ​​FirstThunk​​ 字段中,这是一个函数地址的指针数组,可以用 dds 观察。

0:005> dt 0x400000+0x1b1cc combase!_IMAGE_IMPORT_DESCRIPTOR
+0x000 Characteristics : 0x1b21c
+0x000 OriginalFirstThunk : 0x1b21c
+0x004 TimeDateStamp : 0
+0x008 ForwarderChain : 0
+0x00c Name : 0x1b40c
+0x010 FirstThunk : 0x1b000

0:005> dds 0x400000+0x1b000
0041b000 753c20d0 KERNEL32!IsDebuggerPresentStub
0041b004 753c16c0 KERNEL32!LoadLibraryWStub
0041b008 753c2e80 KERNEL32!GetCurrentProcess
0041b00c 753bf550 KERNEL32!GetProcAddressStub
0041b05c 753b9910 KERNEL32!TerminateProcessStub
...

还有一点要注意,如果你在代码中使用 ​​IsDebuggerPresent()​​​ 方法的话,它会从 ​​0041b000​​ 位置上取函数地址,参考如下汇编代码:

WinDBG详解进程初始化dll是如何加载的_初始化_03

三:总结

对初学者来说,搞懂这些还是有一定困难的,我在网上找了一份很好的参考图,大家可以对照着这张图理解,在此感谢作者。

WinDBG详解进程初始化dll是如何加载的_3c_04

  • INT 是 Windows 需要加载的函数名列表。
  • IAT 是存放 GetProcAddress 返回函数地址的列表。



标签:初始化,WinDBG,0x400000,IMAGE,0x000,dll,DIRECTORY,DATA
From: https://blog.51cto.com/u_15353947/5848591

相关文章

  • initContainer 初始化容器
    initContainer1.概述1.1初始化容器的用途Init容器可以包含一些安装过程中应用容器中不存在的实用工具或个性化代码;Init容器可以安全地运行这些工具,避免这些工具导致......
  • shell 的初始化流程
    目录shell初始化基本概念loginshellinteractiveshell不同的组合读取配置文件的区别这套神秘机制造成的麻烦~/.bashrc与~/.bash_profile之间的互动cron......
  • <四>构造函数初始化列表
    示例代码1点击查看代码classCDate{public:CDate(int_year,int_month,int_day){this->year=_year;this->month=_month;this->d......
  • CSP 202203-1 未初始化警告 C++
    1#include<iostream>2#include<vector>3intmain(){4intx{},y{};5std::cin>>x>>y;//读入第一行6std::vector<std::vector<int>>k......
  • 02-类与对象 进行试验--Java字段初始化的规律
    1.类的构造方法(1)“构造方法”,也称为“构造函数”,当创建一个对象时,它的构造方法会被自动调用。构造方法与类名相同,没有返回值。(2)如果类没有定义构造函数,Java编译器......
  • 洛谷P5309 Ynoi 2011 初始化 题解
    题面。我也想过根号分治,但是题目刷得少,数组不敢开,所以还是看题解做的。这道题目要用到根号分治的思想,可以看看这道题目和我的题解。题目要求处理一个数组a,支持如下操作......
  • 静态初始化块
    构造方法用于对象的普通属性初始化。静态初始化块,用于类的初始化操作,初始化静态属性。在静态初始化块中不能直接访问非static成员。......
  • C# 之静态构造器与静态字段初始化器
    publicclassTest{///<summary>///静态字段初始化器会在调用静态构造器前运行。///如果类型没有静态构造器,字段会在类型被使用前或运......
  • pinia初始化
    pinia安装官网:https://pinia.vuejs.org/zh/introduction.html安装:npmipinia-S//main.tsimport{createApp}from'vue'import{createPinia}from'pinia'......
  • 从 Ynoi2011 初始化 看卡常
    一般情况下,程序运行消耗时间主要与时间复杂度有关,超时与否取决于算法是否正确。但对于某些题目,时间复杂度正确的程序也无法通过,这时我们就需要卡常数,即通过优化一些操作的......