python逆向

LinX Lv1

PS:由于本人没系统学过python,所以只会逆向里常见的函数什么。。

判断

大部分python编辑的exe文件很好识别,或者我们拖入die中(ELF可以采取这招),可以看到它是什么语言写的
比如
alt text
alt text
打包工具pyinstaller是一个比较常见的py打包工具
通常我们拿到的附件就是exe或者elf了

拿到这个文件,我们怎么反编译捏

这时候需要用到两个工具

解包

一个是解包用的pyinstxtractor.py
pyinstxtractor.py可以在github上找到
使用的时候需要我们把它放到我们解包的同一目录下,在终端中执行

1
python pyinstxtractor.py 文件名

解包完之后我们得检查一下struct.pyc和(我们解包的文件名).pyc前两行文件头是否一样(有时候出题人会在这里挖坑)
如果不一样需要把(文件名).pyc的改成struct.pyc
alt text
然后反编译

反编译

uncompyle6适用python2.7到python3.9

1
uncompyle6 文件.pyc > 文件名.py

或者

1
uncompyle6 -o (希望文件输出的地址) 文件.pyc 

pycdc适用python3.9以上版本
使用方法和uncompyle6差不多
这两个工具基本都会用到

然后就可以逆了

嗯嗯就是正常逆向了

一些例题

[isctf]ELF

进行常规解包,这是一个elf,
用pycdc进行反编译得到原码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Source Generated with Decompyle++
# File: main.pyc (Python 3.10)

import base64
import hashlib
import random
flag = '8d13c398b72151b1dad78762553dbbd59dba9b0b2330b03b401ea4f2a6d4731d479220fe900b520f6b4753667fe1cdf9eff8d3b833a0013c4083fa1ad27d056486702bda245f3c1aa0fbf84b237d8f2dec9a80791fe66625adfe3669419a104cbb67293eaada20f79cebf69d84d326025dd35dec09a2c97ad838efa5beba9e72'
def Rep(hash_data):
random.seed(161)
result = list(hash_data)
for i in range(len(result) - 1, 0, -1):
swap_index = random.randint(0, i)
result[i] = result[swap_index]
result[swap_index] = result[i]
return ''.join(result)

