首页
社区
课程
招聘
5
[原创]死磕python字节码-手工还原python源码
发表于: 2018-9-6 11:11 17059

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

2018-9-6 11:11
17059

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:


[招生]科锐逆向工程师培训(2025年3月11日实地,远程教学同时开班, 第52期)!

收藏
免费 5
支持
分享
赞赏记录
参与人
雪币
留言
时间
Jmp.Cliff
为你点赞~
2023-9-8 14:28
PLEBFE
为你点赞~
2022-7-27 01:56
心游尘世外
为你点赞~
2022-7-26 23:56
wngbx
为你点赞~
2020-11-30 12:39
道破红尘
为你点赞~
2020-9-12 21:48
最新回复 (9)
雪    币: 939
活跃值: (1079)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
Mark
2018-9-6 15:40
0
雪    币: 97
活跃值: (86)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
如果opcode没有被改过, 好像有工具直接可以还原出源码
2018-9-6 16:27
0
雪    币: 156
活跃值: (107)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
你博客访问不了
2018-9-6 16:28
0
雪    币: 10072
活跃值: (3029)
能力值: ( LV15,RANK:515 )
在线值:
发帖
回帖
粉丝
5
LiDogEgg 如果opcode没有被改过, 好像有工具直接可以还原出源码
什么工具?
2018-9-7 09:52
0
雪    币: 10072
活跃值: (3029)
能力值: ( LV15,RANK:515 )
在线值:
发帖
回帖
粉丝
6
oopww 你博客访问不了
https://anhkgg.github.io  https://anhkgg.com
2018-9-7 09:53
0
雪    币: 1343
活跃值: (2290)
能力值: ( LV5,RANK:75 )
在线值:
发帖
回帖
粉丝
7
Angelxf 什么工具?
应该指的是uncompyle2,如果opcode没被替换,可以直接反编译出源码,
楼主这篇文档对于理解python汇编和处理opcode替换过的pyc文件比较有用,收藏了
最后于 2018-9-7 14:53 被茅山小僧编辑 ,原因:
2018-9-7 14:53
0
雪    币: 10072
活跃值: (3029)
能力值: ( LV15,RANK:515 )
在线值:
发帖
回帖
粉丝
8
茅山小僧 Angelxf 什么工具? 应该指的是uncompyle2,如果opcode没被替换,可以直接反编译出源码,楼主这篇文档对于理解python汇编和处理o ...
uncompyle2只能用于98 00 11这种bytecode还原,伪代码需手工转为bytecode字节码才能使用uncompyle2
2018-9-7 16:18
0
雪    币: 58
活跃值: (1850)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
9
写的已经非常的全了。非常好。
2020-9-12 21:48
0
雪    币: 66
活跃值: (3047)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
这和.NET的MSIL一样,已经近似于高级语言了。
2020-9-14 15:07
0
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册