Cobalt Strike record

Article directory

0X00 background

Recently, I'm doing penetration test related work. I need to prepare to use Cobalt strike. I knew this artifact for a long time. I've seen official video tutorials for several years, but I didn't understand many of them at that time because of my poor English. I didn't know much about it for various reasons, so I've always been at the level of general understanding. It's not until now that there is a need to start research, and the experience in the process is quite deep. Technology really can't stay at the level of knowing and understanding, just like learning a language, it needs more hands-on practice to be proficient in using. Of course, in the process of in-depth study of a technology, it is inevitable to encounter a variety of problems, step by step to solve these problems is the real learning process. I also encountered many problems in the study and research of cobalt strike. I had to work hard for the help of some unknown masters to solve all the problems. Here, I record some problems and solutions in the process for future reference. At the same time, I hope it can help my friends who have just contacted cobalt strike.

0x01 basic principle

There are a large number of articles and tutorials on basic use and principles on the Internet. I'll just elaborate a few basic points of my personal understanding here. First, I'll talk about stage and stage. In traditional remote control software, we all directly generate a full-featured client (including various function codes required by remote control), such as grey pigeon ( Age is exposed here.. )And then upload the client to the target machine in various ways and run it. After running, the target machine communicates with our control endpoint point-to-point. And Cobalt strike breaks this part into two parts (stage and stage). Stage is a small program, which is usually a manual optimized assembly instruction, used to download stage and inject it into memory for operation. Stage is a block of code with many functions, which is used to accept and execute the tasks of our control side and return the results. Stage downloads stage and injects memory to run through various ways (such as http, dns, tcp, etc.), which is called payload stage. In the same way, Cobalt strike also provides a way similar to the traditional remote control on-line. After the function is packaged and run directly, it can communicate with the team server. This is called payload stages. The client generating stages can be generated under attack - > package - > windows executable (s). In this part, I was only able to figure it out when I studied dns online. I need to thank Master B0y1n4o4 for his help

0x02 about breaking the precepts

At present, most of the online versions are from the official trial version and the maximum version is 3.14 (May 4). I asked Peng to find an official version of 3.14. The original version itself is not as limited as the trial version, so it is relatively easy to break the ring. Just bypass the license authentication, which is in the constructor of the file common/Authorization.class.

public Authorization() {
    String str = CommonUtils.canonicalize("cobaltstrike.auth");
    if (!(new File(str)).exists())
      try {
        File file = new File(getClass().getProtectionDomain().getCodeSource().getLocation().toURI());
        if (file.getName().toLowerCase().endsWith(".jar"))
          file = file.getParentFile(); 
        str = (new File(file, "cobaltstrike.auth")).getAbsolutePath();
      } catch (Exception exception) {
        MudgeSanity.logException("trouble locating auth file", exception, false);
      }  
    byte[] arrayOfByte1 = CommonUtils.readFile(str);
    if (arrayOfByte1.length == 0) {
      this.error = "Could not read " + str;
      return;
    } 
    AuthCrypto authCrypto = new AuthCrypto();
    byte[] arrayOfByte2 = authCrypto.decrypt(arrayOfByte1);
    if (arrayOfByte2.length == 0) {
      this.error = authCrypto.error();
      return;
    } 
    String[] arrayOfString = CommonUtils.toArray(CommonUtils.bString(arrayOfByte2));
    if (arrayOfString.length < 4) {
      this.error = "auth content is only " + arrayOfString.length + " items";
      return;
    } 
    this.licensekey = arrayOfString[0];
    if ("forever".equals(arrayOfString[1])) {
      this.validto = arrayOfString[1];
      MudgeSanity.systemDetail("valid to", "perpetual");
    } else {
      this.validto = "20" + arrayOfString[1];
      MudgeSanity.systemDetail("valid to", CommonUtils.formatDateAny("MMMMM d, YYYY", getExpirationDate()));
    } 
    this.watermark = CommonUtils.toNumber(arrayOfString[2], 0);
    this.valid = true;
    MudgeSanity.systemDetail("id", this.watermark + "");
  }

