首页 字节码与python反序列化
文章
取消

字节码与python反序列化

字节码

什么是机器码

机器码

机器码(machine code),学名机器语言指令,有时也被称为原生码(Native Code),是电脑的CPU可直接解读的数据。

通常意义上来理解的话,机器码就是计算机可以直接执行,并且执行速度最快的代码。

用机器语言编写程序,编程人员要首先熟记所用计算机的全部指令代码和代码的涵义。手编程序时,程序员得自己处理每条指令和每一数据的存储分配和输入输出,还得记住编程过程中每步所使用的工作单元处在何种状态。这是一件十分繁琐的工作,编写程序花费的时间往往是实际运行时间的几十倍或几百倍。而且,编出的程序全是些0和1的指令代码,直观性差,还容易出错。现在,除了计算机生产厂家的专业人员外,绝大多数的程序员已经不再去学习机器语言了。

什么是字节码

字节码

字节码(Bytecode)是一种包含执行程序、由一序列 op 代码/数据对 组成的二进制文件。字节码是一种中间码,它比机器码更抽象,需要直译器转译后才能成为机器码的中间代码。

通常情况下它是已经经过编译,但与特定机器码无关。字节码通常不像源码一样可以让人阅读,而是编码后的数值常量、引用、指令等构成的序列。

字节码主要为了实现特定软件运行和软件环境、与硬件环境无关。字节码的实现方式是通过编译器和虚拟机器。编译器将源码编译成字节码,特定平台上的虚拟机器将字节码转译为可以直接执行的指令。字节码的典型应用为Java bytecode。

字节码的实现方式是通过编译器和虚拟机器。编译器将源码编译成字节码,特定平台上的虚拟机器将字节码转译为可以直接执行的指令。

字节码在运行时通过JVM(JAVA虚拟机)做一次转换生成机器指令,因此能够更好的跨平台运行。

  1. 机器码是电脑CPU直接读取运行的机器指令,运行速度最快,但是非常晦涩难懂,也比较难编写,一般从业人员接触不到。
  2. 字节码是一种中间状态(中间码)的二进制代码(文件)。需要直译器转译后才能成为机器码。

一、Java里的字节码

字节码如何参与java运行过程

我们在编写java的源代码,会被javac转换成字节码文件(.class)

在运行时java虚拟机(JVM)内嵌的解释器将字节码文件转换成机器码

java源文件 -> class字节码文件 -> 机器码

javac:javac 是java语言编程编译器。全称java compiler。javac工具读由java语言编写的类和接口的定义,并将它们编译成字节代码class文件

JVM:JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

跨平台,可移植性,一次编译,就可以在不同操作系统上有相同的执行。JVM在不同操作系统的版本是相对应的,是不同的。(JVM是C++开发)

3.class字节码文件大概是什么

Java 字节码类文件(.class)是 Java 编译器编译 Java 源文件(.java)产生的“目标文件”。它是一种8位字节的二进制流文件, 各个数据项按顺序紧密的从前向后排列, 相邻的项之间没有间隙, 这样可以使得 class 文件非常紧凑, 体积轻巧, 可以被 JVM 快速的加载至内存, 并且占据较少的内存空间(方便于网络的传输)。

Java 源文件在被 Java 编译器编译之后, 每个类(或者接口)都单独占据一个 class 文件, 并且类中的所有信息都会在 class 文件中有相应的描述, 由于 class 文件很灵活, 它甚至比 Java 源文件有着更强的描述能力。

