这里整理了逆向中最基础的知识,计算机基础,特别有帮助

学习链条

1
2
3
4
5
6
7
8
9
进制 / 字长 / 端序
↓
寄存器 / 栈
↓
逆向常用指令速查
↓
CTF 逆向常见题型和套路
↓
Python 逆向实战

一、进制介绍与进制转换

常见进制

  • 二进制(BIN):0、1,基数为 2
  • 十进制(DEC):0~9,基数为 10
  • 十六进制(HEX):09、AF,基数为 16

权重

每一位数字代表的大小叫“权重”,从右往左,第 0 位权重是 基数^0。

进制 第0位 第1位 第2位 第3位 第4位
十进制 1 10 100 1000 10000
二进制 1 2 4 8 16
十六进制 1 16 256 4096 65536

转十进制

每位数字 × 对应权重,再相加。

例:

1
2
11111₂ = 1×16 + 1×8 + 1×4 + 1×2 + 1×1 = 31₁₀
1F₁₆ = 1×16 + 15×1 = 31₁₀

十进制转其他进制

除以基数取余数,倒着读。

例:31 转二进制

1
2
3
4
5
6
31 ÷ 2 = 15 余 1
15 ÷ 2 = 7 余 1
7 ÷ 2 = 3 余 1
3 ÷ 2 = 1 余 1
1 ÷ 2 = 0 余 1
→ 11111₂

二进制 ↔ 十六进制

1 位十六进制 = 4 位二进制。

HEX BIN HEX BIN
0 0000 8 1000
1 0001 9 1001
2 0010 A 1010
3 0011 B 1011
4 0100 C 1100
5 0101 D 1101
6 0110 E 1110
7 0111 F 1111

例:

1
11111₂ → 0001 1111 → 1F₁₆

二、字长

  • 比特(bit):一个二进制位,最基本单位。
  • 字节(byte):内存的基本单位,1 byte = 8 bit。
  • 字(WORD):x86 中为 2 字节 = 16 位。
  • 双字(DWORD):4 字节 = 32 位。
  • 四字(QWORD):8 字节 = 64 位。
  • Int:有32位有64位

注意:广义上“字长”指 CPU 一次能处理的位数,比如 32 位、64 位。
这里说的 WORD / DWORD / QWORD 是 Windows / x86 语境下的定义。

一个字节 01011010:

1
高位 0101    低位 1010

三、端序(字节序)

image-20261028154359374

注意正负号

端序指多字节数据在内存中的存放顺序。

以 0x12345678 为例,它在内存里占 4 个字节:

1
高字节 → 12 34 56 78 ← 低字节

大端序(Big Endian)

高位字节放在低地址,低位字节放在高地址。

内存从低到高:

1
12 34 56 78

符合人类阅读习惯,网络协议常用大端序。

小端序(Little Endian)

低位字节放在低地址,高位字节放在高地址。

内存从低到高:

1
78 56 34 12

x86 / x64 电脑常用小端序。

记忆口诀

  • 大端:高放低,低放高(高位字节在低地址)
  • 小端:低放低,高放高(低位字节在低地址)

四、寄存器

寄存器是 CPU 内部的高速存储,比内存快得多。逆向时看汇编,主要就是看寄存器怎么变。

x86(32 位)常用寄存器

寄存器 名称 常见用途
EAX 累加器 函数返回值、算术运算
EBX 基址寄存器 通用,有时存指针
ECX 计数器 循环计数、this 指针(C++)
EDX 数据寄存器 乘除法高位、函数第三个参数
ESI 源变址 字符串/内存复制源
EDI 目的变址 字符串/内存复制目的
EBP 栈基址指针 指向当前栈帧底部
ESP 栈顶指针 始终指向栈顶
EIP 指令指针 指向下一条要执行的指令

x64(64 位)常用寄存器

x64 把 x86 的寄存器扩展到 64 位,并新增 R8~R15。

64 位 低 32 位 低 16 位 低 8 位
RAX EAX AX AL
RBX EBX BX BL
RCX ECX CX CL
RDX EDX DX DL
RSI ESI SI SIL
RDI EDI DI DIL
RBP EBP BP BPL
RSP ESP SP SPL
R8~R15 R8D~R15D R8W~R15W R8B~R15B

