python 逆向
python 逆向 也是 CTF reverse 的一个重要组成部分(废话)。
题目一般会给一个 exe 文件或者 pyc 文件。
工具
工欲善其事,必先利其器,好的工具是必不可少的。
- exe 转 pyc 工具:
GitHub - WithSecureLabs/python-exe-unpacker: A helper script for unpacking and decompiling EXEs compiled from python code.
GitHub - pyinstxtractor/pyinstxtractor-ng: PyInstaller Extractor Next Generation
- 在线网站(
exe->pyc
):PyInstaller Extractor WEB - 在线网站(
pyc->python
):在线Python pyc文件编译与反编译 或 python反编译 - 有的时候,在线工具不好用,可以用 pycdc(强推),下载后记得
cmake
构建。
git clone https://github.com/zrax/pycdc.git
基本思路
- 如果为如果你明确是一个 python 得到的 exe 文件,用上面的网址,将其转为 .pyc 文件。
- 对于.pyc 我个人喜欢有用
pycdc
。
pycdas.exe xxxx.pyc #转字节码
pycdc.exe xxxx.pyc #转python代码
- 如果可以得到完整的 python 代码,那么就简单了,pycdc 转换失败,那么就只好用 pycdas 配合可能解出的代码(可能没有),分析获取源码。
- 注意:有的时候要对
Magic Number
进行操作,等我有遇到再补充。
题目
多说无益,上题!
我的 pycadc 初体验
如题目,这个题目是我曾经的 python 逆向入门题。
import crypto
from Crypto.Cipher import Blowfish
def encrypt_file(input_path, output_path, key):
Unsupported opcode: BEFORE_WITH
cipher = Blowfish.new(key, Blowfish.MODE_CBC)
bs = Blowfish.block_size
# WARNING: Decompyle incomplete
# 反编译有点问题
key = b'crypto.SomeEncode'
input_path = '.\\input'
output_path = '.\\output'
encrypt_file(input_path, output_path, key)
pycdc
得到的结果是有问题的,上 pycdas
!
NSSCTF.pyc (Python 3.12)
[Code]
File Name: NSSCTF.py
Object Name: <module>
Qualified Name: <module>
Arg Count: 0
Pos Only Arg Count: 0
KW Only Arg Count: 0
Stack Size: 5
Flags: 0x00000000
[Names]
'crypto'
'Crypto.Cipher'
'Blowfish'
'encrypt_file'
'key'
'input_path'
'output_path'
[Locals+Names]
[Constants]
0
None
(
'Blowfish'
)
[Code]
File Name: NSSCTF.py
Object Name: encrypt_file
Qualified Name: encrypt_file
Arg Count: 3
Pos Only Arg Count: 0
KW Only Arg Count: 0
Stack Size: 6
Flags: 0x00000003 (CO_OPTIMIZED | CO_NEWLOCALS)
[Names]
'Blowfish'
'new'
'MODE_CBC'
'block_size'
'open'
'read'
'len'
'iv'
'encrypt'
'write'
[Locals+Names]
'input_path'
'output_path'
'key'
'cipher'
'bs'
'file'
'plaintext'
'padding_length'
'encrypted_data'
[Constants]
None
'input'
'rb'
b'\x00'
'wb'
[Disassembly]
0 RESUME 0
2 LOAD_GLOBAL 1: NULL + Blowfish
12 LOAD_ATTR 2: new
32 LOAD_FAST 2: key
34 LOAD_GLOBAL 0: Blowfish
44 LOAD_ATTR 4: MODE_CBC
64 CALL 2
72 STORE_FAST 3: cipher
74 LOAD_GLOBAL 0: Blowfish
84 LOAD_ATTR 6: block_size
104 STORE_FAST 4: bs
106 LOAD_GLOBAL 9: NULL + open
116 LOAD_CONST 1: 'input'
118 LOAD_CONST 2: 'rb'
120 CALL 2
128 BEFORE_WITH
130 STORE_FAST 5: file
132 LOAD_FAST 5: file
134 LOAD_ATTR 11: read
154 CALL 0
162 STORE_FAST 6: plaintext
164 LOAD_CONST 0: None
166 LOAD_CONST 0: None
168 LOAD_CONST 0: None
170 CALL 2
178 POP_TOP
180 LOAD_FAST 4: bs
182 LOAD_GLOBAL 13: NULL + len
192 LOAD_FAST_CHECK 6: plaintext
194 CALL 1
202 LOAD_FAST 4: bs
204 BINARY_OP 6 (%)
208 BINARY_OP 10 (-)
212 STORE_FAST 7: padding_length
214 LOAD_FAST 6: plaintext
216 LOAD_CONST 3: b'\x00'
218 LOAD_FAST 7: padding_length
220 BINARY_OP 5 (*)
224 BINARY_OP 13 (+=)
228 STORE_FAST 6: plaintext
230 LOAD_FAST 3: cipher
232 LOAD_ATTR 14: iv
252 LOAD_FAST 3: cipher
254 LOAD_ATTR 17: encrypt
274 LOAD_FAST 6: plaintext
276 CALL 1
284 BINARY_OP 0 (+)
288 STORE_FAST 8: encrypted_data
290 LOAD_GLOBAL 9: NULL + open
300 LOAD_FAST 1: output_path
302 LOAD_CONST 4: 'wb'
304 CALL 2
312 BEFORE_WITH
314 STORE_FAST 5: file
316 LOAD_FAST 5: file
318 LOAD_ATTR 19: write
338 LOAD_FAST 8: encrypted_data
340 CALL 1
348 POP_TOP
350 LOAD_CONST 0: None
352 LOAD_CONST 0: None
354 LOAD_CONST 0: None
356 CALL 2
364 POP_TOP
366 RETURN_CONST 0: None
368 PUSH_EXC_INFO
370 WITH_EXCEPT_START
372 POP_JUMP_IF_TRUE 1 (to 376)
374 RERAISE 2
376 POP_TOP
378 POP_EXCEPT
380 POP_TOP
382 POP_TOP
384 JUMP_BACKWARD 103 (to 180)
386 COPY 3
388 POP_EXCEPT
390 RERAISE 1
392 PUSH_EXC_INFO
394 WITH_EXCEPT_START
396 POP_JUMP_IF_TRUE 1 (to 400)
398 RERAISE 2
400 POP_TOP
402 POP_EXCEPT
404 POP_TOP
406 POP_TOP
408 RETURN_CONST 0: None
410 COPY 3
412 POP_EXCEPT
414 RERAISE 1
b'crypto.SomeEncode'
'.\\input'
'.\\output'
[Disassembly]
0 RESUME 0
2 LOAD_CONST 0: 0
4 LOAD_CONST 1: None
6 IMPORT_NAME 0: crypto
8 STORE_NAME 0: crypto
10 LOAD_CONST 0: 0
12 LOAD_CONST 2: ('Blowfish',)
14 IMPORT_NAME 1: Crypto.Cipher
16 IMPORT_FROM 2: Blowfish
18 STORE_NAME 2: Blowfish
20 POP_TOP
22 LOAD_CONST 3: <CODE> encrypt_file
24 MAKE_FUNCTION 0
26 STORE_NAME 3: encrypt_file
28 LOAD_CONST 4: b'crypto.SomeEncode'
30 STORE_NAME 4: key
32 LOAD_CONST 5: '.\\input'
34 STORE_NAME 5: input_path
36 LOAD_CONST 6: '.\\output'
38 STORE_NAME 6: output_path
40 PUSH_NULL
42 LOAD_NAME 3: encrypt_file
44 LOAD_NAME 5: input_path
46 LOAD_NAME 6: output_path
48 LOAD_NAME 4: key
50 CALL 3
58 POP_TOP
60 RETURN_CONST 1: None
根据上面的逻辑可以复原函数【注意:此代码段中存在一些反汇编特有的标记(如 NULL +
),在实际代码中并不存在】:
def encrypt_file(input_path, output_path, key):
# 创建了Blowfish算法的加密对象cipher,使用了给定的密钥key和加密模式CBC(Cipher Block Chaining)。
cipher = Blowfish.new(key, Blowfish.MODE_CBC)
bs = Blowfish.block_size # 这行代码获取了Blowfish算法的块大小
with open(input_path, 'rb') as file:
plaintext = file.read()
# 计算了需要添加的填充长度,以确保加密数据的长度是块大小的整数倍。
padding_length = bs - len(plaintext) % bs
# 向明文末尾添加了填充字符\x00,使得明文的长度符合块大小的整数倍。
plaintext += b'\x00' * padding_length
# 获取了Blowfish算法使用的初始化向量(IV)8字节
iv = cipher.iv
# 对填充后的明文进行加密,并将加密后的数据和IV合并为一个字节串encrypted_data
encrypted_data = iv + cipher.encrypt(plaintext)
# 输出的 output
with open(output_path, 'wb') as file:
file.write(encrypted_data)
注意 key 的获取,不是简单的 b'crypto.SomeEncode'
这个应该是出题人给的提示,打开刚刚得到的反编译的文件夹,发现有一个神奇的加密文件 pycdc.exe crypto.cpython.pyc
# D:\HuaweiMoveData\Users\86135\Desktop\re\exe\NSSCTF.exe_extracted>pycdc.exe crypto.cpython.pyc
# Source Generated with Decompyle++
# File: crypto.cpython.pyc (Python 3.10)
def encrypt(v, k):
v0 = v[0]
v1 = v[1]
(key0, key1, key2, key3) = (k[0], k[1], k[2], k[3])
sum = 0
delta = 0x9E3779B9L
for _ in range(32):
sum = sum + delta & 0xFFFFFFFFL
v0 = v0 + ((v1 << 3) + key0 ^ v1 + sum ^ (v1 >> 4) + key1 ^ 596) & 0xFFFFFFFFL
v1 = v1 + ((v0 << 3) + key2 ^ v0 + sum ^ (v0 >> 4) + key3 ^ 2310) & 0xFFFFFFFFL
return (v0, v1)
def encrypt_all(v, k):
encrypted = []
for i in range(0, len(v), 2):
encrypted.extend(encrypt(v[i:i + 2], k))
return encrypted
v = [
1396533857,
0xCC8AE275L,
0x89FB8A63L,
940694833]
k = [
17,
34,
51,
68]
encrypted = encrypt_all(v, k)
SomeEncode = ''.join((lambda .0: for num in .0:
for i in range(4)[::-1]:
chr(num >> 8 * i & 255))(encrypted))
是一个就是简单的魔改tea,解密【具体逆向常用的 TEA XTEA XXTEA RC4
等先挖一个坑】:
def encrypt(v, k):
v0 = v[0]
v1 = v[1]
(key0, key1, key2, key3) = (k[0], k[1], k[2], k[3])
sum = 0
delta = 0x9E3779B9
for _ in range(32):
sum = sum + delta & 0xFFFFFFFF
v0 = v0 + ((v1 << 3) + key0 ^ v1 + sum ^ (v1 >> 4) + key1 ^ 596) & 0xFFFFFFFF
v1 = v1 + ((v0 << 3) + key2 ^ v0 + sum ^ (v0 >> 4) + key3 ^ 2310) & 0xFFFFFFFF
return (v0, v1)
def decrypt(v, k):
v0 = v[0]
v1 = v[1]
(key0, key1, key2, key3) = (k[0], k[1], k[2], k[3])
sum = 0xC6EF3720
delta = 0x9E3779B9
for _ in range(32):
v1 =v1 - ((v0 << 3) + key2 ^ v0 + sum ^ (v0 >> 4) + key3 ^ 2310) & 0xFFFFFFFF
v0 = v0 - ((v1 << 3) + key0 ^ v1 + sum ^ (v1 >> 4) + key1 ^ 596) & 0xFFFFFFFF
sum = sum - delta & 0xFFFFFFFF
return (v0, v1)
def encrypt_all(v, k):
encrypted = []
for i in range(0, len(v), 2):
encrypted.extend(encrypt(v[i:i + 2], k))
return encrypted
def decrypt_all(v, k):
decrypted = []
for i in range(0, len(v), 2):
decrypted.extend(decrypt(v[i:i + 2], k))
return decrypted
v = [1396533857,
0xCC8AE275,
0x89FB8A63,
940694833]
k = [
17,
34,
51,
68]
encrypted = decrypt_all(v, k)
SomeEncode = ''
for i in range(4):
for j in range(4):
SomeEncode += chr(encrypted[i] >> (8 * (3 - j)) & 0xFF)
print(SomeEncode) # EzNssRevProjects
ok,获得 key ,用 key 解密:
from Crypto.Cipher import Blowfish
def decrypt_file(input_path, output_path, key):
cipher = Blowfish.new(key, Blowfish.MODE_CBC)
bs = Blowfish.block_size
with open(input_path, 'rb') as file:
encrypted_data = file.read()
# 从加密数据中获取初始化向量
iv = encrypted_data[:bs]
# 使用初始化向量设置解密对象的IV
cipher = Blowfish.new(key, Blowfish.MODE_CBC, iv)
# 解密数据,忽略初始化向量
decrypted_data = cipher.decrypt(encrypted_data[bs:])
# 去除填充部分
decrypted_data = decrypted_data.rstrip(b'\x00')
with open(output_path, 'wb') as file:
file.write(decrypted_data)
key = b'EzNssRevProjects' # 密钥
input_path = '.\\output'
output_path = '.\\decrypted_output'
decrypt_file(input_path, output_path, key)
到这里关于 python
就差不多了,剩下的 IDA
打开,就是简单的分析,解密流程:
#include <iostream>
#include <windows.h>
using namespace std;
void de(unsigned char* a1, int* a2, int a3) {
int v5; // [rsp+20h] [rbp-4h]
for ( int i = 0; i < a3; ++i) {
v5 = i % 4;
// 整理一下
switch (v5) {
case 0: a1[i] ^= a2[4] + a2[1]; break;
case 1: a1[i] ^= a2[0] - a2[2]; break;
case 2: a1[i] += a2[1] ^ a2[3]; break;
case 3: a1[i] -= a2[2] + a2[0]; break;
}
}
}
int main()
{
int v4[8]; // [rsp+10h] [rbp-50h] BYREF
v4[0] = 35;
v4[1] = 18;
v4[2] = 69;
v4[3] = 86;
v4[4] = 103;
// 大小端的问题,整体顺序是从前往后看,每一部分的数据是从(后往前看)
unsigned char flag[] = { 0x37,0x8d,0xf,0xab,0x2d,0x98,0x37,0xb5,0x48,0xa6,0x30,0xda,0xc,0xed,0x1b,0xb8,0x0,0xe9,0x24,0x98,0x17,0x81,0xed,0xb6,0x26,0x8c,0x21,0xde,0x4,0xde};
int flag_len = sizeof(flag) / sizeof(flag[0]);
printf("%d\n", flag_len); //30
de(flag, v4, flag_len);
for (int i = 0; i < flag_len; i++) {
printf("%c", flag[i]);
} # NSSCTF{M1xtru3_Py7h0n_1N_Rev}
return 0LL;
}
我的 pycadc 破碎了
题目:第二届黄河流域网络安全技能挑战赛 ezpyc
下载的文件这里找不到了,可以看看网络上有没有。
直接解析是有问题的, 其他方法以后在探索吧。
这里硬读python字节码:
逻辑不难,可以分析得到:
import base64
import sys
def check(ininput):
str1 = 'RitdR+kuGPFoYpX4NVP{PVOx[VSzXhLnO{L2Xkj3[l[8]'
str2 = list(str1)
list1 = list(ininput)
for q in range(len(str2)):
if list1[q] != str2[q]:
print('Wrong!!!')
print('All input is correct!')
def encode(eenv):
# base64编码
env = base64.b64encode(eenv.encode('utf-8')).decode('utf-8')
# 字符串处理
str2 = list(env)
for i in range(len(str2)):
str2[i] = chr(ord(str2[i]) ^ 2)
for j in range(11):
str2[j] = chr(ord(str2[j]) - 6)
for k in range(11, 22):
str2[k] = chr(ord(str2[k]) + 1)
for h in range(22, 33):
str2[h] = str2[h]
for l in range(33, 44):
str2[l] = chr(ord(str2[l]) ^ 3)
# 字符串重组
str3 = ''
for w in range(len(str2)):
str3 += str2[w]
return str3
enc = input('Please input your flag:')
ininput = list(enc)
if len(ininput) != 33:
print('Wrong length!')
sys.exit()
env = encode(enc)
check(env)
题解:
import base64
str1 = 'RitdR+kuGPFoYpX4NVP{PVOx[VSzXhLnO{L2Xkj3[l[8]'
str2 = list(str1)
print(str2)
for l in range(33, 44):
str2[l] = chr(ord(str2[l]) ^ 3)
for h in range(22, 33):
str2[h] = str2[h]
for k in range(11, 22):
str2[k] = chr(ord(str2[k]) - 1)
for j in range(11):
str2[j] = chr(ord(str2[j]) + 6)
for i in range(len(str2)):
str2[i] = chr(ord(str2[i]) ^ 2)
print(str2)
flag = ''.join(str2)
print(len(flag))
print(base64.b64decode(flag))
# 得到结果:flag{293efe59c11c3a41f3e337b96ff}
神奇的数字
这里的本意是要写 Magic Number
,但是本菜鸡还没有遇到,遇到了再补充(大概吧)。
本文来自博客园,作者:harveyX,转载请注明原文链接:https://www.cnblogs.com/harveyX/p/18196920
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)