Here, you need to manually set the watermark, licensekey, validto, and valid instance fields of this class, as shown in the following code

  public Authorization() {
    this.valid = true;
    this.validto = "forever";
    this.licensekey = "Cartier";
    this.watermark = 1;
    MudgeSanity.systemDetail("valid to", "perpetual");
    MudgeSanity.systemDetail("id", this.watermark + "");
  }

Other restrictions on the number of listener s can be referred to below ssooking Master's blog view.

Exit hidden pile

The remaining one is an exit problem left by the author of Cobalt strike. I have to consult master ssooking before I know it. Otherwise, I don't know when to find the Java slag. Thank Master ssooking again for his help.
The author left a function to verify the integrity of the jar file in the program. If the integrity of the jar file is changed, it will be damaged. The author will add an exit instruction to the back door of the command task added 30 minutes after the target goes online, and the target beacon will be automatically disconnected, as shown below.

The code is in Beacon / beacon 2.class

  protected boolean isPaddingRequired() {
    boolean bool = false;
    try {
      ZipFile zipFile = new ZipFile(this.appd);
      Enumeration<? extends ZipEntry> enumeration = zipFile.entries();
      while (enumeration.hasMoreElements()) {
        ZipEntry zipEntry = enumeration.nextElement();
        long l1 = CommonUtils.checksum8(zipEntry.getName());
        long l2 = zipEntry.getName().length();
        if (l1 == 75L && l2 == 21L) {
          if (zipEntry.getCrc() != 1661186542L && zipEntry.getCrc() != 1309838793L)
            bool = true; 
          continue;
        } 
        if (l1 == 144L && l2 == 20L) {
          if (zipEntry.getCrc() != 1701567278L && zipEntry.getCrc() != 3030496089L)
            bool = true; 
          continue;
        } 
        if (l1 == 62L && l2 == 26L && zipEntry.getCrc() != 2913634760L && zipEntry.getCrc() != 376142471L)
          bool = true; 
      } 
      zipFile.close();
    } catch (Throwable throwable) {}
    return bool;
  }
public BeaconC2(Profile paramProfile, BeaconData paramBeaconData, Resources paramResources) {
    this.c2profile = paramProfile;
    this.resources = paramResources;
    this.data = paramBeaconData;
    this.channel_http = new BeaconHTTP(paramProfile, this);
    this.channel_dns = new BeaconDNS(paramProfile, this);
    this.socks = new BeaconSocks(this);
    this.appd = getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
    paramBeaconData.shouldPad(isPaddingRequired());	//Here we call the shouldPad of the beacon data class
    this.parsers.add(new MimikatzCredentials(paramResources));
    this.parsers.add(new MimikatzSamDump(paramResources));
    this.parsers.add(new DcSyncCredentials(paramResources));
    this.parsers.add(new MimikatzDcSyncCSV(paramResources));
    this.parsers.add(new ScanResults(paramResources));
    this.parsers.add(new NetViewResults(paramResources));
  }

Look again at Beacon / beacon data.class

  
  public void shouldPad(boolean paramBoolean) {
    this.shouldPad = paramBoolean;
    this.when = System.currentTimeMillis() + 1800000L;
  }
  
  public void task(String paramString, byte[] paramArrayOfbyte) {
    synchronized (this) {
      List<byte[]> list = getQueue(paramString);
      //Here we judge the integrity of the file and whether the bean is on line for 30 minutes
      if (this.shouldPad && System.currentTimeMillis() > this.when) {
        CommandBuilder commandBuilder = new CommandBuilder();
        commandBuilder.setCommand(3);
        commandBuilder.addString(paramArrayOfbyte);
        list.add(commandBuilder.build());
      } else {
        list.add(paramArrayOfbyte);
      } 
      this.tasked.add(paramString);
    } 
  }

The breaking method is to directly change this.shouldPad = paramBoolean; to this.shouldPad = false;

0x03 CDN + reverse hidden team server

Domain Fronting

