2025腾讯游戏安全PC初级(上)
Ring3
先进入Main函数,找能分析的逻辑
首先进入找到Main函数,然后发现有个vftable,判断是一个虚表

双击虚表,然后对虚表进行分析

sub_1400013D0(ServiceCreateStart )
__int64 __fastcall sub_1400013D0(__int64 a1, char *a2, __int64 a3)
{
unsigned int v6; // edi
wchar_t *v7; // rax
signed __int64 v8; // rcx
__int16 v9; // ax
SC_HANDLE v10; // rax
SC_HANDLE v11; // rbx
WCHAR Filename[264]; // [rsp+20h] [rbp-658h] BYREF
wchar_t Destination[528]; // [rsp+230h] [rbp-448h] BYREF
v6 = 0;
memset(Destination, 0, 0x412ui64);
if ( a2 )
{
if ( a3 )
{
memset(Filename, 0, 0x20Aui64);
if ( GetModuleFileNameW(0i64, Filename, 0x104u) )
{
v7 = wcsrchr(Filename, 0x5Cu);
if ( v7 )
{
v8 = (char *)v7 - a2;
do
{
v9 = *(_WORD *)a2;
*(_WORD *)&a2[v8 + 2] = *(_WORD *)a2;
a2 += 2;
}
while ( v9 );
wcscpy_s(Destination, 0x209ui64, Filename);
v10 = OpenSCManagerW(0i64, 0i64, 0xF003Fu);
v11 = v10;
if ( v10 )
{
if ( (*(unsigned int (__fastcall **)(__int64, SC_HANDLE, __int64, wchar_t *))(*(_QWORD *)a1 + 40i64))(
a1,
v10,
a3,
Destination)
&& (*(unsigned int (__fastcall **)(__int64, SC_HANDLE, __int64))(*(_QWORD *)a1 + 24i64))(a1, v11, a3) )
{
LOBYTE(v6) = (*(unsigned int (__fastcall **)(__int64))(*(_QWORD *)a1 + 56i64))(a1) == 0;
}
CloseServiceHandle(v11);
}
}
}
}
}
return v6;
}
核心功能:获取当前程序路径并拼接自定义字符串,通过 Windows 服务控制管理器创建 / 启动服务。
命名:ServiceCreateStart // 核心:创建并启动服务
sub_140001520(ServiceRemove)
__int64 __fastcall sub_140001520(__int64 a1, __int64 a2)
{
unsigned int v4; // esi
SC_HANDLE v5; // rbx
v4 = 1;
v5 = OpenSCManagerW(0i64, 0i64, 0xF003Fu);
if ( !v5 )
return 1i64;
if ( (*(unsigned int (__fastcall **)(__int64, SC_HANDLE, __int64))(*(_QWORD *)a1 + 32i64))(a1, v5, a2) )
{
if ( !(*(unsigned int (__fastcall **)(__int64, SC_HANDLE, __int64))(*(_QWORD *)a1 + 48i64))(a1, v5, a2) )
v4 = 0;
CloseServiceHandle(v5);
return v4;
}
else
{
CloseServiceHandle(v5);
return 0i64;
}
}
核心功能:通过服务控制管理器(SCM)执行服务的 “停止 / 删除 / 配置” 类操作
命名:ServiceRemove
sub_1400015C0(ServiceStart)
__int64 __fastcall sub_1400015C0(__int64 a1, SC_HANDLE a2, const WCHAR *a3)
{
unsigned int v3; // ebx
SC_HANDLE v4; // rax
SC_HANDLE v5; // rdi
v3 = 1;
v4 = OpenServiceW(a2, a3, 0xF01FFu);
v5 = v4;
if ( !v4 )
return 0i64;
if ( !StartServiceW(v4, 0, 0i64) && GetLastError() != 1056 )
v3 = 0;
CloseServiceHandle(v5);
return v3;
}
核心功能:打开指定名称的 Windows 服务 → 尝试启动 → 兼容 “服务已运行” 的场景 → 返回操作结果;
命名:ServiceStart
sub_140001640(ServiceStop)
SC_HANDLE __fastcall sub_140001640(__int64 a1, SC_HANDLE a2, const WCHAR *a3)
{
unsigned int v3; // ebx
SC_HANDLE result; // rax
SC_HANDLE v5; // rdi
struct _SERVICE_STATUS ServiceStatus; // [rsp+20h] [rbp-38h] BYREF
v3 = 1;
result = OpenServiceW(a2, a3, 0xF01FFu);
v5 = result;
if ( result )
{
if ( !ControlService(result, 1u, &ServiceStatus) && ServiceStatus.dwCurrentState != 1 )
v3 = 0;
CloseServiceHandle(v5);
return (SC_HANDLE)v3;
}
return result;
}
核心功能:打开指定 Windows 服务 → 发送停止控制指令 → 校验服务状态(兼容已停止场景) → 返回操作结果;
最优命名:ServiceStop
sub_1400016C0(FSFilterServiceCreateAndConfigure)
__int64 __fastcall sub_1400016C0(__int64 a1, SC_HANDLE a2, const WCHAR *a3, const WCHAR *a4)
{
unsigned int v7; // esi
SC_HANDLE ServiceW; // rdi
__int64 v9; // rbx
__int64 v10; // rax
DWORD dwDisposition; // [rsp+70h] [rbp-90h] BYREF
HKEY hKey; // [rsp+78h] [rbp-88h] BYREF
HKEY phkResult; // [rsp+80h] [rbp-80h] BYREF
WCHAR SubKey[520]; // [rsp+90h] [rbp-70h] BYREF
v7 = 1;
memset(SubKey, 0, sizeof(SubKey));
phkResult = 0i64;
hKey = 0i64;
ServiceW = CreateServiceW(
a2,
a3,
a3,
0xF01FFu,
2u,
3u,
0,
a4,
L"FSFilter Activity Monitor",
0i64,
L"FltMgr",
0i64,
0i64);
if ( ServiceW || GetLastError() == 1073 )
{
wsprintfW(SubKey, L"SYSTEM\\CurrentControlSet\\Services\\%s\\Instances", a3);
if ( RegCreateKeyExW(HKEY_LOCAL_MACHINE, SubKey, 0, 0i64, 1u, 0xF003Fu, 0i64, &phkResult, &dwDisposition) )
{
v7 = 0;
}
else
{
wsprintfW(SubKey, L"%s Instance", a3);
v9 = -1i64;
v10 = -1i64;
do
++v10;
while ( SubKey[v10] );
if ( RegSetValueExW(phkResult, L"DefaultInstance", 0, 1u, (const BYTE *)SubKey, 2 * v10) )
{
v7 = 0;
}
else
{
wsprintfW(SubKey, L"SYSTEM\\CurrentControlSet\\Services\\%s\\Instances\\%s Instance", a3, a3);
if ( RegCreateKeyExW(HKEY_LOCAL_MACHINE, SubKey, 0, 0i64, 1u, 0xF003Fu, 0i64, &hKey, &dwDisposition) )
{
v7 = 0;
}
else
{
wcscpy_s(SubKey, 0x208ui64, L"312723");
do
++v9;
while ( SubKey[v9] );
if ( RegSetValueExW(hKey, L"Altitude", 0, 1u, (const BYTE *)SubKey, 2 * v9) )
{
v7 = 0;
}
else
{
dwDisposition = 0;
if ( RegSetValueExW(hKey, L"Flags", 0, 4u, (const BYTE *)&dwDisposition, 4u) )
v7 = 0;
}
}
}
}
}
else
{
v7 = 0;
}
if ( hKey )
{
RegCloseKey(hKey);
hKey = 0i64;
}
if ( phkResult )
{
RegCloseKey(phkResult);
phkResult = 0i64;
}
if ( ServiceW )
CloseServiceHandle(ServiceW);
return v7;
}
核心功能:创建文件系统过滤驱动(FSFilter)服务 → 配置驱动所需的注册表项(实例、高度、标志) → 兼容服务已存在场景 → 返回操作结果;
最优命名:FSFilterServiceCreateAndConfigure
sub_1400019A0(ServiceStopAndDelete)
__int64 __fastcall sub_1400019A0(__int64 a1, SC_HANDLE a2, const WCHAR *a3)
{
SC_HANDLE v3; // rax
SC_HANDLE v4; // rbx
struct _SERVICE_STATUS ServiceStatus; // [rsp+20h] [rbp-38h] BYREF
v3 = OpenServiceW(a2, a3, 0xF01FFu);
v4 = v3;
if ( v3 )
{
ControlService(v3, 1u, &ServiceStatus);
DeleteService(v4);
CloseServiceHandle(v4);
}
return 1i64;
}
核心功能:打开指定 Windows 服务 → 强制发送停止指令 → 删除服务 → 释放句柄(无失败校验,恒返回 1);
最优命名:ServiceStopAndDelete
sub_140001A10(FilterConnectACEDriverPort)
HRESULT __fastcall sub_140001A10(__int64 a1)
{
return FilterConnectCommunicationPort(L"\\ACEDriver", 0, 0i64, 0, 0i64, (HANDLE *)(a1 + 8));
}
核心功能:调用 FilterConnectCommunicationPort 连接名为 \ACEDriver 的过滤驱动通信端口,将端口句柄存入 a1+8,并返回连接结果;
最优命名:FilterConnectACEDriverPort
sub_140001A40(FilterSendMessageToACEDriver)
__int64 __fastcall sub_140001A40(
__int64 a1,
int a2,
const void *a3,
unsigned int a4,
LPVOID lpOutBuffer,
DWORD dwOutBufferSize,
DWORD *a7)
{
DWORD v11; // r15d
_DWORD *v12; // rsi
DWORD *v13; // rbx
unsigned int v14; // edi
DWORD BytesReturned; // [rsp+68h] [rbp+10h] BYREF
BytesReturned = 0;
v11 = a4 + 4;
v12 = operator new(a4 + 4);
memset(v12, 0, v11);
*v12 = a2;
if ( a3 && a4 )
memmove(v12 + 1, a3, a4);
v13 = a7;
if ( a7 )
*a7 = 0;
v14 = FilterSendMessage(*(HANDLE *)(a1 + 8), v12, v11, lpOutBuffer, dwOutBufferSize, &BytesReturned);
if ( v13 )
*v13 = BytesReturned;
j_j_free(v12);
return v14;
}
核心功能:构造 “4 字节指令码 + 消息体” 的消息包 → 通过 FilterSendMessage 向 ACEDriver 过滤驱动发送消息 → 接收驱动返回数据 → 释放消息包内存 → 返回发送结果;
最优命名:FilterSendMessageToACEDriver
sub_140001370(ACEDriverInitServiceParams)
__int64 __fastcall sub_140001370(__int64 a1)
{
return (*(__int64 (__fastcall **)(__int64, const wchar_t *, const wchar_t *))(*(_QWORD *)a1 + 8i64))(
a1,
L"ACEDriver.sys",
L"ACEDriver");
}
核心功能:封装调用 a1 对象虚函数表偏移 8 的函数,传入 ACEDriver.sys(驱动名)和 ACEDriver(服务名),并返回该虚函数的执行结果;
最优命名:ACEDriverInitServiceParams
sub_140001390(ACEDriverCleanupAndCloseHandle)
int __fastcall sub_140001390(_QWORD *a1)
{
int result; // eax
void *v3; // rcx
result = (*(__int64 (__fastcall **)(_QWORD *, const wchar_t *))(*a1 + 16i64))(a1, L"ACEDriver");
v3 = (void *)a1[1];
if ( v3 )
{
result = CloseHandle(v3);
a1[1] = 0i64;
}
return result;
}
核心功能:调用 a1 对象虚函数表偏移 16 的函数处理 ACEDriver 资源,再关闭 a1 [1] 指向的句柄并清空,返回最终操作结果;
最优命名:ACEDriverCleanupAndCloseHandle
sub_140001B10(ACEDriverSendTestHelloMessage)
__int64 __fastcall sub_140001B10(__int64 a1)
{
__int64 v2; // rax
int v5; // [rsp+40h] [rbp-148h] BYREF
char v6[40]; // [rsp+48h] [rbp-140h] BYREF
char v7[256]; // [rsp+70h] [rbp-118h] BYREF
v5 = 0;
strcpy(v6, "This is TestHello from r3");
memset(v7, 0, sizeof(v7));
v2 = -1i64;
while ( v6[++v2] != 0 )
;
return (*(__int64 (__fastcall **)(__int64, __int64, char *, _QWORD, char *, int, int *))(*(_QWORD *)a1 + 64i64))(
a1,
1392640i64,
v6,
(unsigned int)(v2 + 1),
v7,
256,
&v5);
}
核心功能:构造测试字符串 "This is TestHello from r3",计算长度后调用 a1 对象偏移 64 的虚函数,向 ACEDriver 驱动发送测试消息(指令码 1392640),并返回虚函数执行结果;
最优命名:ACEDriverSendTestHelloMessage
sub_140001BE0(ACEDriverSendCustomData)
__int64 __fastcall sub_140001BE0(__int64 a1, unsigned int a2, const void *a3)
{
unsigned int v7; // [rsp+40h] [rbp-438h] BYREF
char v8[1036]; // [rsp+44h] [rbp-434h] BYREF
memset(v8, 0, 0x400ui64);
v7 = a2;
memmove(v8, a3, a2);
return (*(__int64 (__fastcall **)(__int64, __int64, unsigned int *, __int64, _QWORD, _DWORD, _QWORD))(*(_QWORD *)a1 + 64i64))(
a1,
1392644i64,
&v7,
1028i64,
0i64,
0,
0i64);
}
核心功能:将外部传入的自定义数据(a3+a2)拷贝到 1024 字节缓冲区,调用 a1 对象偏移 64 的虚函数,向 ACEDriver 驱动发送指令码 1392644 的自定义消息,并返回虚函数执行结果;
最优命名:ACEDriverSendCustomData
虚表分析完毕

