一、前言

上一篇我们从宏观层面聊了 Ghidra 是什么、能干什么。这篇我们来点实战

从零开始,写一个简单的 C 程序做 License 校验
➜ 编译成二进制
➜ 用 Ghidra 打开
➜ 找到校验逻辑、还原正确 License

这类例子在 CTF / 安全面试 / 逆向入门里非常常见:
输入一个“序列号”,程序判断对/错。
我们要做的事就是反推它的规则。

二、准备一个目标程序:简单 License 校验

先写一个非常简单(但足够展示 Ghidra 功能)的例子。
假设源码如下(你可以保存为 license.c):

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

int check_license(const char *key) {
    // 1. 长度必须为 12
    if (strlen(key) != 12) {
        return 0;
    }

    // 2. 前四位固定为 "GHDR"
    if (key[0] != 'G' || key[1] != 'H' || key[2] != 'D' || key[3] != 'R') {
        return 0;
    }

    // 3. 中间四位为数字,且它们的和要等于 20
    int sum = 0;
    for (int i = 4; i < 8; i++) {
        if (key[i] < '0' || key[i] > '9') {
            return 0;
        }
        sum += key[i] - '0';
    }
    if (sum != 20) {
        return 0;
    }

    // 4. 最后四位是前四位数字的“镜像”
    //    key[8] == key[7], key[9] == key[6], key[10] == key[5], key[11] == key[4]
    if (key[8]  != key[7] ||
        key[9]  != key[6] ||
        key[10] != key[5] ||
        key[11] != key[4]) {
        return 0;
    }

    return 1;
}

int main() {
    char buf[256];

    printf("Please input license: ");
    if (scanf("%255s", buf) != 1) {
        printf("Input error\n");
        return 1;
    }

    if (check_license(buf)) {
        printf("OK! License is valid.\n");
    } else {
        printf("Invalid license.\n");
    }

    return 0;
}

⚠️ 实战中你是拿不到源码的,但为了讲解,我们先从“已知真相”出发,等会再从 Ghidra 视角反推一次。

2.1 编译成二进制

Linux / macOS:

gcc -O2 -s license.c -o license
  • -O2:开启优化(实战中常见)
  • -s:strip 符号表,让逆向更接近真实场景(没有函数名)

Windows(MinGW)示例:

gcc -O2 -s license.c -o license.exe

接下来,你就只保留二进制文件license / license.exe),假装源码丢了,用 Ghidra 从头分析。

三、在 Ghidra 中导入与自动分析

3.1 创建工程 & 导入文件

  1. 启动 Ghidra,选择:

    • File -> New Project
    • Non-Shared Project,随便起个名字,比如 ghidra_license_demo
  2. 在左侧 Project 窗格空白处右键:

    • Import File...
    • 选中刚才编译出的 license / license.exe
  3. 导入时 Ghidra 会识别出:

    • 文件格式(ELF / PE)
    • 处理器架构(x86 / x64)

确认无误后,点击 OK

3.2 打开 Program & 执行 Auto Analysis

在 Project 窗格里双击导入的程序,Ghidra 会问你:

“Analyze this program?”

直接选择 Yes

在弹出的 Analysis Options 窗口里:

  • 先保持默认勾选(函数分析、字符串分析、指针分析等)
  • 点击 Analyze

稍等片刻,左侧 Symbol Tree 会出现一堆自动识别出的函数。

四、定位 main 函数和关键逻辑

4.1 找到 main / 程序入口

strip 之后,main 可能不叫 main,而是被识别成类似:

  • _start
  • entry
  • FUN_00101234(Ghidra 自动起的名称)