for i in range(len(YourInput) // 3):
c2b = base64.b64encode(YourInput[i * 3:(i + 1) * 3].encode('utf-8'))
hash = hashlib.md5(c2b).hexdigest()
enc += Rep(hash)
if enc == flag:
print('Your are win!!!')
return None
None('Your are lose!!!')

哇塞,py居然有内置的base64和MD5
注意这里

1
2
result[i] = result[swap_index]
result[swap_index] = result[i]

这里实际上是反编译错误的问题,如果逻辑真的是这样,result[i]中的值会被覆盖
正确逻辑是

1
result[i],result[swap_index]= result[swap_index],result[i]

(这个代码好多地方我看懂了但是没知道该怎么逆。。。写一下心路历程)
先把input用base64加密然后再MD5加密然后自定义加密
本来想把rep逆一下的,但是考虑到MD5只能爆破,都要爆破了那再加密一遍爆破也可以
(正常爆破:MD5(遍历的值)== 加密后的值(rep逆向过来的值)
但是也可以:rep(MD5(遍历的值)== 最终的加密结果)
python也有内置的解密base64函数
MD5爆破范围在ascii码壳打印范围里
然后得到解密脚本

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
import base64
import hashlib
import random
flag = '8d13c398b72151b1dad78762553dbbd59dba9b0b2330b03b401ea4f2a6d4731d479220fe900b520f6b4753667fe1cdf9eff8d3b833a0013c4083fa1ad27d056486702bda245f3c1aa0fbf84b237d8f2dec9a80791fe66625adfe3669419a104cbb67293eaada20f79cebf69d84d326025dd35dec09a2c97ad838efa5beba9e72'
def Rep(hash_data):
random.seed(161)
result = list(hash_data)
for i in range(len(result) - 1, 0, -1):
swap_index = random.randint(0, i)
result[i] ,result[swap_index] = result[swap_index],result[i]
return ''.join(result)

ans=[]
asc=''.join(chr(i)for i in range(32,127))
blocks = [flag[i:i+32] for i in range(0, len(flag), 32)]
for i in blocks:
found = None
for a in asc:
for b in asc:
for c in asc:
s =a+b+c
b64 = base64.b64encode(s.encode('ascii'))
w= hashlib.md5(b64).hexdigest()
if i == Rep(w):
found = s
break
if found:
break
if found:
break

ans.append(found)

print(ans)
print(''.join(ans))

[isctf]ezpy

引入了一个自定义库
这个题也是用比较高的python版本写的,用pycdc反编译。但是报错(

1
2
from mypy import check
# WARNING: Decompyle incomplete

可能是自定义库的问题
然后用pycdas进行反汇编,然后读汇编

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
ezpy.pyc (Python 3.13)
[Code]
File Name: ezpy.py
Object Name: <module>
Qualified Name: <module>
Arg Count: 0
Pos Only Arg Count: 0
KW Only Arg Count: 0
Stack Size: 2
Flags: 0x00000000
[Names]
'mypy'
'check'
'ImportError'
'print'
'exit'
'main'
'__name__'
[Locals+Names]
[Constants]
0
(
'check'
)
'Error: Cannot import mypy module'
1
[Code]
File Name: ezpy.py
Object Name: main
Qualified Name: main
Arg Count: 0
Pos Only Arg Count: 0
KW Only Arg Count: 0
Stack Size: 3
Flags: 0x00000003 (CO_OPTIMIZED | CO_NEWLOCALS)
[Names]
'input'
'strip'
'check'
'print'
[Locals+Names]
'user_input'
[Constants]
None
'Please input your flag: '
'Correct!'
'Wrong!'
[Disassembly]
0 RESUME 0
2 LOAD_GLOBAL 1: NULL + input
12 LOAD_CONST 1: 'Please input your flag: '
14 CALL 1
22 LOAD_ATTR 3: strip
42 CALL 0
50 STORE_FAST 0: user_input
52 LOAD_GLOBAL 5: NULL + check
62 LOAD_FAST 0: user_input
64 CALL 1
72 TO_BOOL
80 POP_JUMP_IF_FALSE 12 (to 106)
84 LOAD_GLOBAL 7: NULL + print
94 LOAD_CONST 2: 'Correct!'
96 CALL 1
104 POP_TOP
106 RETURN_CONST 0: None
108 LOAD_GLOBAL 7: NULL + print
118 LOAD_CONST 3: 'Wrong!'
120 CALL 1
128 POP_TOP
130 RETURN_CONST 0: None
[Exception Table]
'__main__'
None
[Disassembly]
0 RESUME 0
2 NOP
4 LOAD_CONST 0: 0
6 LOAD_CONST 1: ('check',)
8 IMPORT_NAME 0: mypy
10 IMPORT_FROM 1: check
12 STORE_NAME 1: check
14 POP_TOP
16 LOAD_CONST 4: <CODE> main
18 MAKE_FUNCTION
20 STORE_NAME 5: main
22 LOAD_NAME 6: __name__
24 LOAD_CONST 5: '__main__'
26 COMPARE_OP 88 (==)
30 POP_JUMP_IF_FALSE 8 (to 48)
34 LOAD_NAME 5: main
36 PUSH_NULL
38 CALL 0
46 POP_TOP
48 RETURN_CONST 6: None
50 RETURN_CONST 6: None
52 PUSH_EXC_INFO
54 LOAD_NAME 2: ImportError
56 CHECK_EXC_MATCH
58 POP_JUMP_IF_FALSE 19 (to 98)
62 POP_TOP
64 LOAD_NAME 3: print
66 PUSH_NULL
68 LOAD_CONST 2: 'Error: Cannot import mypy module'
70 CALL 1
78 POP_TOP
80 LOAD_NAME 4: exit
82 PUSH_NULL
84 LOAD_CONST 3: 1
86 CALL 1
94 POP_TOP
96 POP_EXCEPT
98 JUMP_BACKWARD_NO_INTERRUPT 42 (to 16)
100 RERAISE 0
102 COPY 3
104 POP_EXCEPT
106 RERAISE 1
[Exception Table]
4 to 16 -> 52 [0]
52 to 96 -> 102 [1] lasti
100 to 102 -> 102 [1] lasti

其实还是很清晰的,check的过程应该都在mypy里,这里没什么别的加密,只是普通输入和输出
将文件夹中的”mypy.cp313-win_amd64.pyd”用ida打开
依旧字符串窗口找关键字
alt text
合理推测sub_36F4D1519函数(图中黄色)

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
37
38
39
40
41
42
43
44
45
__m128i *__fastcall sub_36F4D1519(__int64 a1, __int64 a2)
{
char *v2; // rsi
__m128i *v3; // rbx
__m128i *v5; // rax
unsigned int v6; // eax
__int64 v7; // rax
char v8[274]; // [rsp+26h] [rbp-132h] BYREF
char *Str; // [rsp+138h] [rbp-20h] BYREF

strcpy(v8, "ISCTF2025");
if ( !PyArg_ParseTuple(a2, &unk_36F4D4000, &Str) )
return 0i64;
v2 = Str;
v3 = Py_FalseStruct;
if ( strlen(Str) == 25 )
{
v5 = malloc(0x19ui64);
v3 = v5;
if ( v5 )
{
*v5 = _mm_loadu_si128(v2);
*(v5 + 9) = _mm_loadu_si128((v2 + 9));
v6 = strlen(v8);
sub_36F4D1430(&v8[10], v8, v6);
sub_36F4D149C(&v8[10], v3, 25i64);
v7 = 0i64;
while ( v3->m128i_i8[v7] == byte_36F4D4050[v7] )
{
if ( ++v7 == 25 )
{
free(v3);
return Py_TrueStruct;
}
}
free(v3);
return Py_FalseStruct;
}
else
{
PyErr_NoMemory();
}
}
return v3;
}

key是ISCTF2025
加密数据在byte_36F4D4050里1DD53833AFB551F32C6B6EFE412443D271CFA44CE39A9AB531
用在线解密工具
alt text

z3约束

以前一直没仔细学,这下正好顺手精进一下(bushi
(我并不会py,这里可能会掺杂一些c里面的概念,还请谅解
z3 要先

1
pip install z3-solver

使用时要记得

1
2
3
4
5
from z3 import *
变量名=Solver()
变量名.add() #添加约束
变量名.check() #检查 会有返回值 使用时eg.a = s.check()
变量名.model() #得到 会有返回值 使用时eg.m = s.check()

主要是向量的初始化一直不会,然后怎么print出我想要的东西

1
2
3
4
5
6
7
8
9
10
# 创建8位BitVector变量
x = BitVec('x', 8)
y = BitVec('y', 8)

# 创建常量BitVector
a = BitVecVal(10, 8) # 值为10的8位向量
b = BitVecVal(0xFF, 8) # 值为255的8位向量

#创建一个列表(数组)
a= [BitVec(f'a{i}', 8)for i in range(数字)] #f'a{i}' 中,f 表示这是一个 f-string(格式化字符串字面量) i=0时,为a0,i=1时,为a1

想要print出来,就

1
2
3
4
5
6
7
8
if (check返回值) == sat:
m = 变量名.model()
flag_bytes = [m.evaluate(raw_a1[i]).as_long() for i in range(数字)] #evaluate获取z3模型中的变量值,as_long将z3模型中的变量值转换为interesting类型
try:
flag = ''.join(chr(b) for b in flag_bytes) #join拼接
except:
flag = repr(flag_bytes) #防止flag_bytes是不可打印字符
print(flag)

晚安

  • Title: python逆向
  • Author: LinX
  • Created at : 2026-01-11 03:00:41
  • Updated at : 2026-01-11 03:05:33
  • Link: https://redefine.ohevan.com/2026/01/11/记录一些py逆向/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments