前言
刚开始学习二进制安全,尝试搭建windbg调试vmware的windows7 x86环境,学习一些其中的二进制漏洞。
环境所需
windbg直接下载官网的,windows7的镜像[1]en_windows_7_ultimate_with_sp1_x86_dvd_u_677460.iso,激活windows系统的key为:FJGCP-4DFJD-GJY49-VJBQ7-HYRR2。
配置环境
使用vmware安装好windows7 x86。然后参考教程[2],打开”虚拟机设置“ —> ”添加“ —> ”串行端口“,配置好命名的管道,然后确认,启动windows。
命名的管道为:\\.\pipe\windows2007_debug

然后重新启动windows7,输入命令:bcdedit /copy {current} /d "Windows 7 Debug"

输出命令msconfig,切换到 Boot (引导) 选项卡。看到两个启动项(如果你刚才执行了 copy 命令),选中 Windows 7 Debug。点击 Advanced options (高级选项)。勾选 Debug (调试)。Debug port 选择 COM1。Baud rate 选择 115200。点击 OK,再点击 OK。
系统会提示重启,选择 Exit without restart (稍后重启)。

此时到我们自己的电脑上,打开windbg,选择attach to kernel,port位置输入我们的\\.\pipe\windows7_debug,然后点击ok。

此时我们再去配置符号表,symbol path:SRV*D:\pwn\symbols*http://msdl.microsoft.com/download/symbols,我把符号放在了D:\pwn\symbols位置。

此时我们去启动虚拟机的windows7。启动windows7的时候选择“Windows 7 Debug”

windbg成功连接。

查看进程!process 0 0

windbg调试的一些命令
这里以断点notepad.exe的进程为例。
首先是bc *,清空所有断点。然后查看进程!process 0 0,我只截取了部分,我在windows7中是打开了notepad.exe的。

查看notepad.exe的进程,!process 0 0 notepad.exe。

可以注意到EPROCESS 地址为86ff55a8。.process /i 86ff55a8,告诉windbg聚焦在这个进程上。

加载符号(必做),确保windbg可以看懂notepad调用的函数名。使用.reload /f /user。
然后就是专门的断点。
1 | bp /p <EPROCESS地址> <函数名> |
使用bp /p 86ff55a8 user32!GetMessageW。bl是查看有哪些断点。

此时回到windows7,把鼠标移动到记事本的窗口范围内,或者在记事本里随便按一个键盘按键。第一次没断下来,又尝试了一下,才断下来的。

下面就是去看寄存器的内容,执行r。

EIP (Instruction Pointer):指令寄存器,它指向下一条要执行的指令地址。攻击者(溢出)的核心目标就是篡改 EIP,让 CPU 去执行恶意代码。
ESP (Stack Pointer): 栈顶指针。它指向当前栈的最上方。函数参数和返回地址都这附近。
EAX: 多功能搬运工。通常用于存放函数的返回值。
继续多探究一些命令。
du poi(esp + 4)、du poi(esp + 4)

du命令,d:Display(显示内存)、u:Unicode(宽字节)。
esp + 4:指向第1个参数,因为是32位。
esp + 8:指向第2个参数。
poi是Pointer指针的缩写,就是取地址里的值。
du poi(esp+4)的意思就是,去 esp + 4 的位置拿到一个指针,跳到那个指针指向的内存,并把它作为 Unicode 字符串打印出来。
下面再去断点其它的函数看一看。
bc *清空断点。断点bp /p 86ff55a8 user32!MessageBoxW,这次断点函数user32!MessageBoxW。

然后我windows7,点击notepad的Help -> About Notepad,发现翻车了,并没有断下来。可能是”About Notepad“这个窗口使用的并不是user32!MessageBoxW函数。
经过查询发现使用的是shell32!ShellAboutW函数。这次使用bu断点,bu SHELL32!ShellAboutW,此时点击About Notepad,端下来了。

再查看寄存器的内容。

eu poi(esp+8) "hacker by x2n"编辑寄存器中的内容。eu(edit unicode),可以看到内容已经被修改了。

为什么刚才使用bp没有断下点,使用了bu就可以断下来?
(1)绑定对象的区别
- bp (Break Point) 绑定的是“虚拟内存地址”
- 当你输入
bp 0x77123456或bp shell32!ShellAboutW时,调试器会立即解析出该符号当前对应的线性虚拟地址,并记录这个固定的数值。 - 它不关心这个地址属于哪个模块,它只认这个数值。
- 当你输入
- bu (Break Unresolved) 绑定的是“符号名称”
- 当你输入
bu shell32!ShellAboutW时,调试器记录的是字符串"shell32!ShellAboutW"以及它在模块内的偏移量。 - 它不记录具体的绝对地址。
- 当你输入
(2)生效机制与时机的区别
- bp:立即写入 (Immediate Write)
- 执行命令瞬间,WinDbg 必须能够访问该内存地址。
- 调试器会尝试立即将该地址处的指令字节修改为
0xCC(INT 3)。 - 限制:如果此时模块未加载,或当前进程上下文(页表)中该地址未映射,命令会直接失败或写入错误的物理内存。
- bu:延迟解析 (Deferred Resolution)
- 执行命令时,不需要模块已加载。
- WinDbg 会挂钩系统的“模块加载事件”。每当有一个 DLL 或驱动被加载时,调试器会检查其名称是否匹配。
- 一旦匹配(例如 shell32.dll 被加载),调试器会根据**(当前模块基址 + 函数偏移量)**动态计算出真实地址,然后在那个位置写入
0xCC。
(3) 对 ASLR (地址空间布局随机化) 的处理
- bp:不支持 ASLR
- 如果是硬编码的地址断点,一旦程序重启,操作系统通过 ASLR 机制将 DLL 加载到了新的基地址,原来的地址(如
0x77123456)可能变成了无效内存或别人的代码。bp断点因此失效。
- 如果是硬编码的地址断点,一旦程序重启,操作系统通过 ASLR 机制将 DLL 加载到了新的基地址,原来的地址(如
- bu:原生支持 ASLR
- 当程序重启,DLL 加载到新地址(例如从
0x77000000变到了0x66000000),WinDbg 会收到通知,重新计算函数的最新地址,并自动在新的位置下断点。
- 当程序重启,DLL 加载到新地址(例如从
(4)持久性 (Persistence)
- bp:通常只在当前调试会话有效。一旦进程终止,该地址关联的断点逻辑即被清除(因为地址已无意义)。
- bu:保存在调试器的工作区(Workspace)中。即使你关闭调试器再打开,或者重启计算机,只要符号匹配,断点依然存在。
| 特性 | bp (地址断点) | bu (符号断点) |
|---|---|---|
| 核心索引 | 线性虚拟地址 (Virtual Address) | 符号名 (Symbol Name) + 偏移 |
| 写入时机 | 命令执行时立即写入 | 模块加载时动态写入 |
| 内存要求 | 内存必须存在且可写 | 无需内存存在 |
| ASLR 兼容 | 否 (重启后地址变化即失效) | 是 (自动跟随基址变化) |
| 适用场景 | 临时调试特定的指令行、汇编地址 | 调试 API、系统函数、重启后仍需保留的断点 |
所以总的来说,对于开启了ASLR的windows7 x86系统,最好还是使用bu最可靠。