准备进入动态调试,但是发现一开启动态调试程序就会退出
我们先判断此程序可能有反调试功能,打开Strings,发现CheckRemoteDebuggerPresent函数

然后交叉引用跳过去

__int64 __fastcall sub_7FF752851C90(unsigned __int8 *a1)
{
__int64 result; // rax
HANDLE CurrentProcess; // rax
__int64 perf_frequency; // rbx
__int64 v5; // rax
__int64 v6; // rax
__int64 v7; // rdi
__int64 v8; // rbx
__int64 v9; // rax
__int64 v10; // rbx
__int64 v11; // rcx
__int64 v12; // rax
int v13; // r8d
BOOL pbDebuggerPresent; // [rsp+20h] [rbp-58h] BYREF
xtime v15; // [rsp+28h] [rbp-50h] BYREF
for ( result = *a1; (_BYTE)result; result = *a1 )
{
pbDebuggerPresent = 0;
CurrentProcess = GetCurrentProcess();
CheckRemoteDebuggerPresent(CurrentProcess, &pbDebuggerPresent);
if ( pbDebuggerPresent || NtCurrentPeb()->BeingDebugged == 1 || (unsigned int)sub_7FF752851EA0() )
terminate();
perf_frequency = Query_perf_frequency();
v5 = sub_7FF752855344();
if ( perf_frequency == 10000000 )
v6 = 100 * v5;
else
v6 = 1000000000 * (v5 / perf_frequency) + 1000000000 * (v5 % perf_frequency) / perf_frequency;
v7 = v6 + 1000000000;
if ( v6 > 0x7FFFFFFFC46535FEi64 )
v7 = 0x7FFFFFFFFFFFFFFFi64;
while ( 1 )
{
v8 = Query_perf_frequency();
v9 = sub_7FF752855344();
v10 = v8 == 10000000 ? 100 * v9 : 1000000000 * (v9 / v8) + 1000000000 * (v9 % v8) / v8;
if ( v7 == v10 || v7 < v10 )
break;
v11 = 100 * Xtime_get_ticks();
v12 = v7 - v10;
if ( (double)((int)v7 - (int)v10) <= 8.64e14 )
{
v13 = v11 + v12;
}
else
{
v12 = 864000000000000i64;
v13 = v11 - 1391067136;
}
v15.sec = (v12 + v11) / 1000000000;
v15.nsec = v13 - 1000000000 * LODWORD(v15.sec);
Thrd_sleep(&v15);
}
}
return result;
}
这段代码是反调试 + 延时循环保护函数,核心逻辑是持续检测调试器、并通过高精度计时器实现精准延时循环,直到外部条件(*a1)终止,是恶意代码 / 保护程序中常见的反调试 + 反分析逻辑
分析
- 反调试特征:
- 同时检测
CheckRemoteDebuggerPresent和PEB->BeingDebugged,是经典的双层反调试逻辑; sub_7FF752851EA0()大概率补充检测NtQueryInformationProcess(调试端口)、IsDebuggerPresent等;
- 同时检测
- 延时逻辑目的:
- 高精度延时(1 秒级)+ 无限循环 → 阻碍动态分析(调试时单步执行耗时极长)、防止程序被快速调试;
- 用
Thrd_sleep而非空循环 → 减少 CPU 占用,避免被 “高 CPU 占用” 特征检测;
- 循环终止条件:
*a1是外部控制的 “开关”,逆向时需找到修改*a1的代码(如特定条件满足后置 0),才能退出该循环;
- 潜在绕过思路:
- 补丁
CheckRemoteDebuggerPresent返回 0; - 补丁
PEB->BeingDebugged置 0; - 补丁
terminate()为空函数; - 直接修改
*a1为 0,强制退出循环。
- 补丁
关键区别:“单步指令” vs “单步逻辑”
- ❌ 不是 “每点击一次单步(F7/F8)就等 1 秒”:比如你单步执行反调试检测的代码(
CheckRemoteDebuggerPresent),是瞬间完成的; - ✅ 是 “每执行到延时逻辑这一段,就必须等 1 秒才能过去”:延时逻辑是一个完整的代码块,只要执行到这个块,无论你是单步走还是直接运行,都绕不开 1 秒的等待。
核心功能:无限循环执行反调试检测(调试器附加 / PEB 标志),触发则终止程序;同时通过高性能计数器实现 1 秒级高精度延时,直到\a1置 0 时退出循环;
最优命名:AntiDebugDelayLoop
然后再找AntiDebugDelayLoop函数的交叉引用
(我感觉就是找此函数 被其他函数调用的地方)