一个 Java 类文件大致可以归为 10 个项:

  • Magic:该项存放了一个 Java 类文件的魔数(magic number)和版本信息。一个 Java 类文件的前 4 个字节被称为它的魔数。每个正确的 Java 类文件都是以 0xCAFEBABE 开头的,这样保证了 Java 虚拟机能很轻松的分辨出 Java 文件和非 Java 文件。
  • Version:该项存放了 Java 类文件的版本信息,它对于一个 Java 文件具有重要的意义。因为 Java 技术一直在发展,所以类文件的格式也处在不断变化之中。类文件的版本信息让虚拟机知道如何去读取并处理该类文件。
  • Constant Pool:该项存放了类中各种文字字符串、类名、方法名和接口名称、final 变量以及对外部类的引用信息等常量。虚拟机必须为每一个被装载的类维护一个常量池,常量池中存储了相应类型所用到的所有类型、字段和方法的符号引用,因此它在 Java 的动态链接中起到了核心的作用。常量池的大小平均占到了整个类大小的 60% 左右。
  • Access_flag:该项指明了该文件中定义的是类还是接口(一个 class 文件中只能有一个类或接口),同时还指名了类或接口的访问标志,如 public,private, abstract 等信息。
  • This Class:指向表示该类全限定名称的字符串常量的指针。
  • Super Class:指向表示父类全限定名称的字符串常量的指针。
  • Interfaces:一个指针数组,存放了该类或父类实现的所有接口名称的字符串常量的指针。以上三项所指向的常量,特别是前两项,在我们用 ASM 从已有类派生新类时一般需要修改:将类名称改为子类名称;将父类改为派生前的类名称;如果有必要,增加新的实现接口。
  • Fields:该项对类或接口中声明的字段进行了细致的描述。需要注意的是,fields 列表中仅列出了本类或接口中的字段,并不包括从超类和父接口继承而来的字段。
  • Methods:该项对类或接口中声明的方法进行了细致的描述。例如方法的名称、参数和返回值类型等。需要注意的是,methods 列表里仅存放了本类或本接口中的方法,并不包括从超类和父接口继承而来的方法。使用 ASM 进行 AOP 编程,通常是通过调整 Method 中的指令来实现的。
  • Class attributes:该项存放了在该文件中类或接口所定义的属性的基本信息。

27.191.237.5:7288/data/uploadPlug.php::$data

https://www.kancloud.cn/chandler/programming_road/695586

https://buaq.net/go-25697.html

Python pickle序列化和反序列化

1.什么是序列化?

序列化:把不能够直接存储在文件中的数据变得可存储(保存文件的后缀为”.pkl”,不能直接打开)。所有的数据类型都可以通过pickle模块进行序列化。

pickle.dump(obj, file, protocol=None,*,fix_imports=True)

​ 序列化对象,把任意对象序列化成一个bytes类型。,并将结果数据流写入到文件对象中。

​ 参数file有一点需要注意,必须是以二进制的形式进行操作(写入)。得在之前执行代码 open(‘文件路径’,’wb’),以二进制的形式(’wb’)写入。

​ 参数protocol是序列化模式,默认值为0,,一共有5中不同的类型,即(0,1,2,3,4)。(0,1,2)对应的是python早期的版本,(3,4)则是在python3之后的版本。 此外,参数可选 pickle.HIGHEST_PROTOCOL和pickle.DEFAULT_PROTOCOL。当前,python3.5版本中,pickle.HIGHEST_PROTOCOL的值为4,pickle.DEFAULT_PROTOCOL的值为3。当protocol参数为负数时,表示选择的参数是pickle.HIGHEST_PROTOCOL。

pickle.dumps(obj, protocol=None,*,fix_imports=True)

pickle.dumps()方法跟pickle.dump()方法的区别在于,pickle.dumps()方法不需要写入文件中,它是直接返回一个序列化的bytes对象。

2.什么是反序列化?

反序列化: 把存储在文件中的数据拿出来恢复成原来的数据类型。

pickle.load(file, *,fix_imports=True, encoding=”ASCII”. errors=”strict”) 反序列化对象。将文件中的数据解析为一个Python对象。该方法实现的是将序列化的对象从文件file中读取出来。

​ 参数file,需要注意的是,必须是以二进制的形式进行操作(读取)。得在之前执行代码 open(‘文件路径’,’rb’),以二进制的形式(’rb’)读取。

pickle.loads(bytes_object, *,fix_imports=True, encoding=”ASCII”. errors=”strict”) pickle.loads()方法跟pickle.load()方法的区别在于,pickle.loads()方法是直接从bytes对象中读取序列化的信息,而非从文件中读取。

https://www.jianshu.com/p/83fab384f8be

https://blog.csdn.net/y472360651/article/details/87209589

picker在python2和python3的默认参数是有所差别的:

