Python 字节码与字节码混淆_python字节码混淆

做了那么多年开发,自学了很多门编程语言,我很明白学习资源对于学一门新语言的重要性,这些年也收藏了不少的Python干货,对我来说这些东西确实已经用不到了,但对于准备自学Python的人来说,或许它就是一个宝藏,可以给你省去很多的时间和精力。

别在网上瞎学了,我最近也做了一些资源的更新,只要你是我的粉丝,这期福利你都可拿走。

我先来介绍一下这些东西怎么用,文末抱走。


(1)Python所有方向的学习路线(新版)

这是我花了几天的时间去把Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

最近我才对这些路线做了一下新的更新,知识体系更全面了。

在这里插入图片描述

(2)Python学习视频

包含了Python入门、爬虫、数据分析和web开发的学习视频,总共100多个,虽然没有那么全面,但是对于入门来说是没问题的,学完这些之后,你可以按照我上面的学习路线去网上找其他的知识资源进行进阶。

在这里插入图片描述

(3)100多个练手项目

我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了,只是里面的项目比较多,水平也是参差不齐,大家可以挑自己能做的项目去练练。

在这里插入图片描述

(4)200多本电子书

这些年我也收藏了很多电子书,大概200多本,有时候带实体书不方便的话,我就会去打开电子书看看,书籍可不一定比视频教程差,尤其是权威的技术书籍。

基本上主流的和经典的都有,这里我就不放图了,版权问题,个人看看是没有问题的。

(5)Python知识点汇总

知识点汇总有点像学习路线,但与学习路线不同的点就在于,知识点汇总更为细致,里面包含了对具体知识点的简单说明,而我们的学习路线则更为抽象和简单,只是为了方便大家只是某个领域你应该学习哪些技术栈。

在这里插入图片描述

(6)其他资料

还有其他的一些东西,比如说我自己出的Python入门图文类教程,没有电脑的时候用手机也可以学习知识,学会了理论之后再去敲代码实践验证,还有Python中文版的库资料、MySQL和HTML标签大全等等,这些都是可以送给粉丝们的东西。

在这里插入图片描述

这些都不是什么非常值钱的东西,但对于没有资源或者资源不是很好的学习者来说确实很不错,你要是用得到的话都可以直接抱走,关注过我的人都知道,这些都是可以拿到的。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

a = 1
a += 1
print("aaa")
fun(1, 2, 3)
return

dis.dis(fun)


* 控制台输出内容如下
	+ 第一列对应的是源代码中的行号
	+ 第二列对应的是源代码转化成的字节码
	+ 第三列为此次操作对应的值(括号内为具体值)
* 例如第六行
	+ 解释器先读取了全局对象 `print` 函数,推入程序栈
	+ 程序又将字符串 `'aaa'` 推入程序栈
	+ 调用函数,并解释只有 1 个变量,解释器便会将栈顶的 1 个变量传递给函数,然后调用函数
		- **需要注意,如果有多个参数的话,参数入栈顺序是从左到右,也就是最右边的参数在最顶端**
		- `CALL_FUNCTION` 会在结束后弹出栈顶对应参数数量的元素,但是函数不会被弹出栈,因此最后有一个 `POP_TOP`



python3 -u “/Users/biox/NutStore/Codes/VSCode/python/py1.py”
4 0 LOAD_CONST 1 (1)
2 STORE_FAST 3 (a)

5 4 LOAD_FAST 3 (a)
6 LOAD_CONST 1 (1)
8 INPLACE_ADD
10 STORE_FAST 3 (a)

6 12 LOAD_GLOBAL 0 (print)
14 LOAD_CONST 2 (‘aaa’)
16 CALL_FUNCTION 1
18 POP_TOP

7 20 LOAD_GLOBAL 1 (fun)
22 LOAD_CONST 1 (1)
24 LOAD_CONST 3 (2)
26 LOAD_CONST 4 (3)
28 CALL_FUNCTION 3
30 POP_TOP

8 32 LOAD_CONST 0 (None)
34 RETURN_VALUE


### 常见指令


