欢迎光临
我们一直在努力

软件安全与漏洞--实验7 PE文件格式分析(含详细MSYS2安装 MinGW-w64 32位工具链教程!!)

前言

本文有配套资源 (红色框是x64dbg)x64dbg(也可自行下载)

其实在安装32位的时候顺便把OpenSSL库弄好了

有问题欢迎评论区交流!

实验步骤

一、实验目的和要求

1. 熟悉各种PE编辑查看工具,详细了解PE文件格式;

2. 重点分析PE文件文件头、引入表、引出表,以及资源表。

二、实验内容

首先编写一个win32EXE程序,命名为hello,程序中依次弹出三个对话框,三个对话框分别为:提示框标题为“Hell0-软件安全实验-1”,内容为:“这是”+你的姓名+学号+“第一个对话框”;提示框标题为“Hell0-软件安全实验-2”,内容为:“这是”+你的姓名+学号+“第二个对话框”;提示框标题为“Hell0-软件安全实验-3”,内容为:“这是”+你的姓名+学号+“第三个对话框”。

参考程序源码:

# include <stdlib.h>

# include <stdio.h>

# include <windows.h>

int main()

{

MessageBox(NULL,"这是姓名(学号)的第一个对话框","Hell0-软件安全实验-1",MB_OK);

MessageBox(NULL,"这是姓名(学号)的第二个对话框","Hell0-软件安全实验-2",MB_OK);

MessageBox(NULL,"这是姓名(学号)的第三个对话框","Hell0-软件安全实验-3",MB_OK);

getchar();

return 0;

}

三、实验前准备

1. 安装编译器工具链(MinGW-w64)(稍等后面有32位的!)

VS Code 本身不能编译代码,你需要先安装一个 Windows 下的 C 语言编译器。

  • 下载 MinGW-w64:
    • 你可以通过 MSYS2 官网下载安装器,这是目前在 Windows 上管理 MinGW 最推荐的方式。
    • 或者直接搜索下载 "MinGW-w64" 的预编译版本。
  • 配置环境变量:
    • 安装完成后,找到 mingw64\\bin 文件夹(例如 C:\\msys64\\mingw64\\bin)。
    • 将这个路径添加到系统的 PATH 环境变量中。
    • 验证:打开一个新的命令提示符(CMD),输入 gcc –version。如果出现版本信息,说明配置成功。

2. 配置 VS Code 工作区

  • 创建工作文件夹:在电脑上新建一个文件夹,例如 D:\\pe_lab,然后用 VS Code 打开这个文件夹。
  • 新建文件:在 VS Code 中新建一个文件,命名为 hello.c。
  • 粘贴代码:将实验文档中的代码复制进去,并记得替换你的姓名和学号。
  • 3. 编写代码与编译(核心步骤)

    你需要通过 VS Code 的终端来执行编译命令。

  • 打开终端:在 VS Code 菜单栏点击 终端 (Terminal) -> 新建终端 (New Terminal)。

  • 输入编译命令:

    • 在终端中输入以下命令并回车: gcc -m32 -o hello.exe hello.c -lgdi32

      参数解释:

    • gcc: 调用编译器。
    • -m32: 非常重要。强制生成 32 位程序(实验要求的 Win32 程序),方便后续用 OllyDbg 分析。
    • -o hello.exe: 指定输出文件名为 hello.exe。
    • hello.c: 源文件名。
    • -lgdi32: 链接库。因为 MessageBox 函数属于 Windows 的 GDI 子系统,必须加上这个链接参数,否则会报错 "undefined reference"。

    运行程序:

    • 编译成功后,终端中会生成 hello.exe。
    • 输入 .\\hello.exe 运行程序,检查是否弹出了三个对话框。

    4. 常见问题解决

    • 报错:MessageBox 未定义:
      • 确保你包含了头文件 #include <windows.h>。
      • 确保编译命令末尾加了 -lgdi32。
    • 报错:gcc 不是内部或外部命令:
      • 说明 MinGW 没有正确安装或环境变量没配好,请回到步骤 1 检查。

    由于这个实验要用 OllyDbg 分析,所以要强制生成 32 位程序,下载msys2的32位版本

    或者直接下载x64dbg:x64dbg

    (我这是已经下载了64位编译的没有32位的所以下载一下)嫌麻烦的话,直接下x64dbg

    pacman -S mingw-w64-i686-toolchain

    有问题的话,嫌麻烦直接搞镜像

    在 MSYS2 终端 中执行命令来一键完成配置(不需要手动去文件夹里改文件,超方便!!):

    echo "Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/mingw/i686" > /etc/pacman.d/mirrorlist.mingw32

    注意: 如果你也需要配置 64 位环境或基础系统环境,可以顺便执行这两行:

    # 配置 64 位源
    echo "Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/mingw/x86_64" > /etc/pacman.d/mirrorlist.mingw64
    # 配置基础系统源
    echo "Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/msys/\\$arch" > /etc/pacman.d/mirrorlist.msys

    刷新数据库并安装

    配置写好后,执行更新命令,你会发现速度飞快:

    pacman -Syu

    运行完了msys2直接就关闭了(反正我是这样)

    可能下面这种方式直接搜不到:

    只要去资源管理器里(就是文件夹里)藏在犄角旮旯里

    有需要的话,直接创个快捷方式啥的,都放一起

    以下是我在下载32位版本中出现的问题(没用清华源的)

    可能出现的问题1:

    这是网络问题,不用担心,你的安装已经成功了!

    为什么会出现满屏的红色报错?
  • 网络不稳定/被墙:MSYS2 的默认下载服务器(如 repo.msys2.org)都在国外。在国内访问时,经常会出现连接超时、SSL 握手失败或者数据流中断的情况。
  • 自动重试机制:pacman 这个工具很“执着”。当它发现从第一个服务器下载失败后,会自动尝试列表里的下一个服务器(比如俄罗斯的、荷兰的、德国的镜像站)。
  • 最终成功:虽然前面下载了一堆红色的报错,但你可以看到截图的下半部分,进度条开始走了([####################] 100%),并且显示 Installing…。这说明它最终找到了一个能连上的服务器,把文件下载下来并安装好了。

  • 你现在应该做什么?

    既然最后显示安装完成(回到了 $ 提示符),说明环境已经就绪。可以直接编译代码了

    到vs的终端窗口运行

    i686-w64-mingw32-gcc -fexec-charset=GBK -o hello.exe hello.c

    可能会出现的问题2:

    在vs code 终端执行命令还报错,(因为是要在msys2终端运行的,在vs里还要配置环境)

    这个错误是因为你在 PowerShell 中执行了 i686-w64-mingw32-gcc,但系统找不到这个命令。这通常不是因为你没安装,而是因为 你当前使用的终端环境不对。

    原因分析
    • MSYS2 的环境隔离机制:MSYS2 提供了三种不同的终端环境(MINGW64、MINGW32、UCRT64)。
    • 环境变量未加载:当你通过 pacman 安装了 32 位工具链后,这些新工具的路径只会被自动添加到 MINGW32 终端的环境中。
    • 当前环境不匹配:从你的截图看,你使用的是普通的 PowerShell。在这个环境中,系统只知道默认的 64 位编译器路径,不知道刚才安装的 32 位编译器在哪里,所以报错“无法识别”。
    解决方法(二选一)

    方法一:使用正确的 MSYS2 终端(推荐)

  • 点击 Windows 开始菜单。
  • 找到并打开 MSYS2 MINGW32(图标通常是红色的,或者名字里带 32)。
  • 在这个黑色的终端窗口里,直接输入以下命令即可成功编译: i686-w64-mingw32-gcc -fexec-charset=GBK -o hello.exe hello.c
  • 方法二:在 VS Code 中配置正确的 Shell

    如果你坚持要在 VS Code 的终端里操作,你需要把 VS Code 的默认终端改成 MSYS2 的 MINGW32 环境,或者手动将 32 位编译器的路径添加到系统环境变量 PATH 中(比较麻烦,不推荐新手操作)。

    可能出现的问题3:

    在开始菜单中找不到“MSYS2 MINGW32”这个终端入口。这通常是因为你的 MSYS2 安装版本较新,或者在安装时没有勾选创建所有终端的快捷方式。不过别担心,你依然可以成功编译 32 位程序,只是需要换一种方式来启动正确的环境。

    ✅ 正确解决方法(无需重新安装)

    既然你已经通过 pacman -S mingw-w64-i686-gcc 安装了 32 位编译器,那么它的可执行文件 i686-w64-mingw32-gcc.exe 一定存在于 C:\\msys64\\mingw32\\bin\\ 目录下。你只需要手动启动一个能加载该路径的环境即可。

    方法一:使用 VS Code 终端 + 手动设置环境变量(推荐)

  • 打开 VS Code,进入你的项目目录(比如 E:\\desktop\\pe_lab)。
  • 按  Ctrl+  打开终端,确保当前是 PowerShell 或 CMD。
  • 输入以下命令,手动将 32 位编译器路径加入当前会话的环境变量: $env:PATH = "C:\\msys64\\mingw32\\bin;" + $env:PATH
  • 然后直接运行编译命令:

    i686-w64-mingw32-gcc -fexec-charset=GBK -o hello.exe hello.c

  • 💡 这个方法简单直接,适合临时使用。每次打开新终端都需要重新执行第 3 步。

    方法二:创建一个自定义的“MINGW32”终端快捷方式

    方法三:在 VS Code 中配置自定义终端配置文件

  • 在桌面上右键 → 新建 → 快捷方式。
  • 在“请键入对象的位置”中输入: C:\\msys64\\usr\\bin\\bash.exe –login -c "export PATH=/mingw32/bin:$PATH; exec bash"

  • 点击“下一步”,命名为“MSYS2 MINGW32”,完成。
  • 双击这个快捷方式,你就会进入一个配置好 32 位环境的终端,可以直接使用 i686-w64-mingw32-gcc。
  • 打开 VS Code,按 Ctrl + , 打开设置。
  • 搜索 terminal.integrated.profiles.windows。
  • 点击“在 settings.json 中编辑”。
  • 添加如下配置: "terminal.integrated.profiles.windows": {
    "MSYS2 MINGW32": {
    "path": "C:\\\\msys64\\\\usr\\\\bin\\\\bash.exe",
    "args": ["–login", "-c", "export PATH=/mingw32/bin:$PATH; exec bash"]
    }
    },
    "terminal.integrated.defaultProfile.windows": "MSYS2 MINGW32"

  • 保存后,重启 VS Code,以后每次打开终端都会自动进入 MINGW32 环境。

  • 📌 为什么会出现这个问题?
    • 新版 MSYS2 默认不创建所有终端快捷方式:为了简化安装,新版本可能只创建了最常用的 MINGW64 和 UCRT64 终端。
    • 环境变量隔离设计:MSYS2 的设计哲学是每个终端环境独立,避免不同架构的工具链互相干扰。因此,32 位工具不会自动出现在 64 位终端或系统全局 PATH 中。

    ✅ 最终验证

    无论你选择哪种方法,只要在终端中能成功执行:

    i686-w64-mingw32-gcc –version

    并看到类似 i686-w64-mingw32-gcc (RevX, Built by MSYS2 project) XX.X.X 的输出,就说明环境已就绪,可以正常编译 32 位程序了。

    可能出现的问题4:

    在 PowerShell 中手动修改 $env:PATH 后,仍然无法识别 i686-w64-mingw32-gcc 命令。这并非因为路径没加对,而是因为 PowerShell 的会话环境变量是临时的,且 MSYS2 的工具链依赖其自身环境初始化机制,仅靠添加 PATH 并不足以让系统正确加载所有依赖。

    问题根源分析
    • MSYS2 的环境隔离机制:MSYS2 的 MINGW32 终端不仅添加了 /mingw32/bin 到 PATH,还会设置一系列关键环境变量(如 MINGW_PACKAGE_PREFIX、PKG_CONFIG_PATH、C_INCLUDE_PATH 等),这些变量是编译器正常工作的前提。
    • PowerShell 的局限性:即使你手动将 C:\\msys64\\mingw32\\bin 加入 $env:PATH,PowerShell 也不会自动加载 MSYS2 的环境配置文件(如 /etc/profile 或 ~/.bashrc),导致编译器虽然“可见”,但缺少运行时依赖,最终仍会失败或行为异常。
    • 命令未生效的可能原因:你可能在执行 $env:PATH = … 后,没有重新打开终端或没有在当前会话中立即执行编译命令;或者路径拼写错误(如大小写、斜杠方向)。
    ✅ 终极解决方案(推荐)

    方法一:使用 VS Code 的“自定义终端”功能(最稳定)

  • 打开 VS Code,按  Ctrl+Shift+P  打开命令面板。
  • 输入并选择 “Terminal: Select Default Profile”。
  • 选择 “Command Prompt” 或 “PowerShell”(任选其一)。
  • 然后,在你的项目目录(如 E:\\desktop\\pe_lab)下,创建一个名为 .vscode/settings.json 的文件,内容如下: {
    "terminal.integrated.profiles.windows": {
    "MSYS2 MINGW32": {
    "path": "C:\\\\msys64\\\\usr\\\\bin\\\\bash.exe",
    "args": ["–login", "-c", "export PATH=/mingw32/bin:$PATH; exec bash"]
    }
    },
    "terminal.integrated.defaultProfile.windows": "MSYS2 MINGW32"
    }
  • 保存文件后,重新打开 VS Code 终端,你就会进入一个完整的 MINGW32 环境,可以直接使用 i686-w64-mingw32-gcc。
  • 方法二:创建一个批处理脚本启动 MINGW32 环境

  • 在桌面上新建一个文本文件,重命名为 start_mingw32.bat。
  • 用记事本打开,粘贴以下内容: @echo off
    set PATH=C:\\msys64\\mingw32\\bin;%PATH%
    cd /d E:\\desktop\\pe_lab
    cmd /k
  • 保存并关闭。
  • 双击运行这个 .bat 文件,它会打开一个 CMD 窗口,其中已包含 32 位编译器路径,你可以直接执行编译命令。
  • 方法三:验证你的安装是否真正成功

    在执行任何编译命令前,先确认 i686-w64-mingw32-gcc.exe 确实存在于 C:\\msys64\\mingw32\\bin\\ 目录下。你可以在 PowerShell 中输入:

    Test-Path "C:\\msys64\\mingw32\\bin\\i686-w64-mingw32-gcc.exe"

    如果返回 True,说明文件存在;如果返回 False,说明你之前的 pacman -S mingw-w64-i686-gcc 安装可能失败了,需要重新执行该命令并确保网络通畅。

    💡 重要提醒
    • 不要试图在普通 PowerShell 或 CMD 中“永久”配置 MSYS2 环境,这会破坏系统的稳定性,且容易与其他工具冲突。
    • 始终优先使用 MSYS2 提供的专用终端入口,它们是经过精心配置的,能确保所有工具链正常工作。
    • 如果你只是想快速编译一个 32 位程序用于调试(如 OllyDbg),方法二(批处理脚本)是最简单、最可靠的选择。

    可能出现的问题5:

    这张图片显示你在 CMD(命令提示符) 中尝试运行 i686-w64-mingw32-gcc,但系统依然报错:“不是内部或外部命令”。

    这意味着:

    你的电脑当前环境里,确实找不到这个程序。

    这只有两种可能:

  • 你虽然安装了 MSYS2,但根本没有安装 32 位的编译器包。
  • 你安装了,但是路径完全不对。
  • 解决办法:

    第一步:确认你是否真的安装了 32 位编译器

    MSYS2 默认只带 64 位编译器,32 位的需要手动下载。请在你的 MSYS2 终端(那个黑色的窗口)里输入以下命令并回车:

    pacman -S mingw-w64-i686-gcc

    • 如果它开始下载并安装东西:说明你之前没装成功,或者根本没装。等它跑完(输入 y 确认),然后再去执行编译命令。
    • 如果它提示 already installed(已安装):说明文件确实在硬盘里,只是你的 CMD 找不到它。请看第二步。

    第二步:验证文件是否存在

    在 MSYS2 终端中输入:

    ls /mingw32/bin/i686-w64-mingw32-gcc.exe

    • 如果有输出文件名:说明文件存在。
    • 如果提示 No such file:说明第一步没装好,请重新执行第一步。

    第三步:如何在 CMD 中使用它(关键!)

    既然你现在的截图是 CMD 界面,而且你不想用复杂的配置,最简单的办法是使用绝对路径直接调用它。

    假设你的 MSYS2 安装在默认的 C 盘根目录,请在 CMD 中输入完整的路径来编译:

    C:\\msys64\\mingw32\\bin\\i686-w64-mingw32-gcc.exe -fexec-charset=GBK -o hello.exe hello.c

    (注意:如果你的 MSYS2 安装在 D 盘或其他位置,请把 C:\\msys64 换成对应的盘符)

    总结建议

    看你的截图,你似乎在不同的终端(PowerShell、CMD、VS Code)之间反复横跳。为了避免混乱,建议你:

  • 统一环境:以后写代码编译,就只用 MSYS2 MINGW64(如果是 64 位程序)或者 MSYS2 MINGW32(如果是 32 位程序)这两个自带的黑色终端窗口。
  • 不要强求在 CMD/PowerShell 里跑:除非你把路径加到了系统环境变量里,否则在 CMD 里跑 MSYS2 的程序是非常麻烦的。
  • 如果你只是想赶紧把作业交了: 直接在 MSYS2 的终端里敲 gcc hello.c -o hello.exe (默认就是 64 位),生成的 hello.exe 也是可以在 Windows 上运行的,通常不需要非得编译成 32 位(除非老师强制要求)。

    可能出现的问题6:

    这张图片非常关键,它揭示了你当前操作环境的一个核心问题:你正在 Windows 自带的 CMD(命令提示符) 中尝试运行 pacman,但系统找不到这个命令。

    原因很简单: pacman 是 MSYS2 专用的包管理器,它只能在 MSYS2 自己的终端窗口里运行,不能在 Windows 的 CMD 或 PowerShell 里直接运行。

    为了帮你彻底解决问题,我们需要分两步走:先找到正确的“入口”,再安装编译器。

    解决方法

    第一步:打开正确的“MSYS2 终端”

    你不能直接在桌面的 CMD 里敲命令。请按照以下步骤找到 MSYS2 的专属窗口:

  • 点击电脑左下角的 Windows 开始菜单。
  • 在应用列表中找到 MSYS2 文件夹(或者直接搜索 "MSYS2")。
  • 你会看到几个选项,请点击 “MSYS2 MINGW64”(通常是黑色的图标)或者 “MSYS2 MSYS”。
    • 注意:不要选 CLANG64 或 UCRT64,除非你明确知道自己在做什么。对于初学者,MINGW64 最通用。
  • 第二步:在 MSYS2 终端中安装 32 位编译器

    当你打开了那个黑底白字的 MSYS2 窗口后(此时光标前通常会显示 user@电脑名 MINGW64 ~),在里面输入以下命令并回车:

    pacman -S mingw-w64-i686-gcc

    • 如果系统提示你确认安装(Proceed with installation? [Y/n]),请输入 y 然后回车。
    • 等待进度条跑完,直到出现新的命令行提示符。

    第三步:如何编译你的代码

    安装完成后,你有两种方式来编译你的 C 语言代码:

    方式 A:在 MSYS2 终端里编译(最简单)

  • 你需要先用 cd 命令进入你的代码所在的文件夹。假设你的代码在 E 盘的 desktop\\pe_lab 文件夹:

    cd /e/desktop/pe_lab

    (注意:在 MSYS2 里,E盘要写成 /e/)

  • 然后直接使用刚才安装的编译器:

    i686-w64-mingw32-gcc -fexec-charset=GBK -o hello.exe hello.c

  • 方式 B:回到 VS Code 或 CMD 编译(如果你不想换窗口)

    既然你已经通过上面的步骤安装了编译器,现在你可以回到你截图中的 CMD 窗口,使用绝对路径来调用它(假设 MSYS2 安装在 C 盘默认位置):

    C:\\msys64\\mingw32\\bin\\i686-w64-mingw32-gcc.exe -fexec-charset=GBK -o hello.exe hello.c

    可能出现的问题7:

    你当前遇到的问题是 MSYS2 在安装 32 位 MinGW-w64 工具链时,因网络镜像源失效导致部分包下载失败(如 mingw-w64-i686-tcl-8.6.17-1-any.pkg.tar.zst 返回 404)。这通常是因为默认的国外镜像源已过期或不可用。

    别担心,这个问题很常见,解决起来也很简单。核心思路是:更换为国内高速镜像源 → 重新同步包数据库 → 再次安装工具链。


    解决方法

    第一步:更换为国内镜像源(推荐清华源)

  • 打开 MSYS2 终端(任意一个都可以,比如 “MSYS2 MSYS”)。
  • 编辑镜像配置文件: nano /etc/pacman.d/mirrorlist.mingw32
  • 在文件最顶部添加以下一行(清华源): Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/mingw/i686/
  • 按 Ctrl + O 保存,再按 Ctrl + X 退出编辑器。
  • 同样地,也编辑 64 位的镜像文件(以防万一): nano /etc/pacman.d/mirrorlist.mingw64 添加: Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/mingw/x86_64/
  • 💡 你也可以选择中科大源:https://mirrors.ustc.edu.cn/msys2/mingw/i686/

    其实我换镜像的时候是有问题的,网站问题,反正实在不行就别用olldbg,改用x64dbg


    第二步:更新包数据库并重新安装

  • 执行以下命令同步最新的包信息: pacman -Sy
  • 再次尝试安装 32 位工具链: pacman -S mingw-w64-i686-toolchain
    • 这次应该能顺利下载所有包,不会再出现 404 错误。
    • 如果提示选择包,直接按回车全选即可。

  • 第三步:启动正确的终端并编译

    安装完成后:

  • 关闭当前终端窗口。
  • 从 Windows 开始菜单启动 “MSYS2 MinGW 32-bit”(蓝色图标)。
  • 切换到你的项目目录: cd /e/desktop/pe_lab
  • 编译 32 位程序: gcc -fexec-charset=GBK -o hello.exe hello.c

  • 验证是否成功

    编译完成后,你可以用以下命令确认生成的是 32 位程序:

    file hello.exe

    输出应包含 PE32 executable (console) Intel 80386,表明它是标准的 32 位 PE 文件,可以被 OllyDbg 正常加载和调试。


    ✅ 总结:你遇到的问题本质是“网络镜像源失效”,通过更换为国内镜像源即可彻底解决。这是 MSYS2 用户在新环境部署时最常遇到的坑之一,现在你已经掌握了正确的解决方法!

    出现如图所示就好了:

    图片内容分析

  • 你操作对了环境:

    • 注意看终端顶部的绿色文字 MINGW64。这说明你已经成功进入了 MSYS2 的专用终端(而不是之前的 Windows CMD),这是能运行 pacman 命令的前提。
  • 编译器已存在:

    • 系统提示:warning: mingw-w64-i686-gcc-15.2.0-14 is up to date — reinstalling。
    • 含义:这句话的意思是“这个软件已经是最新版了”,系统正在为你重新确认安装一遍。
    • 结论:这证明 mingw-w64-i686-gcc 这个包在你的电脑里是完好无损且已安装的。

  • 为什么之前报错?

    既然装好了,为什么你在 VS Code 或 CMD 里输入 i686-w64-mingw32-gcc 还是提示“找不到命令”?

    原因只有一个:环境变量没配对。

    • 你现在所在的窗口是 MINGW64 环境(绿色的字)。在这个环境里,系统只认识 64 位的编译器(x86_64-w64-mingw32-gcc)。
    • 你要用的 32 位编译器(i686-w64-mingw32-gcc)虽然装在硬盘里(位于 C:\\msys64\\mingw32\\bin),但当前这个 MINGW64 窗口的 PATH 变量里并没有包含这个路径。
    ✅ 现在的解决方案(二选一)

    既然文件都在,我们不需要再下载任何东西,只需要用对方法。

    方案一:切换到正确的终端(最推荐,最简单)

    既然你要编译 32 位程序(i686),你就应该进入 MINGW32 终端,而不是 MINGW64。

  • 关闭当前的黑色窗口。
  • 点击 Windows 开始菜单。
  • 找到 MSYS2 文件夹,点击 “MSYS2 MINGW32”(图标通常也是黑色的,名字带 32)。
  • 在那个新打开的窗口里,直接输入: i686-w64-mingw32-gcc –version 你应该就能看到版本信息了,然后就可以正常编译代码了。
  • 方案二:如果你非要在当前的 MINGW64 窗口里用

    如果你不想切换窗口,必须手动告诉系统去哪里找 32 位编译器。在当前窗口输入:

    export PATH="/mingw32/bin:$PATH"

    然后再试一次:

    i686-w64-mingw32-gcc –version

    这样也能成功,但每次新开窗口都要输一次上面那行代码。

    建议直接使用方案一,去开始菜单找 "MSYS2 MINGW32" 打开即可解决所有问题。

    若你想使用方案2的话:

    出现这个图的内容就好了

  • 你成功执行了关键命令:

    • export PATH="/mingw32/bin:$PATH":这行命令的作用是把 32 位编译器的路径“临时”加入到了当前窗口的系统变量中。
    • i686-w64-mingw32-gcc –version:随后你尝试运行编译器查看版本。
  • 编译器已成功识别并运行:

    • 屏幕输出了 i686-w64-mingw32-gcc.exe … 15.2.0。
    • 这意味着: 你的电脑里确实有 32 位编译器,而且通过手动修改环境变量,它现在已经可以被调用了!
  • 🚀 接下来怎么做?(两种选择)

    选择一:立刻编译代码(就在当前窗口)

    既然现在环境已经通了,不要关闭窗口,直接在这个窗口里操作:

  • 进入你的代码目录(假设在 E 盘 desktop 下):

    cd /e/desktop/pe_lab

    (注意:MSYS2 里 E 盘要写成 /e/)

  • 执行编译命令:

    i686-w64-mingw32-gcc -fexec-charset=GBK -o hello.exe hello.c

    如果没报错,目录下就会生成 hello.exe。

  • 选择二:一劳永逸(推荐,以后不用每次都输 export)

    为了以后不再折腾,建议你以后直接打开正确的终端。

  • 关掉现在的窗口。
  • 点击 Windows 开始菜单 -> MSYS2 -> 点击 “MSYS2 MINGW32”。
  • 在这个新窗口里,你不需要输入任何 export 命令,直接就能用 i686-w64-mingw32-gcc,因为它是原生支持 32 位的环境。
  • 总结:你的环境没问题,文件也没丢,只是之前没找对“门”。现在你已经找到了钥匙(export 命令),可以开始写代码了!

    四、实验结果(及调试过程)

    步骤一:编译c语言文件

    程序执行情况如下图所示:不报错就行了

    生成exe文件

     运行exe文件

    可能出现的问题1:

    这是一个非常典型的 字符编码不匹配(乱码) 问题。

    简单来说:你的 VS Code 编辑器、编译器(GCC)和 Windows 系统控制台之间,对于“如何解读中文字符”没有达成共识。

    1. 为什么会出现这种情况?
    • VS Code (UTF-8):现代编辑器默认使用 UTF-8 编码保存文件。在 UTF-8 中,一个汉字通常占 3 个字节。
    • Windows 控制台 (GBK/GB2312):传统的 Windows 中文环境(尤其是你使用的 MinGW/GCC 编译器生成的程序)默认期望的是 GBK 编码。在 GBK 中,一个汉字占 2 个字节。

    结果就是: 程序把 UTF-8 的 3 字节数据强行当成 GBK 的 2 字节数据去显示,导致字节错位,原本的文字就变成了毫无意义的乱码符号。


    2. 怎么解决?(两种方法任选其一)

    方法一:修改编译命令(推荐,最简单)

    你需要告诉 GCC 编译器:“我的源文件是 UTF-8 编码的,请帮我转换成 Windows 能看懂的格式”。

    请在 VS Code 的终端中使用以下命令重新编译:

    gcc -fexec-charset=GBK -o hello.exe hello.c

    • 解释:-fexec-charset=GBK 参数强制编译器将执行时的字符集转换为 GBK,这样弹出的对话框就能正常显示中文了。
    • 注意:如果你之前用了 -m32,请把两个参数拼在一起: gcc -m32 -fexec-charset=GBK -o hello.exe hello.c

    方法二:修改代码(使用宽字符)

    这是更规范的 Windows 编程写法,但改动稍微多一点。

  • 修改头文件:包含 <tchar.h>。
  • 修改主函数:将 main 改为 wmain(或者 _tmain)。
  • 修改字符串:在字符串前加 L 或 _T() 宏,表示这是宽字符(Unicode)。
  • 修改函数:使用 MessageBoxW 代替 MessageBox。
  • 修改后的代码示例:

    #include <stdlib.h>
    #include <stdio.h>
    #include <windows.h>

    int wmain() // 改为 wmain
    {
    // 注意字符串前面的 L,表示宽字符 Unicode
    MessageBoxW(NULL, L"这是 的第一个对话框", L"Hello-软件安全实验-1", MB_OK);
    MessageBoxW(NULL, L"这是 的第二个对话框", L"Hello-软件安全实验-2", MB_OK);
    MessageBoxW(NULL, L"这是 的第三个对话框", L"Hello-软件安全实验-3", MB_OK);

    getchar();
    return 0;
    }

    编译命令(无需额外参数):

    gcc -o hello.exe hello.c

    可能出现的问题2:

    你遇到的错误 cannot open output file hello.exe: Permission denied 是 Windows 下使用 GCC 编译时非常常见的权限冲突问题。这不是代码错误,而是系统或进程阻止了编译器写入文件。

    核心原因

    你的 hello.exe 文件正被另一个程序占用(最常见的是上一次运行的程序没有关闭),或者杀毒软件、Windows Defender 正在扫描它,导致 GCC 无法覆盖写入新文件。

    解决方案:关闭正在运行的程序(最常用)
    • 检查任务栏或系统托盘,看是否有 hello.exe 的窗口正在运行。
    • 如果有,点击“X”关闭它。
    • 如果找不到窗口,按 Ctrl + Shift + Esc 打开任务管理器,在“进程”标签页中找到 hello.exe,右键选择“结束任务”。
    • 重新在终端中执行编译命令: gcc -fexec-charset=GBK -o hello.exe hello.c

    步骤二:PE文件格式分析

    可能出现的问题1:

    OllyDbg (OD) 是 32 位的老软件,而你编译出来的 hello.exe 是 64 位的程序。

    OllyDbg 只能调试 32 位程序。因为你在 VS Code 里编译时没有强制指定生成 32 位程序,GCC 默认生成了 64 位版本,所以 OD 打不开它。

    去看本文的第三部分,实验前的准备,已经讲的很详细了

    问题1:

    (1) 使用使用UltraEdit或WinHex观察PE文件例子程序hello.exe的16进制数据,在打印稿中画出该PE文件基本结构,即指出:MZ头部+DOS stub+PE文件头+可选文件头+节表+节等信息。

    并回答:如何判断打开文件是PE格式文件?

    使用 WinHex (或 010 Editor) 查看文件头

    这一步是为了回答实验报告中关于“PE文件结构”的问题。

  • 打开工具:启动 WinHex(如果没有安装,建议下载安装一个试用版,或者用 010 Editor,操作类似)。
  • 加载文件:在 WinHex 中点击 File -> Open,选择你的 hello.exe。
  • 寻找 DOS 头标志 (MZ):
    • 看最左上角(偏移地址 0x00),你会看到 4D 5A。
    • 这就是 "MZ" 的 ASCII 码,代表这是一个可执行文件。
  • 寻找 PE 头入口 (e_lfanew):
    • 找到偏移地址 0x3C 这一行。
    • 读取这里的 4 个字节(例如可能是 80 00 00 00 或 E0 00 00 00,具体取决于编译器)。
    • 记录这个数值(假设是 80)。这意味着真正的 PE 头在文件的第 128 字节处。
  • 定位 PE 签名:
    • 按 Ctrl + G (Go to position),输入刚才看到的数值(如 80)。
    • 你应该能看到 50 45 00 00,即 "PE\\0\\0"。这证实了 PE 头的起始位置。
  • 🧩 PE 文件基本结构分析(基于图片内容)

    根据 WinHex 中显示的十六进制数据和右侧 ASCII 文本,我们可以按顺序定位并标注出 PE 文件的核心组成部分:

    1. MZ 头部(DOS Header)

    • 位置:文件起始处,偏移量 0x00000000。
    • 特征:前两个字节为 4D 5A,对应 ASCII 字符 “MZ”。
    • 作用:这是所有 Windows 可执行文件的“签名”,用于标识这是一个 DOS/Windows 程序。即使现代系统不再依赖 DOS,这个头仍然保留以维持兼容性。

    2. DOS Stub(DOS 存根程序)

    • 位置:紧接在 MZ 头部之后,大约从偏移 0x00000040 开始。
    • 特征:包含一段可读的 ASCII 字符串:“This program cannot be run in DOS mode.”
    • 作用:当用户在纯 DOS 环境下运行此 .exe 文件时,会显示这条提示信息。在现代 Windows 系统中,这段代码会被跳过,直接加载真正的 PE 结构。

    3. PE 文件头(PE Signature + COFF Header)

    • 位置:通过 MZ 头部中的 e_lfanew 字段(位于偏移 0x0000003C,值为 B8 00 00 00,即十进制 184)跳转到该位置。
    • 特征:在偏移 0x000000B8 处,可以看到四个字节 50 45 00 00,对应 ASCII “PE\\0\\0”,这是 PE 文件的正式标志。
    • 作用:标志着真正 Windows 可执行文件格式的开始,后续是 COFF 文件头,描述文件的基本属性(如机器类型、节数等)。

    4. 可选文件头(Optional Header)

    • 位置:紧跟在 PE 签名和 COFF 头之后。
    • 特征:虽然图中未完全展开,但通常在 PE 签名后约 20~24 字节处开始,包含入口点地址、镜像大小、数据目录表等关键信息。
    • 作用:定义了程序如何被加载到内存、从哪里开始执行、需要哪些动态链接库等。

    5. 节表(Section Table)

    • 位置:在可选文件头之后,由多个固定大小的条目组成。
    • 特征:图中可见 .text, .data, .rdata, .bss 等节名出现在 ASCII 区域,表明这些节的名称和属性已定义。
    • 作用:每个节代表一块具有相同访问权限(读/写/执行)的数据或代码块,操作系统据此将不同部分映射到虚拟内存的不同区域。

    6. 各节内容(Sections)

    • .text 节:存放编译后的机器指令(代码),通常是只读且可执行的。
    • .data / .rdata 节:分别存储初始化的全局变量和只读常量(如字符串)。
    • .bss 节:预留空间给未初始化的全局变量,不占用磁盘空间但在运行时分配内存。
    • 位置:从偏移 0x000001A0 起可见 .data,0x000001C0 起可见 .rdata,0x00000220 起可见 .bss。
    ❓ 如何判断一个文件是否为 PE 格式?

    要确认一个文件是否为 PE 格式,只需检查以下两个关键点:

    ✅ 第一步:检查 MZ 签名

    • 打开文件的前两个字节,若为 4D 5A(ASCII “MZ”),则说明它是一个 DOS/Windows 可执行文件的基础结构。

    ✅ 第二步:查找 PE 签名

    • 读取 MZ 头部中偏移 0x3C 处的 4 字节值(称为 e_lfanew),它指向 PE 签名的实际位置。
    • 跳转到该偏移地址,检查接下来的 4 字节是否为 50 45 00 00(ASCII “PE\\0\\0”)。
    • 如果匹配,则该文件是标准的 PE 格式文件;否则可能是其他格式(如 ELF、Mach-O 或损坏文件)。

    💡 示例验证:

    • 图中 hello.exe 的 e_lfanew = B8 00 00 00 → 跳转至偏移 0xB8
    • 在 0xB8 处确实看到 50 45 00 00 → 确认为合法 PE 文件

    问题2:

    (2)使用Ollydbg对该程序进行初步调试,了解该程序功能结构,在内存中观察该程序的完整结构。

    并回答程序入口点位置是多少?

    1. 程序入口点位置

    结论: 程序的入口点(Entry Point)位于内存地址 00621460。

    证据分析(基于截图):

    • 右侧寄存器窗口 (Registers):
      • 请看右上角的 EIP 寄存器行。
      • 显示内容为:EIP 00621460 hello.<ModuleEntryPoint>。
      • 解释: EIP (Extended Instruction Pointer) 寄存器始终指向 CPU 下一条将要执行的指令地址。当程序刚加载或暂停在入口处时,它指向的就是程序的入口点。OllyDbg 已经智能地将其标记为 <ModuleEntryPoint>。
    • 左上角反汇编窗口 (Disassembler):
      • 第一行高亮显示的指令地址正是 00621460。
      • 该地址处的指令是 PUSH EBP,这是 C/C++ 编译器生成的标准函数序言(Function Prologue),标志着 main 函数或入口函数的开始。
    • 底部内存转储窗口 (Dump):
      • 左下角的地址栏显示当前查看的起始地址也是 00621460。
      • 对应的十六进制数据 55 正是 PUSH EBP 指令的机器码。

    2. 程序功能结构初步分析

    通过观察 OllyDbg 中的反汇编代码和调用栈,可以推断出该程序的结构特征:

    典型的控制台程序入口逻辑

    虽然入口点是 00621460,但这通常是编译器生成的启动代码(CRT Startup Code,如 mainCRTStartup),而不是用户编写的 main 函数本身。

    • 初始化环境: 代码开头进行了堆栈平衡操作(PUSH EBP, MOV EBP, ESP)。
    • 参数获取: 随后调用了 msvcrt._getmainargs(在地址 00621438 处可见 CALL <JMP.&msvcrt._getm…>)。这个函数的作用是解析命令行参数(argc, argv),为后续的 main 函数做准备。
    • 异常处理设置: 代码中包含了 SEH (Structured Exception Handling) 相关的指令(如 XOR EBX,EBX, TEST EBX,EBX, SETNE BL),这是为了设置结构化异常处理链,确保程序崩溃时能被系统捕获。

    动态链接库调用

    • 在反汇编窗口的下方(地址 00621487 附近),可以看到对 KERNEL32.SetUnhandledExceptionFilter 的调用引用。这说明程序正在注册一个未处理异常的过滤器,这是现代 Windows 程序的标准行为。

    内存布局观察

    • 代码段 (.text): 当前 EIP 所在的 0062xxxx 区域是可执行代码段。
    • 数据段/堆栈: 右下角的堆栈窗口显示了当前的调用栈情况,包括 RETURN to KERNEL32…,表明程序是从操作系统加载器跳转进来的。
    问题3:

    3.熟悉各类PE文件格式查看和编辑工具(PEViewStud_PEPEditor等)

    这是一个非常经典的逆向工程入门练习。通过手动分析 PE 文件结构,你能深入理解操作系统是如何加载和执行程序的。

    (1)画出PE的各个关键结构和字段文件偏移地址和虚拟内存地址。

    PE 文件的结构就像是一个精密的俄罗斯套娃。要理解它,必须区分“在硬盘上长什么样”(File Offset, FO)和“加载到内存后长什么样”(Virtual Address, VA/RVA)。

    核心概念

    • RVA (Relative Virtual Address):相对虚拟地址。即内存中的地址减去程序加载基址(ImageBase,通常是 0x00400000)。
    • FO (File Offset):文件偏移。即数据在 .exe 文件中的第几个字节。
    • 转换公式:VA = ImageBase + RVA。

    PE 结构映射表

    你可以参考下表来绘制你的结构图:

    结构名称关键字段/内容文件偏移 (FO) 计算方式虚拟内存地址 (RVA/VA) 说明
    DOS Header (MZ头) e_magic: "MZ" (2字节) e_lfanew: 指向PE头的偏移 (4字节) 0x0000 (固定) 0x003C (固定) 此时还未进入内存映射,仅作为文件头存在。
    DOS Stub "This program cannot be run…" 0x0040 ~ e_lfanew 加载到内存后通常被忽略或位于未初始化区域。
    PE Signature "PE\\0\\0" 由 e_lfanew 决定 (例如图中是 0xB8) 标志着 PE 格式正式开始。
    COFF Header (标准PE头) Machine: 机器类型 NumberOfSections: 节数量 SizeOfOptionalHeader: 可选头大小 紧接 PE Signature 之后 (例如 0xB8 + 4 = 0xBC) 包含文件的基本属性,不直接参与内存执行逻辑,但指导加载器。
    Optional Header (可选头) Magic: 32位(10B)或64位(20B) AddressOfEntryPoint: 入口点 RVA ImageBase: 建议加载基址 SectionAlignment: 内存对齐粒度 FileAlignment: 文件对齐粒度 紧接 COFF Header 之后 最关键的部分! 定义了代码在内存中的布局、入口点位置以及数据目录表的位置。
    Section Table (节表) 每个节的描述: Name: 节名 (.text, .data…) VirtualSize: 内存中大小 VirtualAddress: 节在内存中的 RVA SizeOfRawData: 文件中大小 PointerToRawData: 节在文件中的偏移 紧接 Optional Header 之后 数组形式排列,数量由 COFF 头决定。 转换的核心桥梁! 通过对比 VirtualAddress 和 PointerToRawData,我们可以算出任意 RVA 对应的文件偏移。
    Sections (节数据) .text (代码), .data (数据), .rdata (只读数据) 由 PointerToRawData 指定 由 VirtualAddress 指定 加载器会将文件中的数据按 FileAlignment 读取,然后按 SectionAlignment 映射到内存。

    示意图绘制建议

    在你的打印稿上,可以画两个长条矩形,左边代表“磁盘文件”,右边代表“内存映像”,中间用箭头连接:

    (2)找到程序中调用的API函数的地址。

    在 PE 文件中,API 函数(如 printf, MessageBox)的地址并不是写死的,而是通过导入表 (Import Table) 动态获取的。

    方法一:使用工具查看(最快)

    使用 PEView 或 Stud_PE 等工具打开 hello.exe:

  • 找到 IMAGE_DIRECTORY_ENTRY_IMPORT (导入表目录)。
  • 展开它,你会看到列出的 DLL 名称(如 msvcrt.dll, kernel32.dll)。
  • 点击具体的 DLL,下方会列出该程序调用的所有函数名(如 _printf, _getmainargs)。
  • 工具通常会直接显示这些函数的 Thunk RVA(调用桩的地址)。
  • 方法二:手动计算(原理分析)

    如果你需要知道这些 API 在内存中的确切调用地址(IAT 地址),步骤如下:

  • 定位导入表:

    • 在 Optional Header 的数据目录(Data Directory)中,找到第 2 项(索引为 1),即 Import Table。
    • 记下它的 RVA (例如 0x2050) 和 Size。
  • 定位导入描述符 (Import Descriptor):

    • 根据 RVA 0x2050,利用节表将其转换为文件偏移 (FO),在文件中找到 IMAGE_IMPORT_DESCRIPTOR 结构数组。
    • 每个描述符对应一个 DLL。找到 OriginalFirstThunk (INT) 和 FirstThunk (IAT) 的 RVA。
  • 定位导入查找表 (INT) / 导入地址表 (IAT):

    • INT 指向的是函数名称字符串或序号。
    • IAT 是程序实际调用 API 的地方。在程序刚加载还没运行时,IAT 的内容和 INT 是一样的(指向名字)。当程序运行起来后,Windows 加载器会把 IAT 里的内容覆盖成 API 在内存中的真实地址。
  • KERNEL32.GetModuleHandleA

    • 位置: 0062148E
    • 汇编指令: CALL DWORD PTR DS:[<&KERNEL32.GetModule…>]
    • 参数: "libgcc_s_dw2-1.dll" (由上一行 MOV DWORD PTR SS:[LOCAL.11], OFFSET 0062… 传入)
    • 功能: 获取指定模块(这里是 GCC 运行时库)在进程中的句柄。如果模块已加载,返回其基址;否则返回 NULL。

    KERNEL32.LoadLibraryA

    • 位置: 006214A4
    • 汇编指令: CALL DWORD PTR DS:[<&KERNEL32.LoadLibra…>]
    • 参数: "libgcc_s_dw2-1.dll"
    • 功能: 将指定的 DLL 文件映射到调用进程的地址空间。这是典型的动态加载库的行为。
    如何在工具中找到这些地址?

    如果你需要自己找到这些地址,可以使用以下两种方法:

    方法一:在反汇编窗口观察(如截图所示)
  • 寻找 CALL 指令:在代码段中寻找 CALL 指令。
  • 识别导入调用:注意看 CALL 后面的操作数。如果是类似 [<&KERNEL32.FunctionName>] 或 [IAT_Address] 的形式,说明这是一个通过导入地址表进行的 API 调用。
  • 查看注释:OllyDbg 通常会自动解析这些地址,并在右侧注释栏显示具体的 API 名称(如截图中的红色文字)。
  • 方法二:使用 PE 查看工具(PEView / Stud_PE)

    虽然截图是调试器,但题目要求熟悉 PE 工具。要静态找到这些函数的“入口”,你需要查看 导入表。

  • 打开工具:用 PEView 或 Stud_PE 打开该 exe 文件。
  • 定位导入表:
    • 在 PE Header -> Optional Header 中找到 DataDirectory[1] (Import Table)。
    • 记录它的 RVA(相对虚拟地址)和 Size。
  • 解析导入表:
    • 跳转到该 RVA 对应的文件偏移位置。
    • 你会看到一个 IMAGE_IMPORT_DESCRIPTOR 结构数组。
    • 找到名为 KERNEL32.dll 的描述符。
    • 在该描述符下,查看 OriginalFirstThunk (INT) 或 FirstThunk (IAT) 指向的列表。
    • 列表中会列出所有调用的函数名,如 GetModuleHandleA 和 LoadLibraryA。
  • 问题4:
    攻击程序:手工修改hello.exe程序,使得其只弹出第三个对话框。

    要修改 hello.exe 使其只弹出第三个对话框,我们需要通过逆向分析找到控制这三个对话框的逻辑(通常是连续的 CALL 指令或跳转指令),然后修改汇编代码来跳过前两个对话框的执行。

    具体的操作步骤:

    1. 寻找入口点与主逻辑

    首先,你需要在 OllyDbg 中找到程序的主函数(main 函数)。

    • 定位入口: 打开程序后,OllyDbg 通常会停在系统领空(System Breakpoint)或者程序的入口点(OEP)。
    • 进入用户代码: 按 F8(单步步过)几次,直到你看到类似 PUSH EBP, MOV EBP, ESP 这样的标准函数开头,这通常标志着进入了 main 函数。
    • 识别对话框调用: 向下浏览反汇编代码,寻找调用消息框的 API。
      • 如果是 ANSI 编码程序,找 CALL <JMP.&USER32.MessageBoxA>。
      • 如果是 Unicode 编码程序,找 CALL <JMP.&USER32.MessageBoxW>。
      • 你会看到连续出现了三次类似的 CALL 指令,分别对应第一、二、三个对话框。

    2. 分析执行流程

    假设你在反汇编窗口中看到了如下结构(地址仅为示例):

    目标: 我们要让 CPU 直接“飞”到第 3 个对话框的代码处执行,忽略前两个。

    3. 修改代码(核心步骤)

    你有三种主要方法来实现这个目的。推荐使用 方法 A,因为它最简单且不容易破坏堆栈平衡。

    方法 A:使用 JMP 指令跳转(推荐)

  • 选中第一行代码: 在反汇编窗口中,右键点击 main 函数的第一条指令(例如上面的 00401000 处的 PUSH EBP),选择 "Assemble"(汇编) 或者直接按 空格键。
  • 输入跳转指令: 在弹出的汇编编辑框中,输入跳转到第三个对话框位置的指令。
    • 假设第三个对话框的 CALL 指令地址是 0040105E。
    • 输入:JMP 0040105E
  • 确认修改: 点击 "Assemble" 按钮。OllyDbg 会用 E9 xx xx xx xx (相对跳转)覆盖原来的代码。
    • 注意:如果原来的指令很短(比如只有1字节),而 JMP 需要5字节,可能会覆盖掉后面的一两条指令。只要不影响堆栈(ESP/EBP)的初始化即可。如果覆盖了 PUSH EBP,可能会导致程序崩溃。
    • 更稳妥的做法: 如果 main 函数开头有堆栈初始化代码(如 PUSH EBP, MOV EBP, ESP),不要修改这几行。请找到第一个 CALL MessageBox 的位置,将其修改为 JMP 到第三个 CALL 的位置。
  • 第一步:定位代码
  • 打开程序: 在 OllyDbg 中加载 hello.exe。
  • 进入主函数: 按 F8 单步步过几次,直到看到标准的函数头(PUSH EBP, MOV EBP, ESP)。
  • 找到弹窗逻辑: 向下看,你会看到类似这样的结构(假设地址如下):
    • 00401050: CALL <MessageBoxA> —— 这是第1个弹窗
    • … (中间可能有一些清理堆栈的代码)
    • 00401060: CALL <MessageBoxA> —— 这是第2个弹窗
    • 00401070: CALL <MessageBoxA> —— 这是第3个弹窗(我们要保留的)
  • 代码分析

    让我们拆解一下这三段几乎一模一样的代码块:

    • 第一个弹窗 (005C1561 – 005C1584):

      • PUSH … OFFSET 005C404… (参数:标题/内容字符串)
      • CALL EAX (调用 USER32.MessageBoxA) -> 这是第 1 个弹窗
    • 第二个弹窗 (005C1589 – 005C15AC):

      • PUSH … OFFSET 005C409… (参数:标题/内容字符串)
      • CALL EAX (调用 USER32.MessageBoxA) -> 这是第 2 个弹窗
    • 第三个弹窗 (005C15B1 – 005C15D4):

      • PUSH … OFFSET 005C40E… (参数:标题/内容字符串)
      • CALL EAX (调用 USER32.MessageBoxA) -> 这是第 3 个弹窗
    第二步:写入跳转指令
  • 选中 008D1560 这一行。
  • 按 空格键 (Assemble) 或右键选择“汇编”。
  • 输入以下指令:
  • JMP SHORT 008D15BA

  • 点击“汇编”或回车确认。
  • 原理说明:

    • 目标地址 008D15BA:这是第三个 MessageBoxW 开始设置参数(SUB ESP,10)的地方。跳到这里,程序就会像正常流程一样去准备第三个弹窗的参数并弹出它。
    • 指令长度:JMP SHORT 生成的机器码是 2 个字节 (EB xx)。
    • 覆盖情况:它会覆盖掉原本的 PUSH EBP (1字节) 和 MOV EBP,ESP (2字节) 的一部分。虽然这破坏了标准的函数栈帧建立过程,但因为我们是直接跳过中间所有逻辑去执行后面的代码,且后面的代码并没有依赖 EBP 来寻址局部变量(它是通过 ESP 偏移或者直接传参),所以这样改是安全的,能成功只弹出第三个框。
    出现的问题(很简单解决):

    这个报错 "No room for this command" 的意思非常直白:空间不足。

    🔍 为什么会报错?

    • 你的指令太长了:你输入的 JMP SHORT 008D15BA 需要占用 2个字节(机器码是 EB 47)。
    • 原来的位置太小了:调试器默认只允许你修改当前这一行指令的空间。地址 008D1560 处的原始指令是 PUSH EBP,它只有 1个字节(机器码 55)。
    • 冲突:你想把一条 2字节的指令塞进 1字节的空间里,而且你还勾选了 "Keep size" (保持大小),调试器为了防止破坏下一行代码,就拒绝执行并报错。

    ✅ 解决方法(二选一)

    方法一:取消勾选 "Keep size"(最简单,推荐)

  • 在报错的那个窗口中,取消勾选 那个 "Keep size" 复选框。
  • 再次点击 "Assemble"。
  • 此时调试器会提示你这会覆盖下一行指令,点击 "Yes" 确认。
    • 原理:这样调试器就会允许写入 2个字节,虽然会吞掉下一行 MOV EBP,ESP 的一部分,但因为我们要直接跳走,被吞掉也没关系。
  • 方法二:使用近跳转 JMP NEAR(更规范)

    如果你不想取消勾选,或者想写得更“正规”一点,可以使用长跳转指令。

  • 保持 "Keep size" 勾选(或者不勾也行,只要空间够,如果报错了就取消勾选)。
  • 将指令改为: JMP 008D15BA

  • 第三步:保存文件
  • 选中修改区域 在反汇编窗口中,用鼠标左键点击并拖动,选中你修改过的所有代码行(例如从 008D1560 到 008D15BA 的跳转指令及后续被覆盖的代码)。

  • 右键菜单操作 在选中的代码区域上点击 鼠标右键,在弹出的菜单中选择:

    Edit → Copy to executable → All modifications

  • 确认保存新文件 此时会弹出一个新的窗口(标题通常为 “File” 或 “Executable”),显示的是修改后程序的完整内存镜像。 在这个新窗口中,再次点击 鼠标右键,选择:

    Save file…

    这里点”是“就好了

    为什么会出现这个弹窗? 你之前的操作生效了:你在反汇编窗口里把 JMP SHORT 008D15BA 写入到了内存中。 OD 的保护机制:当你试图关闭 OD 或者保存文件时,它会检查:“嘿,我在内存里跑的代码和你硬盘上那个原始的 exe 不一样了,你要不要把硬盘上的那个也改一下?”

  • 输入新文件名 在弹出的文件保存对话框中,输入一个新文件名(如 hello_patched.exe),选择保存路径,点击“保存”。

  • 方法 B:使用 NOP 填充(nop sled)

    如果你不想处理复杂的跳转计算,可以用空指令填满前两个对话框的代码区域。

  • 选中区域: 从第一个对话框的参数准备代码开始(例如 PUSH 0),一直选中到第二个对话框的 CALL 指令结束。
  • 填充 NOP: 右键点击选中的区域 -> "Binary"(二进制) -> "Fill with NOPs"(用 NOP 填充)。
  • 效果: 这些指令变成了 90 (NOP),CPU 会空转滑过这些代码,直接执行下面的第三个对话框代码。
    • 缺点:如果中间夹杂了局部变量的分配(如 SUB ESP, xx),全部 NOP 掉可能会导致后续代码访问内存出错。
  • 第一步:确定要 NOP 掉的区域(关键)

    你需要找到前两个 MessageBox 的调用位置。根据之前的分析,通常在 CALL 指令之前会有参数压栈(PUSH)。

  • 向下滚动反汇编窗口,找到第一个对话框的代码块。
    • 通常长这样: PUSH 0 ; 参数4 (uType)
      PUSH offset … ; 参数3 (Caption)
      PUSH offset … ; 参数2 (Text)
      PUSH 0 ; 参数1 (hWnd)
      CALL USER32.MessageBoxA ; <— 关键点
  • 继续向下找,找到第二个对话框的类似代码块,直到它的 CALL USER32.MessageBoxA 结束。
  • 确定范围:
    • 起始点:第一个 PUSH 指令的地址。
    • 结束点:第二个 CALL 指令的下一行地址(即选中包含这个 CALL 指令)。
  • 详细分析
  • 第一个对话框区域(红框 1)

    • 起始点:001E1560 (PUSH EBP)。虽然 PUSH EBP 通常是函数开头的保护现场指令,但在这个上下文中,它是这一整块连续代码的开始。如果你想把这块彻底“抹掉”,从这里开始选是完全没问题的。
    • 核心操作:中间的 MOV 指令是在准备参数(比如设置 "Hello" 字符串),最后的 CALL EAX (在 001E158F) 是真正的弹窗动作。
    • 结束点:001E1591 (ADD ESP, 10)。注意! 这个 ADD ESP, 10 是用来平衡堆栈的(清理刚才压入的4个参数)。如果你把这也 NOP 掉了,程序可能会因为堆栈不平衡而崩溃。建议保留这一行,或者只 NOP 到 CALL EAX 为止。
  • 第二个对话框区域(红框 2)

    • 同样的逻辑,从 001E159C 开始准备参数,到 001E15BA (CALL EAX) 执行弹窗。
    • 同样要注意后面的 ADD ESP, 10 (001E15BC) 最好保留。
  • ⚠️ 警告:避开堆栈平衡指令 在两个对话框之间,或者第二个对话框之后,可能会有 ADD ESP, xx 或 SUB ESP, xx 这样的指令。

    • 千万不要 NOP 掉这些指令! 否则会导致程序崩溃(栈不平衡)。
    • 只 NOP 掉负责“准备参数”和“调用函数”的那几行。
    第二步:执行填充操作
  • 选中代码:
    • 鼠标左键点击第一行要 NOP 的代码(例如第一个 PUSH)。
    • 按住 Shift 键,点击最后一行要 NOP 的代码(例如第二个 CALL)。
    • 此时选中的区域会变成高亮颜色(通常是浅蓝色或黄色)。
  • 右键菜单:
    • 在选中的高亮区域上点击 鼠标右键。
  • 选择填充:
    • 在菜单中选择 "edit"。
    • 在子菜单中选择 "Fill with NOPs"(用 NOP 填充)。
    • 注:如果是汉化版 OD,可能是 "二进制" -> "用 NOP 填充"。
  • 第三步:验证与保存
  • 观察变化:
    • 你会看到选中的所有指令都变成了 90 (机器码) 和 NOP (助记符)。
    • 红色的跳转箭头(如果有)应该消失了,因为代码现在是连续向下的。
  • 保存修改(重复之前的步骤):
    • 在反汇编窗口任意位置 右键。
    • 选择 "Copy to executable" 。
    • 在弹出的新窗口中 右键 -> "Save file"。
    • 保存为新的文件名(如 hello_nop.exe)。
  • 💡 为什么这种方法有时候会失败?

    正如你在提示中看到的,如果代码结构是这样的:

    SUB ESP, 20 ; 分配局部变量空间 (千万别 NOP 这个!)
    PUSH 0 ; \\
    PUSH offset … ; |–> 这些可以 NOP
    CALL MessageBox ; /
    ADD ESP, 8 ; 恢复堆栈 (千万别 NOP 这个!)

    如果你把中间全 NOP 了,程序运行到下面需要用到局部变量时,就会因为找不到数据而崩溃。

    总结建议: 对于简单的 Hello World 弹窗程序,通常参数都是直接 PUSH 进去的,中间没有复杂的堆栈操作,所以 NOP 填充法 在这里是非常安全且有效的。大胆尝试吧!

    方法C:手动汇编修改法(NOP 替换)

    (其实和方法b没太大区别)。

    第一步:跟之前一样定位到代码之后,选中整个“区块”

    不要只选 CALL 那一行。

    • 从第1个弹窗的第一行 MOV … (或者上面的 SUB ESP) 开始选。
    • 一直选到第2个弹窗的最后一行 CALL 结束。
    • 也就是把截图中 005C1561 到 005C1588 这一大坨全部选中。
    第二步:一键 NOP(推荐)
    • 在选中的这一大坨灰色区域上 右键。
    • 选择 Binary -> Fill with NOPs。或者直接按快捷键 Ctrl+E
    • 这一步其实就是我之前说的“方法二”(还更麻烦了一些),但这比你一个一个按空格键去改 CALL 要快得多,而且绝对不会出错(因为它会把中间的 MOV、SUB 全部抹平,不会留下任何隐患)。
    第三步:填充 NOP (90)
  • 此时会弹出一个十六进制编辑窗口(Hex Editor),里面显示的是刚才选中区域的机器码。
  • 在这个窗口里,把所有灰色的数字都选中。再次 点击鼠标右键。
  • 点击菜单里的 Zero selection。此时所有的数字都会变成 00 00 00 …。
  • 在弹出的输入框里输入:90 (90 就是 NOP 指令的机器码)。
  • 点击 OK。
    • 你会看到窗口里的所有数据都变成了 90 90 90 …。
  • 点击 Apply 或 OK 关闭这个窗口。
  • 第四步:回到反汇编窗口确认

    现在你应该能看到反汇编窗口里,选中的那些代码全部变成了灰色的 NOP。

    4. 验证与保存

  • 运行测试: 按 F9 运行程序。
    • 如果成功,程序应该直接弹出第三个对话框,点击确定后程序正常退出。
    • 如果程序崩溃(Crash),说明你修改破坏了堆栈平衡(例如跳过了 SUB ESP 但没跳过对应的 ADD ESP,或者跳过了 PUSH EBP)。此时需要重新加载程序(Ctrl+F2)并调整修改位置。
    • 之后保存了再打开文件,前两个弹窗内容就会显示灰色
  • 保存到文件:
    • 确认修改无误后,在反汇编窗口中,右键点击你修改过的地方(或者全选)。
    • 选择 "Copy to executable"(复制到可执行文件) -> "All modifications"(所有修改)。
    • 在弹出的新窗口中,再次右键 -> "Save file"(保存文件)。
    • 将文件另存为 hello_patched.exe。
  • 总结操作流
  • F8 步入主函数。
  • 找到 第3个 MessageBox 的调用地址(记为 Target_Addr)。
  • 找到 第1个 MessageBox 的调用地址(记为 Start_Addr)。
  • 在 Start_Addr 处按 空格,输入 JMP Target_Addr。
  • F9 运行验证。
  • Copy to executable -> Save 保存结果。
  • 按照上述步骤即可完成对程序的修改与验证。

    四、实验结论与体会

        通过本次实验,我掌握了使用OllyDbg对程序进行动态分析与修改的方法。在尝试NOP填充跳过前两个弹窗时,我深刻理解了堆栈平衡的重要性——若误将ADD ESP,10等清理指令也NOP掉,会导致程序崩溃。这让我认识到,逆向修改不仅要关注功能逻辑,更要尊重底层调用约定,否则看似简单的“空转”也可能引发严重错误。实践出真知,细节决定成败。

    注意:

    要快速编译.c文件,那就按我下面说的做,(x64位的)能用x64dbg打开

    1.cd到要编译的.c文件夹里去

    cd /e/desktop/pe_lab

    2.执行编译文件命令

    gcc -fexec-charset=GBK -o hello.exe hello.c

    3.查看文件夹,是否编译成功

    编译成功,出现exe文件就好了

    如果还是想弄x32位的,又出现了问题,可以再翻到本篇文章开头,“可能出现的问题”里已经讲的很详细了,感觉我把改踩的坑都踩完了,还有问题可以在评论区讨论

    赞(0)
    未经允许不得转载:171主机测评 » 软件安全与漏洞--实验7 PE文件格式分析(含详细MSYS2安装 MinGW-w64 32位工具链教程!!)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址