bad_python 题解 🐱
题目描述:the .pyc is broken ,can you help me recover?
第一步:初见文件 🎀
下载附件,是一个名为 bdbc0757-1971-4134-8f3c-aa4b0a7add42.zip 的压缩包,解压后得到一个 pyre.cpython-36.pyc 文件。这是一个 Python 3.6 版本的字节码文件,但题目提示它被“破坏”了,需要我们帮忙恢复~
通常,.pyc 文件是 Python 源码编译后的字节码,可以通过反编译工具将其还原为 Python 代码。但直接反编译可能会失败,因为文件头部可能被破坏了喵~
第二步:尝试反编译 🛠
首先,尝试用常用的反编译工具 uncompyle6 来反编译:
1 2
| pip install uncompyle6 uncompyle6 -o . pyre.cpython-36.pyc
|
但出现了错误提示:
咦?文件名是 cpython-36,说明应该是 Python 3.6 编译的,但工具似乎检测到了不匹配的版本。可能是文件头部的魔数(magic number)不对,导致工具识别错误。
第三步:修复文件头 🔧
Python 的 .pyc 文件头部有一个魔数,用来标识 Python 版本。我们需要将其修正为正确的 Python 3.6 的魔数。
用十六进制编辑器(如 WinHex)打开 pyre.cpython-36.pyc,查看头部。直接修改文件头,仍反编译失败。
我们可以在本地用 Python 3.6 编译一个简单的脚本,生成一个正确的 .pyc 文件,比较头部。
在 Python 3.6 中,.pyc 文件头结构为:
1 2 3 4 5
| struct { uint32_t magic; uint32_t bit_field; uint64_t timestamp; }
|
先写一个1.py,再用以下命令生成它的pyc文件:
1
| python3.6 -m py_compile 1.py
|
然后比较两个文件的头部。发现原文件的魔数不正确,于是将原文件的前 16 个字节替换为正确的 Python 3.6 的头部(可以从1.py中复制)。

修复后,再次使用 uncompyle6 反编译:
1
| uncompyle6 -o . pyre.cpython-36.pyc
|

成功啦!得到了 pyre.py 文件,里面就是被反编译的 Python 源码。
第四步:分析源码 🧐
打开 pyre.py,内容如下:
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
| def encrypt(v, k): v0 = c_uint32(v[0]) v1 = c_uint32(v[1]) sum1 = c_uint32(0) delta = 195935983 for i in range(32): v0.value += (v1.value << 4 ^ v1.value >> 7) + v1.value ^ sum1.value + k[sum1.value & 3] sum1.value += delta v1.value += (v0.value << 4 ^ v0.value >> 7) + v0.value ^ sum1.value + k[sum1.value >> 9 & 3]
return (v0.value, v1.value)
if __name__ == "__main__": flag = input("please input your flag:") k = [255, 187, 51, 68] if len(flag) != 32: print("wrong!") exit(-1) a = [] for i in range(0, 32, 8): v1 = bytes_to_long(bytes(flag[i:i + 4], "ascii")) v2 = bytes_to_long(bytes(flag[i + 4:i + 8], "ascii")) a += encrypt([v1, v2], k)
enc = [4006073346, 2582197823, 2235293281, 558171287, 2425328816, 1715140098, 986348143, 1948615354] for i in range(8): if enc[i] != a[i]: print("wrong!") exit(-1)
print("flag is flag{%s}" % flag)
|
加密算法是一个类似 XTEA 的分组密码,但有一些变体。我们需要编写解密算法,从密文反推明文。
注意到该算法的核心是两个bytes_to_long函数,对其逆向即可。
第五步:编写解密脚本 🐾
观察加密过程,每轮操作如下:
1 2 3
| v0.value += (v1.value << 4 ^ v1.value >> 7) + v1.value ^ sum1.value + k[sum1.value & 3] sum1.value += delta v1.value += (v0.value << 4 ^ v0.value >> 7) + v0.value ^ sum1.value + k[sum1.value >> 9 & 3]
|
其中,sum1 初始为 0,每轮加上 delta,共 32 轮。解密时,我们需要从最后一轮开始逆运算,逐步恢复出原始的 v0 和 v1。
解密脚本如下:
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
| def decrypt_pair(v0, v1, k): delta = 195935983 mask = 0xFFFFFFFF for i in range(31, -1, -1): s_i = (i * delta) & mask s_prime = ((i + 1) * delta) & mask g = ((v0 << 4) ^ (v0 >> 7)) + v0 g ^= (s_prime + k[(s_prime >> 9) & 3]) g &= mask v1 = (v1 - g) & mask f = ((v1 << 4) ^ (v1 >> 7)) + v1 f ^= (s_i + k[s_i & 3]) f &= mask v0 = (v0 - f) & mask return v0, v1
k = [255, 187, 51, 68] enc = [4006073346, 2582197823, 2235293281, 558171287, 2425328816, 1715140098, 986348143, 1948615354]
pairs = [(enc[i], enc[i + 1]) for i in range(0, 8, 2)]
flag_bytes = b'' for v0, v1 in pairs: p0, p1 = decrypt_pair(v0, v1, k) flag_bytes += p0.to_bytes(4, 'big') flag_bytes += p1.to_bytes(4, 'big')
flag = flag_bytes.decode('ascii') print("flag is: flag{%s}" % flag)
|
好累的说QAQ
💬 评论与讨论
分享你的想法和见解