前言

好久不见,甚是思念,2026年的第一篇文章,希望看到这篇博客的大家都能发财。
新的一年,我们将开启一个新的专栏,也就是之前就略有涉及的安全,你可以叫他信息安全,或者网络空间安全,今天我们来进行一次简单二进制爆破的逆向工程,虽然名字听着很唬人,但其实也就那样,哈哈,话不多说,直接开始。

一、visual studio 编写程序

#include <stdio.h>

// 一个简单的加法函数,方便我们观察参数是如何通过寄存器传递的
int secret_add(int a, int b) {
    int result = a + b;
    return result;
}

int main() {
    int x = 5;
    int y = 5;
    int sum = secret_add(x, y);

    if (sum > 25) {
        printf("Success! Sum is %d\n", sum);
    }
    else {
        printf("Failed!\n");
    }

    return 0;
}

这是一段很简单的程序代码,首先定义了一个函数secret_add,它将两个整数作为输入,将它们的和作为输出,主程序中将两个5作为参数输入,再判断输出的结果是否大于25,大于则输出success,不大于,也就是小于或等于则输出failed。很明显,我们输入的5+5=10,10<25,所以结果应该是failed。

下面,我们将上面这段程序在visual studio中运行,
在这里插入图片描述
结果如下:
在这里插入图片描述
可以看到,这里输出的结果为failed

二、visual studio分析汇编代码

1.调试

在 int x=5这个地方,我们打一个断点,然后我们进行调试,查看汇编代码和c语言代码之间的对应关系,并且分析这里面的逻辑·。

在这里插入图片描述

2.分析汇编

第一部分:变量赋值与函数调用

这部分对应 C 语言的 int x = 5; int y = 5; int sum = secret_add(x, y);。
mov dword ptr [x], 5将常数 5 放入内存中变量 x 的位置。
mov dword ptr [y], 5将常数 5 放入内存中变量 y 的位置。
mov edx, dword ptr [y]将 y 的值(5)加载到 edx 寄存器。
mov ecx, dword ptr [x]将 x 的值(5)加载到 ecx 寄存器。
call secret_add (…)跳转到 secret_add 函数执行。
mov dword ptr [sum], eax函数返回值通常存在 eax 中,这行将其存入 sum。

第二部分:核心逻辑判断 (If-Else)

1.比较与跳转

cmp dword ptr [sum], 19h
指令: cmp (Compare) 比较指令。
意义: 拿 sum 的值和 19h(十六进制的 19 等于十进制的 25)做比较。
底层: 它其实是在做减法运算 sum - 25,但不保存结果,只根据结果修改 CPU 的状态标志位(比如是否为零、是否为负)。

jle main+50h (07FF7…)
指令: jle (Jump if Less or Equal) 小于或等于则跳转。
逻辑: 如果刚才比较的结果是 sum <= 25,CPU 就直接“跳”到偏移地址为 50h 的地方(即 else 分支)。

2.True 分支 (If 成立)

如果没跳走,就会执行打印 Success 的代码:
mov edx, dword ptr [sum]:把 10 传给 edx(对应 printf 中的 %d)。
lea rcx, [string “Success!..”]:把字符串的地址传给 rcx。
call printf:调用打印函数。
jmp main+5Dh:执行完后,必须用一个 无条件跳转指令 jmp 跨过 else 部分,直接去执行 return 0。

3. False 分支 (Else 逻辑)

如果 sum <= 25,程序会跳到这里:
lea rcx, [string “Failed!\n”]:加载 “Failed!” 字符串地址。
call printf:执行打印。

第三部分:函数收尾

对应 return 0; }。
xor eax, eax
技巧: 在汇编中,xor eax, eax(自己异或自己)是将寄存器清零最快的方式。
意义: 设置返回值为 0。
ret
意义: 返回调用者。

三、x86dbg 进行爆破

1.编译原理知识补充

