天野29期软件逆向破解实战

118 阅读5分钟

获课地址:666it.top/14016/
时间限制是软件保护中常见的手段,包括试用期、软件订阅和定时自毁等。逆向新手在面对时间校验时,常试图简单修改系统时钟或暴力跳转,却往往触发暗桩或导致程序功能异常。本文将系统分析时间校验的多种形式,并提供从静态到动态的完整突破思路。

常见误区:粗暴修改系统时间与跳转指令

  1. 修改系统时间:  在运行软件前将系统时间调整到试用期内。这种方法对于联网校验或具有时间连续性检查的软件完全无效,且用户体验极差。

  2. 暴力NOP或JMP:  在反汇编中找到GetLocalTime等API调用后的比较跳转指令,直接将其改为无条件跳转(JMP)或空指令(NOP)。这种方法极易掉入“陷阱”:

    • 多时间点校验:  程序在启动、保存、关键功能等多个地方进行时间检查,修补一处,别处仍会触发。
    • 完整性校验:  程序会检查自身代码段是否被修改,导致触发保护。
    • 加密密钥依赖:  程序的解密密钥可能来源于时间校验的结果,粗暴跳过会导致后续数据解密失败,程序崩溃。

保护机制突破思路:理解逻辑与精准打击

突破时间校验的核心在于“欺骗”,而非“破坏”。我们需要提供一个符合程序期望的、可控的“假时间”。

  1. API Hook(动态):  创建一个DLL,在目标进程加载时注入其中。然后钩取(Hook)关键的时间获取API(如GetLocalTimeGetSystemTimeAsFileTime),让这些API返回我们指定的、合法的日期。
  2. 内存修补(动态/静态):  找到程序中存储首次运行时间、到期时间或当前时间计算结果的全局变量或内存地址,在运行时使用调试器或内存修改工具将其修改为合法值。
  3. 逻辑分析(静态):  彻底逆向时间校验算法,理解其校验逻辑。例如,它可能计算当前时间与一个固定基准时间的差值。我们可以通过修改基准时间(例如,注册表中存储的安装日期)来“延长”试用期。

代码实战:多种时间校验的对抗

代码1:简单的试用期检查

c

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

// 模拟从注册表或文件读取安装日期
FILETIME get_install_time() {
    SYSTEMTIME st = {2023, 10, 3, 1, 12, 0, 0, 0}; // 安装时间:2023-10-1
    FILETIME ft;
    SystemTimeToFileTime(&st, &ft);
    return ft;
}

BOOL is_trial_expired() {
    FILETIME install_ft = get_install_time();
    FILETIME now_ft;
    GetSystemTimeAsFileTime(&now_ft); // 获取当前系统时间

    // 将FILETIME转换为64位整数进行计算
    ULARGE_INTEGER install, now;
    install.LowPart = install_ft.dwLowDateTime;
    install.HighPart = install_ft.dwHighDateTime;
    now.LowPart = now_ft.dwLowDateTime;
    now.HighPart = now_ft.dwHighDateTime;

    // 试用期30天 (100-nanosecond intervals)
    const ULONGLONG trial_period = 30ULL * 24 * 60 * 60 * 10000000;

    if (now.QuadPart - install.QuadPart > trial_period) {
        printf("错误:软件试用期已过。\n");
        return TRUE; // 已过期
    }
    printf("剩余试用期:%llu 天。\n", (trial_period - (now.QuadPart - install.QuadPart)) / (10000000ULL * 60 * 60 * 24));
    return FALSE; // 未过期
}

int main() {
    if (is_trial_expired()) {
        return -1;
    }
    // 程序主逻辑
    printf("软件正常运行...\n");
    return 0;
}

对抗方法:

  • 内存修补:  调试器在GetSystemTimeAsFileTime返回后,找到存储now_ft的内存地址,将其修改为一个与安装时间相差小于30天的值。
  • API Hook:  编写DLL Hook GetSystemTimeAsFileTime,使其始终返回一个固定的、合法的假时间。

代码2:依赖时间结果的解密(暗桩)

c

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

// 一个简单的异或加密函数,密钥来源于时间
void decrypt_data(char* data, int len, DWORD key) {
    for (int i = 0; i < len; i++) {
        data[i] ^= (key + i) & 0xFF;
    }
}

void critical_function() {
    // 一段被加密的关键代码或字符串
    char encrypted_msg[] = {0x95, 0xA3, 0xB4, 0x87, 0x92, 0x83, 0x8A, 0x8C, 0x8F, 0x00}; // 加密的"HelloWorld"

    DWORD time_seed = GetTickCount(); // 使用系统运行时间作为解密密钥的一部分
    decrypt_data(encrypted_msg, strlen(encrypted_msg), time_seed);

    // 如果解密密钥不对,解密出的字符串是乱码,后续比较会失败
    if (strcmp(encrypted_msg, "HelloWorld") == 0) {
        printf("关键功能执行成功: %s\n", encrypted_msg);
    } else {
        printf("程序状态异常,即将退出!\n"); // 暗桩触发!
        exit(1);
    }
}

int main() {
    if (is_trial_expired()) {
        return -1;
    }
    // 如果粗暴跳过了时间校验,这里的time_seed会是“错误”的值,导致暗桩触发
    critical_function();
    return 0;
}

对抗方法:

  • 逻辑分析:  必须静态分析critical_function,发现其解密过程依赖于GetTickCount()。如果我们要跳过主时间校验,就必须保证GetTickCount()的返回值在我们的控制范围内,或者直接修补decrypt_data函数,使其不做任何操作(NOP掉循环),或者修改比较指令。

总结

对抗时间校验是一场“信息”的战争。盲目修改跳转是最低级且危险的方法。最高效的策略是动态欺骗时间API,提供稳定的假时间源。而对于复杂的、将时间作为算法因子的保护,则必须进行深入的静态分析,理解其整体逻辑,才能进行精准的、不影响程序稳定性的修补。