This part of the principle refers to the article of garbage can master( Click here. ), here help the dustbin master fill in a hole he said in the article.


Here, when the dustbin master adds the Listener, the Host fills in the CDN address. When he downloads the stage using powershell, the stage runs. When the stage goes to download the stage, he directly accesses the CDN address to download, but the mallable Profile does not configure the behavior of setting a stage, so it cannot be downloaded back to the team server. Here, you only need to configure the HTTP stage module in the profile file, and specify the Host like HTTP get to access the team server download stage from CDN.

Proxy

The principle of reverse agent here borrows the picture of garbage can master. I will not elaborate on it in detail. The garbage can master has explained it very clearly.

I use Nginx as the reverse proxy. If you just study this, you may encounter that the IP of the client is the Nginx server IP after it goes online, and it appears to be the CDN node IP when you go CDN. Here are two solutions. First, look at the code in the server/ServerUtils.class class class class:

  public static String getRemoteAddress(Profile paramProfile, Map paramMap) {
    boolean bool = paramProfile.option(".http-config.trust_x_forwarded_for");
    if (bool && paramMap.containsKey("X-Forwarded-For")) {
      String str1 = (String)paramMap.get("X-Forwarded-For");
      if (str1.indexOf(",") > -1) {
        str1 = CommonUtils.strrep(str1, " ", "");
        StringStack stringStack = new StringStack(str1, ",");
        str1 = stringStack.shift();
      } 
      if (CommonUtils.isIP(str1) || CommonUtils.isIPv6(str1))
        return str1; 
      CommonUtils.print_error("remote address '" + (String)paramMap.get("X-Forwarded-For") + "' in X-Forwarded-For header is not valid.");
    } 
    String str = (String)paramMap.get("REMOTE_ADDRESS");
    return "".equals(str) ? "" : str.substring(1);
  }
}

Here, Cobatl Strike can obtain IP from remote address and x-forward-for in HttpHeader. We can either set the remote address value when Nginx is reverse proxy, or set the trust x_forward_forvalue to true in the HTTP config module in the profile configuration file. This is also seen from the code. The English slag indicates that we are ashamed and the official writing is very detailed.
There's a problem here. When the reverse proxy is used to customize remote? Address, it's often invalid. I don't know what the specific situation is. I've successfully tested it on other machines before.

0x04 DNS Online

An unfilled pit

This pit is the largest pit for research and use of Cobalt Strike, which has not been solved until today. The problem is that when using the DNS listener, whether it is a beacon [DNS / reverse] HTTP or a beacon [DNS / reverse] DNS [TXT], if using the staging method, the stager will crash when downloading the stage and injecting it into the memory, as shown in the following figure.

If you use beacon  dns / reverse  http, you can choose the non pure dns mode. In the non pure dns mode, the stage uses the http mode when downloading the stage. As long as the stage is successfully downloaded and injected into memory, the mode can use the dns mode to communicate. If any master knows what's going on, he will teach you.

DNS Listener attribute

Finally, with the instruction of master B0y1n4o4, there is no problem to use stageless mode to go online. But when using dns to go online, we need to pay attention to one more problem. When you add a Listener, you need to fill in the port information for both the beacon [dns / reverse] HTTP and beacon [dns / reverse] dns [TXT], as shown in the following figure.

If port 80 is used, http mode is preferred for communication after going online. If you want to use pure DNS communication, you need to use mode command to switch to DNS, DNS TXT or dns6 mode after going online. Add listener to set a non-80 port to be online, so the communication will adopt DNS mode by default, and mode cannot be used to switch to http mode.

0x05 conclusion

All of the above are my personal research and test conclusions. Please correct more if you have any shortcomings. Cobalt Strike is really a powerful tool, and there are many contents and technologies to be studied. I am also learning Java, trying to read the kernel code as soon as possible.

Published 5 original articles, praised 0, visited 1078
Private letter follow

Tags: DNS Nginx Attribute Java

Posted on Tue, 10 Mar 2020 08:18:17 -0400 by JCF22Lyoko