初探 RE:从入门认知到 BUUCTF 简单题练习

这篇文章记录我刚开始接触 CTF 逆向时的学习过程。第一天主要是了解 RE 到底在做什么、常见工具和大致解题流程;第二天开始上手刷一些 BUUCTF 的简单题,在做题里熟悉最基础的分析方法。

目前我的想法也比较明确:先通过题目建立直觉,遇到不会的知识点再回头补,这样学起来更有目标感。

Day1

第一天没有急着做题,主要先梳理方向,尽量对 CTF 逆向建立一个整体认识。

逆向学习框架

我参考了一篇系统性入门文章,感觉下面这句话很适合初学阶段:

在逆向工程领域知识碎片化的今天,强大的自学能力和持之以恒的精神尤为关键。教程可以帮助我们建立方向感,但真正的提升还是要靠持续练习。

RE 学习框架

CTF 中的逆向工程在做什么

CTF 里的逆向工程,核心目标通常是从题目给出的程序或文件中找出隐藏的 flag

如果用一个更形象的比喻来理解:

  • 出题人像厨师,把“配料”通过一系列加工步骤做成成品。
  • 做逆向的人则需要通过观察程序行为、分析代码逻辑、还原处理流程,最终推导出被藏起来的核心信息,也就是 flag

为什么需要逆向工具

程序从高级语言编译到二进制之后,很多信息都会丢失,比如:

  • 注释
  • 变量名
  • 函数名
  • 原始代码结构

因此我们通常没法把程序完整还原成最初的高级语言源码,只能借助逆向工具把它转换成更容易分析的汇编代码或伪代码,再继续往下看逻辑。

我目前主要使用的工具还是 IDA

常见逆向类型

拿到一道题后,第一步通常是先判断文件类型。最直观的方法是看后缀,但也不能完全相信后缀,因为有些题会故意伪装。

逆向类型 核心文件格式 常见文件后缀 / 类型 说明
Windows 逆向 PE .exe.dll.sys Windows 可执行文件大多是 PE 格式。
Linux 逆向 ELF 无固定后缀、.so.ko Linux 下可执行文件通常没有固定后缀。
Android 逆向 DEX、APK、ELF .apk.dex.so APK 本质上是压缩包,内部可能同时包含 Java 层和 Native 层内容。

看完这些基础内容之后,我给自己定的路线是:

  1. 先从 Windows 逆向入手。
  2. 再逐步过渡到 Linux 逆向。
  3. 前两部分打一些基础之后,再去接触 Android 逆向。

逆向题常见解题流程

RE 解题流程

1. 判断文件类型

这一阶段的目标是先确认题目文件的格式,例如 ELFPEPython 字节码等,方便后续选择分析工具和环境。

常见方法:

  • 使用 file 命令识别文件类型。
  • 结合题目名称、文件名、附件说明一起判断。
  • 注意后缀名可能是伪装的,不能只看表面。

2. 静态分析

静态分析是在不运行程序的情况下,通过 IDAGhidra 等工具分析程序逻辑。

这一阶段通常会做这些事情:

  • 查字符串
  • 看关键函数
  • 判断是否存在加壳、花指令、反调试
  • 分析是否有明显的加密或校验逻辑

3. 脱壳与修复函数

如果程序有壳,通常要先脱壳。

  • 如果是常见 UPX 壳,可以直接用 upx -d 文件名
  • 如果是魔改壳,就可能需要手动调试,找到 OEP 再脱壳。

脱壳之后,有时还要修复函数结构,不然反汇编和伪代码可能都比较混乱,不方便继续分析。

4. 厘清逻辑

把程序主流程捋顺之后,题目大致会落到几种情况里:

  • 直接显示 flag
    这种题可能能直接从字符串或关键逻辑里看到答案。
  • 存在加密逻辑
    这时就需要逆向算法,再写脚本还原明文。
  • 静态分析受阻
    如果变量是运行时生成的,或者代码有动态行为,就需要进一步动态调试。

5. 动态调试

动态调试就是把程序跑起来,用调试器跟踪关键位置,比如:

  • 验证函数
  • 加密函数
  • 字符串处理函数

常用工具包括:

  • IDA 动态调试
  • x64dbg
  • gdb

通过断点、单步、查看寄存器和内存,往往能更直观地看到数据是怎么变化的。

6. 写脚本解密

