开始

域前置已经不是一个新鲜的话题。通信流程如下:

而其中最为关键的地方在于在client中使用自定义的Host字段。在CobaltStrike的相关页面中可以看到相关的配置方法。 https://www.cobaltstrike.com/help-http-beacon

在cs4.0后可以直接在新建监听器时直接指定HTTP Host Header。而在此之前想要指定client的host只能通过修改profile后进行重载。而这两种方式的优先级又是怎样?今天通过简单的逆向对这个疑惑进行了验证。

验证

直接拿到CS.jar进行解压,然后在idea中打开即可看到反编译出的代码,其中关键部分如下:(beacon\BeaconPayload.class)

String var17 = this.listener.getHostHeader();
    if (var17 != null && var17.length() != 0) {
        if (Profile.usesHostBeacon(this.c2profile)) {
            var16.addString(54, "", 128);
            CommonUtils.print_stat("Now `althost` is not null,but will use profile");
        } else {
            var16.addString(54, "Host: " + this.listener.getHostHeader() + "\r\n", 128);
            CommonUtils.print_stat("Now `althost` is not null, will use althost, this value is "+this.listener.getHostHeader());
        }
    } else {
        CommonUtils.print_stat("Now `althost` is null, will find `Host` in profile.(If specified)");
        var16.addString(54, "", 128);
}

public String getHostHeader() {
    return DialogUtils.string(this.options, "althost");
}

public static boolean usesHostBeacon(Profile paramProfile) {
    return (paramProfile.usesHost(".http-get.client.metadata") || paramProfile.usesHost(".http-post.client.id") || paramProfile.usesHost(".http-post.client.output"));
}


public boolean usesHost(String paramString) {
    Program program = getProgram(paramString);
    return (program != null && program.usesHost());
}

public Program getProgram(String paramString) {
    return (Program)this.data.get(paramString);
}
public boolean program.usesHost() {
    return this.host;
}

(为了方便理解,我做了一些修改与注释)

通过代码的阅读,我们可以看出,this.listener.getHostHeader() 是获取监听器中HTTP Host Header的方法。但是只有在 this.listener.getHostHeader()的结果不为空与Profile.usesHostBeacon(this.c2profile)的值不为真的情况下才会使用this.listener.getHostHeader() 的值。而 Profile.usesHostBeacon(this.c2profile) 的值取决于profile文件中,http-post.client.id、.http-post.client.output、.http-get.client.metadata中是否包含有host字段的相关操作(相关判断代码位于c2profile\Loader.class中 public int parse方法内,通过此方法最终使program.usesHost() 返回为True)。

例如 这个 https://github.com/rsmudge/Malleable-C2-Profiles/blob/26323784672913923d20c5a638c6ca79459e8529/normal/microsoftupdate_getonly.profile#L80

http-post {
    set uri "/c/msdownload/update/others/2016/12/3215234_";
    set verb "GET";
    client {
        header "Accept" "*/*";
        #session ID
        id {
            prepend "download.windowsupdate.com/c/";
            header "Host";  ## 此处作为关键判断
        }
        #Beacon's responses
        output {
            base64url;
            append ".cab";
            uri-append;
        }
    }

    server {
        header "Content-Type" "application/vnd.ms-cab-compressed";
        header "Server" "Microsoft-IIS/8.5";
        header "MSRegion" "N. America";
        header "Connection" "keep-alive";
        header "X-Powered-By" "ASP.NET";

        #empty
        output {
            print;
        }
    }
}

效果如下:

所以host字段的选取流程如下:

  1. 加载profile文件,将profile中的http header相关配置加载到变量中。此时如果有http-get \ http-post 下的client 中含有header "Host" "www.baidu.com"的话,通信过程中的host字段设置为此。

  2. 判断监听器中是否有指定了http host header字段

  3. 如果上步为真,进一步判断profile文件中http-post.client.id、.http-post.client.output、.http-get.client.metadata中是否包含有host字段

  4. 如果上步为真,则不改变加载profile中的相关配置【准确来说是添加一个空值,即var16.addString(54, “”, 128);】。

    如果为假 设定host字段为监听器中指定的http host header字段【var16.addString(54, “Host: " + this.listener.getHostHeader() + “\r\n”, 128);】,覆盖掉第一步中的Host配置。

另外一个值得注意的点。现在可以通过固定的算法进行未授权下载stage后并解密,其中有一项配置为 host 。如果单纯只使用profile文件来指定host的话,该配置项解密出的结果为空。

回编译

将需要修改的文件保存为 xxx.java ,xxx表示原始的文件名,该文件名不要做改动。然后使用javac进行编译时指定cs原始包作为依赖即可:

"C:\Program Files\Java\jdk1.7.0_80\bin\javac.exe" -cp .\cobaltstrike.jar ".\ServerUtils.java"

编译完成后得到.\ServerUtils.class,可以直接把cs.jar当作压缩包打开进行覆盖。