核心功能:分配线程参数(退出开关 + 反调试函数)→ 创建独立线程执行 AntiDebugDelayLoop → 错误处理 + 防护校验 → 保存线程信息并注册退出清理函数;
最优命名:StartAntiDebugDelayThread
如何跳过反调试?
先找到函数结束地址

rip = rip + 0x6F 的作用
这个条件断点的逻辑是:
- 当程序执行到
0x7FF6847E10D4(mov ecx, 10h)时触发断点; - 调试器自动把当前
rip(指向0x10D4)加上0x6F,让rip直接跳到0x1143; - 程序会从 0x1143继续执行,完全跳过中间所有代码:
operator new内存分配- 线程参数构造
_beginthreadex反调试线程创建- 全局变量校验
atexit注册清理函数(也被跳过,直接到ret)
为什么是 0x6F 而不是 0x78?
0x10D4 + 0x6F = 0x1143,对应指令:lea rcx, sub_7FF684804820(atexit的参数准备)- 再下一条是
jmp atexit,之后就是add rsp, 48h+ret - 加
0x6F是为了刚好跳到atexit调用前,既跳过了反调试线程创建,又保留了程序退出时的清理逻辑(atexit仍会执行),避免因资源泄漏导致程序崩溃。 - 如果加
0x78会直接跳到0x114C,会跳过atexit注册,可能导致线程资源未释放,但对绕过反调试来说也可行,只是不够 “干净”。
重新回到main函数进行分析

