CTF-Reverse学习记录

未来的研究方向里有好多要用到逆向工程的地方

感觉是绕不开逆向了,因此打算猛猛突击一下

IDA常用快捷键

快捷键 功能
F2 添加断点
F4 开始运行
F5 反编译成伪代码
F7 进入函数
F8 单步调试
F9 运行到下一个断点
Space 切换反汇编窗口(列表视图&图形视图)
Tab 返回栈视图
ESC 跳转到返回前的地址
/ 添加注释
N 修改变量名
D 代码解析成数据
C 数据解析成代码
G 跳转到指定地址查看
H 转换为十进制数
Q 转换为十六进制数
R 转换为字符
X 查看函数的交叉引用
Y 指定当前函数的命名
Shift+F12 打开字符串窗口
Shift+E 批量复制数据为字符串或者c数组格式

逆向中常见的数据类型

数据类型 位数
1 byte = 8 bit 8 位
1 word = 2 byte 16 位
1 dword(Double Word) = 2 word 32 位
1 qword(Quadra Word) = 2 dword 64 位

汇编基础

汇编指令 示例 含义 说明
MOV MOV EAX, ECX EAX = ECX 将ECX的值赋给EAX
ADD ADD EAX, ECX EAX += ECX 将EAX的值加上ECX的值
SUB SUB EAX, ECX EAX -= ECX 将EAX的值减去ECX的值
INC INC EAX EAX++ 将EAX的值+1
DEC DEX EAX EAX– 将EAX的值-1
LEA LEA EAX, ECX EAX = ECX的地址 将ECX的地址存入EAX
AND AND EAX, ECX EAX = EAX & ECX EAX与ECX进行与运算
OR OR EAX, ECX EAX = EAX | ECX EAX与ECX进行或运算
XOR XOR EAX, ECX EAX = EAX ^ ECX EAX与ECX进行异或运算
NOT NOT EAX EAX = ~EAX 将EAX的值取反
SHL SHL EAX, 3 EAX = EAX « 3 将EAX的值左移三位
SHR SHR EAX, 3 EAX = EAX » 3 将EAX的值右移三位
CMP CMP EAX, ECX if(EAX == ECX) ZF = 1
else ZF = 0
对EAX和ECX的值进行比较并根据比较结果设置ZF标志的值
TEST TEST EAX, EAX if(EAX == 0) ZF = 1
else ZF = 0
将EAX的值与0进行比较并根据比较结果设置ZF标志的值
JE[JZ] JZ 04001000 if(ZF == 1)
GOTO 04001000
若ZF为1则跳转至 04001000
JNE[JNZ] JNZ 04001000 if(ZF == 0)
GOTO 04001000
若ZF为0则跳转至 04001000
JMP JMP 04001000 GOTO 04001000 直接跳转至 04001000
CALL CALL printf
CALL $+5
调用函数printf
跳转到下一条指令
PUSH PUSH EAX 将EAX的值压入栈顶
POP POP EAX 将栈顶的值弹给EAX

常见的编码与加密

base64

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
int __fastcall base64_encode(char *Str2, char *Str)
{
  int v3; // [rsp+24h] [rbp-1Ch]
  int v4; // [rsp+34h] [rbp-Ch]
  int v5; // [rsp+38h] [rbp-8h]
  int v6; // [rsp+3Ch] [rbp-4h]

  v3 = strlen(Str);
  if ( v3 % 3 )
    v6 = 4 * (v3 / 3 + 1);
  else
    v6 = 4 * (v3 / 3);
  Str2[v6] = 0;
  v5 = 0;
  v4 = 0;
  while ( v6 - 2 > v5 )
  {
    Str2[v5] = base64_table[(unsigned __int8)Str[v4] >> 2]; // 右移2位,获得第一个字符前6位的数据
    Str2[v5 + 1] = base64_table[(16 * (Str[v4] & 3)) | ((unsigned __int8)Str[v4 + 1] >> 4)]; // 获取第二个6位
    Str2[v5 + 2] = base64_table[(4 * (Str[v4 + 1] & 0xF)) | ((unsigned __int8)Str[v4 + 2] >> 6)]; // 获得第三个6位
    Str2[v5 + 3] = base64_table[Str[v4 + 2] & 0x3F]; // 获得第四个6位
    v4 += 3;
    v5 += 4;
  }
  if ( v3 % 3 == 1 )
  {
    Str2[v5 - 2] = "=";
    Str2[v5 - 1] = "=";
  }
  else if ( v3 % 3 == 2 )
  {
    Str2[v5 - 1] = "=";
  }
  return putchar("\n");
}

RC4

TEA XTEA XXTEA

MD5

常见的壳

可以先把待逆向的文件拖入 DIE(Detect-It-Easy) 中查看是用的什么壳

upx

一种压缩壳,可以尝试直接用upx脱壳

1
2
3
4
# 下载
upx: https://github.com/upx/upx
# 使用方法
upx -d re.exe

也可以尝试手动脱upx

Python逆向

有部分exe是由 pyinstaller 打包的,通常可以直接根据文件的图标看出来

针对这种exe的逆向,我可以按照如下步骤:

1、使用 pyinstxtractor 先进行解包

pyinstxtractor: https://github.com/extremecoders-re/pyinstxtractor