我们先来看一下visual studio中上面这个项目相关的一些文件截图,了解一下它与我们后面的逆向之间的关系。
在这里插入图片描述
以上是图1
在这里插入图片描述
以上是图2
在这里插入图片描述
以上是图3

图1:源代码与工程核心 (Root 目录)

list2.cpp:
定义:你的 C++ 源文件。
意义:这是你的命根子。所有的逻辑(secret_add、if-else)都写在这里。它是人类能读懂的文本文件。

Project1.sln:
定义:解决方案文件 (Solution)。
意义:VS 的入口。它并不包含代码,而是记录了一个或多个项目(Project)的组织关系。双击它就能打开整个开发环境。

Project1.vcxproj:
定义:项目文件。
意义:这是给 MSBuild(微软编译引擎)看的说明书。里面记录了:包含哪些 .cpp 文件、使用了什么编译选项(是 x64 还是 x86)、引用了哪些库。

.vs (隐藏文件夹):
定义:VS 本地缓存。
意义:记录了你上次打开了哪些文件、光标在哪里、断点设在哪里。删掉它不影响代码,但你下次打开项目时,VS 会像失忆了一样重新加载。

图2:中间编译产物 (Debug 目录)

list1.obj / list2.obj:
定义:目标文件 (Object File)。
意义:极其重要! 编译器把 .cpp 翻译成机器语言后,先存成 .obj。它是二进制格式,但还不能运行。它就像是拼图的一块。

vc145.pdb / Project1.pdb:
定义:程序数据库文件 (Program Database)。
意义:逆向的神器。它存储了“调试符号”。它记录了内存地址 0x00401000 对应源码的第 10 行,以及变量 sum 存在哪个寄存器里。没有它,你在 x64dbg 里看到的就全是冷冰冰的地址,没有函数名。

Project1.ilk:
定义:增量链接文件。
意义:为了让你下次修改代码后编译更快,链接器会记录上一次链接的信息。

Project1.log / debug.log:
定义:编译日志。
意义:记录了编译过程中有没有报错、报了什么警告。

图3:最终产物 (x64\Debug 目录)

Project1.exe:程序的“执行体”
定义:这是最终的可执行文件(二进制机器码)。
本质:链接器(Linker)将之前生成的 .obj 文件与系统标准库(如 printf 的实现)缝合在一起的产物。
逆向视角:它是 x64dbg 唯一关心的东西。在没有 PDB 的情况下,它就是一堆无意义的 0 和 1。

Project1.pdb:程序的“说明书”
定义:程序数据库文件(Program Database)。
本质:它是一个调试符号文件。它不包含可执行代码,但记录了二进制地址与你写的源代码(list2.cpp)之间的映射关系。
重要性:
在 Visual Studio 中:有了它,你才能在调试时看到变量名、函数名和行号。
在逆向对抗中:正因为 PDB 暴露了代码的所有商业秘密,所以你在分析商业软件(如深信服 EDR)时永远看不到它。

知识分享:商业软件为什么难以破解

很多人以为写完代码按运行,.cpp 就直接变 .exe 了。其实中间经过了编译器 (Compiler) 生成了 .obj,然后由链接器 (Linker) 把它们缝合在一起。而那个 .pdb 文件,则是连接‘代码世界’和‘二进制世界’的桥梁。如果你在逆向时没有 PDB 文件,你就是在‘盲操’,这也就是为什么破解商业软件比破解自己写的代码难得多的原因。

商业软件(比如 Office、游戏、甚至是深信服的防护软件)之所以不提供 .pdb 文件,最根本的原因就是:为了增加逆向工程的难度,保护核心代码不被轻易看穿。

1.什么是 PDB 泄露?(信息差的消失)

