pyc逆向以及DASCTF里的ezpython复现

发布时间 2023-11-28 22:38:51作者: yuhury

pyc文件结构分析

pyc文件是Python在解释执行源代码时生成的字节码文件,可以直接由Python虚拟机执行。重点了解了下文件头,还不太了解其他部分怎么出题。
参考python pyc 加花指令

pyc文件头

参考
深入理解 python 虚拟机:pyc 文件结构
Python代码保护 | pyc 混淆从入门到工具实现
magic number + 源代码文件信息 + PyCodeObject

  • Magic Number  4字节
  • Bit Field      4字节
  • 最后修改日期    4字节
  • 文件大小      4字节
  • CodeObject

magic number
可以直接从python库中找到。具体的路径是:Python根目录\Lib_bootstrap_external.py文件中。如果不清楚路径的话可以通过图中的命令查询。
image
因为magic number生成的方法在importlib库中的util文件中,通过util中的记录可以找到magic number是在同文件夹下的_bootstrap_external.py中存放
关于magic number更详细的信息可以参考Python Uncompyle6 反编译工具使用 与 Magic Number 详解

Bit Field一般全0(至少python 3.11中是这样),最后修改日期和文件大小对反汇编无影响。修复文件头关键是修复Magic Number。值得注意,一旦能进行反汇编,不论是否完全,都说明此时修复的magic number正确,反汇编不完全是后面字节码的问题

pyc逆向

鉴于有好用的工具,优先考虑使用工具反编译出源码(而非硬看字节码)常见的工具有uncompyle6, pycdc。二者的使用此处不再说明,不过更推荐pycdc,它支持更高Python版本(目前已支持到3.11),而且pycdc工程还提供了pycdas用于反汇编将pyc文件转换成字节码,贴个地址吧。pycdc | github

DASCTF2023 11月月赛ezpython

提示了用python3.11编译,因此uncompyle不支持,只能用pycdc。用010editor打开后发现文件头被覆盖成key:yuanshen,找了py3.11最新版本的magic number填上后可以反编译出源码,但是不完全。pycdc还原出的源码如下:

点击查看代码
#!/usr/bin/env python
# visit https://tool.lu/pyc/ for more information
# Version: Python 3.11

import pyDes

def adjust_length(str):
    if len(str) < 8:
        str = str.ljust(8, '0')
    elif len(str) > 8:
        str = str[:8]
    return str


def yuanshen(array, start, end):
    num = len(array)
    dis = [
        float('inf')] * num
    tree = [
        False] * num
    parent = [
        -1] * num
    dis[start] = 0
# WARNING: Decompyle incomplete


def qidong(input, key, IV):
    cipher = pyDes.des(key, pyDes.CBC, IV, pad = None, padmode = pyDes.PAD_PKCS5)
    encrypted_data = cipher.encrypt(input)
    encrypted_hex_list = encrypted_data()
    return encrypted_hex_list


def main():
    data = [
        159,
        41,
        201,
        125,
        67,
        60,
        44,
        34,
        203,
        56,
        116,
        186,
        13,
        71,
        125,
        30,
        84,
        123,
        109,
        54,
        106,
        56,
        17,
        124,
        87,
        236,
        25,
        12,
        80,
        178,
        165,
        123]
    key = input('请输入key: ')
    if len(key) != 8:
        print('wrong key lenth!')
        exit()
    flag = input('请输入flag: ')
    array = [
        [
            0,
            float('inf'),
            float('inf'),
            1,
            3,
            4,
            float('inf'),
            float('inf'),
            float('inf')],
        [
            float('inf'),
            0,
            float('inf'),
            float('inf'),
            float('inf'),
            2,
            float('inf'),
            4,
            float('inf')],
        [
            float('inf'),
            float('inf'),
            0,
            8,
            1,
            float('inf'),
            float('inf'),
            float('inf'),
            1],
        [
            1,
            float('inf'),
            8,
            0,
            3,
            5,
            1,
            2,
            float('inf')],
        [
            3,
            float('inf'),
            1,
            3,
            0,
            float('inf'),
            1,
            5,
            3],
        [
            4,
            2,
            float('inf'),
            5,
            float('inf'),
            0,
            float('inf'),
            1,
            float('inf')],
        [
            float('inf'),
            float('inf'),
            float('inf'),
            1,
            1,
            float('inf'),
            0,
            float('inf'),
            5],
        [
            float('inf'),
            4,
            float('inf'),
            2,
            5,
            1,
            5,
            0,
            float('inf')],
        [
            float('inf'),
            float('inf'),
            1,
            float('inf'),
            3,
            float('inf'),
            float('inf'),
            float('inf'),
            0]]
    t = yuanshen(array, 1, 8)
    IV = (lambda .0: pass# WARNING: Decompyle incomplete
)(t())
    IV = adjust_length(IV)
    check = qidong(flag, key, IV)
    if check == data:
        print('yes,yes,yes!!')
        return None
    ''.join('bad,bad,bad!!')

main()

提示有字节码不能识别,需要手动修复。找到对应位置看pycdas反汇编出的字节码可以慢慢修出来。最重要的是修复关键函数“yuanshen”,个人修复的结果如下

