potatosafe
- 关注

前言
因为之前发了一些反序列化利用链子,这段时间本来把链子又写了几个的,但是怕有些小伙伴可能不理解(原谅我的菜扣脚的文笔和技术),所以准备在写一些Java反序列化基础的东西,给之后的Java链做个铺垫,这些其实也是我自己的学习笔记包括思路,其实有时候我感觉这些基础才是实打实的东西,废话不多说,直接开始。
一、课前实验
这里我写了三个加载器(不清楚这块的小伙伴也可以直接往下看,后面文章里面都有说),可以看到外面的自定义加载器和扩展类加载器都可以输出,但是启动类加载器是无法输出的。
接下来我们使用系统类加载器加载一个我们的自定义类,这里我就用之前写的CC1吧。
二、断点调试流程
我们首先打一个断点,可以看到此时我们调用的是Launcher类下的AppClassLoader下的loadclass,我们跟进Launcher
我们可以先看一下Launcher的结构
我们跟到AppClassloader下的loadClass,可以看到需要传入两个参数所以它会一直跳到它的父类ClassLoader下的loadClass。
我们来到了ClassLoader下的一个参数的LoadClass(),它返回了两个参数的loadClass。所以当我们最开始直接往下调试会到ClassLoader.loadClass。
继续向下,我们跳回了Launcher的AppClassLoader的loadClass,我们继续看代码最终它调用了它super的loadClass,向下调试进入
我们又回到了ClassLoader但这次走两个参数的loadClass(String name, boolean resolve)
我们看一下这里的代码,我们开始继续向下
其实从这里开始我们就是双向委派机制了,我们了解一下双向委派,这里我画了一张图,大家看图应该就能理解了,其实还是属于一个单向过程的。如果还是不清楚可以去找些资料学习一下。
我们继续向下分析,跟到findLoadedClass,这个方法主要是判断类是否已加载。我们分析一下这块。跳转进该方法,我们看到它将我们的类name传到了checkName中,继续向下。
看代码我们可以知道,如果name里有“/”、使用数值但虚拟机不支持class数组返回false,其他情况或者空name返回true,所以我们返回了true。VM.allowArraySyntax字面翻译允许数组语法,这里可能有的小伙伴在JBOSS调试的时候可能遇到过这个问题。所以这里我们稍微继续看一下,跟进VM.allowArraySyntax()。
跟到VM.allowArraySyntax()下面,我们可以看到allowArraySyntax = defaultAllowArraySyntax而defaultAllowArraySyntax = false;可以看到这里的默认值是false,这里我去查了一下资料,这个参数默认值是true,而在jdk1.6中这个参数的默认值是false。我们继续看一下这个常量都是谁调用过的吧。
这里我们可以看到saveAndRemoveProperties调用的。
我们跟到saveAndRemoveProperties方法,方法的如图的位置设置了AllowArraySyntax,也就是将传入的props配置的sun.lang.ClassLoader.allowArraySyntax赋值给allowArraySyntax。这里有注释。
// Set a boolean to determine whether ClassLoader.loadClass accepts
// array syntax. This value is controlled by the system property
// "sun.lang.ClassLoader.allowArraySyntax".
翻译为 设置一个布尔值以确定是否使用ClassLoader.loadClass接受数组语法。此值由系统属性控制。
接下来我们看看谁调用了saveAndRemoveProperties方法。
查看调用方法,最后跟到了initializeSystemClass,这里相当于初始化sun.misc相关的环境变量在往下的东西就不看了。有点说多了,我们继续返回到findLoadedClass()
跳回findLoadedClass0,checkName(name)最终返回为true。取反之后是false所以我们就走到了findLoadedClass0,这个方法还是一个native的方法,这里就不多说了。
之后我去查了一些资料,在ClassLoader的loadClass方法中第一步是去加锁,代码里面写的很清楚,也就是为了并发安全,第二步就会调用findLoadedClass方法判断该类是否已经加载过,加载过就返回Class对象,未加载过就返回null。
好了这块其实已经明白了,我们只要知道此处类加载过就返回Class对象,未加载过就返回null。我们回到ClassLoader继续看。
返回继续看代码这里如果类没有被加载就去加载类,之后判断parent,如果父类加载器不为空,让父类加载器去加载这个类。
我们此时有递归到了ClassLoader的loadClass,这里的extclassloder也是和appclassload是一样的(他俩其实就是逻辑上的调用关系),都是没用loadclass方法,然后向上找父类就到了ClassLoader的loadClass。我们继续向下调试
这里我们可以看到外面的findLoadClass返回为空,然后我们parent返回为null,这里其实我们是无法调用启动类加载器的,因为Bootstran加载器是底层的加载器我们是无法在java里面获得的所以就为null,我们开篇的实验就是为了说明这里。所以我们继续往下走。
这里我们走到了findBootstrapClassOrNull,这里和findLoadedClass的检查方式其实是一样的,我就不多进行赘述了。
我们继续往下走,我们的c依旧为空,所以我们最后是进到了findClass这里
这里我们就进到了URLClassLoader类下的findClass。这里是因为我们的AppClassLoader和ExtClassLoader是没有findClass这个方法的,他们的直接父类是URLClassLoader。我们继续向下分析。
这里我们是属于ExtClassLoader所以我们这里是无法加载到的,所以我们跳出循环
我们又回到了ClassLoad.loadclass,此时我们是appclassloader,继续往下跟进
我们再次进入findclass,这次应该就是最后一次了
我们回到了URLClassLoader,此时我们就找到了我们的加载类,我们继续跟进。
进入URLClassLoader的defineClass,我们们又走到它他下面的defineClass,这个方法就是我们利用的地方。继续跟进去
我们这里进入了SecureClassLoader下的defineClass,这一套又一套的,继续跟吧
这时我们又回到了ClassLoader的defineClass,这里可以看到我们调用的了defineClass1,此处就是关键,我们看一下这个方法
我们看到又是个native,这里应该是个本地接口和C应该是有关系了,到这块就不看了有兴趣的师傅可以继续往下看一看吧。
之后就是一层一层把C返回来就Ok了
可爱小尾巴~~~
我只是个基础很差 技术很菜 脚本小子里面的小菜鸡,文章里面有什么写的不对的地方,望师傅们多加指正,我肯定狂奔加小跑的学。这个类加载其实在Java反序列化中都是很常用的东西关于动态加载利用的CC链或者CB链还有一些通过自定义加载类打利用等等。年前争取在发几篇文章,前几年净瞎打实战了,好好学学这些基础,争取能看到师傅们的脚印。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)