网鼎杯第四场逆向题, 死磕python字节码, 手工还原python源码!

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

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

dis.dis() 将CPython字节码转为可读的伪代码(类似于汇编代码)。结构如下:(私信小编007自动获取大量Python学习资料)

如果你感觉学不会?莫慌,小编推荐大家加入群,

前面548中间377后面875,群里有志同道合的小伙伴,

互帮互助,还可以拿到许多视频教程!

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.变量

扫描二维码关注公众号,回复: 3088893 查看本文章

1.const

LOAD_CONST 加载 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的方式如下:

[x for x in xlist if x!=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代码是:

[for x in sieve if x != 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=024 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=024 6 LOAD_FAST 0 (i) 9 LOAD_CONST 2 (5) 12 COMPARE_OP 0 (<) 15 POP_JUMP_IF_FALSE 2625 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 4627 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 = 0if 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 3355 ...67 >> 139 LOAD_FAST 2 (fs) 142 RETURN_VALUE70 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_xxxi = i + 1 //使用BINARY_xxxx

猜你喜欢

转载自blog.csdn.net/qq_41841569/article/details/82430003