如果一个商业软件带了 .pdb 文件,那么在逆向者眼里,它就变成了“半透明”的:
函数名全显:原本在 x64dbg 里只是一串冷冰冰的地址(如 sub_7FF71234),有了 PDB 就会变成 CheckUserLicense(检查用户授权)或 EncryptPassword(加密密码)。
变量名泄露:原本是 [rbp-0x10],有了 PDB 会显示为 IsTrialExpired(试用期是否结束)。
源码行对齐:逆向者可以直接看到汇编指令对应哪一行 C++ 源码。
结论:提供 PDB 就相当于把保险柜的内部构造图直接贴在了保险柜门上。

2.商业对抗:符号剥离 (Symbol Stripping)

为了防止被逆向,商业软件发布时会进行“符号剥离”:
只保留运行必须的代码:CPU 运行程序只需要地址,不需要名字。所以发布版(Release 版)会删掉所有的调试符号。
代码混淆 (Obfuscation):有些公司甚至会故意把函数名改成无意义的乱码(如 a1, b2),让逆向者即使猜也猜不出来逻辑。

3.为什么微软会公开部分系统的 PDB?

你可能会发现,在调试系统问题时,VS 可以从微软服务器下载 ntdll.dll 等系统文件的 PDB。
为了兼容性和开发:微软希望全球的开发者能更方便地调试与系统底层相关的 Bug。
核心逻辑依然隐藏:虽然公开了 PDB,但那些 PDB 通常是“精简版”,只包含函数名,不包含具体的源码路径和局部变量名。

2.x86dbg精准爆破

(1)导入文件

在这里插入图片描述
我们在x86dbg打开链接生成的exe文件,导入后界面如下
在这里插入图片描述
我们先来看下各个部分的含义:

a.反汇编窗口 (Disassembly Window) —— 核心“手术台”

位于界面左上角,这是你观察和修改代码的地方。
地址栏:最左侧的十六进制数字(如 00007FF7…),代表指令在内存中的具体位置。
机器码:紧随其后的十六进制数(如 7E 12),是 CPU 真正执行的数据。
反汇编指令:人类可读的助记符(如 jle、cmp、nop)。
注释与引用:最右侧,x64dbg 会自动标注出字符串(如 “Success!”)或对应的源码行号。

b.寄存器窗口 (Registers Window) —— CPU 的“演算纸”

位于右侧。它实时显示 CPU 内部的状态。
RAX/RBX/RCX 等:这些是临时存放数据的“小抽屉”。比如函数返回值通常存在 RAX 中。
RIP (Instruction Pointer):最重要的寄存器,指向 CPU 即将执行 的下一条指令地址。
EFLAGS (标志位):在执行 cmp 指令后,ZF(零标志)、SF(符号标志)等会变色,决定了接下来的 jle 是否跳转。

c.内存布局/十六进制转储 (Hex Dump) —— 程序的“原始肉体”

位于底部。它以最原始的十六进制形式展示内存中的数据。
你可以这里看到程序加载的所有 DLL、全局变量或未加密的硬编码字符串。

d.堆栈窗口 (Stack Window) —— 函数的“备忘录”

位于右下角。它记录了函数调用时的局部变量、返回地址和参数传递情况。

(2) nop的艺术

第一部分:操作步骤

在这里插入图片描述
在汇编代码区域单击右键,点击搜索-所有模块-字符串,我们会进入下面的界面

在这里插入图片描述
在这里我们找到“success”这一行,双击打开

在这里插入图片描述
找到success上面的“jle 0x00007FF7B72E18F0”,将它改为nop

随后我们按住ctrl+p,调出补丁,将文件修复后,可以选择保存到桌面,命名为“baopo_success.exe”

在这里插入图片描述
这时候,我们的终端防护系统会发出警报,为什么会发出警报,我们后面再说。

最后我们打开桌面的cmd,运行这个exe程序,得到的结果是success
在这里插入图片描述
这是为什么呢?

第二部分:底层原理
(1)程序的“正常轨道”与“分叉路口”

