2020年年前开始研究Java反序列化,一直没有研究Java反序列化文件的格式。最近闲来无事,研究一下java反序列化文件的格式。扯这么多的原因主要是要达成两个目标
首先我们先大致讲解一下java序列化以及相关东西。
序列化中也有基础类型。我们需要定义几个函数用来读取基础类型。
例如
前面我们提到,读取一个Byte作为控制指令,控制指令的作用是标记后面的数据类型,序列化中控制指令如下
TC_NULL = b'\x70'
标记后面的数据为空,对应java就是NullTC_REFERENCE = b'\x71
java序列化协议是一个格式十分紧凑的协议,是不会出现两个一摸一样的对象,类等。如果第二次出现,则会通过reference去指向之前的那个内容。你可以把这个类比为指针。TC_CLASSDESC = b'\x72'
这个是处理并返回类描述符。。与下面的class的区别在于,这个返回的是描述类的一个对象,主要包括类的名称,suid等各种属性。TC_CLASS = b'\x76'
在java序列化中,类的传输通过名称,suid等属性,对端通过名称查找classpath中该类。而这个TC_CLASS将会根据上面的类描述符,通过Class.forName去查找这个类。TC_OBJECT = b'\x73'
标记后面的数据为Object对象TC_STRING = b'\x74'
标记后面的数据字符串。与基本类型中字符串的区别在于,这里面读取的字符串将会被缓存,如果出现第二个一模一样的字符串,则通过reference的方式,直接读取缓存中的字符串TC_ARRAY = b'\x75'
标记后面的数据为数组类型TC_BLOCKDATA = b'\x77'
在对象的WriteObject方法中,我们可以自定义的写入数据,除了非Object数据,其他所有数据将会被写在一起,也就是BlockData。当然,只有readObject方法中,合适的读取顺序才可以成功还原blockdata。TC_ENDBLOCKDATA = b'\x78'
在readObject中,表明数据已经读取完毕TC_EXCEPTION = b'\x7B'
表明后面需要读取一个exception类型的对象TC_PROXYCLASSDESC = b'\x7D'
读取一个动态代理的对象有了上面的知识作为基础,下面我们尝试还原反序列化流中的内容。当然我们并没有按照某些特定的顺序去讲解。
类的描述符为序列化协议中的基石,它表明了后面的数据类型以及读取方法。类的描述符,一共有以下几个字段
这里只包含类的数据类型的名称与类型,不包括值。一定注意
由于java不支持多继承,所以在这里只有两种情况,继承自一个父类和没有父类这两种情况。在这里我们只需要递归读取,直到控制指令为TC_NULL,即父类为空,作为结束递归的条件。
类的描述符,记得要缓存。计算handle的时候,后面的值可能会引用该类的描述符
下面用一图总结类的描述符的的协议结构
| utf 类名|4Byte suid|1Byte flags|2Byte 字段数量|字段详细内容|父类|类的额外数据|
我们知道,java的数组中的内容只能为同一类型。所以在处理数组信息的时候,首先读取类描述符,表明数组中的内容的数据类型。然后读取数组的长度。最后按照数组长度以及数组类型,去读取并还原数组中的数据。
|ClassDesc|Int length|数组数据|
数组也会被缓存,并被计算为handle
还原对象的数据其实特别简单。首先读取类的描述符,然后紧接着按照类描述符的字段读取数据即可。所以在这里,一个byte出错,将会导致后面的数据全部读取错误。
在这里需要注意几点
当然,对象中字段的值有可能还是对象,需要递归读取,直到读取所有的字段为基础数据类型。在这里建议设置递归的最大深度,防止出现爆栈的异常。
目前完成各种复杂对象的读取,例如x友的exp读取,weblogic 反序列化Exp的读取,并转换为json。不过代码还未写完,地址暂时为https://github.com/potats0/javaSerializationDump/blob/main/main.py
截图如下,读取x友exp
x友exp转json的结果
https://gist.github.com/potats0/108fe530350d14b11aba79736d6a3f0c#file-ncexp-json
本文作者:宽字节安全
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/163887.html