回到main,发现要求他会检查输入的Flag,开头四个字节得是"ACE_"开头
然后一段一段分析

sub_7FF7528520D0

这个函数是自定义的 Base58 编码实现(区别于 Base64,使用 58 个字符集的编码算法),核心作用是将输入的二进制数据(或字符串)转换为 Base58 编码格式,同时包含内存分配、边界检查、数据反转等辅助逻辑,是 Flag 校验流程中 “编码转换” 的关键环节
Base58 编码的本质是 “将二进制数据视为大整数,反复除以 58 取余,余数映射到 Base58 字符集”,代码中对应逻辑:
// 核心循环:逐字节处理输入数据,转换为 Base58 余数
do {
v24 = *v22; // 取当前输入字节
if ( v23 > 0 ) {
// 多字节拼接:(前值 << 8) + 当前字节 → 模拟大整数
v27 = ((unsigned __int8)*v25 << 8) + v24;
*v25 = v27 % 58; // 取余(Base58 核心:余数对应字符集索引)
v24 = v27 / 58; // 商继续参与计算
++v25;
--v26;
}
// 循环除以 58,直到商为 0
for ( ; v24 > 0; v24 /= 0x3Au ) { // 0x3A = 58
*((_BYTE *)v21 + v23) = v24 % 0x3Au; // 存储余数
++v3;
++v23;
}
++v22;
} while ( v22 != v12 );
| Base58 核心特征 | 代码中的对应实现 |
|---|---|
| 基数 58 | 多处出现 0x3A(58)、% 58、/ 58 |
| 大整数除法取余 | 循环拼接多字节为大整数,反复除以 58 取余 |
| 结果反转 | 调用 _std_reverse_trivially_swappable_1 反转字符序列 |
| 开头零补充 | v13 统计开头 0 字节,补充对应 Base58 零字符 |
| 字符集映射 | 从 qword_7FF75288AFD8 读取 Base58 字符集 |
核心功能:该函数是 Base58 编码实现,将输入的二进制 / 字符串数据(如用户输入的 Flag)转换为 Base58 编码格式,包含内存分配、边界检查、字符集映射、结果反转等完整逻辑;
关键特征:基数 58(0x3A)、大整数取余、结果反转、Base58 字符集映射是识别该函数的核心标识
qword_7FF75288AFD8,动态调试,查看qword_7FF75288AFD8内存
abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ1234567890!@+/
虽然得到了63个字符,但只能用到前58个,因为源函数一直是对58取余

