未来的研究方向里有好多要用到逆向工程的地方
感觉是绕不开逆向了,因此打算猛猛突击一下
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
|