逆向时看到 AL、AX、EAX、RAX,其实是同一个寄存器的不同宽度。

标志寄存器

  • x86:EFLAGS
  • x64:RFLAGS

常用标志位:

标志 名称 含义
ZF 零标志 结果为 0 时置 1
SF 符号标志 结果为负时置 1
OF 溢出标志 有符号溢出时置 1
CF 进位标志 无符号进位/借位时置 1
PF 奇偶标志 低 8 位中 1 的个数为偶数时置 1

cmp、test 后跟 jz、jnz、jg 等跳转,就是看这些标志位。


五、栈

栈是一块从高地址向低地址生长的内存区域。

  • ESP / RSP 始终指向栈顶。
  • PUSH 让栈顶下移(ESP 减小),POP 让栈顶上移(ESP 增大)。

PUSH / POP

1
2
push eax    ; esp -= 4, [esp] = eax
pop ebx ; ebx = [esp], esp += 4

x64 下 push rax 是 rsp -= 8。

栈帧

函数调用时,会在栈上开辟一块区域,叫栈帧。

典型 x86 栈帧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
高地址
┌─────────────┐
│ 参数 n │
│ ... │
│ 参数 1 │
├─────────────┤
│ 返回地址 │ ← call 指令自动压入
├─────────────┤
│ 旧的 EBP │ ← push ebp
├─────────────┤ ← EBP
│ 局部变量 │
│ ... │
├─────────────┤ ← ESP
低地址

标准函数开头:

1
2
3
push ebp
mov ebp, esp
sub esp, 0x20 ; 开辟局部变量空间

函数结尾:

1
2
3
mov  esp, ebp
pop ebp
ret

或者用 leave; ret 等价。

栈溢出

如果局部变量写入超过分配大小,就会覆盖 EBP、返回地址,这就是栈溢出漏洞。CTF PWN 题的核心之一。


六、逆向常用指令速查

指令 含义
mov 传送
lea 取地址
push / pop 入栈 / 出栈
call 调用函数
ret 返回
jmp 无条件跳转
jz / je 为零/相等跳转
jnz / jne 非零/不等跳转
cmp 比较
test 按位与,常用来判断是否为 0
add / sub 加 / 减
inc / dec 自增 / 自减
and / or / xor 位运算
shl / shr 左移 / 右移
nop 空指令

七、CTF 逆向常见题型和套路

逆向题的核心一句话:

看懂程序在干什么,找到它判断 flag 的逻辑,然后逆回去。

不同题型只是“藏 flag 的方式”不同。

按 flag 存放位置分类

1. 明文 flag

最简单。flag 直接以字符串形式存在程序里。

特征:

  • 扔进反编译工具或 strings 就能看到 flag{...} 或 moectf{...}

套路:

1
2
strings challenge | grep -i flag
strings challenge | grep -i moe

工具:strings、Ghidra、010 Editor

2. 简单异或 / 加减加密

flag 存在程序里,但每个字节被异或或加减了一个固定值。

特征:

  • 反汇编里出现 xor、add、sub 循环
  • 有一段密文数组

套路:

  1. 找到密文数组和运算逻辑
  2. 写脚本逆运算

例:

1
2
3
4
5
6
// 加密逻辑
for (int i = 0; i < len; i++)
enc[i] = flag[i] ^ 0x42;

// 解密脚本
flag = ''.join(chr(c ^ 0x42) for c in enc)

工具:Python、Ghidra

3. 逐字符比较

程序把输入的每个字符和正确 flag 逐位比较。

特征:

  • 大量 cmp 指令
  • 或者 if (input[i] != target[i]) 结构

套路:

  1. 找到比较逻辑
  2. 把目标数组提取出来
  3. 直接转成字符串

变种:比较顺序被打乱,需要按索引还原。

4. 校验和 / 哈希

程序对输入做哈希,和固定值比较。

特征:

  • 出现 md5、sha1、crc32 等常量或函数
  • 比较的是一个 16/32/64 字节的值