点击查看代码
# Source Generated with Decompyle++
# File: ezpython.pyc (Python 3.11)

import pyDes

def adjust_length(str):
    if len(str) < 8:
        str = str.ljust(8, '0')
    elif len(str) > 8:
        str = str[:8]
    return str


def yuanshen(array, start, end)://缺失函数
    num = len(array)
    dis = [float('inf')] * num
    tree = [False] * num
    parent = [-1] * num
    dis[start] = 0
    for _ in range(num):
        min_dis=float('inf')
        min_index=-1
        for v in range(num):
            if(tree[v]):
                continue
            if(dis[v]>=min_dis):
                continue
            else:
                min_dis=dis[v]
                min_index=v
        if(min_index!=-1):
            tree[min_index]=True
            for v in range(num):
                if(tree[v]):
                    continue
                if(array[min_index][v]!=float('inf')):
                    if(dis[min_index]+array[min_index][v]<dis[v]):
                        dis[v]=dis[min_index]+array[min_index][v]
                        parent[v]=min_index
                    else:
                        continue
                else:
                    continue
    if(dis[end]==float('inf')):
        return 0
    else:
        t=[]
        current=end
        while((current!=-1)):
            t.append(current)
            current=parent[current]
        
        
        return t[::-1]

def qidong(input, key, IV):
    cipher = pyDes.des(key, pyDes.CBC, IV, pad = None, padmode = pyDes.PAD_PKCS5)
    encrypted_data = cipher.encrypt(input)
    encrypted_hex_list = encrypted_data()
    return encrypted_hex_list


def main():
    data = [
        159,
        41,
        201,
        125,
        67,
        60,
        44,
        34,
        203,
        56,
        116,
        186,
        13,
        71,
        125,
        30,
        84,
        123,
        109,
        54,
        106,
        56,
        17,
        124,
        87,
        236,
        25,
        12,
        80,
        178,
        165,
        123]
    key = input('请输入key: ')
    if len(key) != 8:
        print('wrong key lenth!')
        exit()
    flag = input('请输入flag: ')
    array = [
        [
            0,
            float('inf'),
            float('inf'),
            1,
            3,
            4,
            float('inf'),
            float('inf'),
            float('inf')],
        [
            float('inf'),
            0,
            float('inf'),
            float('inf'),
            float('inf'),
            2,
            float('inf'),
            4,
            float('inf')],
        [
            float('inf'),
            float('inf'),
            0,
            8,
            1,
            float('inf'),
            float('inf'),
            float('inf'),
            1],
        [
            1,
            float('inf'),
            8,
            0,
            3,
            5,
            1,
            2,
            float('inf')],
        [
            3,
            float('inf'),
            1,
            3,
            0,
            float('inf'),
            1,
            5,
            3],
        [
            4,
            2,
            float('inf'),
            5,
            float('inf'),
            0,
            float('inf'),
            1,
            float('inf')],
        [
            float('inf'),
            float('inf'),
            float('inf'),
            1,
            1,
            float('inf'),
            0,
            float('inf'),
            5],
        [
            float('inf'),
            4,
            float('inf'),
            2,
            5,
            1,
            5,
            0,
            float('inf')],
        [
            float('inf'),
            float('inf'),
            1,
            float('inf'),
            3,
            float('inf'),
            float('inf'),
            float('inf'),
            0]]
    t = yuanshen(array, 1, 8)
    IV=""//修复时额外添加
    for i in t:
        IV.join(str(i))//缺失部分
    IV = adjust_length(IV)
    check = qidong(flag, key, IV)
    if check == data:
        print('yes,yes,yes!!')
        return None
    ''.join('bad,bad,bad!!')

main()

再贴下官方wp给出的源码吧
点击查看代码
# 初始化变量
num = len(array)
dis = float('inf')
tree = [False] * num
parent = [-1] * num

# 设置起始位置
start[0] = 0//疑似写错?

# 遍历范围
for _ in range(num):
    min_dis = float('inf')
    min_index = -1

    # 遍历节点
    for v in range(num):
        # 如果节点未被访问
        if not tree[v]:
            # 计算距离
            dist_v = dis[v]
            
            # 如果距离小于当前最小距离
            if dist_v < min_dis:
                min_dis = dist_v
                min_index = v

    # 标记节点为已访问
    tree[min_index] = True

    # 更新距离和父节点
    for v in range(num):
        if not tree[v]:
            new_dis = dis[min_index] + array[min_index][v]

            if new_dis < dis[v]:
                dis[v] = new_dis
                parent[v] = min_index

# 构建路径
current = end
t = []
while current != -1:
    t.append(current)
    current = parent[current]

# 反转路径
t = t[::-1]

# 返回路径
return t

此处不得不赞美gpt,修复好后把代码丢进去就能识别出是求图中起始点到终点最短路径的dijkstra算法
image

无向图是在array中用邻接矩阵的方式表示
image
可以根据邻接矩阵画图直接找最短路径:15736428
image
以上两图都转自官方wp
最后得出key是yuanshen,IV是15736428,用cyberchef即可解出flag。
hint:在des的cbc模式下,IV只影响前八位的取值,而flag的前七位已知,因此只需要爆破一位即可

花指令干扰pyc反编译

下篇更,光看了原理还没上手做