环境搭建

idea新建一个maven项目:

之后去https://mvnrepository.com/artifact/commons-collections/commons-collections/3.1获取依赖,加入pom.xml中,最终pom.xml内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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>CommonsCollections_practice</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>

</project>

之后mvn install,再打开External Librarites,就会发现已经添加了CommonsCollections的依赖:

如果没有的话右键点击pom.xml,之后点击Maven->Reload project即可

执行命令

src/main/java目录下创建名为invokerTransformerTest.java的文件,其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
import org.apache.commons.collections.functors.InvokerTransformer;

public class invokerTransformerTest {
public static void main(String[] args) {
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{new String("calc")}
);
invokerTransformer.transform(Runtime.getRuntime());
}
}

执行一下,就会弹出计算器。

分析

动态调一下很容易就能发现,计算器是在invokerTransformer.transform(Runtime.getRuntime())运行完之后弹出来的,所以我们首先来分析transform这个方法,其源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}

看起来好像很复杂,其实大多数都在做异常处理,去掉异常处理就是这样:

1
2
3
4
5
6
7
8
9
public Object transform(Object input){
if (input == null) {
return null;
} else {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
}
}

input就是我们传入的Runtime.getRuntime()。简单搜搜,就会发现在
java里我们可以用Runtime.getRuntime().exec("calc")这样的语句来执行calc,也就是弹出计算器。

所以我们现在只要能调用exec方法,再传入参数,就可以执行命令了

那么如何调用呢?可以用java的反射机制:

  1. 找到对象对应的类
  2. 根据方法名和传入的参数类型来找到该对象对应的类中指定的方法(由于多态的问题,所以需要传入参数类型来找到唯一的函数)
  3. 调用找到的方法

当然 中间可能会出各种各样的报错——例如没找到这个方法,所以开发就会写一堆的异常处理代码

上面的三步的代码实现对应到transform方法里就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
public Object transform(Object input){
// input为传入的对象,可以暂时把它当成Runtime.getRuntime()
if (input == null) {
return null;
} else {
// 找到对象对应的类
Class cls = input.getClass();
// 找该类中指定的方法
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
// 调用该方法
return method.invoke(input, this.iArgs);
}
}

其中涉及到的变量名根据字面意思也很容易明白其含义。那么问题来了,去哪控制这些this开头的变量呢?从构造函数中:

1
2
3
4
5
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}

我们在测试代码中使用了下面的语句

1
2
3
4
5
new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{new String("calc")}
);

所以很明确了,想查找的方法名为exec,传入的参数类型为String,传入的参数为calc

传入之后,再执行invokerTransformer.transform(Runtime.getRuntime()),就相当于调用Runtime.getRuntime().exec("calc")

优化

来给他加个回显功能

也很简单,获取程序的输出流,之后再打印出来就行

在测试的时候发现输入部分命令时会报java.io.IOException: Cannot run program "dir": CreateProcess error=2, 系统找不到指定的文件类似的错,只需要在命令前加cmd.exe /c即可

优化之后的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import org.apache.commons.collections.functors.InvokerTransformer;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class invokerTransformerTest {
public static void main(String[] args) {
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{new String("cmd.exe /c time /T")}
);
try{
Process process = (Process) invokerTransformer.transform(Runtime.getRuntime());
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = null;
while ( (line = bufferedReader.readLine()) != null){
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

运行上面的代码后,就会输出当前时间