在汇编层面,程序的执行顺序默认是从上往下一行行走的。但是 if-else 逻辑制造了一个“分叉路口”。正常逻辑(未修改时):CPU 执行 cmp 指令,算出 5+5=105+5=105+5=10,发现 10≤2510 \le 251025。CPU 执行 jle 指令。jle 的意思是“如果小于等于,就跳走”。发生跳转:CPU 的执行头(RIP 寄存器)直接瞬移到了 Failed 打印的代码块地址。结果:它跳过了中间那段 Success 的代码。

(2)为什么是 NOP?

NOP 的全称是 No Operation(机器码为 0x90)。对 CPU 来说,它是一条合法的指令,但它的作用只有一个:让 CPU 浪费一个时钟周期,然后什么都不做,直接执行下一行。

(3)为什么改为 NOP 就能成功?(

核心原理)当你把 jle 这一行指令改写成 NOP 时,你实际上是拆掉了路标,铺平了道路。修改后的逻辑(爆破后):CPU 执行 cmp,依然算出结果小于 25。CPU 走到原本 jle 的位置,发现现在这里变成了 NOP。关键点来了:因为没有了“跳走”的指令,CPU 就失去了“瞬移”的能力。它只能呆头呆脑地继续往下走。而紧接着 NOP 后面的下一行代码,恰恰就是打印 Success 的指令。结果:CPU “被迫”撞进了 Success 分支。

(4) 深度类比:

、铁轨上的道岔你可以把程序想象成一辆在铁轨上行驶的火车:原本的 jle:是一个道岔。如果条件满足,道岔就会把火车引导向“失败”的侧线。你的 NOP 操作:相当于你把这个道岔给拆了,换成了两段笔直的铁轨。结果:无论火车的条件是什么,它现在都只能沿着主线(Success 分支)一直开下去。

第三部分:为什么深信服终端防护系统会发出警报

a.静态特征检测 (File Integrity & Signature)

这是最基础的一道防线。
哈希校验 (Hashing):当你第一次编译生成 Project1.exe 时,它有一个唯一的“指纹”(Hash值)。当你用 x64dbg 修改了其中一个字节并保存为 Project1_OK.exe 时,这个文件的指纹就彻底变了。
签名缺失:正规软件都有数字签名。你的修改导致原有的(如果有的话)签名失效,或者干脆是一个未知的“三无”程序在尝试执行敏感操作。
补丁特征 (Patching Patterns):深信服的病毒库里记录了大量“指令修改”的特征。你把 jle 改成 nop(机器码从 7E 变成 90),这种在代码段(Code Section)直接改写指令的行为,是典型的木马/破解补丁特征。

b.行为监测 (Behavioral Analysis)

即使你成功保存了文件,运行时的行为也会被监控。
可疑修改:EDR 会监控系统中所有文件的修改句柄。当它发现 x64dbg.exe 正在频繁读写另一个可疑的 .exe 内存或磁盘空间时,它会判定这是一种**“代码注入”或“非法修补”**行为。
未知风险:深信服 EDR 有一套云端查询机制。当它发现一个本地新生成的、从未在白名单中出现过的程序尝试启动,它会默认采取“先拦截、后询问”的高压策略。

c.沙箱与 AI 启发式引擎

现在的安全系统(如你截图中的深信服 EDR)通常内置了 AI 引擎:
启发式扫描:它不只是看你改了什么,而是分析这种改动后的程序“长得像不像病毒”。
虚拟执行:在你的程序真正跑起来之前,EDR 可能已经在后台的一个微型沙箱里模拟运行了它。如果发现程序逻辑异常(比如跳过了关键校验),它就会触发预警。

总结

今天我们用visual studio和x86dbg进行了一次简单的二进制爆破,主要是通过修改汇编指令,修改了CPU的寻址顺序,最终实现了对if语句逻辑的更改。虽然过程简单,但是从中可以学到一些编译原理和底层的逻辑。
下次我们玩些更有意思的东西,有兴趣的朋友可以点个关注哟。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