Day17:python逆向

pyc逆向,python可执行文件逆向

pyc文件

  • pyc文件是py文件编译过程中产生的中间文件,是一种二进制文件。pyc文件可以由python虚拟机直接执行。不同版本的python编译出来的pyc文件是不同的

pyc文件结构

  • 一个pyc文件由以下几个部分组成:

    pyc文件格式(图片截取自CTF wiki)

    其中CodeObject是经过序列化处理的python源码的二进制码

pyc字节码

  • pyc文件是二进制文件,因此也可以进行反汇编。pyc文件有专门的汇编代码,就像java有smali汇编一样。

  • 下面是一个pyc文件的反汇编代码

    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
    原代码行号|指令的偏移|指令符号|指令参数(索引值)|参数实际值	     
    0 0 RESUME 0

    1 2 LOAD_CONST 0 (<code object add at 0x00000186246D3770, file "test.py", line 1>)
    4 MAKE_FUNCTION 0
    6 STORE_NAME 0 (add)//把add函数存入函数表,给索引值0

    3 8 PUSH_NULL
    10 LOAD_NAME 1 (print)//加载函数表索引为1的函数print
    12 LOAD_CONST 1 ('hello python')//加载常量表索引为1的"hello python"
    14 CALL 1//call索引为1的函数print
    22 POP_TOP

    4 24 PUSH_NULL
    26 LOAD_NAME 1 (print)
    28 PUSH_NULL
    30 LOAD_NAME 0 (add)
    32 LOAD_CONST 2 (1)
    34 LOAD_CONST 3 (2)
    36 CALL 2
    44 CALL 1
    52 POP_TOP
    54 RETURN_CONST 4 (None)

    Disassembly of <code object add at 0x00000186246D3770, file "test.py", line 1>:
    1 0 RESUME 0

    2 2 LOAD_FAST 0 (a)
    4 LOAD_FAST 1 (b)
    6 BINARY_OP 0 (+)
    10 RETURN_VALUE

    对上面的一些指令进行解释:

    • LOAD_CONST用于加载常量,后面跟着的数字是常量在常量表中的索引
    • LOAD_NAME用于加载函数,后面跟着的数字是函数在函数表中的索引
    • LOAD_FAST用于加载局部变量(如函数定义时候的参数或者内部变量)
    • 可以注意到LOAD_NAME总是先于LOAD_CONST的,即函数比参数先一步加载
    • python虚拟机是基于栈的,所以可以看到POP等字眼,LOAD系列操作实际上是入栈操作

pyc文件逆向

python可执行文件

  • python文件可以被打包成可执行文件,默认图标长这样:

    图标

  • 对于此类问题,先使用pyinstxtractor解包程序(这里拿一个不知道哪里来的pythonexe文件举例):

    解包

    pyinstxtractor会把可执行文件解包到带后缀extracted的一个文件夹里,除此之外可以看到,程序会给出可能的入口文件,以及可执行文件的python版本,然后就可以分析这些pyc文件了

  • python文件被打包的时候还可以指定加密参数,解密的时候根据打包的pyinstaller版本可以分为两种,大于4.0的和小于4.0的,可以使用下面的脚本解密:

    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
    //大于4.0版本
    import tinyaes
    import zlib

    CRYPT_BLOCK_SIZE=16

    key = butes('here_be_your_key','utf-8')

    inf=open('here_be_your_file_name','rb')
    outf=open('here_be_your_output_file','wb')

    iv=inf.read(CRYPT_BLOCK_SIZE)

    cipher=tinyaes.AES(key,iv)

    plaintext=zlib.decompress(cipher.CTR_xcrypt_buffer(inf.read()))

    outf.write(b'\x55\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0')#here be your magic number from pyfile in the same dir,16bytes

    outf.write(plaintext)

    inf.close()
    outf.close()

    //小于4.0版本
    from Crypto.Cipher import AES
    import zlib

    CRYPT_BLOCK_SIZE=16

    key=b'here_be_your_key'

    inf=open('here_be_your_cryptedfile_name','rb')
    outf=open('here_be_your_output_file_name','wb')

    iv=inf.read(CRYPT_BLOCK_SIZE)

    cipher-AES.new(key,AES.MODE_CFB,iv)

    plaintext=zlib.decompress(cipher,hecrypt(inf.read()))

    outf.write('here_be_your_magicnumber')#8bytes

    outf.write(plaintext)

    inf.close()
    outf.close()

    代码里面的key可以找可疑的pyc文件转成python代码查看,而版本可以通过查看pyimod01_archive.pyc导入的加密库来识别(上面两个版本的脚本导入的加密库分别是tinyaes和AES,是不一样的)

作者

SydzI

发布于

2025-10-05

更新于

2025-10-05

许可协议

评论