套路:

  • 如果是弱哈希(CRC32、简单异或和),可以爆破或反推
  • 如果是 MD5/SHA,通常需要已知明文或字典
  • 有时候是分块哈希,逐块爆破

按保护/难度分类

5. 无壳无混淆(入门题)

特征:

  • 直接能反编译出接近源码的伪代码

套路:

  • 反编译 → 看主逻辑 → 写解密脚本

6. 加壳

特征:

  • 程序入口点奇怪
  • 反编译工具打开看到少量代码 + 一堆数据
  • PEiD、DIE 检测出壳名(UPX、ASPack 等)

套路:

  • UPX:upx -d challenge.exe
  • 其他壳:手动脱壳(ESP 定律、内存 dump)
  • 脱完再扔反编译工具

工具:DIE、PEiD、UPX、Scylla

7. 花指令

特征:

  • 反汇编出现大量无意义跳转、垃圾字节
  • 反编译报错或伪代码混乱

套路:

  • 手动 patch 掉花指令(nop 掉垃圾跳转)
  • 或用脚本批量清理
  • 找到真实入口后重新分析

8. 控制流平坦化(OLLVM)

特征:

  • 一个巨大的 while(1) + switch 结构
  • 状态变量决定下一步走哪

套路:

  • 找状态变量
  • 动态调试跟踪状态变化
  • 或用反混淆工具(deflat、D810)

工具:D810 插件、Angr、动态调试

9. 虚拟机保护(VM)

特征:

  • 有一段“解释器”循环
  • 自定义字节码
  • 大量 switch 或跳转表

套路:

  1. 找到 VM 的入口
  2. 分析每条 handler 干什么
  3. 还原字节码语义
  4. 写反汇编器或直接模拟执行

难度:高,CTF 里属于压轴题。

按语言/平台分类

10. C/C++ 逆向

最常见。看反编译伪代码即可。

注意:

  • C++ 有虚表、this 指针、名字修饰
  • STL 容器在汇编里很啰嗦

11. Python 逆向

特征:

  • 文件是 .pyc,或打包成 exe
  • exe 里有 python3x.dll

套路:

  • .pyc → uncompyle6 / decompyle3 / pycdc
  • 打包 exe → pyinstxtractor 提取 → 再反编译
  • 有混淆 → 动态调试或手动还原

工具:uncompyle6、pycdc、pyinstxtractor、PyLingual

12. Java 逆向

特征:

  • .jar 或 .class 文件

套路:

  • jd-gui、CFR、Procyon 反编译
  • 混淆过的用动态调试(JDB)

13. .NET 逆向

特征:

  • .exe 用 DIE 检测出 .NET

套路:

  • dnSpy 直接反编译 + 调试 + 改代码
  • 混淆过的用 de4dot 去混淆

14. Android 逆向

特征:

  • .apk 文件

套路:

  • jadx 反编译 Java
  • apktool 解包资源
  • so 层用反编译工具
  • 动态调试用 Frida

按输入方式分类

16. 静态输入 flag

程序启动读参数或标准输入,直接比较。

套路:静态分析即可。

17. 多阶段校验

程序分多步校验,每步给一部分 flag。

套路:

  • 逐段分析
  • 或者动态调试,每步跟进去

18. 需要满足约束条件

flag 的每个字符满足一组方程/不等式。

特征:

  • 大量 if 判断
  • 或者数学运算

套路:

  • 提取约束
  • 用约束求解器求解

例:

1
2
# 伪代码,使用约束求解器
# 定义变量,添加约束,求解

工具:Angr

19. 需要爆破

flag 空间小,可以直接枚举。

套路:

  • 写 C/Python 爆破脚本
  • 或用 Angr 符号执行

按技巧分类

20. 反调试

特征:

  • IsDebuggerPresent、ptrace、NtQueryInformationProcess
  • 检测到调试器就输出假 flag 或崩溃

套路:

  • patch 掉检测
  • 或 hook 掉 API
  • 或用 ScyllaHide 插件

21. 动态解密

特征:

  • 代码段在运行时才解密
  • 静态看不到真实逻辑