摸清楚前一段Base58后,再往下看
Base58(v58, v50);
Src[0] = (void *)'\0';
v19 = '\0';
*(_QWORD *)&v49 = '\0';
v20 = '\x0F';
*((_QWORD *)&v49 + 1) = '\x0F';
v21 = '\0';
v22 = (void **)Block[0];
if ( v59 )
{
v23 = v55 >= 0x10;
do
{
v24 = v58;
if ( v60 >= 0x10 )
v24 = (__int64 *)v58[0];
v25 = Block;
if ( v23 )
v25 = v22;
v26 = *((_BYTE *)v24 + v21) ^ *((_BYTE *)v25 + v21 % v54);
if ( v19 >= v20 )
{
sub_7FF7D10C29A0(Src);
}
else
{
*(_QWORD *)&v49 = v19 + 1;
v27 = Src;
if ( v20 >= 0x10 )
v27 = (void **)Src[0];
*((_BYTE *)v27 + v19) = v26;
*((_BYTE *)v27 + v19 + 1) = 0;
}
++v21;
v20 = *((_QWORD *)&v49 + 1);
v19 = v49;
}
while ( v21 < v59 );
v6 = v42;
}
if ( v60 >= 0x10 )
{
v28 = (void *)v58[0];
if ( v60 + 1 >= 0x1000 )
{
v28 = *(void **)(v58[0] - 8);
if ( (unsigned __int64)(v58[0] - (_QWORD)v28 - 8) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
j_j_free(v28);
}
v56 = *(_OWORD *)Src;
v57 = v49;
if ( v52 >= 0x10 )
{
v29 = (void *)v50[0];
if ( v52 + 1 >= 0x1000 )
{
v29 = (void *)*((_QWORD *)v50[0] - 1);
if ( (unsigned __int64)(v50[0] - (const void *)v29 - 8) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
j_j_free(v29);
}
v51 = 0i64;
v52 = '\x0F';
LOBYTE(v50[0]) = 0;
if ( v55 >= 0x10 )
{
v30 = v22;
if ( v55 + 1 >= 0x1000 )
{
v22 = (void **)*(v22 - 1);
这段代码是 Base58 编码后的数据加解密 / 校验逻辑 —— 先对输入做 Base58 编码,再通过异或(XOR) 与预设的 Block 数据做逐字节混淆,最后进行内存清理和边界校验,是 Flag 校验的核心环节(Base58 编码后的二次验证)
1.初始化阶段(准备异或操作)
Base58(v58, v50); // 先对输入(Flag截取后的数据)做Base58编码,结果存在v58
Src[0] = (void *)'\0';
v19 = '\0';
*(_QWORD *)&v49 = '\0';
v20 = '\x0F'; // 缓冲区最大长度(15字节,0x0F=15)
*((_QWORD *)&v49 + 1) = '\x0F';
v21 = '\0'; // 异或循环的索引(从0开始遍历每个字节)
v22 = (void **)Block[0]; // Block是预设的异或密钥,v22指向密钥起始地址
关键:v20 = 0x0F 限定了异或处理的最大字节数为 15,超过则触发缓冲区扩容(sub_7FF7D10C29A0)。
2.核心:逐字节异或混淆(Flag 校验的关键)
if ( v59 ) // v59是Base58编码结果的长度,非0则执行异或循环
{
v23 = v55 >= 0x10; // v55是Block的长度,判断是否≥16字节(指针偏移兼容)
do
{
// 步骤1:定位Base58编码后的当前字节(兼容指针偏移)
v24 = v58;
if ( v60 >= 0x10 )
v24 = (__int64 *)v58[0]; // 长度≥16时,v58[0]是实际数据地址
// 步骤2:定位Block密钥的当前字节(兼容指针偏移)
v25 = Block;
if ( v23 )
v25 = v22; // Block长度≥16时,v22是实际密钥地址
// 步骤3:核心异或操作!Base58字节 XOR Block字节(取模避免密钥越界)
v26 = *((_BYTE *)v24 + v21) ^ *((_BYTE *)v25 + v21 % v54);
// 步骤4:将异或结果写入输出缓冲区Src(边界校验)
if ( v19 >= v20 ) // 超过15字节则扩容
sub_7FF7D10C29A0(Src);
else
{
*(_QWORD *)&v49 = v19 + 1; // 计数+1
v27 = Src;
if ( v20 >= 0x10 )
v27 = (void **)Src[0]; // 长度≥16时,Src[0]是实际输出地址
*((_BYTE *)v27 + v19) = v26; // 写入异或结果
*((_BYTE *)v27 + v19 + 1) = 0; // 末尾补0(字符串结束符)
}
++v21; // 索引+1,处理下一个字节
v20 = *((_QWORD *)&v49 + 1);
v19 = v49;
} while ( v21 < v59 ); // 遍历完Base58编码的所有字节
}
最核心逻辑:v26 = Base58字节 ^ Block字节
异或的特点是 “可逆”(A^B=C → C^B=A),程序会把异或后的结果 v26 与预设的 “正确结果” 对比,一致则 Flag 校验通过,否则失败。
取模操作 v21 % v54:如果 Base58 结果长度超过 Block 密钥长度,循环使用 Block 密钥(比如 Block 长度 5,第 6 个字节用 Block [0])。
3.内存释放阶段(避免泄漏 + 防溢出)
// 释放v58的临时内存(Base58编码时分配的)
if ( v60 >= 0x10 )
{
v28 = (void *)v58[0];
if ( v60 + 1 >= 0x1000 ) // 大内存(≥4096字节)特殊释放逻辑
{
v28 = *(void **)(v58[0] - 8); // 取原始分配地址(对齐时记录的)
if ( (unsigned __int64)(v58[0] - (_QWORD)v28 - 8) > 0x1F )
invalid_parameter_noinfo_noreturn(); // 越界则终止程序
}
j_j_free(v28); // 释放内存(等价于free())
}
// 释放v50的临时内存(同理)
if ( v52 >= 0x10 )
{
v29 = (void *)v50[0];
// 同上:大内存特殊释放 + 越界校验
j_j_free(v29);
}
关键:程序对内存释放做了严格的边界校验(比如 >0x1F 则终止),是防止内存溢出攻击的保护逻辑。
4.收尾阶段(保存结果 + 重置变量)
v56 = *(_OWORD *)Src; // 保存异或后的结果(16字节)到v56,用于后续校验
v57 = v49; // 保存处理的字节数
v51 = 0i64;
v52 = '\x0F';
LOBYTE(v50[0]) = 0; // 重置缓冲区
v56 是后续 Flag 校验的 “关键对比值”—— 程序会把 v56 与硬编码的正确异或结果对比,一致则 Flag 正确。
找到 Block 密钥
Block是预设的异或密钥,定位Block[0]的地址(比如qword_7FF7D10Fxxxx),提取其内存数据(比如 15 字节的固定值);- 动态调试:在
v26 = A ^ B这行下断点,查看*((_BYTE *)v25 + v21 % v54)的值,记录完整的 Block 密钥。
简单来说:这段代码的核心是 Base58 编码 + 异或混淆 + 结果校验,破解的关键是提取 Block 密钥和正确的异或结果,再通过异或可逆性反向推导 Base58 原始值,最终还原 Flag。
下一段

memmove(v41, v31, v19);
if ( (*(unsigned int (__fastcall **)(_QWORD *, _QWORD, void *))(*v6 + 96i64))(v6, (unsigned int)v49, v41) )
v33 = "Flag not correct!";
else
v33 = "Flag is correct!";
v34 = sub_7FF7D10C4A60(v32, v33);
v4 = v47;
v10 = (char *)v45[0];
v3 = *((_QWORD *)&v57 + 1);
}
else
{
LABEL_54:
v34 = sub_7FF7D10C4A60(v11, "Flag not correct!");
v5 = v41;
}
sub_7FF7D10C4CF0(v34);
(*(void (__fastcall **)(_QWORD *))(*v6 + 80i64))(v6);
}
else
{
v8 = sub_7FF7D10C4A60(v7, "Driver loading failed, please check the environment.");
sub_7FF7D10C4CF0(v8);
v10 = (char *)v45[0];
}



96i64 = 十六进制 0x60
对应表中 +0x60 条目:ACEDriverSendCustomData
然后我们进入ACEDriverSendCustomData这个函数查看

Ring3通信发送到ACEDriver,命令码是0x154004。核心是 Flag 校验相关数据
目的:让内核驱动完成最终的 Flag 对比,防止用户态逆向 / 篡改
简单说:用户态程序只负责处理 Flag 格式(ACE_开头、Base58、异或),但最终 “对不对” 的判定权在驱动手里,这也是这类程序难破解的核心 —— 关键对比逻辑在内核态,用户态逆向只能拿到 “处理流程”,拿不到 “正确结果”。
自此,完成Ring3层的分析
Ring0
驱动无法加载

把sys拖进ida,发现有回调函数
- 混淆,把程序逻辑转换成更晦涩但等价的形式。
- 加花,对抗反汇编引擎,利用反汇编工具的算法缺陷、漏洞来迫使分析者必须花费大量时间处理错误的反汇编结果,让诸如控制流视图之类的工具失效。

ConnectNotifyCallback
一、这段代码的核心特征:花指令 + 动态计算跳转地址
这是典型的内核态反逆向混淆代码(ConnectNotifyCallback 是驱动的端口连接回调函数),核心目的是隐藏真实的函数入口 / 逻辑,防止静态分析。
1. 花指令(Junk Code)特征
- 无意义指令:
not r9、xchg rax, r9这类指令只是打乱寄存器状态,对业务逻辑无任何作用。 - 数据插在代码中间:
dw 5AE9h、db 0E9h,41h,58h...是直接插入的垃圾数据,破坏反汇编器的指令解析,让 IDA 无法正确识别函数边界。 - 冗余操作:
pushfq+popfq会保存并恢复标志寄存器,本质是干扰单步调试(破坏 TF 标志)。
2. 动态跳转(Obfuscated Control Flow)特征
代码里有两处典型的“先算地址,再跳转”逻辑:
第一处(rdx 分支):
mov rdx, 1A11B4C41h
pushfq
add rdx, 0FFFFFFFF9EE552D6h ; 等价于 rdx -= 0x611AAAD2Ah
popfq
jmp rdx
静态无法直接看出跳转目标,必须计算:
0x1A11B4C41h + 0xFFFFFFFF9EE552D6h = 0x1A11B4C41h - 0x611AAAD2Ah = 0xE0009171h(实际地址需结合模块基址修正)
目的:让静态分析器无法直接解析跳转目标,必须动态计算或调试才能看到真实代码。
第二处(r8 分支):
mov r8, 11075C6FEh
pushfq
add r8, 2F8AD83Eh
popfq
jmp r8
计算后目标:0x11075C6FEh + 0x2F8AD83Eh = 0x140009F3Ch
这是跳转到下一段真实逻辑的入口,中间被花指令隔开。
二、ConnectNotifyCallback 的作用
在 Windows 驱动开发中,ConnectNotifyCallback 是 过滤通信端口(Filter Communication Port) 的连接回调函数:
- 当用户态程序调用
FilterConnectCommunicationPort尝试连接驱动时,内核会触发这个回调; - 它的核心职责是:
- 验证连接请求的合法性(比如检查进程名 / 权限,防止恶意程序连接驱动);
- 分配连接上下文;
- 返回是否允许连接(
STATUS_SUCCESS或拒绝状态)。
这个回调是 用户态 EXE 连接 ACE 驱动的 “第一道门”,只有通过它的验证,用户态才能和驱动建立通信,后续才能发送 Flag 校验数据。
代码里的混淆 / 花指令,就是为了防止逆向者篡改验证逻辑(比如让任意程序都能连接驱动)。
解决花指令方法:
步骤 1:按 u(Undefine,取消定义)
-
操作:光标定位到
41E9h对应的行,按下快捷键u; -
作用:
- 把 IDA 误识别成 “指令” 的
41E9h还原成原始字节数据(不再解析为指令); - 解除错误的指令绑定,为后续修正做准备。
- 把 IDA 误识别成 “指令” 的
步骤 2:跳过 E9 字节(定位到正确指令起始位)
-
操作:
- 查看
41E9h的字节布局:实际是两个字节0xE9(第一个字节) +0x41(第二个字节); - 把光标移动到
0xE9的下一个字节(即0x41之后的位置);
- 查看
-
原理:
0xE9是花指令插入的垃圾字节,必须跳过它,才能找到真实指令的起始位置。把鼠标光标放在5A之后:
步骤 3:按 c(Code,重新定义为代码)
- 操作:光标停在跳过
0xE9后的正确位置,按下快捷键c; - 作用:
- IDA 会从这个位置重新开始反汇编,解析出正确的指令流;
- 原本被花指令打乱的
jmp r10/lea r10, [...]等核心指令,会被正确识别; - 花指令被 “剥离”,你能看到
ConnectNotifyCallback的真实逻辑
观察花指令
MessageNotifyCallback
然后截图处是一块,将地址计算完进行jmp,计算出来实际就是jmp到下面pop处,说明这一段是无用的,可以直接将push到pop全部nop即可。其他地方都和这地方混淆差不多类型,都直接跳过花指令后nop即可。
push r10
lea r10, cs:172AA3EECh
lea r10, [r10-32A99F2Dh]
jmp r10
计算目标地址:0x172AA3EECh−0x32A99F2Dh=0x140009FBFh而 0x140009FBFh 正好是下方 pop r10 指令的地址,
所以:jmp r10 本质就是 跳转到 pop r10,整个 push → lea → jmp 流程完全是冗余的。
批量 NOP 时,在 IDA 中选中这段地址范围,右键 → Edit → Patch program → Assemble,输入 nop 即可批量替换
计算目标地址:
0x1A1D0F0C9−0x61D050E3=0x140009FE6
而 0x140009FE6 正好是下方 mov edi, 3FFFFFF2h 指令的起始地址,所以这段代码本质是:
绕了一大圈,最后直接跳转到下一行正常指令。
为什么可以 NOP?
- 无业务逻辑:这段代码没有读写任何全局变量、没有调用内核函数,只是做了一次无意义的地址计算和跳转,对程序功能完全没有影响。
- 栈平衡:
pushfq和popfq成对出现,即使把中间所有指令(从mov r11到jmp r11)全部 NOP 掉,栈指针也不会失衡,程序能正常执行。 - 纯混淆目的:和你之前清理的其他花指令一样,这段代码的唯一作用就是干扰 IDA/WinDbg 的静态分析,让控制流变得混乱。
具体操作
- NOP 范围:从
0x140009FCF(mov r11, ...)到0x140009FE2(jmp r11)的所有指令,包括中间的pushfq/popfq。 - 清理后效果:IDA 会直接显示下方的
mov edi, 3FFFFFF2h,代码变得干净清晰,没有任何冗余混淆。
总结
db 0E8h,41h,5Bh 是插入的垃圾数据字节,而非 “IDA 解析错误”,所以:
- ❌
u+c只能改 IDA 的解析逻辑,改不了原始字节,运行时仍会出问题; - ✅ 直接
NOP替换成空指令,既让 IDA 正确解析后续代码,又让 CPU 运行时跳过这些垃圾字节,是最彻底的处理方式。
下一段
计算目标地址:
0x17DFA87AD−0x3DF99E744=0x17DFA87AD+0xFFFFFFFFFC206185C=0x14000A006
jmp rax 最终跳转到 0x14000A006,也就是下方 xchg r11, rcx 指令的起始位置。
✅ 结论:可以安全 NOP 掉
无业务逻辑:这段代码仅用于动态计算跳转,没有读写全局变量、没有调用内核函数,对程序功能无任何影响。
栈平衡:pushfq 和 popfq 成对出现,NOP 掉后栈指针(RSP)不会失衡,程序能正常运行。
纯混淆目的:和之前清理的花指令一样,这段代码的唯一作用是干扰 IDA/WinDbg 的静态分析,让控制流变得混乱。
处理建议
- NOP 跳转块:从
0x140009FF3(mov rax, ...)到0x14000A006(jmp rax)全部 NOP 掉。 - NOP 垃圾数据:从
0x14000A008到0x14000A047的dq ...垃圾数据全部 NOP 掉。
下一段
计算目标地址:
0x1722CBF2F−0x322C240CD=0x1722CBF2F+0xFFFFFFFFCDD3E133=0x14000A062
jmp rdx 最终跳转到 0x14000A062,也就是下方 add al, 17h 指令的起始位置。
✅ 结论:可以安全 NOP 掉,原因如下:
- 无业务逻辑:这段代码仅用于动态计算跳转,没有读写全局变量、没有调用内核函数,对程序功能无任何影响。
- 栈平衡:
pushfq和popfq成对出现,NOP 掉后栈指针(RSP)不会失衡,程序能正常运行。 - 纯混淆目的:和之前清理的花指令一样,这段代码的唯一作用是干扰 IDA/WinDbg 的静态分析,让控制流变得混乱。
额外注意:中间的垃圾数据
下方 0x14000A061 开始的 db 0E8h,5Ah,... 是插入的垃圾数据,和之前的 db 0E8h,41h,5Bh 一样:
- 这些字节会破坏指令流解析,必须和前面的跳转花指令一起批量 NOP 掉;
- 否则 IDA 会把这些垃圾数据误解析为指令,导致后续
add al, 17h等代码解析错位。
处理建议
- NOP 跳转块:从
0x14000A04C(mov rdx, ...)到0x14000A05F(jmp rdx)全部 NOP 掉。 - NOP 垃圾数据:从
0x14000A061到0x14000A067的db ...垃圾数据全部 NOP 掉。 - 还原代码:处理后 IDA 会正确识别下方的
add al, 17h等真实指令。
下一段
计算目标地址:
0x12278A45E+0x1D87FC28=0x14000A086
jmp r8 最终跳转到 0x14000A086,也就是下方 jnb loc_14000A2E8 指令的起始位置。
这段代码是否可以 NOP 掉?
✅ 结论:可以安全 NOP 掉,原因如下:
- 无业务逻辑:这段代码仅用于动态计算跳转,没有读写全局变量、没有调用内核函数,对程序功能无任何影响。
- 栈平衡:
pushfq和popfq成对出现,NOP 掉后栈指针(RSP)不会失衡,程序能正常运行。 - 纯混淆目的:和之前清理的花指令一样,这段代码的唯一作用是干扰 IDA/WinDbg 的静态分析,让控制流变得混乱。
额外注意:中间的垃圾数据
下方 0x14000A085 开始的 db 0E9h,41h,58h 是插入的垃圾数据,和之前的 db 0E8h,41h,5Bh 一样:
- 这些字节会破坏指令流解析,必须和前面的跳转花指令一起批量 NOP 掉;
- 否则 IDA 会把这些垃圾数据误解析为指令,导致后续
jnb loc_14000A2E8等代码解析错位。
处理建议
- NOP 跳转块:从
0x14000A06F(mov r8, ...)到0x14000A082(jmp r8)全部 NOP 掉。 - NOP 垃圾数据:从
0x14000A085到0x14000A087的db ...垃圾数据全部 NOP 掉。 - 还原代码:处理后 IDA 会正确识别下方的
jnb loc_14000A2E8、jmp loc_14000A094等真实指令。
下一段
计算目标地址:
0x189E67159−0x49E85D05=0x189E67159+0xFFFFFFFFFB61A2F5B=0x14000A0B4
jmp r9 最终跳转到 0x14000A0B4,也就是下方 C8 0F 83 ... 指令的起始位置。
处理建议
- NOP 跳转块:从
0x14000A09D(mov r9, ...)到0x14000A0B0(jmp r9)全部 NOP 掉。 - NOP 垃圾数据:从
0x14000A0B3到0x14000A11F的db ...和dq ...垃圾数据全部 NOP 掉。 - 还原代码:处理后 IDA 会正确识别下方的真实指令流。
下一段
计算目标地址:
0x109122C9D+0x36EE74B1=0x14000A14E
jmp rcx 最终跳转到 0x14000A14E,也就是下方 test eax, eax 指令的起始位置
下方 0x14000A14D 开始的 db 0E8h,59h,48h 是插入的垃圾数据:
下一段
计算目标地址:
0xC85BAA45+0x77A4F73F=0x14000A184
jmp rdx 最终跳转到 0x14000A184,也就是下方 not rcx 指令的起始位置。
下方 0x14000A183 开始的 db 0E9h,5Ah,48h,87h,0CEh 是插入的垃圾数据
下一段
lea r8, cs:173BC73E1h
lea r8, [r8-33BBD233h]
jmp r8
计算目标地址:
0x173BC73E1−0x33BBD233=0x14000A1AE
jmp r8 最终跳转到 0x14000A1AE,也就是下方 xchg rdx, rsi 指令的起始位置。
处理
NOP 跳转块:从 0x14000A19A(push r8)到 0x14000A1AA(jmp r8)全部 NOP 掉(前面的 mov r8d, r15d 和 not rsi 是真实业务逻辑,不要 NOP)。
NOP 垃圾数据:从 0x14000A1AD 到 0x14000A1AF 的 db ... 垃圾数据全部 NOP 掉
下一段
lea r9, cs:16207C72Ch
lea r9, [r9-22072556h]
jmp r9
计算目标地址:
0x16207C72C−0x22072556=0x14000A1D6
jmp r9 最终跳转到 0x14000A1D6,也就是下方 mov rbx, rcx 指令的起始位置。
下方 0x14000A1D5 开始的 db 0E8h,41h,59h 是插入的垃圾数据
处理建议
- NOP 跳转块:从
0x14000A1C2(push r9)到0x14000A1D2(jmp r9)全部 NOP 掉。 - NOP 垃圾数据:从
0x14000A1D5到0x14000A1D7的db ...垃圾数据全部 NOP 掉。
下一段
mov r10, 11A17ED71h
pushfq
add r10, 25E8B49Eh
popfq
jmp r10
计算目标地址:
0x11A17ED71+0x25E8B49E=0x14000A20F
jmp r10 最终跳转到 0x14000A20F,也就是下方 pop rdx 指令的起始位置。
下方 0x14000A20E 开始的 dw 41E8h(即 db 0E8h, 0x41h)是插入的垃圾数据
处理建议
- NOP 跳转块:从
0x14000A1F6(push r10)到0x14000A20B(jmp r10)全部 NOP 掉。 - NOP 垃圾数据:从
0x14000A20E到0x14000A20F的dw ...垃圾数据全部 NOP 掉。
下一段
push r11
lea r11, cs:byte_14000A23B ; 加载垃圾数据标签地址
lea r11, [r11+1] ; 地址 +1,计算目标
jmp r11 ; 跳转
byte_14000A23B 的地址是 0x14000A23B,[r11+1] 相当于直接跳转到了 0x14000A23C
0x14000A23B 处的 db 0E9h, 41h, 5Bh, 4Dh, 87h 是典型的伪指令垃圾
0xE9 是 jmp 指令的操作码。如果不 NOP,IDA 会把 0xE9 误识别为一个跳转指令,把后面的 4 个字节当作偏移。
处理
NOP 范围
- 第一部分:
0x14000A228~0x14000A238(push r11到jmp r11)。 - 第二部分:
0x14000A23B~0x14000A23F(byte_14000A23B及其后面的垃圾字节,直到0xA250之前)。
下一段
计算目标地址:
0x11D28C2E9+0x22D7DF7E=0x14000A267
jmp rax 最终跳转到 0x14000A267,也就是下方 not rcx 指令的起始位置。
下方 0x14000A266 开始的 dw 58E9h(即 db 0xE9h, 0x58h)是插入的垃圾数据
处理建议
- NOP 跳转块:从
0x14000A250(push rax)到0x14000A264(jmp rax)全部 NOP 掉。 - NOP 垃圾数据:从
0x14000A266到0x14000A267的dw ...垃圾数据全部 NOP 掉。
下一段
典型的两次 lea 计算地址 + 跳转花指令
计算目标地址:
0x186D3B244−0x46D30FB0=0x14000A294
jmp rcx 最终跳转到 0x14000A294,也就是下方 and al, 58h 指令的起始位置。
下方 0x14000A293 开始的 db 0E8h,59h,48h,8Bh,5Ch 是插入的垃圾数据
处理建议
- NOP 跳转块:从
0x14000A282(push rcx)到0x14000A291(jmp rcx)全部 NOP 掉(前面的not edi是真实业务逻辑,不要 NOP)。 - NOP 垃圾数据:从
0x14000A293到0x14000A297的db ...垃圾数据全部 NOP 掉。
下一段
计算目标地址:
0x176E74261−0x36E69FA5=0x14000A2BC
jmp rdx 最终跳转到 0x14000A2BC,也就是下方 not rcx 指令的起始位置。
下方 0x14000A2BB 开始的 db 0E8h,5Ah,48h,87h,0CBh 是插入的垃圾数据
处理建议
- NOP 跳转块:从
0x14000A2AA(push rdx)到0x14000A2B9(jmp rdx)全部 NOP 掉。 - NOP 垃圾数据:从
0x14000A2BB到0x14000A2BF的db ...垃圾数据全部 NOP 掉。
下一段
这段是典型的两次 lea 计算地址 + 跳转花指令
计算目标地址:
0x0CD5F4469+0x72A15E77=0x14000A2E0
jmp r8 最终跳转到 0x14000A2E0,也就是下方 pop r8 指令的起始位置。
下方 0x14000A2DF 开始的 db 0E9h 是插入的垃圾数据
处理建议
- NOP 跳转块:从
0x14000A2CC(push r8)到0x14000A2DC(jmp r8)全部 NOP 掉。 - NOP 垃圾数据:从
0x14000A2DF到0x14000A2DF的db ...垃圾数据全部 NOP 掉。
下一段
计算目标地址:
0x0E7550831+0x58AB9AD2=0x14000A303
jmp r9 最终跳转到 0x14000A303,也就是下方 and al, 60h 指令的起始位置。
下方 0x14000A302 开始的 dw 41E9h, 4859h, 748Bh(即 db 0xE9h, 0x41h, 0x59h, 0x48h, 0x8Bh, 0x74h)是插入的垃圾数据
处理建议
- NOP 跳转块:从
0x14000A2EF(push r9)到0x14000A2FF(jmp r9)全部 NOP 掉。 - NOP 垃圾数据:从
0x14000A302到0x14000A307的dw ...垃圾数据全部 NOP 掉
下一段
计算目标地址:
0x14000A320+0xB=0x14000A32B
jmp r10 最终跳转到 0x14000A32B,也就是下方 and al, 8 指令的起始位置。
下方 0x14000A32A 开始的 dw 41E8h, 485Ah, 648Dh(即 db 0xE8h, 0x41h, 0x5Ah, 0x48h, 0x8Dh, 0x64h)是插入的垃圾数据
处理建议
- NOP 跳转块:从
0x14000A317(push r10)到0x14000A327(jmp r10)全部 NOP 掉。 - NOP 垃圾数据:从
0x14000A32A到0x14000A32F的dw ...垃圾数据全部 NOP 掉。
下一段
下一段
处理建议
- NOP 跳转块:从
0x14000A36E(push rax)到0x14000A382(jmp rax)全部 NOP 掉。 - NOP 垃圾数据:从
0x14000A384到0x14000A387的dd ...垃圾数据全部 NOP 掉。
什么时候用 u+c?什么时候用 NOP?
1. 用 u+c 的场景(仅修正解析,不改字节)
- 例子:IDA 把正常的
jmp r10误解析成db 0FFh, 0E2h; - 核心:字节本身是合法指令,只是 IDA 解析边界错了,
u+c让 IDA 重新识别即可,无需改字节。
2. 用 NOP 的场景(替换垃圾字节,消除影响)
- 例子:插入的
db 0E8h,41h,5Bh、dw 41E9h等垃圾数据; - 核心:字节本身是非法 / 无意义的,会破坏指令流或导致运行崩溃,必须替换成 NOP 空指令。
| 操作方式 | 本质作用 | 适用场景 |
|---|---|---|
u+c |
修正 IDA 的指令解析边界,让 IDA 重新识别指令(仅改 “解析逻辑”,不改字节) | 花指令是 “指令被误解析成数据” 或 “数据被误解析成指令”,但字节本身不影响运行 |
直接 NOP |
替换原始字节为 0x90(空指令),彻底消除垃圾字节的影响(改 “原始字节”) |
花指令是 “插入的垃圾数据字节”,会破坏指令流 / 导致运行崩溃 |
浙公网安备 33010602011771号