排行榜 统计
  • 文章总数:356 篇
  • 评论总数:1 条
  • 分类总数:9 个
  • 最后更新:昨天 14:39

Native 程序的安全攻防:破解技术与补丁策略​

本文阅读 6 分钟
首页 软件安全 正文

认识 Native 程序

Native 程序是一类能够被计算机系统直接加载并由 CPU 执行的程序,其文件格式因操作系统而异。在 Windows 系统中,常见的有 PE 格式(如.exe、.dll 文件);Linux 系统中则是 ELF 格式;而 macOS 系统采用 Mach-O 格式。这些文件包含特定处理器指令集(像 x86、amd64、arm、arm64)的机器码,CPU 可直接执行这些指令。

通常情况下,C/C++、Go、Rust 等语言编译后会生成 Native 程序;部分虚拟机语言(如特定的 Java/.NET 实现)通过 AOT 编译也能生成此类程序。

不少人觉得 Native 程序很安全,因为机器码难以被人眼直接阅读。但这种观点有一定局限性,掌握汇编语言的技术人员借助反汇编器、调试器等工具,能将机器码转换为可分析的汇编代码,进而理解程序行为、定位关键逻辑点,这就带来了诸多安全风险:

  • 绕过功能限制:修改许可证或注册状态验证代码,实现对程序的未授权使用。
  • 窃取核心逻辑:通过分析汇编代码,推导出程序关键算法的实现细节。
  • 植入恶意代码:覆盖原始机器码指令,向程序中注入病毒或后门功能。
  • 威胁知识产权:程序的核心功能与设计经逆向分析后被暴露,增加了被侵权的风险。

破解实例解析

实例程序代码

以下是一个用于演示的实例程序代码:

#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

反汇编过程

使用反汇编工具对编译后的程序进行反编译,得到如下结果:

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。

修改后的执行结果如下:

r15:
Enter username: plmqazvbnm
Enter key:0
Verification Successful
Λ∧
(o.0)
You got a kitten!

程序补丁技术

根据前文分析,只需修改验证流程中的单个关键指令(如条件跳转指令),就能绕过验证逻辑实现暴力破解,且无需逆向实际算法。这种补丁技术主要有两类核心实现方式:

动态补丁(内存补丁)

  • 操作时机:在目标程序运行过程中实施。
  • 技术原理:借助调试器(如 x64dbg/OllyDbg)定位内存中的指令地址。
  • 典型操作:将 JNZ(非零跳转)改为 JMP(无条件跳转)或 NOP(空操作)。
  • 优势:不需要修改原始文件,可规避文件校验。
  • 局限:补丁效果仅能持续到进程结束。

静态补丁(文件补丁)

  • 操作对象:直接对磁盘上的可执行文件(如 PE/ELF)进行修改。
  • 技术流程:
  1. 使用反汇编器(IDA Pro/Ghidra)定位目标函数偏移。
  2. 计算新指令的机器码(如 90 对应 NOP)。
  3. 利用十六进制编辑器(HxD/010 Editor)覆盖原指令。
  • 持久性:补丁能够永久生效。
  • 风险:可能会破坏文件签名。

这类补丁技术会带来一定的安全影响,它可完全绕过许可证验证,使得收费功能被未授权使用,同时也暴露了程序核心控制流的弱点。因此,开发者需要结合代码混淆、反调试校验等技术来强化软件防护。

Native 代码安全防护

这里推荐一款专注于 Native 代码安全的防护工具 ——Virbox Protector。该工具通过代码混淆和虚拟化技术,能有效隐藏程序的核心逻辑和执行路径。其中,混淆技术可使代码结构变得复杂,增加逆向分析的难度;虚拟化技术则将关键指令转化为私有虚拟指令集,防止代码逻辑被直接解析。

同时,Virbox Protector 结合实时内存校验机制,能够阻止静态补丁篡改;其调试器检测功能也能在一定程度上防止逆向者进行动态分析,从而最大程度地保护软件代码安全。

本文来自投稿,不代表本站立场,如若转载,请注明出处:https://firsource.cn/soft/2347.html
百度网盘转存就有收益:开启轻松副业新路径
« 上一篇 07-23