验证 cobaltstrike.jar 是否为原始文件:https://verify.cobaltstrike.com/
xxxxxxxxxxsha256sum cobaltstrike.jar# Cobalt Strike 4.4 (August 04, 2021)7af9c759ac78da920395debb443b9007fdf51fa66a48f0fbdaafb30b00a8a858 Cobalt Strike 4.4 Licensed (cobaltstrike.jar)
安装 IntelliJ IDEA
xxxxxxxxxxflatpak install flathub com.jetbrains.IntelliJ-IDEA-Community反编译 cobaltstrike.jar
xxxxxxxxxxfind ~ -name *java-decompiler.jar*cp /files/IDEA-C/plugins/java-decompiler/lib/java-decompiler.jar .mkdir cobaltstrikedocker run -it --rm -v $(pwd):/cs -w /cs openjdk:17 java -cp java-decompiler.jar org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler -dgs=true cobaltstrike.jar cobaltstrikecd cobaltstrike && unzip cobaltstrike.jar && rm cobaltstrike.jar下载 OpenJDK,并配置路径到 IntelliJ IDEA 中
xxxxxxxxxxwget https://download.java.net/java/ga/jdk11/openjdk-11_linux-x64_bin.tar.gztar xzvf openjdk-11_linux-x64_bin.tar.gz新建项目
创建目录结构如下
xxxxxxxxxx.├── cobaltstrike 反编译的代码├── lib 原始 jar 文件└── src 二次开发的代码
文件 - 项目结构 - 项目设置 - 模块
文件 - 项目结构 - 项目设置 - 工件
主类
xxxxxxxxxxaggressor.Aggressor
删除 META-INF 文件夹
cobaltstrike.jar 有两种启动方式
作为客户端启动:aggressor.Aggressor
xxxxxxxxxxprivate final void A(String[] var1) { ParserConfig.installEscapeConstant('c', "\u0003"); ParserConfig.installEscapeConstant('U', "\u001f"); ParserConfig.installEscapeConstant('o', "\u000f"); (new UseSynthetica()).setup(); Requirements.checkGUI(); License.checkLicenseGUI(new Authorization()); B = new MultiFrame(); super.initializeStarter(this.getClass()); (new ConnectDialog(B)).show();}作为服务端启动:server.TeamServer
xxxxxxxxxxpublic static void main(String[] var0) { int var1 = CommonUtils.toNumber(System.getProperty("cobaltstrike.server_port", "50050"), 50050); if (!AssertUtils.TestPort(var1)) { System.exit(0); } Requirements.checkConsole(); Authorization var2 = new Authorization(); License.checkLicenseConsole(var2); MudgeSanity.systemDetail("scheme", QuickSecurity.getCryptoScheme() + ""); ……}common.License
xxxxxxxxxxpublic static void checkLicenseGUI(Authorization var0) { if (!var0.isValid()) { CommonUtils.print_error("Your authorization file is not valid: " + var0.getError()); JOptionPane.showMessageDialog((Component)null, "Your authorization file is not valid.\n" + var0.getError(), (String)null, 0); System.exit(0); } if (!var0.isPerpetual()) { if (var0.isExpired()) { CommonUtils.print_error("Your Cobalt Strike license is expired. Please contact sales@strategiccyber.com to renew. If you did renew, run the update program to refresh your authorization file."); JOptionPane.showMessageDialog((Component)null, "Your Cobalt Strike license is expired.\nPlease contact sales@strategiccyber.com to renew\n\nIf you did renew, run the update program to refresh your\nauthorization file.", (String)null, 0); System.exit(0); } if (var0.isAlmostExpired()) { CommonUtils.print_warn("Your Cobalt Strike license expires in " + var0.whenExpires() + ". Email sales@strategiccyber.com to renew. If you did renew, run the update program to refresh your authorization file."); JOptionPane.showMessageDialog((Component)null, "Your Cobalt Strike license expires in " + var0.whenExpires() + "\nEmail sales@strategiccyber.com to renew\n\nIf you did renew, run the update program to refresh your\nauthorization file.", (String)null, 1); } }}
public static void checkLicenseConsole(Authorization var0) { if (!var0.isValid()) { CommonUtils.print_error("Your authorization file is not valid: " + var0.getError()); System.exit(2); } if (!var0.isPerpetual()) { if (var0.isExpired()) { CommonUtils.print_error("Your Cobalt Strike license is expired. Please contact sales@strategiccyber.com to renew. If you did renew, run the update program to refresh your authorization file."); System.exit(2); } if (var0.isAlmostExpired()) { CommonUtils.print_warn("Your Cobalt Strike license expires in " + var0.whenExpires() + ". Email sales@strategiccyber.com to renew. If you did renew, run the update program to refresh your authorization file."); } }}common.Authorization
xxxxxxxxxxprotected int watermark = 0;protected String validto = "";protected String error = null;protected boolean valid = false;
public Authorization() { String var1 = CommonUtils.canonicalize("cobaltstrike.auth"); if (!(new File(var1)).exists()) { try { File var2 = new File(this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI()); if (var2.getName().toLowerCase().endsWith(".jar")) { var2 = var2.getParentFile(); } var1 = (new File(var2, "cobaltstrike.auth")).getAbsolutePath(); } catch (Exception var19) { MudgeSanity.logException("trouble locating auth file", var19, false); } } byte[] var20 = CommonUtils.readFile(var1); ……}common.Authorization
License 认证需要以下几部分:
xxxxxxxxxxprotected int watermark = 1234567890;protected String validto = "forever";protected boolean valid = true;protected byte[] key = { 94, -104, 25, 74, 1, -58, -76, -113, -91, -126, -90, -87, -4, -69, -110, -42 };可以参考 CSAgent:
xxxxxxxxxxif (className.equals("common/Authorization")) { ctClass = this.classPool.makeClass(new ByteArrayInputStream(classfileBuffer)); String func = "public static byte[] hex2bytes(String s) { int len = s.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16)); } return data;}"; CtMethod hex2bytes = CtNewMethod.make(func, ctClass); ctClass.addMethod(hex2bytes); CtConstructor mtd = ctClass.getDeclaredConstructor(new CtClass[0]); mtd.setBody("{$0.watermark = 1234567890;$0.validto = \"forever\";$0.valid = true;common.MudgeSanity.systemDetail(\"valid to\", \"perpetual\");common.MudgeSanity.systemDetail(\"id\", String.valueOf($0.watermark));common.SleevedResource.Setup(hex2bytes(\"" + this.hexkey + "\"));}"); return ctClass.toBytecode();}修改如下:
xxxxxxxxxxpublic class Authorization { protected int watermark = 128; protected String validto = "forever"; protected String error = null; protected boolean valid = true; protected byte[] key = {94, -104, 25, 74, 1, -58, -76, -113, -91, -126, -90, -87, -4, -69, -110, -42};
public Authorization() { try { MudgeSanity.systemDetail("valid to", "perpetual"); MudgeSanity.systemDetail("id", this.watermark + ""); SleevedResource.Setup(key); } catch (Exception var18) { MudgeSanity.logException("auth file parsing", var18, false); } }
public boolean isPerpetual() { return "forever".equals(this.validto); }
public boolean isValid() { return this.valid; }
public String getError() { return this.error; }
public String getWatermark() { return this.watermark + ""; }
public long getExpirationDate() { return CommonUtils.parseDate(this.validto, "yyyyMMdd"); }}Cobalt Strike 中存在多处 class CRC 校验,因此在上一步通过 license 认证后还无法正常使用,需要移除几处暗桩
可以通过全局搜索 .class" 、 getCrc、System.currentTimeMillis()、System.exit(0) 等定位暗桩
common.Helper
xxxxxxxxxxpublic class Helper { public final boolean startHelper(Class var1) { return true; }}common.Starter
xxxxxxxxxxpublic abstract class Starter { protected final void initializeStarter(Class var1) {}}common.Starter2
xxxxxxxxxxpublic abstract class Starter2 { protected final void initialize(Class var1) {}}beacon.BeaconData
xxxxxxxxxxpublic void shouldPad(boolean var1) { this.shouldPad = false; this.when = System.currentTimeMillis() + 1800000L;}beacon.CommandBuilder
xxxxxxxxxx移除 static 中的所有内容
运行/调试配置 - 编辑配置 - 添加新的运行配置
JAR 应用程序
虚拟机选项:
xxxxxxxxxxjava -XX:ParallelGCThreads=4 -XX:+AggressiveHeap -XX:+UseParallelGC -Duser.language=en -javaagent:SwingHTML.jar环境变量:GDK_SCALE=2
应用程序
主类:server.TeamServer
虚拟机选项:
xxxxxxxxxxjava -XX:ParallelGCThreads=4 -Dcobaltstrike.server_port=44433 -Dcobaltstrike.server_bindto=127.0.0.1 -Djavax.net.ssl.keyStore=cobaltstrike.store -Djavax.net.ssl.keyStorePassword=Microsoft -server -XX:+AggressiveHeap -XX:+UseParallelGC -classpath cobaltstrike.jar -Duser.language=en生成 cobaltstrike.store
xxxxxxxxxxdocker run -it --rm -v $(pwd):/cs -w /cs openjdk:11 keytool -keystore cobaltstrike.store -storepass Microsoft -keypass Microsoft -genkey -keyalg RSA -alias Microsoft -dname "CN=www.microsoft.com, O=Microsoft Corporation, L=Redmond, S=WA, C=US"aggressor.Prefs
xxxxxxxxxxprotected File myFile() { return new File(".aggressor.prop");}cloudstrike.NanoHTTPD
xxxxxxxxxxSystem.setProperty("https.protocols", "SSLv3,SSLv2Hello,TLSv1,TLSv1.1,TLSv1.2,TLSv1.3");server.TeamServer
xxxxxxxxxxSystem.setProperty("https.protocols", "SSLv3,SSLv2Hello,TLSv1,TLSv1.1,TLSv1.2,TLSv1.3");Security.setProperty("jdk.tls.disabledAlgorithms", Security.getProperty("jdk.tls.disabledAlgorithms").replace("TLSv1", "").replace("TLSv1.1", ""));TeamServer var7 = new TeamServer(var0[0], var1, var0[1], var6, var2);cloudstrike.WebServer
这个文件 java-decompiler.jar 反编译的代码无法直接编译,此处建议使用 JD-GUI 进行反编译
在 _serve 中添加以下判断:
xxxxxxxxxxif(!uri.startsWith("/")) { return processResponse(uri, method, header, param, false, null, new Response(HTTP_BADREQUEST, MIME_PLAINTEXT, ""));}我这里的做法是所有涉及 checksum8 的代码都会返回 404,因此使用 stager 必须进行以下配置:
xxxxxxxxxxset uri_x86 "……";set uri_x64 "……";
xxxxxxxxxxpublic Response _serve(String uri, String method, Properties header, Properties param) { String useragent = (header.getProperty("User-Agent") + "").toLowerCase(); boolean blockedByUA = false; for (String blockedUA : this.blockedUAArray) { if (blockedUA.trim().length() > 0 && CSUtils.matchesSimpleGeneric(useragent, blockedUA.trim())) blockedByUA = true; } boolean allowedByUA = true; if (this.allowedUAArray.length > 0) { allowedByUA = false; for (String allowedUA : this.allowedUAArray) { if (allowedUA.trim().length() > 0 && CSUtils.matchesSimpleGeneric(useragent, allowedUA.trim())) allowedByUA = true; } } if (!allowedByUA || blockedByUA) { String remoteAddress = header.getProperty("REMOTE_ADDRESS"); if (!allowedByUA) print_warn("Request not allowed for useragent '" + useragent + "'. URI=" + uri + " Method=" + method + " Remote Address=" + remoteAddress); if (blockedByUA) print_warn("Request blocked for useragent '" + useragent + "'. URI=" + uri + " Method=" + method + " Remote Address=" + remoteAddress); return processResponse(uri, method, header, param, false, (WebService)null, new Response("404 Not Found", "text/plain", "")); } if(!uri.startsWith("/")) { return processResponse(uri, method, header, param, false, null, new Response(HTTP_BADREQUEST, MIME_PLAINTEXT, "")); } if (method.equals("OPTIONS")) { Response r = processResponse(uri, method, header, param, false, (WebService)null, new Response("200 OK", "text/html", "")); r.addHeader("Allow", "OPTIONS,GET,HEAD,POST"); return r; } if (this.hooks.containsKey(uri)) { WebService service = (WebService)this.hooks.get(uri); return processResponse(uri, method, header, param, true, service, service.serve(uri, method, header, param)); } if (this.hooksSecondary.containsKey(uri)) { WebService service = (WebService)this.hooksSecondary.get(uri); return processResponse(uri, method, header, param, false, service, service.serve(uri, method, header, param)); } if (this.hooks.containsKey(uri + "/")) { WebService service = (WebService)this.hooks.get(uri + "/"); return processResponse(uri + "/", method, header, param, true, service, service.serve(uri, method, header, param)); } if (uri.startsWith("http://")) { WebService service = (WebService)this.hooks.get("proxy"); if (service != null) return processResponse(uri, method, header, param, true, service, service.serve(uri, method, header, param)); return processResponse(uri, method, header, param, false, (WebService)null, new Response("404 Not Found", "text/plain", "")); } if (isStagerX64Strict(uri) && this.hooks.containsKey("stager64")) { WebService service = (WebService)this.hooks.get("stager64"); return processResponse(uri, method, header, param, false, (WebService)null, new Response("404 Not Found", "text/plain", "")); } if (isStagerStrict(uri) && this.hooks.containsKey("stager")) { WebService service = (WebService)this.hooks.get("stager"); return processResponse(uri, method, header, param, false, (WebService)null, new Response("404 Not Found", "text/plain", "")); } Iterator<Map.Entry> i = this.hooksSecondary.entrySet().iterator(); while (i.hasNext()) { Map.Entry e = i.next(); WebService svc = (WebService)e.getValue(); String hook = (new StringBuilder()).append(e.getKey()).append("").toString(); if (uri.startsWith(hook) && svc.isFuzzy()) return processResponse(uri, method, header, param, false, svc, svc.serve(uri.substring(hook.length()), method, header, param)); } if (isStagerX64(uri) && this.hooks.containsKey("stager64")) { WebService service = (WebService)this.hooks.get("stager64"); return processResponse(uri, method, header, param, false, (WebService)null, new Response("404 Not Found", "text/plain", "")); } if (isStagerX64(uri)) { print_warn("URI Matches staging (x64) URL, but there is no stager bound...: " + uri); return processResponse(uri, method, header, param, false, (WebService)null, new Response("404 Not Found", "text/plain", "")); } if (isStager(uri) && this.hooks.containsKey("stager")) { WebService service = (WebService)this.hooks.get("stager"); return processResponse(uri, method, header, param, false, (WebService)null, new Response("404 Not Found", "text/plain", "")); } if (isStager(uri)) { print_warn("URI Matches staging (x86) URL, but there is no stager bound...: " + uri); return processResponse(uri, method, header, param, false, (WebService)null, new Response("404 Not Found", "text/plain", "")); } return processResponse(uri, method, header, param, false, (WebService)null, new Response("404 Not Found", "text/plain", ""));}SwingHTML.java
xxxxxxxxxximport javassist.*;import java.io.ByteArrayInputStream;import java.io.IOException;import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.IllegalClassFormatException;import java.lang.instrument.Instrumentation;import java.lang.instrument.UnmodifiableClassException;import java.security.ProtectionDomain;
public class SwingHTML { static class Transformer implements ClassFileTransformer { public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if(className.equals("javax/swing/plaf/basic/BasicHTML")) { try { ClassPool cp = ClassPool.getDefault(); cp.appendClassPath(new LoaderClassPath(loader)); CtClass cc = cp.makeClass(new ByteArrayInputStream(classfileBuffer), false); CtMethod cm = cc.getDeclaredMethod("isHTMLString"); cm.setBody("return false;"); return cc.toBytecode(); } catch (NotFoundException | IOException | CannotCompileException e) { throw new RuntimeException(e); } } return null; } }
public static void premain(String agentArgs, Instrumentation inst) { inst.addTransformer(new Transformer(), true); Class[] allLoadedClasses = inst.getAllLoadedClasses(); for (Class loadedClass : allLoadedClasses) { if (loadedClass.getName() == "javax.swing.plaf.basic.BasicHTML") { try { inst.retransformClasses(loadedClass); } catch (UnmodifiableClassException e) { throw new RuntimeException(e); } } } }}META-INF/MANIFEST.MF
xxxxxxxxxxManifest-Version: 1.0Premain-Class: SwingHTMLCan-Retransform-Classes: trueCan-Redefine-Classes: true
在上一部分禁用 Swing 中的 HTML 后,需要修改 resources/about.html、aggressor.browsers.Connect、aggressor.dialogs.JavaSmartAppletDialog、aggressor.dialogs.ProxyServerDialog、beacon.pivots.BrowserPivotPortForward、beacon.pivots.SOCKSPivot、dialog.DialogUtils、tunnel.TunnelReversePortForward、ui.ATable 等对受影响的 ui 进行修复,可以通过全局搜索 <html> 或者 <[^>]+> 进行定位
xxxxxxxxxxdocker run -it --rm -v $(pwd):/cs -w /cs openjdk:11 bash -c 'javac -encoding UTF-8 -classpath cobaltstrike.jar CrackSleeve.java && java -classpath cobaltstrike.jar:. CrackSleeve decode'CrackSleeve.java
xxxxxxxxxximport common.*;import dns.SleeveSecurity;import java.io.*;import java.util.Enumeration;import java.util.Objects;import java.util.jar.JarEntry;import java.util.jar.JarFile;
public class CrackSleeve { private static final byte[] Key = { 94, -104, 25, 74, 1, -58, -76, -113, -91, -126, -90, -87, -4, -69, -110, -42 }; private final String DecDir = "Decode/sleeve"; private final String EncDir = "Encode/sleeve";
public static void main(String[] args) { String option = args[0]; CrackSleeve Cracker = new CrackSleeve(); CrackSleevedResource.Setup(Key); if (option.equals("decode")) { Cracker.DecodeFile(); } else if (option.equals("encode")) { Cracker.EncodeFile(); } }
private void DecodeFile() { File saveDir = new File(this.DecDir); if (!saveDir.isDirectory()) saveDir.mkdirs(); try { String path = Objects.requireNonNull(this.getClass().getClassLoader().getResource("sleeve")).getPath(); String jarPath = path.substring(5, path.indexOf("!/")); Enumeration<JarEntry> jarEnum = new JarFile(new File(jarPath)).entries(); while (jarEnum.hasMoreElements()) { JarEntry Element = jarEnum.nextElement(); String FileName = Element.getName(); if (FileName.contains("sleeve") && !FileName.equals("sleeve/")) { System.out.print("[+] Decoding " + FileName + "......"); byte[] decBytes = CrackSleevedResource.DecodeResource(FileName); if (decBytes.length > 0) { System.out.println("Done."); CommonUtils.writeToFile(new File(saveDir, "../"+FileName), decBytes); } else { System.out.println("Fail."); } } } } catch (IOException e) { e.printStackTrace(); } }
private void EncodeFile() { File saveDir = new File(this.EncDir); if (!saveDir.isDirectory()) saveDir.mkdirs(); File decDir = new File(this.DecDir); File[] decFiles = decDir.listFiles(); if (decFiles != null && decFiles.length == 0) { System.out.println("[-] There's no file to encode, please decode first."); System.exit(0); } if (decFiles != null) { for (File file : decFiles) { String filename = decDir.getPath() + "/" + file.getName(); System.out.print("[+] Encoding " + file.getName() + "......"); byte[] encBytes = CrackSleevedResource.EncodeResource(filename); if (encBytes.length > 0) { System.out.println("Done."); CommonUtils.writeToFile(new File(saveDir, file.getName()), encBytes); } else { System.out.println("Fail."); } } } }}
class CrackSleevedResource { private static CrackSleevedResource singleton; private final SleeveSecurity data = new SleeveSecurity();
public static void Setup(byte[] paramArrayOfbyte) { singleton = new CrackSleevedResource(paramArrayOfbyte); }
public static byte[] DecodeResource(String paramString) { return singleton._DecodeResource(paramString); }
public static byte[] EncodeResource(String paramString) { return singleton._EncodeResource(paramString); }
private CrackSleevedResource(byte[] paramArrayOfbyte) { this.data.registerKey(paramArrayOfbyte); }
private byte[] _DecodeResource(String paramString) { byte[] arrayOfByte1 = CommonUtils.readResource(paramString); if (arrayOfByte1.length > 0) { System.currentTimeMillis(); return this.data.decrypt(arrayOfByte1); } byte[] arrayOfByte2 = CommonUtils.readResource(paramString); if (arrayOfByte2.length == 0) { CommonUtils.print_error("Could not find sleeved resource: " + paramString + " [ERROR]"); } else { CommonUtils.print_stat("Used internal resource: " + paramString); } return arrayOfByte2; }
private byte[] _EncodeResource(String paramString) { try { File fileResource = new File(paramString); InputStream fileStream = new FileInputStream(fileResource); byte[] fileBytes = CommonUtils.readAll(fileStream); if (fileBytes.length > 0) { return this.data.encrypt(fileBytes); } } catch (FileNotFoundException e) { e.printStackTrace(); } return null; }}beacon.BeaconPayload
xxxxxxxxxxpublic static byte[] beacon_obfuscate(byte[] var0) { byte[] var1 = new byte[var0.length]; for(int var2 = 0; var2 < var0.length; ++var2) { var1[var2] = (byte)(var0[var2] ^ 32); } return var1;}在 BeaconPayload.java 中搜索 .dll ,提取出以下文件需要修改:
xxxxxxxxxxbeacon.dll beacon.x64.dlldnsb.dll dnsb.x64.dllpivot.dll pivot.x64.dllextc2.dll extc2.x64.dll
在解密后的文件中搜索 xor、2e 定位密钥位置,反编译代码如下:
xxxxxxxxxxdo { "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRR"[iVar6] = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRR"[iVar6] ^ 0x2e; iVar6 = iVar6 + 1;} while (iVar6 < 0x1000);修改后重新加密
xxxxxxxxxxdocker run -it --rm -v $(pwd):/cs -w /cs openjdk:11 java -classpath cobaltstrike.jar:. CrackSleeve encode