可以这样做:

  1. 打开 Symbol Tree -> Functions

  2. 在比较新的编译环境中,Ghidra 仍然能识别出 main(有时会显示为 main

  3. 如果没有明显标识:

    • 先打开 _start,一路往下跟调用
    • 通常会看到某个函数负责:设置栈、调用 __libc_start_main,后者的参数里就有 main 的地址

这部分如果你第一次玩可以大概看下,不用追求搞懂所有启动流程,先找到“真正的业务函数”就行。

4.2 打开 Decompiler 视图

找到 main 之后:

  1. 双击 main 函数,打开反汇编视图
  2. 打开 Window -> Decompiler
  3. 把 Decompiler 窗口拖到右侧固定

这时右侧应该能看到类似 C 的伪代码,其中包括:

  • printf("Please input license: ");
  • scanf(...)
  • 调用一个内部函数(名称类似 FUN_00101320

这个内部函数,基本就是我们的 check_license

小技巧:
在 Decompiler 窗口中,选中函数名右键,选择 Rename,把它改成 check_license,后续阅读更友好。

五、分析 check_license 函数:从伪代码还原规则

现在我们把注意力放在 check_license 上。

5.1 函数基本结构

打开 check_license 后,在 Decompiler 中你会看到一个大概这样的结构(不同编译器/优化级别略有差异):

int check_license(char * key)

{
  size_t len;
  int i;
  int sum;
  
  len = strlen(key);
  if (len != 12) {
    return 0;
  }

  if ((key[0] != 'G') || (key[1] != 'H') || (key[2] != 'D') || (key[3] != 'R')) {
    return 0;
  }

  sum = 0;
  for (i = 4; i < 8; i = i + 1) {
    if ((key[i] < '0') || ('9' < key[i])) {
      return 0;
    }
    sum = sum + (int)key[i] - 0x30;
  }

  if (sum != 20) {
    return 0;
  }

  if (((((key[8] != key[7]) || (key[9] != key[6])) || (key[10] != key[5])) || (key[11] != key[4]))) {
    return 0;
  }

  return 1;
}

现在,假装你没有源码,我们来“逆向还原”规则。

5.2 规则一:长度校验

len = strlen(key);
if (len != 12) {
    return 0;
}

非常直观:

  • License 必须长度为 12 个字符

5.3 规则二:前缀校验

if ((key[0] != 'G') || (key[1] != 'H') || (key[2] != 'D') || (key[3] != 'R')) {
    return 0;
}

这说明:

  • key[0..3] 必须是 "GHDR"
  • 即 License 格式为:GHDR????????

你可以在 Ghidra 把这些部分加上注释,例如在 Decompiler 中右键 -> Add Comment

// key[0..3] must be "GHDR"

5.4 规则三:中间四位为数字且和为 20

sum = 0;
for (i = 4; i < 8; i = i + 1) {
    if ((key[i] < '0') || ('9' < key[i])) {
        return 0;
    }
    sum = sum + (int)key[i] - 0x30;
}

if (sum != 20) {
    return 0;
}

拆解一下:

  1. for (i = 4; i < 8; i++):循环处理 key[4] ~ key[7]

  2. if (key[i] < '0' || '9' < key[i]) return 0;

    • 这一位必须是 0-9 的字符(ASCII 数字)
  3. sum += key[i] - 0x30;

    • 把字符转成数字(‘0’ 的 ASCII 是 0x30)
  4. if (sum != 20) return 0;

    • 这四个数字之和必须等于 20

于是我们可以归纳出:

  • 中间四位是数字,且四位数字之和为 20

例如:1235(1+2+3+5=11)不行,5681(5+6+8+1=20)就行。

5.5 规则四:最后四位是前四位数字的“镜像”

if (((((key[8] != key[7]) || (key[9] != key[6])) ||
      (key[10] != key[5])) || (key[11] != key[4]))) {
    return 0;
}

逐条看:

  • key[8] != key[7] → 要求 key[8] == key[7]
  • key[9] != key[6] → 要求 key[9] == key[6]
  • key[10] != key[5] → 要求 key[10] == key[5]
  • key[11] != key[4] → 要求 key[11] == key[4]

也就是说:

  • key[8] == key[7]
  • key[9] == key[6]
  • key[10] == key[5]
  • key[11] == key[4]

最后四位,是中间四位数字的“反向拷贝”:

  • 设中间四位为:a b c d(对应 key[4]~key[7])
  • 最后四位为:d c b a(对应 key[8]~key[11])

六、根据逆向结果构造一个合法 License

现在我们从 Ghidra 里已经“还原”出所有规则:

  1. 总长度 = 12
  2. 前四位固定:GHDR
  3. 中间四位是数字且和为 20
  4. 最后四位是中间四位的镜像

随便构造一个满足条件的数字组合,例如:

  • 取中间四位:5 6 8 1
  • 5 + 6 + 8 + 1 = 20 ✅
  • 镜像后:1 8 6 5

那么整个 License 可以写成:

GHDR56811865

你可以试着运行程序验证:

./license
Please input license: GHDR56811865
OK! License is valid.

至此,我们完成了一个完整的“从零到一”的逆向过程:

  • 只有程序的二进制
  • 用 Ghidra 分析结构
  • 在 Decompiler 中逐行理解逻辑
  • 总结出规则
  • 构造出合法 License

七、这次实战中用到的 Ghidra 核心能力回顾

在整个分析过程中,我们实际用到了 Ghidra 的哪几类能力?

  1. 自动分析(Auto Analysis)

    • 函数识别、字符串分析、控制流构建
    • 让我们可以直接从 main 开始,而不是在一坨汇编里迷路
  2. 多视图协作

    • 汇编视图:可以看到真实的指令
    • Decompiler 视图:更接近源码,利于快速理解
    • Symbol Tree:方便浏览函数和符号列表
  3. 重命名与注释

    • FUN_00101320 改成 check_license
    • 给变量加语义化名字(如 len, sum
    • 给关键代码块加注释,这样以后再打开不会忘
  4. 数据流 & 条件分析

    • 通过观察条件判断(if (...) return 0;
    • 推导出真正的业务规则:长度 / 前缀 / 数字和 / 镜像

八、你可以继续玩的几个进阶方向

这只是一个非常简单的入门 demo,真正的程序会复杂很多。你可以在此基础上往下玩:

  1. 增加反调试、字符串混淆

    • 把字符串 GHDR 加密存放,运行时解密
    • 使用 memcmpstrncpy 等多种方式混淆逻辑
  2. 把 License 规则改成更“数学化”的

    • 某几位数字做异或 / 乘法 / 取模
    • 用一个小的自定义 hash 函数
  3. 尝试开启更高级别优化

    • -O3-Os 编译,看看 Ghidra 反编译出的结构有什么变化
  4. 给自己出题

    • 写一个你自己都容易忘记逻辑的校验程序
    • 过一段时间后删掉源码,只留下二进制
    • 用 Ghidra 把规则完整还原一遍

这会非常锻炼你对“汇编 + 伪代码”的综合理解能力。

九、总结

这篇文章我们做了一次完整的 Ghidra 实战演练:

  • 从一个简单的 License 校验程序出发
  • 编译为无符号的二进制
  • 在 Ghidra 中导入分析
  • main 函数顺藤摸瓜找到核心校验函数
  • 用 Decompiler 视图和一点点耐心,把算法和规则完整还原

相比纯理论,这种“自己写一个 ➜ 自己反编译 ➜ 自己逆向”的方式,会让你对 Ghidra 和逆向的理解扎实很多

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