• 【转载】死磕python字节码手工还原python源码


    0x1.前言

    Python 代码先被编译为字节码后,再由Python虚拟机来执行字节码, Python的字节码是一种类似汇编指令的中间语言, 一个Python语句会对应若干字节码指令,虚拟机一条一条执行字节码指令, 从而完成程序执行。
    Python dis 模块支持对Python代码进行反汇编, 生成字节码指令。

     

    dis.dis()将CPython字节码转为可读的伪代码(类似于汇编代码)。结构如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    7           0 LOAD_CONST               1 (0)
                3 STORE_FAST               1 (local1)
     
    8           6 LOAD_CONST               2 (101)
                9 STORE_GLOBAL             0 (global1)
     
    9          12 LOAD_FAST                1 (local1)
               15 PRINT_ITEM
               16 LOAD_FAST                0 (arg1)
               19 PRINT_ITEM
               20 LOAD_GLOBAL              0 (global1)
               23 PRINT_ITEM
               24 PRINT_NEWLINE
               25 LOAD_CONST               0 (None)
               28 RETURN_VALUE

    其实就是这样的结构:

    1
    源码行号 | 指令在函数中的偏移 | 指令符号 | 指令参数 | 实际参数值

    0x2.变量

    1.const

    LOAD_CONST加载const变量,比如数值、字符串等等,一般用于传给函数的参数

    1
    2
    3
    4
    55       12 LOAD_GLOBAL              1 (test)
             15 LOAD_FAST                0 (2) #读取2
             18 LOAD_CONST               1 ('output')
             21 CALL_FUNCTION            2

    转为python代码就是:

    1
    test(2, 'output')

    2.局部变量

    LOAD_FAST一般加载局部变量的值,也就是读取值,用于计算或者函数调用传参等。
    STORE_FAST一般用于保存值到局部变量。

    1
    2
    3
    4
    61          77 LOAD_FAST                0 (n)
                 80 LOAD_FAST                3 (p)
                 83 INPLACE_DIVIDE
                 84 STORE_FAST               0 (n)

    这段bytecode转为python就是:

    1
    n = n / p

    函数的形参也是局部变量,如何区分出是函数形参还是其他局部变量呢?

     

    形参没有初始化,也就是从函数开始到LOAD_FAST该变量的位置,如果没有看到STORE_FAST,那么该变量就是函数形参。

     

    而其他局部变量在使用之前肯定会使用STORE_FAST进行初始化。

     

    具体看下面的实例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    4           0 LOAD_CONST               1 (0)
                3 STORE_FAST               1 (local1)
     
    5           6 LOAD_FAST                1 (local1)
                9 PRINT_ITEM
               10 LOAD_FAST                0 (arg1)
               13 PRINT_ITEM
               14 PRINT_NEWLINE
               15 LOAD_CONST               0 (None)
               18 RETURN_VALUE

    对应的python代码如下,对比一下就一目了然。

    1
    2
    3
    def test(arg1):
        local1 = 0
        print local1, arg1

    3.全局变量

    LOAD_GLOBAL用来加载全局变量,包括指定函数名,类名,模块名等全局符号。

     

    STORE_GLOBAL用来给全局变量赋值。

    1
    2
    3
    4
    8           6 LOAD_CONST               2 (101)
                9 STORE_GLOBAL             0 (global1)
                20 LOAD_GLOBAL              0 (global1)
                23 PRINT_ITEM

    对应的python代码

    1
    2
    3
    4
    def test():
        global global1
        global1 = 101
        print global

    0x3.常用数据类型

    1.list

    BUILD_LIST用于创建一个list结构。

    1
    2
    3
    4
    13           0 LOAD_CONST               1 (1)
                 3 LOAD_CONST               2 (2)
                 6 BUILD_LIST               2
                 9 STORE_FAST               0 (k)

    对应python代码是:

    1
    k = [1, 2]

    另外再看看一种常见的创建list的方式如下:

    1
    [x for x in xlist if x!=0 ]

    一个实例bytecode如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    22         235 BUILD_LIST               0 //创建list,为赋值给某变量,这种时候一般都是语法糖结构了
               238 LOAD_FAST                3 (sieve)
               241 GET_ITER
           >>  242 FOR_ITER                24 (to 269)
               245 STORE_FAST               4 (x)
               248 LOAD_FAST                4 (x)
               251 LOAD_CONST               2 (0)
               254 COMPARE_OP               3 (!=)
               257 POP_JUMP_IF_FALSE      242 //不满足条件contine
               260 LOAD_FAST                4 (x)//读取满足条件的x
               263 LIST_APPEND              2 //把每个满足条件的x存入list
               266 JUMP_ABSOLUTE          242
           >>  269 RETURN_VALUE

    转为python代码是:

    1
    [for x in sieve if x != 0]

    2.dict

    BUILD_MAP用于创建一个空的dict。STORE_MAP用于初始化dict的内容。

    1
    2
    3
    4
    5
    13           0 BUILD_MAP                1
                 3 LOAD_CONST               1 (1)
                 6 LOAD_CONST               2 ('a')
                 9 STORE_MAP
                10 STORE_FAST               0 (k)

    对应的python代码是:

    1
    k = {'a': 1}

    再看看修改dict的bytecode:

    1
    2
    3
    4
    14          13 LOAD_CONST               3 (2)
                 16 LOAD_FAST                0 (k)
                 19 LOAD_CONST               4 ('b')
                 22 STORE_SUBSCR

    对应的python代码是:

    1
    k['b'] = 2

    3.slice

    BUILD_SLICE用于创建slice。对于list、元组、字符串都可以使用slice的方式进行访问。

     

    但是要注意BUILD_SLICE用于[x:y:z]这种类型的slice,结合BINARY_SUBSCR读取slice的值,结合STORE_SUBSCR用于修改slice的值。

     

    另外SLICE+n用于[a:b]类型的访问,STORE_SLICE+n用于[a:b]类型的修改,其中n表示如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    SLICE+0()
    Implements TOS = TOS[:].
     
    SLICE+1()
    Implements TOS = TOS1[TOS:].
     
    SLICE+2()
    Implements TOS = TOS1[:TOS].
     
    SLICE+3()
    Implements TOS = TOS2[TOS1:TOS].

    下面看具体实例:

    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
    13           0 LOAD_CONST               1 (1)
                  3 LOAD_CONST               2 (2)
                  6 LOAD_CONST               3 (3)
                  9 BUILD_LIST               3
                 12 STORE_FAST               0 (k1) //k1 = [1, 2, 3]
     
     14          15 LOAD_CONST               4 (10)
                 18 BUILD_LIST               1
                 21 LOAD_FAST                0 (k1)
                 24 LOAD_CONST               5 (0)
                 27 LOAD_CONST               1 (1)
                 30 LOAD_CONST               1 (1)
                 33 BUILD_SLICE              3
                 36 STORE_SUBSCR                    //k1[0:1:1] = [10]
     
     15          37 LOAD_CONST               6 (11)
                 40 BUILD_LIST               1
                 43 LOAD_FAST                0 (k1)
                 46 LOAD_CONST               1 (1)
                 49 LOAD_CONST               2 (2)
                 52 STORE_SLICE+3                   //k1[1:2] = [11]
     
     16          53 LOAD_FAST                0 (k1)
                 56 LOAD_CONST               1 (1)
                 59 LOAD_CONST               2 (2)
                 62 SLICE+3
                 63 STORE_FAST               1 (a)  //a = k1[1:2]
     
     17          66 LOAD_FAST                0 (k1)
                 69 LOAD_CONST               5 (0)
                 72 LOAD_CONST               1 (1)
                 75 LOAD_CONST               1 (1)
                 78 BUILD_SLICE              3
                 81 BINARY_SUBSCR
                 82 STORE_FAST               2 (b) //b = k1[0:1:1]

    0x4.循环

    SETUP_LOOP用于开始一个循环。SETUP_LOOP 26 (to 35)35表示循环退出点。

    while循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    23           0 LOAD_CONST               1 (0)
                 3 STORE_FAST               0 (i) // i=0
     
    24           6 SETUP_LOOP              26 (to 35)
           >>    9 LOAD_FAST                0 (i) //循环起点
                12 LOAD_CONST               2 (10)
                15 COMPARE_OP               0 (<)
                18 POP_JUMP_IF_FALSE       34     //while i < 10:
     
    25          21 LOAD_FAST                0 (i)
                24 LOAD_CONST               3 (1)
                27 INPLACE_ADD                    
                28 STORE_FAST               0 (i) // i += 1
                31 JUMP_ABSOLUTE            9    // 回到循环起点
           >>   34 POP_BLOCK
           >>   35 LOAD_CONST               0 (None)

    对应python代码是:

    1
    2
    3
    i = 0
        while i < 10:
            i += 1

    for in结构

    1
    2
    3
    4
    5
    6
        238 LOAD_FAST                3 (sieve)#sieve是个list
        241 GET_ITER                    //开始迭代sieve
    >>  242 FOR_ITER                24 (to 269) //继续iter下一个x
        245 STORE_FAST               4 (x)
        ...
        266 JUMP_ABSOLUTE          242 //循环

    这是典型的for+in结构,转为python代码就是:

    1
    for x in sieve:

    0x5.if

    POP_JUMP_IF_FALSEJUMP_FORWARD一般用于分支判断跳转。POP_JUMP_IF_FALSE表示条件结果为FALSE就跳转到目标偏移指令。JUMP_FORWARD直接跳转到目标偏移指令。

    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
    23           0 LOAD_CONST               1 (0)
                 3 STORE_FAST               0 (i) //i=0
    24           6 LOAD_FAST                0 (i)
                 9 LOAD_CONST               2 (5)
                12 COMPARE_OP               0 (<)
                15 POP_JUMP_IF_FALSE       26
     
    25          18 LOAD_CONST               3 ('i < 5')
                21 PRINT_ITEM
                22 PRINT_NEWLINE
                23 JUMP_FORWARD            25 (to 51)
     
    26     >>   26 LOAD_FAST                0 (i)
                29 LOAD_CONST               2 (5)
                32 COMPARE_OP               4 (>)
                35 POP_JUMP_IF_FALSE       46
     
    27          38 LOAD_CONST               4 ('i > 5')
                41 PRINT_ITEM
                42 PRINT_NEWLINE
                43 JUMP_FORWARD             5 (to 51)
     
    29     >>   46 LOAD_CONST               5 ('i = 5')
                49 PRINT_ITEM
                50 PRINT_NEWLINE
           >>   51 LOAD_CONST               0 (None)

    转为python代码是:

    1
    2
    3
    4
    5
    6
    7
    i = 0
    if i < 5:
        print 'i < 5'
    elif i > 5:
        print 'i > 5'
    else:
        print 'i = 5'

    0x6.分辨函数

    1.函数范围

    前面介绍第二列表示指令在函数中的偏移地址,所以看到0就是函数开始,下一个0前一条指令就是函数结束位置,当然也可以通过RETURN_VALUE来确定函数结尾

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    54         0 LOAD_FAST                1 (plist) //函数开始
               3 LOAD_CONST               0 (None)
               6 COMPARE_OP               2 (==)
               9 POP_JUMP_IF_FALSE        33
     
    55         ...
     
    67     >>  139 LOAD_FAST              2 (fs)
               142 RETURN_VALUE
    70         0 LOAD_CONST               1 ('FLAG') //另一个函数开始
               3 STORE_FAST               0 (flag)

    2.函数调用

    函数调用类似于push+call的汇编结构,压栈参数从左到右依次压入(当然不是push,而是读取指令LOAD_xxxx来指定参数)。

     

    函数名一般通过LOAD_GLOBAL指令指定,如果是模块函数或者类成员函数通过LOAD_GLOBAL+LOAD_ATTR来指定。

     

    先指定要调用的函数,然后压参数,最后通过CALL_FUNCTION调用。

     

    CALL_FUNCTION后面的值表示有几个参数。

     

    支持嵌套调用:

    1
    2
    3
    4
    5
    6
    7
    6           0 LOAD_GLOBAL              0 (int) //int函数
                  3 LOAD_GLOBAL              1 (math)//math模块
                  6 LOAD_ATTR                2 (sqrt)//sqrt函数
                  9 LOAD_FAST                0 (n) //参数
                 12 CALL_FUNCTION            1
                 15 CALL_FUNCTION            1
                 18 STORE_FAST               2 (nroot)

    这段bytecode转换成python代码就是

    1
    nroot = int(math.sqrt(n)) //其中n是一个局部变量或者函数参数,具体看上下文

    0x7.其他指令

    其他常见指令,一看就明白,就不具体分析了,更多详细内容请看官方文档

    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
    INPLACE_POWER()
    Implements in-place TOS = TOS1 ** TOS.
     
    INPLACE_MULTIPLY()
    Implements in-place TOS = TOS1 * TOS.
     
    INPLACE_DIVIDE()
    Implements in-place TOS = TOS1 / TOS when from __future__ import division is not in effect.
     
    INPLACE_FLOOR_DIVIDE()
    Implements in-place TOS = TOS1 // TOS.
     
    INPLACE_TRUE_DIVIDE()
    Implements in-place TOS = TOS1 / TOS when from __future__ import division is in effect.
     
    INPLACE_MODULO()
    Implements in-place TOS = TOS1 % TOS.
     
    INPLACE_ADD()
    Implements in-place TOS = TOS1 + TOS.
     
    INPLACE_SUBTRACT()
    Implements in-place TOS = TOS1 - TOS.
     
    INPLACE_LSHIFT()
    Implements in-place TOS = TOS1 << TOS.
     
    INPLACE_RSHIFT()
    Implements in-place TOS = TOS1 >> TOS.
     
    INPLACE_AND()
    Implements in-place TOS = TOS1 & TOS.
     
    INPLACE_XOR()
    Implements in-place TOS = TOS1 ^ TOS.
     
    INPLACE_OR()
    Implements in-place TOS = TOS1 | TOS.

    基础运算还有一套对应的BINARY_xxxx指令,两者区别很简单。

    1
    2
    i += 1 //使用INPLACE_xxx
    i = i + 1 //使用BINARY_xxxx

    参考资料

    1. python dis官方文档
    2. google搜索dis指令
    3. https://github.com/vstinner/bytecode
    4. https://blog.hakril.net/articles/2-understanding-python-execution-tracer.html
    5. A Python Interpreter Written in Python
    6. https://blog.csdn.net/qs9816/article/details/51661659
    7. https://github.com/Mysterie/uncompyle2

    本文转载来源:https://bbs.pediy.com/thread-246683.htm

  • 相关阅读:
    PDA设备小知识--(IP)工业防护等级含义
    实施项目--如何推进项目实施进度
    Git.Framework 框架随手记--存储过程简化
    Git.Framework 框架随手记--SQL配置文件的使用
    Git.Framework 框架随手记--ORM查询数据集合 二
    介绍 Scratch 3.0:扩展编码创造力
    微服务架构:引领数字化转型的基石
    网易云技术开放日 云安全专场分享圆满结束
    JVM调优推荐
    springboot + mybatis + 多数据源
  • 原文地址:https://www.cnblogs.com/gisen_6/p/15786379.html
Copyright © 2020-2023  润新知