* 详细内容见[官方文档](https://bbs.csdn.net/topics/618317507)
* 一般指令与一元操作指令




| 指令 | 作用 |
| --- | --- |
| NOP | 无作用,用于占位 |
| POP\_TOP | 弹出栈顶元素 |
| LOAD\_CONST | 将读取的值推入栈 |
| LOAD\_GLOBAL | 将全局变量对象压入栈顶 |
| STORE\_FAST | 将栈顶指令存入对应局部变量 |
| COMPARE\_OP | 比较操作符 |
| CALL\_FUNCTION | 调用函数 |
| BUILD\_SLICE | 调用切片,跟的参数为切片的值的个数,一般从上到下为 [Val1:Val2:Val3] |
| JUMP\_ABSOLUTE | 向下跳转几句操作符,变量为跳转偏移量 |
| UNARY\_POSITIVE | 实现 Val1 = +Val1 |
| UNARY\_NEGATIVE | 实现 Val1 = -Val1 |
| UNARY\_NOT | 实现 Val1 = not Val1 |
| UNARY\_INVERT | 实现 Val1 = ~Val |
| FOR\_ITER | for 循环 |
| GET\_ITER | 获取迭代器(一般后面跟循环) |
| GET\_YIELD\_FROM\_ITER | 获取 yield 生成器 |


* 二元操作指令




| 指令 | 作用 |
| --- | --- |
| BINARY\_POWER | 乘方,栈顶数为指数 |
| BINARY\_MULTIPLY | 乘法 |
| BINARY\_MATRIX\_MULTIPLY | 矩阵乘法,3.5 引入的新功能 |
| BINARY\_FLOOR\_DIVIDE | 除法,结果向下取整 |
| BINARY\_TRUE\_DIVIDE | 除法 |
| BINARY\_MODULO | 取余 |
| BINARY\_ADD | 加法 |
| BINARY\_SUBTRACT | 减法 |
| BINARY\_SUBSCR | 数组取下标,栈顶为下标 |
| BINARY\_LSHIFT | 左移操作符(乘2) |
| BINARY\_RSHIFT | 右移操作符(除2向下取整) |
| BINARY\_AND | 按位与 |
| BINARY\_XOR | 异或 |
| BINARY\_OR | 按位或 |
| STORE\_SUBSCR | 列表下标存储,例如 Val1[Val2] = Val3 |
| DELETE\_SUBSCR | 按下标删除元素,例如 del Val1[Val2] |


* 自身操作指令,类似 `b += 1` ,就是上方有 BINARY 的指令的 BINARY 改成 INPLACE
* 其他指令见[官方文档](https://bbs.csdn.net/topics/618317507)


## Pyc 文件解析


* Pyc 文件是 PythonCodeObject 对象的持久化保存方式
* 有时候会见到 Pyo 文件,这个是经过 Python 解释器优化后生成的字节码
	+ 这个优化只是缩小了文件的体积,在代码运行速度上和 Pyc 差不多
* 尤其对于被 import 的文件,Python 解释器为了加快下一次被引用文件的读取速度,都会生成一个对应的 Pyc 文件
	+ 当后续被 import 的时候,解释器会优先寻找持久化存储的对象
* 在 Python 源代码运行的时候,Python 解释器会先将代码处理成 PythonCodeObject 对象,保存在内存中处理
* **除去预处理 PythonCodeObject 对象的过程,在执行速度上 Pyc 文件、Pyo 文件和源代码文件的速度相差无几**
* **需要注意的是,Pyc 文件只能运行在生成出此文件的解释器版本上**
	+ **Python 在生成 Pyc 文件的时候也引入了 MagicNumber,来标示此 Pyc 文件对应的版本号**
* 在 Python 解释器目录下 `./lib/python3.7/importlib/_bootstrap-external.py` 中有明确的版本号记录
	+ 这里的版本号是解释器字节码更新的版本号



Magic word to reject .pyc files generated by other Python versions.

It should change for each incompatible change to the bytecode.

The value of CR and LF is incorporated so if you ever read or write

a .pyc file in text mode the magic number will be wrong; also, the

Apple MPW compiler swaps their values, botching string constants.

There were a variety of old schemes for setting the magic number.

The current working scheme is to increment the previous value by

10.

Starting with the adoption of PEP 3147 in Python 3.2, every bump in magic

number also includes a new “magic tag”, i.e. a human readable string used

to represent the magic number in __pycache__ directories. When you change

the magic number, you must also set a new unique magic tag. Generally this

can be named after the Python major version of the magic number bump, but

it can really be anything, as long as it’s different than anything else

that’s come before. The tags are included in the following table, starting

with Python 3.2a0.

Known values:

Python 1.5: 20121

Python 1.5.1: 20121

Python 1.5.2: 20121

Python 1.6: 50428

Python 2.0: 50823

Python 2.0.1: 50823

Python 2.1: 60202

Python 2.1.1: 60202

Python 2.1.2: 60202

Python 2.2: 60717

Python 2.3a0: 62011

Python 2.3a0: 62021

Python 2.3a0: 62011 (!)

Python 2.4a0: 62041

Python 2.4a3: 62051

Python 2.4b1: 62061

Python 2.5a0: 62071

Python 2.5a0: 62081 (ast-branch)

Python 2.5a0: 62091 (with)

Python 2.5a0: 62092 (changed WITH_CLEANUP opcode)

Python 2.5b3: 62101 (fix wrong code: for x, in …)

Python 2.5b3: 62111 (fix wrong code: x += yield)

Python 2.5c1: 62121 (fix wrong lnotab with for loops and

storing constants that should have been removed)

Python 2.5c2: 62131 (fix wrong code: for x, in … in listcomp/genexp)

Python 2.6a0: 62151 (peephole optimizations and STORE_MAP opcode)

Python 2.6a1: 62161 (WITH_CLEANUP optimization)

Python 2.7a0: 62171 (optimize list comprehensions/change LIST_APPEND)

Python 2.7a0: 62181 (optimize conditional branches:

introduce POP_JUMP_IF_FALSE and POP_JUMP_IF_TRUE)

Python 2.7a0 62191 (introduce SETUP_WITH)

Python 2.7a0 62201 (introduce BUILD_SET)

Python 2.7a0 62211 (introduce MAP_ADD and SET_ADD)

Python 3000: 3000

3010 (removed UNARY_CONVERT)

3020 (added BUILD_SET)

3030 (added keyword-only parameters)

3040 (added signature annotations)

3050 (print becomes a function)

3060 (PEP 3115 metaclass syntax)

3061 (string literals become unicode)

3071 (PEP 3109 raise changes)

3081 (PEP 3137 make __file__ and __name__ unicode)

3091 (kill str8 interning)

3101 (merge from 2.6a0, see 62151)

3103 (__file__ points to source file)

Python 3.0a4: 3111 (WITH_CLEANUP optimization).

Python 3.0b1: 3131 (lexical exception stacking, including POP_EXCEPT

                      #3021)

Python 3.1a1: 3141 (optimize list, set and dict comprehensions:

change LIST_APPEND and SET_ADD, add MAP_ADD #2183)

Python 3.1a1: 3151 (optimize conditional branches:

introduce POP_JUMP_IF_FALSE and POP_JUMP_IF_TRUE

                      #4715)

Python 3.2a1: 3160 (add SETUP_WITH #6101)

tag: cpython-32

Python 3.2a2: 3170 (add DUP_TOP_TWO, remove DUP_TOPX and ROT_FOUR #9225)

tag: cpython-32

Python 3.2a3 3180 (add DELETE_DEREF #4617)

Python 3.3a1 3190 (__class__ super closure changed)

Python 3.3a1 3200 (PEP 3155 __qualname__ added #13448)

Python 3.3a1 3210 (added size modulo 2**32 to the pyc header #13645)

Python 3.3a2 3220 (changed PEP 380 implementation #14230)

Python 3.3a4 3230 (revert changes to implicit __class__ closure #14857)

Python 3.4a1 3250 (evaluate positional default arguments before

keyword-only defaults #16967)

Python 3.4a1 3260 (add LOAD_CLASSDEREF; allow locals of class to override

free vars #17853)

Python 3.4a1 3270 (various tweaks to the __class__ closure #12370)

Python 3.4a1 3280 (remove implicit class argument)

Python 3.4a4 3290 (changes to __qualname__ computation #19301)

Python 3.4a4 3300 (more changes to __qualname__ computation #19301)

Python 3.4rc2 3310 (alter __qualname__ computation #20625)

Python 3.5a1 3320 (PEP 465: Matrix multiplication operator #21176)

Python 3.5b1 3330 (PEP 448: Additional Unpacking Generalizations #2292)

Python 3.5b2 3340 (fix dictionary display evaluation order #11205)

Python 3.5b3 3350 (add GET_YIELD_FROM_ITER opcode #24400)

Python 3.5.2 3351 (fix BUILD_MAP_UNPACK_WITH_CALL opcode #27286)

Python 3.6a0 3360 (add FORMAT_VALUE opcode #25483)

Python 3.6a1 3361 (lineno delta of code.co_lnotab becomes signed #26107)

Python 3.6a2 3370 (16 bit wordcode #26647)

Python 3.6a2 3371 (add BUILD_CONST_KEY_MAP opcode #27140)

Python 3.6a2 3372 (MAKE_FUNCTION simplification, remove MAKE_CLOSURE

#27095)

Python 3.6b1 3373 (add BUILD_STRING opcode #27078)

Python 3.6b1 3375 (add SETUP_ANNOTATIONS and STORE_ANNOTATION opcodes

#27985)

Python 3.6b1 3376 (simplify CALL_FUNCTIONs & BUILD_MAP_UNPACK_WITH_CALL

                      #27213)

Python 3.6b1 3377 (set __class__ cell from type.__new__ #23722)

Python 3.6b2 3378 (add BUILD_TUPLE_UNPACK_WITH_CALL #28257)

Python 3.6rc1 3379 (more thorough __class__ validation #23722)

Python 3.7a1 3390 (add LOAD_METHOD and CALL_METHOD opcodes #26110)

Python 3.7a2 3391 (update GET_AITER #31709)

Python 3.7a4 3392 (PEP 552: Deterministic pycs #31650)

Python 3.7b1 3393 (remove STORE_ANNOTATION opcode #32550)

Python 3.7b5 3394 (restored docstring as the first stmt in the body;

this might affected the first line number #32911)

MAGIC must change whenever the bytecode emitted by the compiler may no

longer be understood by older implementations of the eval loop (usually

due to the addition of new opcodes).

Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array

in PC/launcher.c must also be updated.


* 对于 pyc 文件整体的 C 结构体,可以在 `./include/python2.7/code.h` 或不同版本类似的文件中找到
	+ 具体代码如下



/* Bytecode object /
typedef struct {
PyObject_HEAD
int co_argcount; /
#arguments, except *args /
int co_nlocals; /
#local variables /
int co_stacksize; /
#entries needed for evaluation stack /
int co_flags; /
CO_…, see below */
PyObject co_code; / instruction opcodes */
PyObject co_consts; / list (constants used) */
PyObject co_names; / list of strings (names used) */
PyObject co_varnames; / tuple of strings (local variable names) */
PyObject co_freevars; / tuple of strings (free variable names) */
PyObject co_cellvars; / tuple of strings (cell variable names) /
/
The rest doesn’t count for hash/cmp */
PyObject co_filename; / string (where it was loaded from) */
PyObject co_name; / string (name, for reference) /
int co_firstlineno; /
first source line number */
PyObject co_lnotab; / string (encoding addr<->lineno mapping) See
Objects/lnotab_notes.txt for details. */
void co_zombieframe; / for optimization only (see frameobject.c) */
PyObject co_weakreflist; / to support weakrefs to code objects */
} PyCodeObject;


* 将 Python 源代码生成为 Pyc 文件,这里使用的版本是 Python 2.7.6
	+ 这里我们将一个名为 `py1.py` 的文件



import py_compile
py_compile.compile(‘./py1.py’)


* 会在源代码文件目录下找到编译后的文件
* 使用 010 editor 打开,会提示是否需要安装 pyc 字节码辅助插件,虽然说只支持 2.4 - 2.7
	+ Python 3 以后的都不能用…


![](https://img-blog.csdnimg.cn/img_convert/752cc8b2295c5e42229357c982162d86.png)


* 此处以中南大学2020校赛的一道逆向题目 [py&flower.pyc](https://bbs.csdn.net/topics/618317507) 作为介绍,使用 010 editor 打开


![](https://img-blog.csdnimg.cn/img_convert/4af582fc5deba83e3ba1d94c7d8737d5.png)


* 最前面的 4 个字节为 Magic Number ,其中前两个直接为解释器的版本号


	+ 此处前两个字节为 62211,也就是 Python 2.7.0a0 版本的字节码解释器
	+ 注意这里是小端序,就是高位在后面,所以是 0xF303
* Magic Number 之后的四个字节为时间戳,这里是 0x5EC652B0,之后就是 Python 代码对象
* 代码对象首先一个字节表示此处的对象类型,这里值为 TYPE\_CODE,值为 0x63,
* 此后四个字节表示参数的个数,也就是 co\_argcount 的值
* 往后四个字节是局部变量的个数 co\_nlocals
* 往后四个字节是栈空间大小 co\_stacksize
* 往后四个字节是 co\_flags
* 之后就是 co\_code 了,也就是编译好的字节码的部分


	+ co\_code 部分首先的一个字节也是表示此处的对象类型,这里是 TYPE\_STRING,为 0x73
	+ 接下来四个字节表示此 co\_code 对象的长度,此后就是代码对象,这里的代码长度为 0xA7
	+ 也就是后方 163 个字节的长度都是代码对象


![](https://img-blog.csdnimg.cn/img_convert/346e2eb881a6b35beb8c700d976defbb.png)


* 此 co\_code 对象的字节码内容结束后,接着是 co\_consts 内容,也就是用到的常量的内容
	+ 最开始是 TYPE\_TUPLE,表示这是个元组类型
	+ 此后四个字节是元素个数,这里是 0x23,之后每一个字节与对应的值一组,一共 0x23 组
		- 每组中第一个字节表示元素类型,比如 0x69 指 TYPE\_INT,此后为对应的值
* 后方也对应结构体中的相应内容


![](https://img-blog.csdnimg.cn/img_convert/9480f0dfa71b43385089f56bbd2992da.png)


## 字节码混淆


### Anti-uncompyle6


* 对于正常的 pyc 文件,使用 uncompyle6 插件可以正常的进行字节码逆向,得到原来的代码



uncompyle6 ./py2.pyc


* 如果需要使 uncompyle6 失效的话,只要在 co\_code 头部加上 `0x71 0x03 0x00` ,然后把记录 co\_code 长度的数据加 3
	+ 这段字节码指 `JUMP_ABSOLUTE 3` ,也就是向后跳 3 个字节后继续执行,实际上没有改变代码逻辑
	+ 但是 uncompyle6 插件的还原逻辑就没办法识别此字节码原先的意思,导致解析异常


### Anti-dis


* 上文的改法会导致 uncompyle6 插件异常,但是这个方法的实质只是增加了一句字节码
* Python 可以借助自带的 dis 库和 marshal 库解析 pyc 二进制文件中的信息,此处以一个简单的代码作为例子



def fun1():
enc = “Ua|{f.4V}$l4h4Vx{s.4|``dg.;;vx{s:v}$l:wz;4h4Dxqugq4}zp}wuq4|q4g{afwq4urqf4m{af4pqf}bu`}{z”
flag = “”
for i in enc:
flag += chr(ord(i) ^ 0x14)
print flag
fun1()




**一、Python所有方向的学习路线**

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。

![img](https://img-blog.csdnimg.cn/1d40facda2b84990b8e1743f5487d455.png)  
![img](https://img-blog.csdnimg.cn/0fc11d4a31bd431dbf124f67f1749046.png)

**二、Python必备开发工具**

工具都帮大家整理好了,安装就可直接上手!![img](https://img-blog.csdnimg.cn/ff266f529c6a46c4bc28e5f895dec647.gif#pic_center)

**三、最新Python学习笔记**

当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。

![img](https://img-blog.csdnimg.cn/6d414e9f494742db8bcc3fa312200539.png)

**四、Python视频合集**

观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

![img](https://img-blog.csdnimg.cn/a806d9b941c645858c61d161aec43789.png)

**五、实战案例**

纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。![img](https://img-blog.csdnimg.cn/a353983317b14d3c8856824a0d6186c1.png)

**六、面试宝典**

![在这里插入图片描述](https://img-blog.csdnimg.cn/97c454a3e5b4439b8600b50011cc8fe4.png)

![在这里插入图片描述](https://img-blog.csdnimg.cn/111f5462e7df433b981dc2430bb9ad39.png)

###### **简历模板**![在这里插入图片描述](https://img-blog.csdnimg.cn/646863996ac44da8af500c049bb72fbd.png#pic_center)




**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618317507)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 21
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值