如:python2默认的序列化参数protocol是0,python2默认的序列化参数protocol是3。

一般python3里用pickle.dumps(data, protocol=2)

pickle.loads(info, encoding=”utf-8”)

就能达到Python2Python3互相序列化与反序列化的效果。

PVM( Python Virtual Machine , Python 虚拟机 )

PVM 有什么用呢 ?

在使用 C , C++ 等编译性语言编写的程序时 , 解释器需要先将源代码文件转换成计算机使用的机器语言( 也就是常说的 “ 编译 “ 过程 ) , 然后经过链接器链接之后形成了二进制可执行文件( 也就是常说的 “ 链接 “ 过程 ) . 运行该程序的时候 , 计算机会将二进制可执行文件从硬盘载入到内存中并运行 .

但是对于 Python 而言 , 它可以直接从源代码运行程序 . Python解释器会将源代码编译为字节码 , 然后将编译后的字节码转发到 Python 虚拟机中执行 .

所以说 PVM 的作用非常简单 , 它是一个用来解释字节码的解释引擎 .

它的执行流程是怎样的呢 ?

一般来说 , 当运行 Python 程序时 , PVM 会执行两个步骤 .

首先 , PVM 会把源代码编译成字节码 . 字节码是 Python 语言特有的一种表现形式 , 它不是二进制机器码 , 需要进一步编译才能被机器执行 . 如果 Python 进程在主机上有写入权限 , 那么它会把程序字节码保存为一个以 .pyc 为扩展名的文件 . 如果没有写入权限 , 则 Python 进程会在内存中生成字节码 , 在程序执行结束后被自动丢弃 .

一般来说 , 在构建程序时最好给 Python 进程在主机上的写入权限 , 这样只要源代码没有改变 , 生成的 .pyc 文件就可以被重复利用 , 提高执行效率 , 同时隐藏源代码 .

然后 , Python 进程会把编译好的字节码转发到 PVM( Python 虚拟机 ) 中 , PVM会循环迭代执行字节码指令 , 直到所有操作被完成 .

Pickle是一门基于栈的编程语言 , 有不同的编写方式 , 其本质就是一个轻量级的 PVM .

这个轻量级的 PVM 由三个部分组成 , 如下所示

  • 指令处理器( Instruction processor )

    从数据流中读取操作码和参数 , 并对其进行解释处理 . 指令处理器会循环执行这个过程 , 不断改变 stack 和 memo 区域的值 . 直到遇到 “ . “ 这个结束符号 . 这时 , 最终停留在栈顶的的值将会被作为反序列化对象返回 .

  • 栈区( stack )

    由 Python 的列表( list )实现 , 作为流数据处理过程中的暂存区 , 在不断的进出栈过程中完成对数据流的反序列化操作,并最终在栈顶生成反序列化的结果

  • 标签区( memo )

    由 Python 的字典( dict )实现 , 可以看作是数据索引或者标记 , 为 PVM 的整个生命周期提供存储功能 .

这里需要重点关注指令处理器可读取的操作码 . 完整的指令集可以参考 Python pickle 反序列化实例分析 这篇文章的 , 现在我只讲比较重要的几个 .

pickle反序列化漏洞

原理分析

反序列化漏洞的根源出在 __reduce__() 魔术方法 .

很多反序列化过程都会存在像 “构造函数” 或者 “析构函数” 这样的函数( 原谅我不知道还能怎么形容 ) . 每当反序列化过程开始或者结束时 , 都会自动调用这类函数 . 而这恰好是反序列化漏洞经常出现的地点 , PHP 的 __wakeup() 魔术方法是这样 , __reduce__() 魔术方法也是这样 .

而在反序列化过程中 , 编程语言需要根据序列化字符串去解析出自己独特的语言数据结构 . 为了实现这点,就必然要在内部把解析出来的结果去执行一下 . 好呗 ! 有了这个执行的操作 , 反序列化过程不出事还好 , 一出事就是一个天大的 RCE —— 毕竟这些编程语言的应用是非常广泛的~

本文由作者按照 CC BY 4.0 进行授权
文章内容

Enable Google Page Views

我的渗透测试总结