如果题目最终落在某种编码或加密逻辑上,通常就要写脚本。

例如,若程序逻辑是:

1
input[i] ^ 0x12 == cipher[i]

那就可以直接逆回来:

1
flag = bytes([c ^ 0x12 for c in cipher])

7. 解出 Flag

最终目标其实很明确,就是通过静态分析、动态调试或者脚本还原,把题目里的 flag 找出来。

看完几篇入门文章之后,我对 RE 已经有了一个比较基础的认识。接下来我更想直接做题,在题目里学习不会的知识点。

参考文章

Day2

第二天开始正式刷题,顺手练一练最基础的静态分析和简单脚本还原。

UPX 脱壳

先记录一个最常见的脱壳命令:

1
upx -d "E:\3 pwn题文件\rebuuctf\93c43c5c-3d4d-4d17-a9a1-4ffb65ebb2fb\新年快乐.exe" -o 1.exe

UPX 脱壳

刷题记录

reverse1

reverse1

这题的核心就是字符串替换,把 ASCII 码为 111 的字符替换成 48,也就是把 o 替换成 0

reverse1 分析 1

reverse1 分析 2

最后得到:

1
flag{hell0_w0rld}

reverse2

reverse2

这题同样是字符串替换,把 ASCII 码为 105114 的字符替换成 49,也就是把 ir 替换成 1

最终可以得到:

1
flag{hack1ng_fo1_fun}

内涵的软件

内涵的软件

进去之后就能看到明文内容,我一开始尝试了几种解码方式,但最后发现只需要把 DBAPP 换成 flag 即可。

最终结果:

1
flag{49d3c93df25caad81232130f3d2ebfad}

新年快乐

新年快乐 1

这题是 UPX 壳,先脱壳。

新年快乐 2

新年快乐 3

脱壳之后就能直接看到 flag,补上格式即可:

1
flag{HappyNewYear!}

xor

xor

这题是经典异或逻辑。题目数据是逐位和前一个字符异或后的结果,因此只要按照逆过程还原,就能恢复原始字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
target = [
0x66, 0x0A, 0x6B, 0x0C, 0x77, 0x26, 0x4F, 0x2E, 0x40, 0x11, 0x78,
0x0D, 0x5A, 0x3B, 0x55, 0x11, 0x70, 0x19, 0x46, 0x1F, 0x76, 0x22,
0x4D, 0x23, 0x44, 0x0E, 0x67, 0x06, 0x68, 0x0F, 0x47, 0x32, 0x4F,
]


def recover_flag(data):
plain = [data[0]]
for i in range(1, len(data)):
plain.append(data[i] ^ data[i - 1])
return bytes(plain).decode()


if __name__ == "__main__":
print(recover_flag(target))

得到:

1
flag{QianQiuWanDai_YiTongJiangHu}

reverse3

reverse3

这题的核心逻辑在主函数 _main_0 中。程序先读取输入,然后调用一个函数对输入进行处理。通过分析可以发现,这个函数内部用到了典型的 Base64 字符表:

1
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=

所以可以判断这一步是在做 Base64 编码。

随后程序把编码结果复制到目标数组,再额外执行一轮变换:

1
Destination[j] += j;

也就是让每个字符的 ASCII 值都加上自己的下标。最后程序拿结果去和常量字符串:

1
e3nifIH9b_C@n@dH

进行比较。

因此逆向思路就是:

  1. 先把目标字符串每一位减去对应下标,恢复出原始 Base64 字符串。
  2. 再对恢复后的 Base64 字符串解码,得到原始输入。

还原脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
import base64

target = "e3nifIH9b_C@n@dH"

# 逆掉 Destination[j] += j
b64 = "".join(chr(ord(c) - i) for i, c in enumerate(target))

# Base64 解码得到原始输入
flag = base64.b64decode(b64).decode()

print("base64:", b64)
print("flag:", flag)

最终得到:

1
flag{i_l0ve_you}

小结

这两天的学习下来,我对 RE 的整体流程已经有了一个初步认识:

  • 先判断文件类型
  • 再做静态分析
  • 必要时脱壳、修复函数
  • 遇到复杂逻辑就调试或写脚本还原

现在这个阶段,我还是会继续以刷简单题为主,把字符串处理、编码转换、异或、简单加密这些基础题型先打熟。等这些内容更熟练一些之后,再继续往更复杂的 Windows 和 Linux 逆向题里走。