收获

  • 熟悉 python 逆向中反编译工具 uncompyle6 的使用方法

  • 了解 .pyc 文件头的结构

  • 通过自己手动生成对应 python 版本的 .pyc 文件来修复文件头


【攻防世界】bad_python


思路

解压得到一个 .pyc 的 Python 反编译文件

用本机 uncompyle6 反编译失败,提示 KeyError: ‘3.10.4’,本机用的 Python 版本是 3.10.4,应该是 Python 版本不对

攻防世界-bad_python1.png

根据文件名“pyre.cpython-36.pyc”,应该是提示该文件由 python 3.6 编译而来

拖到 win10 虚拟机,安装 Python 3.6.0

用虚拟机的 uncompyle6 反编译:pip install uncompyle6

攻防世界-bad_python2.png

编译失败,提示:

assert iscode(co), f"""{co} does not smell like code"""

AssertionError: None does not smell like code

用记事本打开,内容是乱码,用 exeinfo pe 查看,发现并未识别成 .pyc 文件

攻防世界-bad_python3.png

用 WinHex 查看文件头很奇怪,应该是文件头被修改

攻防世界-bad_python4.png

按照网上给的 pyc 文件头:03 F3 0D 0A,修改之后发现没什么用

pyc 文件头占文件最开始的 16字节,只需修改第一行即可
并且 pyc 文件头好像并不固定,比如文件头包含:python 的版本、文件修改时间等信息

攻防世界-bad_python5.png

尝试自己生成一个由 python 3.6.0 版本编译的 .pyc 文件,再将正常的文件头拷过来,恢复损坏的文件

随意创建一个 python 文件,使用 python -m py_compile C:\Users\wyy\Desktop\My_test.py 生成 .pyc

攻防世界-bad_python6.png

成功后,会在 .py 文件夹下生成一个 __pycache__ 文件夹

攻防世界-bad_python7.png

查看自己生成的 pyc 文件的文件头,正常

攻防世界-bad_python8.png

用正常的文件头数据替换掉损坏文件的第一行,再次编译,编译成功

攻防世界-bad_python9.png

将代码复制出来:

from ctypes import *
from Crypto.Util.number import bytes_to_long
from Crypto.Util.number import long_to_bytes
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)

逻辑就是将 flag 每 4 字节进行 bytes_to_long() 的转换
然后每相邻的两个 4 字节的 bytes_to_long() 结果为一组进行加密处理
加密过程比较类似于 tea 算法,对循环过程进行逆向解密即可


脚本

from ctypes import *  
from Crypto.Util.number import bytes_to_long  
from Crypto.Util.number import long_to_bytes  


def decrypt(v, k):  
    v0 = c_uint32(v[0])
    v1 = c_uint32(v[1])
    sum1 = c_uint32(0)  
    delta = 195935983  
    sum1.value = delta * 32  
    for i in range(32):  
        v1.value -= (v0.value << 4 ^ v0.value >> 7) + v0.value ^ sum1.value + k[(sum1.value >> 9 & 3)]  
        sum1.value -= delta  
        v0.value -= (v1.value << 4 ^ v1.value >> 7) + v1.value ^ sum1.value + k[(sum1.value & 3)]  

    return v0.value, v1.value  


enc = [4006073346, 2582197823,  
       2235293281, 558171287,  
       2425328816, 1715140098,  
       986348143, 1948615354]  

k = [255, 187, 51, 68]  

b = []  
for i in range(0, 8, 2):  
    b += decrypt([enc[i], enc[i + 1]], k)  

print(b)  
# [1416114547, 1597076319, 1096762721, 1937334096, 2037671984, 1851744082, 863397202, 1936023344]  

flag = b''  
for i in range(0, 8):  
        v = long_to_bytes(b[i])
        print(v, end='     ')  
        flag += v 

print()  
print(flag)  
# b'Th1s_1s_A_Easy_Pyth0n__R3veRse_0'

结果

Th1s_1s_A_Easy_Pyth0n__R3veRse_0

攻防世界-bad_python10.png