套路:

  • 在解密完成后 dump 内存
  • 找 OEP,dump 再修 IAT

22. 自修改代码

特征:

  • 程序运行时修改自己的指令

套路:

  • 动态跟踪
  • 或模拟执行

23. 算法逆向

特征:

  • 出现加密算法(AES、RC4、TEA、XXTEA、Base64)
  • 但可能改过常量或轮数

套路:

  1. 识别算法(看常量:AES 的 S 盒、TEA 的 delta 0x9E3779B9)
  2. 确认是否有魔改
  3. 写解密脚本

常见算法常量:

算法 特征常量
TEA / XXTEA 0x9E3779B9
AES S 盒、0x1B
RC4 256 字节初始化
Base64 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
MD5 0x67452301、0xEFCDAB89

24. 迷宫 / 走图题

特征:

  • 一个大数组
  • 输入是 WASD 或方向
  • 有起点终点

套路:

  1. 提取地图数组
  2. 找起点终点
  3. BFS/DFS 找路径
  4. 路径转成输入

工具:Python + BFS

25. 数独 / 棋盘题

特征:

  • 9×9 数组
  • 输入是填数

套路:

  • 提取初始盘
  • 写数独求解器
  • 输出填法

通用解题流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1. 看文件类型
file challenge
DIE / PEiD 查壳

2. 跑一下
看提示、输入、输出

3. 静态分析
Ghidra 打开
找 main、找字符串、找关键比较

4. 动态调试
GDB / lldb
下断点,跟输入流

5. 识别算法
看常量、看结构、看轮数

6. 写脚本
Python 逆运算 / Angr

7. 验证
拿到的 flag 扔回程序确认

常用工具速查

用途 工具
查壳/查类型 DIE、PEiD、file
静态反编译 Ghidra、Binary Ninja
动态调试 GDB + pwndbg、lldb
Python 反编译 uncompyle6、pycdc、PyLingual
Java 反编译 jd-gui、CFR
.NET 反编译 dnSpy、de4dot
Android jadx、apktool、Frida
符号执行 Angr
脱壳 UPX、Scylla
十六进制 010 Editor、HxD

新手建议

  1. 先跑一遍程序,看它要什么输入、输出什么。
  2. 优先看字符串,很多题 flag 或提示直接暴露。
  3. 用反编译工具先看主逻辑,不要一上来就啃汇编。
  4. 遇到算法先搜常量,大概率是已知算法。
  5. 卡住了就动态调试,静态看不清就下断点跟。
  6. 多写脚本,Python 是逆向最好的朋友。
  7. 做完记录套路,CTF 题重复率很高,笔记比刷题更重要。

小结

题型 核心思路
明文 flag strings
异或/加减 写逆运算
逐字符比较 提数组
哈希校验 爆破或反推
加壳 脱壳再分析
花指令 patch 清理
OLLVM 动态跟踪 + 反混淆
VM 分析 handler
Python/Java/.NET 用对应反编译器
约束求解 约束求解器
反调试 patch/hook
算法逆向 识别常量 + 写解密
迷宫 BFS/DFS

八、Python 逆向实战

Python 逆向在 CTF 里非常常见,核心就三类:

  1. .pyc 文件:直接反编译
  2. PyInstaller 打包的 exe:解包出 .pyc 再反编译
  3. 混淆/加密的 Python 代码:动态调试或字节码分析

判断文件类型

1
file challenge

常见输出:

输出 含义
Python script, ASCII text 明文 Python 源码
python 3.x byte-compiled .pyc 文件
PE32 executable Windows exe,可能是 PyInstaller
ELF 64-bit Linux 可执行文件,可能是打包的 Python

查壳:

1
die challenge.exe

如果 DIE 显示 PyInstaller,就走 PyInstaller 解包流程。

.pyc 文件反编译

1. 查看 Python 版本

.pyc 文件开头 4 个字节是 magic number,对应 Python 版本。

1
xxd -l 4 challenge.pyc

也可以用 Python 查看:

1
2
import importlib.util
print(importlib.util.MAGIC_NUMBER)

