> Author: shaobaobaoer
> Codes : https://github.com/ninthDevilHAUNSTER/JavaSecLearning
> Mail: shaobaobaoer@126.com
> WebSite: shaobaobaoer.cn
> Time: Thursday, 24. July 2020 07:29PM
Java原生提供了对本地系统命令执行的支持,对于开发者来说执行本地命令来实现某些程序功能(如:ps 进程管理、top内存管理等)是一个正常的需求,而对于黑客来说本地命令执行是一种非常有利的入侵手段。
Runtime
/** * 利用 Runtime 执行命令 * * @throws IOException */ public static void execCommandRuntime() throws IOException { System.out.println(IOUtils.toString(Runtime.getRuntime().exec("cmd /c dir").getInputStream(), "GBK")); }
故意引入一个错误,可以查看Runtime的调用栈。内容较多,就不贴出来了,最终可以查到,Runtime.exec调用的最低层函数为 java.lang.ProcessImpl.create
,打开IDEA可以看到较为清晰的调用链(选中create函数,然后Navigate》Call Hierarchy)
PS:图中最后一项没有展开,展开中有一项就是 Runtime.exec
综上,Runtime()的调用是这样的。
Runtime.exec(xxx)
java.lang.ProcessBuilder.start()
new java.lang.ProccessImpl(xxx)
new ProccessImpl()
调用操作系统级别 create
(Windows)执行命令并返回 create
的 PID
。
JDK9的时候把 UNIXProcess
合并到了 ProcessImpl
当中了,但是我并没有找到 javasec
所说的 forkAndExec
的 native
方法,我推测Windows系统没有这个方法。
ProcessBuilder
/** * 利用 ProcessBuilder 执行命令 * * @throws IOException */ public static void execCommandProcessBuilder() throws IOException { System.out.println(IOUtils.toString(new ProcessBuilder("cmd /c dir").start().getInputStream(), "GBK")); }
从之前Runtime的调用链可以发现,Runtime实际上是对ProcessBuilder进一步分装而已。
之前有讨论过反射的相关写法,现在我们可以把反射Runtime写的更加迷惑性一些,利用 new String(new byte[]{})
的方法动态创建String
/** * 动态调用 Runtime 执行命令 */ public static void execCommandWithoutRuntime() { try { // 获取Runtime类对象 String cmd = "cmd /c dir"; // 获取 Runtime类 Class<?> evil_class = Class.forName(new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101})); // 获取 .getRuntime方法 Method getRuntime = evil_class.getMethod(new String(new byte[]{103, 101, 116, 82, 117, 110, 116, 105, 109, 101})); // 执行 .getRuntime方法 由于这是个静态方法,无输入参数,所以第一个参数为Null Object obj0 = getRuntime.invoke(null); // 获取 .exec 方法 Method exec = evil_class.getMethod(new String(new byte[]{101, 120, 101, 99}), String.class); // 执行 Runtime().getRuntime().exec() 方法 该方法是动态方法,第一个为类名 Runtime()类,第二个参数为字符串 Object obj1 = exec.invoke(obj0, cmd); // 获取 .getInputStream 方法, Method getInputStream = obj1.getClass().getMethod(new String(new byte[]{103, 101, 116, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109})); getInputStream.setAccessible(true); Object obj2 = getInputStream.invoke(obj1); System.out.println(IOUtils.toString((InputStream) obj2, "GBK")); } catch (Exception e) { e.printStackTrace(); } }
在之前分析Runtime的调用链时候,就已经发现了Runtime的核心是调用了ProcessImpl类下的start方法来执行命令,而这个类是 final
的,意味着只能通过反射来执行
final class ProcessImpl extends Process {...}
但是没有关系,可以通过 setAccessible()
来强行调用该类。另外,通过阅读源码,还能够发现 ProcessImpl.start
函数是一个静态函数,其接口如下所示
static Process start(String cmdarray[], java.util.Map<String,String> environment, String dir, ProcessBuilder.Redirect[] redirects, boolean redirectErrorStream)
那么如果要反射,只要照虎画猫,构造好Method接口,就可以反射了。具体实现代码如下所示
public static void execCommandProcessImpl() throws IOException { try { String[] cmds = new String[]{"cmd", "/c", "dir"}; // 这里不能写一起 Class<?> clazz = Class.forName("java.lang.ProcessImpl"); // 按照start的样子构造方法 Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class); method.setAccessible(true); Process e = (Process) method.invoke(null, cmds, null, ".", null, true); System.out.println(IOUtils.toString(e.getInputStream(), "GBK")); } catch (Exception e) { e.printStackTrace(); } }
通过JNI接口C/C++和Java可以互相调用(存在跨平台问题)。Java可以通过JNI调用来弥补语言自身的不足(代码安全性、内存操作等)。
关于如何去生成一个动态链接库,我不在该文中赘述了。详细可以参考代码库中的 该篇REAME
我非常简单得写了一个 CommandExecution 类。并生成了动态链接文件 cmd.dll
package jni_sec; public class CommandExecution { public static native String exec(String cmd); }
之后,可以调用如下代码来完成JNI的命令执行。
/** * 利用JNI文件,调用动态链接库来执行结果 * * @throws Exception */ public static void execCommandJNIFile() { String cmd = "whoami";// 定于需要执行的cmd try { ClassLoader loader = new ClassLoader(JavaLocalCmdExec.class.getClassLoader()) { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { return super.findClass(name); } catch (ClassNotFoundException e) { return defineClass(COMMAND_CLASS_NAME, COMMAND_CLASS_BYTES, 0, COMMAND_CLASS_BYTES.length); } } }; // 测试时候换成自己编译好的lib路径 File libPath = new File("D://java_box//java_sec_learning//lib//cmd.dll"); // load命令执行类 Class<?> commandClass = loader.loadClass("jni_sec.CommandExecution"); Method loadLibrary0Method = ClassLoader.class.getDeclaredMethod("loadLibrary0", Class.class, File.class); loadLibrary0Method.setAccessible(true); loadLibrary0Method.invoke(loader, commandClass, libPath); String content = (String) commandClass.getMethod("exec", String.class).invoke(null, cmd); System.out.println(content); } catch (Exception e) { e.printStackTrace(); } }
END