Native安全之破解与补丁
Native程序
Native程序指能被计算机系统直接加载并由CPU执行的程序。其文件格式常见有Windows的PE(如.exe, .dll)、Linux的ELF、macOS的Mach-O。这些文件包含特定处理器指令集(如x86, amd64, arm, arm64)的机器码, 由CPU直接执行这些指令。C/C++、Go、Rust等语言编译后通常生成此类程序;部分虚拟机语言(如特定Java/.NET实现)通过AOT编译也能生成Native程序。
许多人因机器码人眼难以阅读而认为Native程序很安全,但这一观点存在局限性。掌握汇编语言的技术人员配合反汇编器、调试器等工具,可将机器码转换为可分析的汇编代码,从而理解程序行为并定位关键逻辑点。这将带来实际安全风险:
-
绕过功能限制:修改许可证或注册状态验证代码,实现未授权使用。
-
窃取核心逻辑:通过分析汇编代码推导关键算法实现细节。
-
植入恶意代码:覆盖原始机器码指令,注入病毒或后门功能。
-
威胁知识产权:程序核心功能与设计经逆向分析暴露,增加侵权风险。
破解
实例程序代码
CPP
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
string keygen(const string& userName)
{
string key;
uint8_t xorValue = userName.at(0);
for_each(begin(userName) + 1, end(userName), [&xorValue](char ch) {
xorValue ^= ch;
});
for (int i = userName.size() - 1; i >= 0; --i)
{
char uch = userName.at(i);
int idx = ((uch - i ^ xorValue) ^ xorValue) % 26;
key.insert(key.begin(), (char)('a' + idx));
}
return key;
}
void print_cat()
{
cout << " /\\_/\\ " << endl;
cout << " ( o.o ) " << endl;
cout << " > ^ < " << endl;
cout << " You got a kitten!" << endl;
}
bool verifyAccount(const string& userName, const string& key)
{
if (userName.size() != key.size())
return false;
string key;
uint8_t xorValue = userName.at(0);
for_each(begin(userName) + 1, end(userName), [&xorValue](char ch) {
xorValue ^= ch;
});
for (int i = userName.size() - 1; i >= 0; --i)
{
char uCh = userName.at(i);
char keyCh = key.at(i);
int idx = ((uCh - i ^ xorValue) ^ xorValue) % 26;
if ('a' + idx != keyCh)
return false;
}
return true;
}
int main()
{
string userName, key;
cout << "Enter username: ";
cin >> userName;
cout << "Enter key: ";
cin >> key;
if (verifyAccount(userName, key))
{
cout << "Verification Successful" << endl;
print_cat();
}
else
{
cout << "Verification Failed" << endl;
}
cin.ignore();
getchar();
return 0;
}
这里提供一个正确的UserName和Key: plmqazvbnm - iddgpninyw
成功的情况:
Enter username: plmqazvbnm
Enter key: iddgpninyw
Verification Successful
/\_/\
( o.o )
> ^ <
You got a kitten!
失败的情况:
Enter username: plmqazvbnm
Enter key: 0
Verification Failed
反汇编
使用反汇编工具反编译编译后的程序,结果如下:
CPP
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v3; // r8
void **v4; // r8
unsigned __int64 v5; // r15
char *v6; // rbx
__int32 v7; // r10d
unsigned __int64 v8; // r9
__int128 *v9; // rax
__int64 v10; // rax
__int64 v11; // r8
__int64 v12; // rax
__int64 v13; // r8
__int64 v14; // rax
__int64 v15; // r8
__int64 v16; // rax
const char *v17; // rdx
__int64 v18; // rax
char *v19; // rax
void *v20; // rcx
__int128 v22; // [rsp+50h] [rbp-78h] BYREF
__m128i si128; // [rsp+60h] [rbp-68h]
void *Block[2]; // [rsp+70h] [rbp-58h] BYREF
__m128i v25; // [rsp+80h] [rbp-48h]
v22 = 0LL;
si128 = _mm_load_si128((const __m128i *)&xmmword_140021720);
LOBYTE(v22) = 0;
*(_OWORD *)Block = 0LL;
v25 = si128;
LOBYTE(Block[0]) = 0;
sub_140001470(&qword_140033540, "Enter username: ", envp);
sub_140001220(&qword_1400334A0, &v22);
sub_140001470(&qword_140033540, "Enter password: ", v3);
sub_140001220(&qword_1400334A0, Block);
v5 = v25.m128i_u64[1];
v6 = (char *)Block[0];
if ( si128.m128i_i64[0] == v25.m128i_i64[0] )
{
if ( !si128.m128i_i64[0] )
goto LABEL_25;
v7 = si128.m128i_i32[0] - 1;
v8 = si128.m128i_i32[0] - 1;
if ( si128.m128i_i32[0] - 1 >= 0 )
{
while ( si128.m128i_i64[0] > v8 )
{
v9 = &v22;
if ( si128.m128i_i64[1] > 0xFuLL )
v9 = (__int128 *)v22;
if ( v25.m128i_i64[0] <= v8 )
break;
v4 = Block;
if ( v25.m128i_i64[1] > 0xFuLL )
v4 = (void **)Block[0];
if ( (*((char *)v9 + v8) - v7) % 26 + 97 != *((char *)v4 + v8) )
goto LABEL_13;
--v7;
if ( (--v8 & 0x8000000000000000uLL) != 0LL )
goto LABEL_12;
}
LABEL_25:
unknown_libname_4();
}
LABEL_12:
v10 = sub_140001470(&qword_140033540, "Verification Successful", v4);
sub_140001880(v10);
v12 = sub_140001470(&qword_140033540, " /\\_/\\ ", v11);
sub_140001880(v12);
v14 = sub_140001470(&qword_140033540, " ( o.o ) ", v13);
sub_140001880(v14);
v16 = sub_140001470(&qword_140033540, " > ^ < ", v15);
sub_140001880(v16);
v17 = " You got a kitten!";
}
else
{
LABEL_13:
v17 = "Verification Failed";
}
v18 = sub_140001470(&qword_140033540, v17, v4);
sub_140001880(v18);
sub_140002DD0(&qword_1400334A0, 1LL, 0xFFFFFFFFLL);
fgetchar();
if ( v5 > 0xF )
{
v19 = v6;
if ( v5 + 1 >= 0x1000 )
{
v6 = (char *)*((_QWORD *)v6 - 1);
if ( (unsigned __int64)(v19 - v6 - 8) > 0x1F )
invoke_watson(0LL, 0LL, 0LL, 0, 0LL);
}
j_j_j__free_base(v6);
}
if ( si128.m128i_i64[1] > 0xFuLL )
{
v20 = (void *)v22;
if ( (unsigned __int64)(si128.m128i_i64[1] + 1) >= 0x1000 )
{
v20 = *(void **)(v22 - 8);
if ( (unsigned __int64)(v22 - (_QWORD)v20 - 8) > 0x1F )
invoke_watson(0LL, 0LL, 0LL, 0, 0LL);
}
j_j_j__free_base(v20);
}
return 0;
}
修改代码逻辑-动态补丁
反汇编代码和源代码确实差别很大,但是代码执行逻辑还是能看出来的,成功跳转LABEL_12
,不成功跳转LABEL_13
,所以只要在验证逻辑无条件跳转到LABEL_12
就破解成功了。找到对应的汇编代码, 将jnz LABEL_13
修改成jmp LABEL_12
。
程序补丁
根据前文分析,仅需修改验证流程中的单个关键指令(如条件跳转指令),即可绕过验证逻辑实现暴力破解,无需逆向实际算法。这种补丁技术分为两类核心实现方式:
动态补丁(内存补丁)
-
操作时机:在目标程序运行时实施
-
技术原理:通过调试器(如x64dbg/OllyDbg)定位内存中的指令地址
-
典型操作:将JNZ(非零跳转)改为JMP(无条件跳转)或NOP(空操作)
-
优势:无需修改原始文件,规避文件校验
-
局限:补丁效果仅持续到进程结束
静态补丁(文件补丁)
-
操作对象:直接修改磁盘上的可执行文件(如PE/ELF)
-
技术流程:
-
使用反汇编器(IDA Pro/Ghidra)定位目标函数偏移
-
计算新指令的机器码(如90对应NOP)
-
用十六进制编辑器(HxD/010 Editor)覆盖原指令
-
-
持久性:补丁永久生效
-
风险:可能破坏文件签名
安全影响:此类补丁技术可完全绕过许可证验证,使收费功能未授权可用,同时暴露核心控制流弱点。开发者需结合代码混淆、反调试校验等技术强化防护。
Native代码安全
这里推荐一款专注于 Native 代码安全的防护工具:Virbox Protector。该工具通过代码混淆和虚拟化技术,有效隐藏程序核心逻辑和执行路径。混淆技术使代码结构复杂化,增加逆向分析的难度;虚拟化技术将关键指令转化为私有虚拟指令集,防止直接解析代码逻辑。同时,结合实时内存校验机制,能够阻止静态补丁篡改;调试器检测功能也在一定程度上防止逆向者进行动态分析,从而最大程度地保护软件代码安全。