Windows逆向学习之CrackMe
字数 1362 2025-08-24 16:48:07

Windows逆向工程之CrackMe分析教程

免责声明

本教程仅用于学习和研究目的,严禁用于商业用途和非法用途。所有分析内容均基于技术研究角度,不鼓励任何形式的软件破解行为。

第一部分:KeygenMe分析

1.1 基本信息

  • 程序类型:32位GUI程序
  • 编程语言:TASM/MASM
  • 加壳情况:无壳

1.2 程序功能

  • 需要输入Name和Serial
  • 未输入时提示"Give me more material hehe!!"
  • 输入错误时提示错误信息

1.3 逆向分析

1.3.1 关键API

  • GetDlgItemTextA:获取文本框输入内容
  • lstrlen:计算字符串长度
  • MessageBoxA:显示提示信息

1.3.2 注册算法流程

  1. 检查Name和Serial是否已输入
  2. 计算Name的算法结果:
    • 对Name的每个字符进行以下计算:
      • EBX = 字符的ASCII值
      • EBX = EBX * EBX (字符ASCII值的平方)
      • ESI += EBX
      • EBX = 字符的ASCII值
      • EBX = EBX >> 1 (字符ASCII值除以2)
      • EBX += 3
      • EBX = EBX * 字符的ASCII值
      • EBX -= 字符的ASCII值
      • ESI += EBX
      • ESI += ESI (ESI值乘以2)
  3. 将计算结果与输入的Serial前4字节比较

1.3.3 注册机实现

#include <iostream>
#include <sstream>
#include <iomanip>
#include <string>
#include <algorithm>
#include <cmath>
#include <Windows.h>

std::string generateSequentialName(int length, unsigned long long index);
DWORD name_To_Serialnum(std::string& name);
std::string serialnum_To_Serial(DWORD num);

int main() {
    // 生成Name和对应Serial的代码
    // ...
}

DWORD name_To_Serialnum(std::string& name) {
    DWORD num = 0;
    for (auto c : name) {
        num += c * c;
        int temp = c;
        temp /= 2;
        temp += 3;
        temp *= c;
        temp -= c;
        num += temp;
        num += num;
    }
    return num;
}

std::string serialnum_To_Serial(DWORD num) {
    // 将计算结果转换为可显示的Serial
    // ...
}

1.3.4 破解方法

修改0040133C处的跳转指令JNZ SHORT KeygenMe.00401353为NOP指令,绕过验证。

第二部分:crackme_0006分析

2.1 基本信息

  • 程序类型:32位GUI程序
  • 编程语言:TASM/MASM
  • 加壳情况:无壳

2.2 关键API

  • GetDlgItemTextA:获取文本框输入
  • GetVolumeInformationA:获取卷序列号信息
  • lstrcmpA:字符串比较

2.3 注册算法分析

2.3.1 卷序列号计算

  1. 获取C盘和D盘的卷序列号
  2. 计算步骤:
    • 将两个序列号相加
    • 结果左移5位(相当于乘以32)
    • 循环左移0x0D位
    • 计算两个序列号的平方和的平方根(勾股定理)

2.3.2 Name算法

Name-Step1算法

00401036  /$  55            PUSH EBP
00401037  |.  8BEC          MOV EBP,ESP
00401039  |.  8B75 08       MOV ESI,DWORD PTR SS:[EBP+8]  ; Name地址
0040103C  |.  FC            CLD                           ; 正向处理字符串
0040103D  |.  33D2          XOR EDX,EDX
0040103F  |.  B8 01000000   MOV EAX,1                     ; EAX初始为1
00401044  |>  0FB60E        /MOVZX ECX,BYTE PTR DS:[ESI]  ; 取字符
00401047  |.  46            |INC ESI                      ; 指针后移
00401048  |.  0BC9          |OR ECX,ECX                   ; 检查是否结束
0040104A  |.  74 06         |JE SHORT CrackMe_.00401052
0040104C  |.  F7E1          |MUL ECX                      ; EAX *= ECX
0040104E  |.  03C2          |ADD EAX,EDX                   ; 加上高位
00401050  |.^ EB F2         \JMP SHORT CrackMe_.00401044

Name-Step2算法

  1. 将Step1结果循环左移1位
  2. 与卷序列号计算结果进行OR运算
  3. 与0x0FFFFFFF进行AND运算

Name-Step3算法

  1. 使用字符串"071362de9f8ab45c"作为转换表
  2. 将Step2结果反复除以16取余数,用余数索引转换表
  3. 将结果除以4,直到结果为0

2.3.3 校验

比较生成的Serial与输入的Serial,使用lstrcmpA函数。

2.3.4 注册机实现

#include <iostream>
#include <string>
#include <Windows.h>
#include <cstdint>
#include <cmath>

// 获取卷序列号信息
DWORD GetVolumeInformationNumber() {
    // 实现获取C盘和D盘序列号并计算的代码
    // ...
}

