python逆向
python
python是一门解释性的语言
源代码—>中间代码—>机器语言
编译器先将源代码编译成虚拟机指令,再由解释器对中间代码进行解释。
python有两种常见的解释器:
CPython:C语言开发,使用最广,默认的解释器
PyPy:采用JIT技术,对python代码进行动态编译,追求执行速度
Python 字节码与字节码混淆 - 简书 (jianshu.com)关于python字节码的知识
深入理解python之Opcode备忘录 - 简书 (jianshu.com)这个博客里有关于字节码指令的详细解释
pyc文件
pyc是编译py后生成的字节码文件,可以用来隐藏源代码。pyc内容跟python
版本有关。
不同版本的python可能运行不了同一个pyc文件。
1 | python -m py_compile test.py |
学习python字节码学习搭配chatGPT,能提高学习效率
CPython使用基于堆栈的虚拟机
在执行python文件时候,第一步: python解释器会将你写的python代码先编译为字节码
第二步: 当你每一次调用函数,或者刚开始运行python的时候,cpython会建立一个新的Frame,然后在这个Frame框架下,cpython会一条一条的执行编译后的ByteCode, 每一条ByteCode在C语言中有相应的代码去执行它。
另外,在每一个Frame里, cpython都会维护一个stack,然后ByteCode会和这个Stack进行交互操作。
python字节码官方文档dis —- Python 字节码反汇编器 — Python 3.12.0 文档
变量指令解析
1 | 源码行号 | 指令偏移量 | 指令符号 | 指令参数 | 实际参数值 |
一下解析均来自chatGPT
FAST
LOAD_FAST
LOAD_FAST
是 Python 字节码的一种指令,用于加载局部变量的值。具体来说,它从当前函数的局部变量数组中加载指定名称的变量的值,并将其推送到堆栈上。
以下是关于 LOAD_FAST
指令的一些细节:
- 指令名称:
LOAD_FAST
- Opcode:
0x7C
- 参数: 一个整数索引,表示局部变量在局部变量数组中的位置
LOAD_FAST
的作用是加载函数的局部变量的值。局部变量是在函数中定义的变量,只在函数的范围内可见。
GLOBAL
LOAD_GLOBAL
LOAD_GLOBAL
是 Python 字节码的一种指令,用于加载全局变量的值。具体来说,它从当前模块的全局命名空间中加载指定名称的变量的值,并将其推送到堆栈上。
以下是关于 LOAD_GLOBAL
指令的一些细节:
- 指令名称:
LOAD_GLOBAL
- Opcode:
0x74
- 参数: 一个整数索引,表示在常量池中的全局变量名称的位置
LOAD_GLOBAL
的作用是加载全局变量的值。全局变量是在模块级别定义的变量,可以在整个模块内被访问。
STORE_NAME
STORE_NAME
是 Python 字节码的一种指令,用于将值存储到局部变量的名称中。具体来说,它从堆栈中弹出一个值,然后将该值存储到当前作用域中具有指定名称的变量中。
以下是关于 STORE_NAME
指令的一些细节:
- 指令名称:
STORE_NAME
- Opcode:
0x5
- 参数: 一个表示变量名称的字符串(变量名)
STORE_NAME
的作用是将堆栈顶部的值存储到局部变量中,变量的名称由参数指定。这通常是在赋值操作时使用的指令。
LIST
BUILD_LIST
BUILD_LIST
是 Python 字节码的一种指令,用于创建一个列表对象。具体来说,它从堆栈上弹出指定数量的元素,然后创建一个新的列表并将这些元素放入列表中,最后将列表对象推送回堆栈。
以下是关于 BUILD_LIST
指令的一些细节:
- 指令名称:
BUILD_LIST
- Opcode:
0x14
- 参数: 一个整数,表示要从堆栈中弹出的元素数量
BUILD_LIST
的作用是根据堆栈上的元素创建一个新的列表对象。
dict
BUILD_MAP
BUILD_MAP
是 Python 字节码的一种指令,用于创建一个字典对象。具体来说,它从堆栈上弹出两倍于指定元素数量的元素,然后创建一个新的字典并将这些元素添加到字典中,最后将字典对象推送回堆栈。
以下是关于 BUILD_MAP
指令的一些细节:
- 指令名称:
BUILD_MAP
- Opcode:
0x15
- 参数: 一个整数,表示要从堆栈中弹出的键值对数量(通常是键值对的数量的两倍)
BUILD_MAP
的作用是根据堆栈上的键值对创建一个新的字典对象。
LOOP
SETUP_LOOP
SETUP_LOOP
是 Python 字节码的一种指令,用于在循环开始的位置设置循环的头部。它指定了循环体的开始位置.
以下是关于 SETUP_LOOP
指令的一些细节:
- 指令名称:
SETUP_LOOP
- Opcode:
0x13
- 参数: 两个字节的有符号偏移量(表示循环体的结束位置)
SETUP_LOOP
的作用是标记循环的开始位置,并在堆栈上推送一个块堆栈(block stack)记录循环的开始位置和结束位置。当循环体结束时,POP_BLOCK
指令将从块堆栈中弹出该记录。
示例:
1 | for i in range(5): |
对应的字节码可能包含:
1 | 0 SETUP_LOOP 28 # 设置循环的头部,指定循环体的结束位置 |
在这个例子中,SETUP_LOOP
用于设置循环的头部,POP_BLOCK
将在循环体结束时弹出块堆栈。循环体的开始和结束位置由 FOR_ITER
和 POP_BLOCK
来标记。
POP_BLOCK
POP_BLOCK
是 Python 字节码指令之一,用于在异常处理中管理块的结束。具体来说,它用于弹出当前块的信息,以便程序继续正常执行而不是执行异常处理。在 CPython 的实现中,这通常与异常处理语句(try
, except
, finally
)一起使用。
在 Python 字节码中,POP_BLOCK
通常出现在异常处理块的末尾,它会将与该块相关的信息从块堆栈中弹出。
例如,下面是一个简单的 Python 代码片段和对应的字节码:
Python 代码片段:
1 | pythonCopy codetry: |
对应的字节码:
1 | scssCopy code 1 0 SETUP_EXCEPT 16 (to 19) |
在上述字节码中,POP_BLOCK
出现在 POP_EXCEPT
和 END_FINALLY
之后,用于清理块堆栈。这样,程序可以继续执行正常的代码,而不是进入异常处理块。
BINARY_SUBSCR
BINARY_SUBSCR
是 Python 字节码的一种指令,它用于在可迭代对象(如列表、元组、字典等)上执行下标(索引)操作。具体来说,它从堆栈中弹出两个值,一个是容器对象,另一个是索引值,然后将容器对象中指定索引的元素推送回堆栈。
下面是关于 BINARY_SUBSCR
指令的一些细节:
- 指令名称:
BINARY_SUBSCR
- Opcode:
0x25
- 参数: 无
- 作用: 从容器对象中获取指定索引的元素,并将其推送到堆栈上。
STORE_SUBSCR
STORE_SUBSCR
是 Python 字节码的一种指令,用于在容器对象(如列表、字典等)中的指定索引位置存储一个值。具体来说,它从堆栈中弹出三个值:容器对象、索引值和要存储的值,然后将值存储到容器对象的指定索引位置。
以下是关于 STORE_SUBSCR
指令的一些细节:
- 指令名称:
STORE_SUBSCR
- Opcode:
0x3A
- 参数: 无
- 作用: 从堆栈中弹出容器对象、索引值和值,将值存储到容器对象的指定索引位置。
这个指令通常在对列表、字典等进行索引赋值操作时使用。例如,如果有一个列表 my_list
,栈顶有一个整数表示索引,而下面的两个值分别是容器对象和要存储的值,STORE_SUBSCR
将在列表的指定索引位置存储新的值。
跳转指令
POP_JUMP_IF_FALSE
POP_JUMP_IF_FALSE
是 Python 字节码的一种条件跳转指令。它用于在堆栈上弹出一个值,并在该值为假(False)时执行跳转。如果弹出的值为假,指令将跳转到指定的目标地址,否则继续执行下一条指令。
以下是关于 POP_JUMP_IF_FALSE
指令的一些细节:
- 指令名称:
POP_JUMP_IF_FALSE
- Opcode:
0x72
- 参数: 两个字节的有符号偏移量(表示跳转目标的相对位置)
POP_JUMP_IF_FALSE
的作用是从堆栈中弹出一个值,如果该值为假(False),则执行跳转到指定的目标地址。否则,继续执行下一条指令。
示例:
1 | x = False |
上述代码的字节码可能包含类似下面的部分:
1 | 2 0 LOAD_GLOBAL 0 (x) |
在这个例子中,POP_JUMP_IF_FALSE
指令用于检查变量 x
的真假,并根据结果选择性地执行不同的分支。
JUMP_FORWARD
JUMP_FORWARD
是 Python 字节码的一种无条件跳转指令。它用于在执行过程中无条件地跳转到指定的相对位置。
以下是关于 JUMP_FORWARD
指令的一些细节:
- 指令名称:
JUMP_FORWARD
- Opcode:
0x28
- 参数: 两个字节的有符号偏移量(表示跳转的相对位置)
JUMP_FORWARD
的作用是使程序跳转到当前位置之后的指定位置。它通常用于实现循环、条件语句等结构,其中需要在不同条件下跳转到不同的代码块。
示例:
1 | x = True |
上述代码的字节码可能包含类似下面的部分:
1 | 2 0 LOAD_GLOBAL 0 (x) |
在这个例子中,JUMP_FORWARD
用于在条件判断为真时跳过 else
语句块。在字节码中,JUMP_FORWARD
的目标是跳过指定数量的字节码指令,从而实现程序的流程控制。
JUMP_ABSOLUTE
JUMP_ABSOLUTE
是 Python 字节码的一种无条件跳转指令。它用于无条件地跳转到指定的绝对位置。
以下是关于 JUMP_ABSOLUTE
指令的一些细节:
- 指令名称:
JUMP_ABSOLUTE
- Opcode:
0x71
- 参数: 两个字节的有符号偏移量(表示跳转的绝对位置)
JUMP_ABSOLUTE
的作用是使程序跳转到指定的绝对位置。这个位置通常是由 bytecode
的相对位置计算得到的,而不是 Python 源代码中的行号。
示例:
1 | x = 5 |
对应的字节码可能包含类似下面的部分:
1 | 2 0 LOAD_NAME 0 (x) |
在这个例子中,JUMP_ABSOLUTE
指令用于在条件判断为真时跳转到指定的位置。在字节码中,JUMP_ABSOLUTE
的目标是跳转到指定的绝对位置,绕过相应的代码块。
运算指令
- BINARY_ADD: 从堆栈中弹出两个值,相加,并将结果推回堆栈。
- BINARY_SUBTRACT: 从堆栈中弹出两个值,相减,并将结果推回堆栈。
- BINARY_MULTIPLY: 从堆栈中弹出两个值,相乘,并将结果推回堆栈。
- BINARY_DIVIDE: 从堆栈中弹出两个值,相除,并将结果推回堆栈。
- BINARY_MODULO: 从堆栈中弹出两个值,取模,并将结果推回堆栈。
- BINARY_POWER: 从堆栈中弹出两个值,计算幂,并将结果推回堆栈。
- BINARY_LSHIFT: 将堆栈顶部的值左移指定位数,并将结果推回堆栈。
- BINARY_RSHIFT: 将堆栈顶部的值右移指定位数,并将结果推回堆栈。
- BINARY_AND: 从堆栈中弹出两个值,进行按位与操作,并将结果推回堆栈。
- BINARY_OR: 从堆栈中弹出两个值,进行按位或操作,并将结果推回堆栈。
- BINARY_XOR: 从堆栈中弹出两个值,进行按位异或操作,并将结果推回堆栈。
- INPLACE_ADD: 从堆栈中弹出两个值,相加,并将结果就地存储回栈顶的位置。
- INPLACE_SUBTRACT: 从堆栈中弹出两个值,相减,并将结果就地存储回栈顶的位置。
- INPLACE_MULTIPLY: 从堆栈中弹出两个值,相乘,并将结果就地存储回栈顶的位置。
- INPLACE_DIVIDE: 从堆栈中弹出两个值,相除,并将结果就地存储回栈顶的位置。
比较指令COMPARE_OP
COMPARE_OP
是 Python 字节码的一种指令,用于执行比较操作。它弹出两个值,执行指定的比较操作,然后将比较结果的布尔值推送回堆栈。以下是关于
COMPARE_OP
指令的一些细节:- 指令名称:
COMPARE_OP
- Opcode:
0x6E
- 参数: 一个表示比较操作类型的整数码(具体的操作由该参数确定)
COMPARE_OP
的作用是执行两个值之间的比较操作,比较操作的类型由参数确定。这可以是诸如等于、不等于、小于、大于等操作。比较操作的参数码和对应的比较操作如下:
0
:<
(小于)1
:<=
(小于等于)2
:==
(等于)3
:!=
(不等于)4
:>
(大于)5
:>=
(大于等于)6
:in
(成员测试)7
:not in
(不是成员测试)8
:is
(同一对象测试)9
:is not
(不是同一对象测试)10
:exception match
(异常匹配)11
:BAD
(无效的比较)
示例:
1
2
3
4# 比较操作: a < b
a = 5
b = 10
result = a < b对应的字节码可能包含:
1
2
30 LOAD_CONST 1 (5)
2 LOAD_CONST 2 (10)
4 COMPARE_OP 0 (<) # 执行 a < b 的比较操作在这个例子中,
COMPARE_OP
指令执行了小于(<
)的比较操作,将结果推送回堆栈。- 指令名称:
用户输入
在 Python 2 中,使用 raw_input
函数来接收用户的输入。raw_input
会将用户输入的内容作为字符串返回,不进行任何解析或转换。
从 Python 3 开始,raw_input
被移除,而 input
函数则获得了与 Python 2 中的 raw_input
类似的行为,即将用户输入的内容作为字符串返回。
yield关键字
首先了解一下python迭代器
python迭代器
迭代器有两个重要函数 iter()
用于创建一个迭代器(所有的序列都可以转化为迭代器)。 next()
可以返回迭代器的下一元素。若迭代器数据耗尽时仍然调用next()函数,则会引发StopIteration异常。
1 | import sys |
Python3 迭代器与生成器 | 菜鸟教程 (runoob.com)
yield
在 Python 中,使用了 yield 的函数被称为生成器(generator)。
生成器是一个返回迭代器的函数。
1 |
|
生成器函数的优势是它们可以按需生成值,避免一次性生成大量数据并占用大量内存。此外,生成器还可以与其他迭代工具(如for循环)无缝配合使用,提供简洁和高效的迭代方式。