常见 magic:

Python 版本 magic(十六进制)
3.8 550d0d0a
3.9 610d0d0a
3.10 6f0d0d0a
3.11 a70d0d0a
3.12 cb0d0d0a

版本不对,反编译会失败。

2. 反编译工具

工具 支持版本 安装
uncompyle6 Python 2.7 ~ 3.8 pip install uncompyle6
decompyle3 Python 3.7 ~ 3.8 pip install decompyle3
pycdc 较广,支持 3.11+ 编译安装
PyLingual 在线,支持 3.11+ 网页版

用法:

1
2
3
uncompyle6 -o . challenge.pyc
decompyle3 -o . challenge.pyc
pycdc challenge.pyc

如果 uncompyle6 报错,先确认 Python 版本。

3. 反汇编字节码

反编译失败时,用 dis 模块看字节码:

1
2
3
4
5
6
7
8
import dis
import marshal

with open('challenge.pyc', 'rb') as f:
f.read(16) # 跳过文件头,Python 3.7+
code = marshal.load(f)

dis.dis(code)

关注:

  • co_consts:常量,可能有密文
  • co_names:变量名、函数名
  • co_code:原始字节码

PyInstaller 打包的 exe

1. 解包

用 pyinstxtractor:

1
python pyinstxtractor.py challenge.exe

会生成 challenge.exe_extracted 文件夹。

进去找主文件,通常是一个无后缀文件或 .pyc。

2. 修复文件头

PyInstaller 提取出的 .pyc 可能缺少 magic number,需要补上。

详见本博客 python逆向 这篇文章

然后再用 uncompyle6 或 pycdc 反编译。

3. 常用工具

工具 用途
pyinstxtractor 解包 PyInstaller
pyinstxtractor-ng 新版,支持更多版本
uncompyle6 反编译
pycdc 反编译
PyLingual 在线反编译

混淆与加密

1. PyArmor

特征:

  • 代码里有 pyarmor 字样
  • 大量 __pyarmor__ 变量
  • 字符串加密

套路:

  • 动态调试,在解密后 dump 代码
  • 用 sys.settrace 跟踪
  • 找解密函数,手动还原

2. 变量名混淆

特征:

  • 变量名是 _0x1234、a1、b2 之类

套路:

  • 根据逻辑重命名
  • 用 IDE 重构

3. 控制流平坦化

特征:

  • 大量 while True + if 跳转
  • 状态变量控制流程

套路:

  • 动态跟踪状态变量
  • 或写脚本还原

4. 代码加密

特征:

  • exec(base64.b64decode(...))
  • eval(zlib.decompress(...))

套路:

  • 把 exec 换成 print
  • 或手动解码

例:

1
2
3
import base64, zlib
code = base64.b64decode('...')
print(zlib.decompress(code).decode())

实战例子:简单异或校验

假设有 challenge.pyc,反编译后得到:

1
2
3
4
5
6
7
flag = input('Input: ')
enc = [0x12, 0x34, 0x56, 0x78]
for i in range(len(flag)):
if (ord(flag[i]) ^ 0x42) != enc[i]:
print('Wrong')
exit()
print('Correct')

解密脚本:

1
2
3
enc = [0x12, 0x34, 0x56, 0x78]
flag = ''.join(chr(c ^ 0x42) for c in enc)
print(flag)

常用命令速查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 判断文件
file challenge

# 查看 pyc magic
xxd -l 4 challenge.pyc

# 反编译 pyc
uncompyle6 -o . challenge.pyc
decompyle3 -o . challenge.pyc
pycdc challenge.pyc

# 反汇编 pyc
python -m dis challenge.pyc

# 解包 PyInstaller
python pyinstxtractor.py challenge.exe

新手建议

  1. 先 file 和 strings,判断类型和找提示。
  2. .pyc 先看 magic,版本不对就换 Python。
  3. PyInstaller 先解包,再反编译。
  4. 反编译失败就用 dis,看字节码手动还原。
  5. 遇到 exec / eval,先替换成 print。
  6. 多写 Python 脚本,解密、爆破都靠它。

站内搜索

没有找到内容!