// 循环左移
DWORD RotateLeft(DWORD value, BYTE count) {
    const BYTE num_bits = sizeof(value) * 8;
    count %= num_bits;
    return (value << count) | (value >> (num_bits - count));
}

// 计算平方和的平方根
DWORD CalculateHypotenuse(DWORD a, DWORD b) {
    double a_float = static_cast<double>(a);
    double b_float = static_cast<double>(b);
    double sum_of_squares = (a_float * a_float) + (b_float * b_float);
    return static_cast<DWORD>(std::round(std::sqrt(sum_of_squares)));
}

// Name-Step1计算
DWORD NameStep1(const std::string& name) {
    unsigned long long num = 1;
    unsigned long num_low32 = 0;
    unsigned long num_high32 = 0;
    for (auto& c : name) {
        num *= c;
        num_low32 = num & 0xFFFFFFFF;
        num_high32 = num >> 32;
        num_low32 += num_high32;
    }
    return num_low32;
}

// Name-Step2计算
DWORD NameStep2(DWORD num1, DWORD volume_information_number) {
    DWORD num2 = RotateLeft(num1, 0x1);
    num2 |= volume_information_number;
    num2 &= 0x0FFFFFFF;
    return num2;
}

// Name-Step3计算
std::string NameStep3(DWORD num2) {
    std::string check_chars("071362de9f8ab45c");
    std::string serial;
    int num = num2;

    while (true) {
        DWORD remainder = num % 0x10;
        serial.push_back(check_chars[remainder]);
        num /= 0x04;
        if (num == 0) {
            break;
        }
    }
    return serial;
}

2.3.5 破解方法

修改004012D5处的跳转指令JNZ SHORT CrackMe_.004012EF为NOP指令,绕过验证。

总结

本教程详细分析了两个CrackMe程序的逆向过程和算法实现,包括:

  1. KeygenMe的简单乘法算法
  2. crackme_0006的复杂算法,涉及:
    • 卷序列号处理
    • 乘法累加
    • 位操作
    • 数学计算
    • 查表转换

通过理解这些算法,可以更好地掌握Windows逆向工程技术,提高软件安全分析能力。

Windows逆向工程之CrackMe分析教程 免责声明 本教程仅用于学习和研究目的,严禁用于商业用途和非法用途。所有分析内容均基于技术研究角度,不鼓励任何形式的软件破解行为。 第一部分:KeygenMe分析 1.1 基本信息 程序类型:32位GUI程序 编程语言:TASM/MASM 加壳情况:无壳 1.2 程序功能 需要输入Name和Serial 未输入时提示"Give me more material hehe! !" 输入错误时提示错误信息 1.3 逆向分析 1.3.1 关键API GetDlgItemTextA :获取文本框输入内容 lstrlen :计算字符串长度 MessageBoxA :显示提示信息 1.3.2 注册算法流程 检查Name和Serial是否已输入 计算Name的算法结果: 对Name的每个字符进行以下计算: EBX = 字符的ASCII值 EBX = EBX * EBX (字符ASCII值的平方) ESI += EBX EBX = 字符的ASCII值 EBX = EBX >> 1 (字符ASCII值除以2) EBX += 3 EBX = EBX * 字符的ASCII值 EBX -= 字符的ASCII值 ESI += EBX ESI += ESI (ESI值乘以2) 将计算结果与输入的Serial前4字节比较 1.3.3 注册机实现 1.3.4 破解方法 修改0040133C处的跳转指令 JNZ SHORT KeygenMe.00401353 为NOP指令,绕过验证。 第二部分:crackme_ 0006分析 2.1 基本信息 程序类型:32位GUI程序 编程语言:TASM/MASM 加壳情况:无壳 2.2 关键API GetDlgItemTextA :获取文本框输入 GetVolumeInformationA :获取卷序列号信息 lstrcmpA :字符串比较 2.3 注册算法分析 2.3.1 卷序列号计算 获取C盘和D盘的卷序列号 计算步骤: 将两个序列号相加 结果左移5位(相当于乘以32) 循环左移0x0D位 计算两个序列号的平方和的平方根(勾股定理) 2.3.2 Name算法 Name-Step1算法 : Name-Step2算法 : 将Step1结果循环左移1位 与卷序列号计算结果进行OR运算 与0x0FFFFFFF进行AND运算 Name-Step3算法 : 使用字符串"071362de9f8ab45c"作为转换表 将Step2结果反复除以16取余数,用余数索引转换表 将结果除以4,直到结果为0 2.3.3 校验 比较生成的Serial与输入的Serial,使用 lstrcmpA 函数。 2.3.4 注册机实现 2.3.5 破解方法 修改004012D5处的跳转指令 JNZ SHORT CrackMe_.004012EF 为NOP指令,绕过验证。 总结 本教程详细分析了两个CrackMe程序的逆向过程和算法实现,包括: KeygenMe的简单乘法算法 crackme_ 0006的复杂算法,涉及: 卷序列号处理 乘法累加 位操作 数学计算 查表转换 通过理解这些算法,可以更好地掌握Windows逆向工程技术,提高软件安全分析能力。