1
2
3
# 可以用如下命令进行解包
[+] Usage: pyinstxtractor.py <filename>
# 这里要注意,如果本地版本和出题人打包用的版本不同,某些依赖可能无法正常解包

2、反编译解包后得到的 pyc 文件

第一种方法是用如下的在线网站反编译:

1
2
3
https://tool.lu/pyc/
https://www.lddgo.net/string/pyc-compile-decompile
https://www.toolkk.com/tools/pyc-decomplie

第二中方法就是用本地工具进行反编译,这里有以下三个常用的反编译工具:

uncompyle6: https://github.com/rocky/python-uncompyle6

1
2
3
4
5
6
# 直接 pip 安装即可
pip3 install uncompyle6
# 然后将 Script 目录添加到环境变量中
C:\Users\XXX\AppData\Local\Programs\Python\Python38\Scripts
# 之后就可以直接在终端中使用了
uncompyle6 filename.pyc > decompile.py

pyinstxtractor: https://github.com/extremecoders-re/pyinstxtractor

1
python pyinstxtractor.py reverse_1_PyHaHa.pyc

pycdc: https://github.com/zrax/pycdc

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 下载并编译
git clone https://github.com/zrax/pycdc.git
mkdir build && cd build
cmake ..
make -j$(nproc)

# 使用方法
Usage:  ./pycdc [options] input.pyc

Options:
  -o <filename>  Write output to <filename> (default: stdout)
  -c             Specify loading a compiled code object. Requires the version to be set
  -v <x.y>       Specify a Python version for loading a compiled code object
  --help         Show this help text and then exit

Tips:

有时候会遇到pyc文件魔术头被修改的情况,可以复制解包后的struct.pyc到要反编译的pyc文件中(就是文件前16个字节)

常用的一些逆向工具

Z3 约束求解器

Z3-solver是微软开发的自动定理证明器和约束求解器,能够将复杂问题转化为数学约束条件,然后自动寻找满足所有条件的解。它特别擅长处理包含布尔逻辑、算术运算、位运算等多种约束的复杂问题,广泛应用于程序验证、网络安全、人工智能等领域。Z3通过先进的符号推理和约束求解技术,能够解决传统方法难以处理的非线性约束和复杂逻辑关系,是一个强大的数学问题求解工具。

安装方法: pip3 install z3-solver

基本用法如下,更多用法可以参考官方文档

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from z3 import *

# 1. 创建变量
x = Int('x')      # 整型变量
y = Int('y')      # 整型变量
p = Bool('p')     # 布尔变量p
q = Bool('q')     # 布尔变量q
r = Bool('r')     # 布尔变量r

# 2. 创建求解器
s = Solver()

# 3. 添加约束条件
# 数值约束
s.add(x > 0)                    # x必须大于0
s.add(y > 0)                    # y必须大于0
s.add(x + y == 10)              # x+y等于10
s.add(Implies(p, x > y))        # 如果p为真,则x>y

# 逻辑约束
s.add(Implies(p, q))            # p → q (如果p为真,则q为真)
s.add(r == Not(q))              # r = ¬q (r等于q的否定)
s.add(Or(Not(p), r))            # ¬p ∨ r (p的否定或r为真)

# 4. 检查约束是否可满足
if s.check() == sat:
    # 5. 获取解
    m = s.model()
    print(f"找到解:x = {m[x]}, y = {m[y]}")
    print(f"逻辑变量:p = {m[p]}, q = {m[q]}, r = {m[r]}")
else:
    print("约束不可满足")

示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from z3 import *

enc = [0x0000B1B0, 0x00005678, 0x00007FF2, 0x0000A332, 0x0000A0E8, 0x0000364C, 0x00002BD4, 0x0000C8FE, 0x00004A7C, 0x00000018, 0x00002BE4, 0x00004144, 0x00003BA6, 0x0000BE8C, 0x00008F7E, 0x000035F8, 0x000061AA, 0x00002B4A, 0x00006828, 0x0000B39E, 0x0000B542, 0x000033EC, 0x0000C7D8, 0x0000448C, 0x00009310, 0x00008808, 0x0000ADD4, 0x00003CC2, 0x00000796, 0x0000C940, 0x00004E32, 0x00004E2E, 0x0000924A, 0x00005B5C]

s = Solver()

input = [BitVec(f"input{i}", 8) for i in range(34)] # input0-input33 都是 0-255
var = [BitVec(f"var{i}",32) for i in range(34)] # var0-var31 都是 0-4294967295

# 模拟加密算法
for i in range(34):
    var[i] = 47806 * (ZeroExt(24, input[i]) + i) # 高位补零,将8位扩展至32位
    if i:
        var[i] ^= var[i-1] ^ 0x114514
    var[i] %= 51966

# 添加约束条件
for i in range(34):
    s.add(var[i] == enc[i])

while True:
    if s.check() == sat: # 找到解
        m = s.model()
        ascii_list = [m[input[i]].as_long() for i in range(34)] # 将Z3的符号值转换为Python的普通整数
        flag = "moectf{"  + "".join(chr(ascii_list[i]) for i in range(34)) + "}"
        print(flag)

        # 添加阻塞条件,寻找下一个解
        block = []
        for i in range(34):
            block.append(input[i] != m[input[i]])
        s.add(Or(block)) # 至少有一个变量与当前解不同
    else:
        break
0%