死磕python字节码-手工还原python源码


死磕python字节码-手工还原python源码_第1张图片

0x1.前言

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

Python dis 模块支持对Python代码进行反汇编, 生成字节码指令。

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

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

其实就是这样的结构:

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


0x2.变量

1.const

55      12 LOAD_GLOBAL              1 (test)

        15 LOAD_FAST                0 (2) #读取2

        18 LOAD_CONST              1 ('output')

        21 CALL_FUNCTION            2

转为python代码就是:

test(2,'output')


2.局部变量

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

STORE_FAST一般用于保存值到局部变量。

61          77 LOAD_FAST                0 (n)

            80 LOAD_FAST                3 (p)

            83 INPLACE_DIVIDE

            84 STORE_FAST              0 (n)

这段bytecode转为python就是:

n = n / p

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

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

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

具体看下面的实例:

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代码如下,对比一下就一目了然。

def test(arg1):

    local1 = 0

    print local1, arg1


3.全局变量

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

STORE_GLOBAL用来给全局变量赋值。

8          6 LOAD_CONST              2 (101)

            9 STORE_GLOBAL            0 (global1)

            20 LOAD_GLOBAL              0 (global1)

            23 PRINT_ITEM

对应的python代码

def test():

    global global1

    global1 = 101

    print global


0x3.常用数据类型

1.list

BUILD_LIST用于创建一个list结构。

13          0 LOAD_CONST              1 (1)

            3 LOAD_CONST              2 (2)

            6 BUILD_LIST              2

            9 STORE_FAST              0 (k)

对应python代码是:

k=[1,2]

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

[xforxinxlistifx!=0]

一个实例bytecode如下:

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代码是:

[forxinsieveifx !=0]

2.dict

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

13          0 BUILD_MAP                1

            3 LOAD_CONST              1 (1)

            6 LOAD_CONST              2 ('a')

            9 STORE_MAP

            10 STORE_FAST              0 (k)

对应的python代码是:

k={'a':1}

再看看修改dict的bytecode:

14          13 LOAD_CONST              3 (2)

            16 LOAD_FAST                0 (k)

            19 LOAD_CONST              4 ('b')

            22 STORE_SUBSCR

对应的python代码是:

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表示如下:

SLICE+0()

Implements TOS = TOS[:].

SLICE+1()

Implements TOS = TOS1[TOS:].

SLICE+2()

Implements TOS = TOS1[:TOS].

SLICE+3()

Implements TOS = TOS2[TOS1:TOS].

下面看具体实例:

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循环

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代码是:

i = 0

    while i < 10:

        i += 1

for in结构

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代码就是:

for x in sieve:

0x5.if

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

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代码是:

i = 0

if i < 5:

    print 'i < 5'

elif i > 5:

    print 'i > 5'

else:

    print 'i = 5'


0x6.分辨函数

1.函数范围

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

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后面的值表示有几个参数。

支持嵌套调用:

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代码就是:

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


0x7.其他指令

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

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指令,两者区别很简单。

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


参考资料

python dis官方文档

google搜索dis指令

https://github.com/vstinner/bytecode

https://blog.hakril.net/articles/2-understanding-python-execution-tracer.html

A Python Interpreter Written in Python

https://blog.csdn.net/qs9816/article/details/51661659



原文作者:Angelxf

原文链接:https://bbs.pediy.com/thread-246683.htm


看雪阅读推荐:

1、[原创]小白文——好玩不贵的无线遥控器克隆指南

2、[翻译]内核模式Rootkits:文件删除保护

3、[翻译]国外2018最新区块链教程英文版,大胆翻译,助力论坛『区块链安全』开设第四棒!

4、[原创]看雪安全峰会—《从WPA2四次握手看KRACK密钥重装攻击》

5、[原创]2018看雪CTF第十五题WP

你可能感兴趣的:(死磕python字节码-手工还原python源码)