关于CobaltStrike生成shell的过程和执行流程的分析学习

​ 可能我们在后渗透阶段使用CobaltStrike使用的是比较多的,关于一款工具,我们不仅仅只能停留在如何使用它,我们也应该了解一下它实现的原理,本文将带着大家和我一起学习关于CoblatStrike这款工具shell生成的过程还有执行流程。我这里是以CobaltStrike4.0为例来进行分析的。

​ 首先我们将CobaltStrike导入到IDEA中,对着CobaltStrike.jar右键选择-add as libirary,那样我们就可以在IDEA中查看这个包反编译的代码,IDEA反编译代码的还原度还是非常高的。

image-20200831173535104

payload generator

​ CobaltStrike所有的ui在aggressor\dialogs\目录下,因此我们如果想要知道在我们点击了某个按键后CobaltStrike执行了什么操作,在这个目录下找就可以了,因为我们想查看在生成paylad的时候执行了什么操作,因此找payload generator就可以了,我们先看一下dialogAction这个方法中的逻辑。

image-20200831174257725

​ 这个逻辑我们根据生成payload的窗口可能更好理解一些,我把我的分析写到注释里

image-20200831174544179

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void dialogAction(ActionEvent var1, Map var2) {
this.options = var2;
boolean var3 = DialogUtils.bool(var2, "x64"); //判断是否生成x64的payload
String var4 = DialogUtils.string(var2, "listener"); //接收listener的值
this.stager = ListenerUtils.getListener(this.client, var4).getPayloadStager(var3 ? "x64" : "x86"); //首先调用getListener获取到Listener,再调用getPayloadStager获取stager
if (this.stager.length == 0) { //判断stager是否生成成功,如果为空,则报错
if (var3) {
DialogUtils.showError("No x64 stager for listener " + var4);
} else {
DialogUtils.showError("No x86 stager for listener " + var4);
}

} else {
Map var5 = DialogUtils.toMap("ASPX: aspx, C: c, C#: cs, HTML Application: hta, Java: java, Perl: pl, PowerShell: ps1, PowerShell Command: txt, Python: py, Raw: bin, Ruby: rb, COM Scriptlet: sct, Veil: txt, VBA: vba"); //将内容转换为Map形式
String var6 = DialogUtils.string(var2, "format"); //从var2这个hashmap中获取键为format对应的值
String var7 = "payload." + var5.get(var6); //拼接内容大概是payload.format
SafeDialogs.saveFile((JFrame)null, var7, this); //调用save方法
}
}

​ 我们将下面这句代码扩展分析一下,getListener故名思意就是获取Listener,跟进去后发现会返回一个SCListener对象。

1
this.stager = ListenerUtils.getListener(this.client, var4).getPayloadStager(var3 ? "x64" : "x86");

image-20200831181115893

​ 我们主要关注一下getPayloadStager是如何运行的,跟进后发现getPayloadStager仅仅返回了Stagers.shellcode的执行结果

image-20200831181157627

​ 跟进shellcode方法,执行了两个操作,首先调用resolve方法进行解析,返回一个GenericStager对象,再调用这个对象的generate方法。

image-20200831181808133

​ 跟进resolve看看执行了什么操作,在if中会判断var3是否是x86结构,如果是将this.x86_stagers赋值给var4,再判断var6是否包含var2关键字,如果包含,则调用create方法。

image-20200831191822995

​ 我们先看看x86_stagers是怎么来的,在Stagers类的开始创建了X86_stagers和x64_stagers,并且调用了Stagers()这个构造方法,我们可以看到在这个方法中调用了add方法来执行操作

image-20200831192312035

​ 再跟进去看看add方法执行了什么操作,首先通过掉用TestAech方法判断是否含有x86或x64,如果正常以后再判断是否是x86,如果是则向x86_stagers则以键值对的形式写入内容,如果不是则向x64_stagers写入内容。

image-20200831192737834

​ 我们选择一个BeaconHTTPStagerX86来看看,payload函数执行了什么操作,通过下面的代码可以看到返回了windows/beacon_http/reverse_http,其他的payload函数返回的内容类似,就不一一举例了。

image-20200831193156043

​ 那么var1的内容是什么呢?还是以http x86的stager为例,我们发现它是new BeaconHTTPStagerX86()后返回的结果。

image-20200831203310084

​ 那么BeaconHTTPStagerX86在构造方法中又做了什么操作呢,我们通过下面的代码可以看到它调用了父类GenericHTTPStagerX86的构造方法

image-20200831203430325

​ 跟进GenericHTTPStagerX86的构造方法,发现其又调用了父类GenericHTTPStager的构造方法

image-20200831203519702

​ 跟进GenericHTTPStager的构造方法,发现其又调用了父类GenericStager的构造方法

image-20200831203558124

​ 在GenericStager方法中,可以看到其实什么也没做

image-20200831203712286

​ 最后其实比较关键的代码是调用了generate方法

image-20200831203932934

​ 因为我的listener是http x86的listener,所以最终调用stagers\GenericHTTPStager.class的generate方法如下,

image-20200831204215357

​ 在该代码种首先通过resouce方法加载资源,资源文件,资源文件是通过getStagetFile()获得的,在GenericHTTPStager.class中的getStagetFile是一个抽象方法,而且GenericHTTPStager也是一个抽象类,所以我们要在继承了GenericHTTPStager的子类中寻找getStagetFile的实现,最终在GenericHTTPStagerX86找到了getStagetFile的实现,是加载resources/httpstager.bin文件

image-20200901094550546

​ 下面的代码中,读取httpstager.bin文件的内容,并且对其中的某些值进行替换,httpstager.bin其实就是shellcode生成的一个模板文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public byte[] generate() {
try {
InputStream var1 = CommonUtils.resource(this.getStagerFile()); //读取httpstager.bin的内容
byte[] var2 = CommonUtils.readAll(var1);
String var3 = CommonUtils.bString(var2); //从byte类型转换为字符串类型
var1.close();
var3 = var3 + this.getListener().getStagerHost() + '\u0000'; //从option中获取host
Packer var4 = new Packer(); //类中内置了对于字符串,字节,hex等处理的方法
var4.little(); //字节顺序,决定大端字节和小端字节的读取问题
var4.addShort(this.getListener().getPort()); //从option中获取port值
AssertUtils.TestPatchS(var2, 4444, this.getPortOffset()); //判断getPortOffset的对应的值是否是4444
var3 = CommonUtils.replaceAt(var3, CommonUtils.bString(var4.getBytes()), this.getPortOffset()); //将4444替换为option设置的端口地址
var4 = new Packer();
var4.little();
var4.addInt(1453503984); //exit对应的偏移地址
AssertUtils.TestPatchI(var2, 1453503984, this.getExitOffset());
var3 = CommonUtils.replaceAt(var3, CommonUtils.bString(var4.getBytes()), this.getExitOffset()); //exit对应的偏移地址替换,不过这个看起来默认是没变的
var4 = new Packer();
var4.little();
var4.addShort(this.getStagePreamble());//在getStagePreamble中判断是否为forign类型,如果不是,则返回stage_offset。
AssertUtils.TestPatchS(var2, 5555, this.getSkipOffset()); //判断getSkipOffset的返回值是否是5555
var3 = CommonUtils.replaceAt(var3, CommonUtils.bString(var4.getBytes()), this.getSkipOffset()); //将stage_offset的值进行替换
var4 = new Packer();
var4.little();
var4.addInt(this.getConnectionFlags()); //判断是否是https
AssertUtils.TestPatchI(var2, this.isSSL() ? -2069876224 : -2074082816, this.getFlagsOffset());
var3 = CommonUtils.replaceAt(var3, CommonUtils.bString(var4.getBytes()), this.getFlagsOffset());
String var5;
if (CommonUtils.isin(CommonUtils.repeat("X", 303), var3)) {
var5 = this.getConfig().pad(this.getHeaders() + '\u0000', 303);
var3 = CommonUtils.replaceAt(var3, var5, var3.indexOf(CommonUtils.repeat("X", 127))); //将hreader中的值进行替换
}

int var6 = var3.indexOf(CommonUtils.repeat("Y", 79), 0);
var5 = this.getConfig().pad(this.getURI() + '\u0000', 79);
var3 = CommonUtils.replaceAt(var3, var5, var6); //从config中获取url的值,并进行替换
return CommonUtils.toBytes(var3 + this.getConfig().getWatermark()); //以字节数组的形式返回
} catch (IOException var7) {
MudgeSanity.logException("HttpStagerGeneric: " + this.getStagerFile(), var7, false);
return new byte[0];
}
}

​ 通过上面的分析,我们可以看出来,这里所作的操作就是读取httpstager.bin这个模板文件,然后对模板文件中的请求地址,请求头等地方进行替换。

​ 再回到aggressor\dialogs\PayloadGeneratorDialog.class中,我们看下dialogResult方法,这个方法会针对我们选择不同的shellcode的生成类型,将stager转换为不同的类型,最后写入到文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public void dialogResult(String var1) {
String var2 = DialogUtils.string(this.options, "format");
boolean var3 = DialogUtils.bool(this.options, "x64");
String var4 = DialogUtils.string(this.options, "listener");
if (var2.equals("C")) {
this.stager = Transforms.toC(this.stager);
} else if (var2.equals("C#")) {
this.stager = Transforms.toCSharp(this.stager);
} else if (var2.equals("Java")) {
this.stager = Transforms.toJava(this.stager);
} else if (var2.equals("Perl")) {
this.stager = Transforms.toPerl(this.stager);
} else if (var2.equals("PowerShell") && var3) {
this.stager = (new ResourceUtils(this.client)).buildPowerShell(this.stager, true);
} else if (var2.equals("PowerShell") && !var3) {
this.stager = (new ResourceUtils(this.client)).buildPowerShell(this.stager);
} else if (var2.equals("PowerShell Command") && var3) {
this.stager = (new PowerShellUtils(this.client)).buildPowerShellCommand(this.stager, true);
} else if (var2.equals("PowerShell Command") && !var3) {
this.stager = (new PowerShellUtils(this.client)).buildPowerShellCommand(this.stager, false);
} else if (var2.equals("Python")) {
this.stager = Transforms.toPython(this.stager);
} else if (!var2.equals("Raw")) {
if (var2.equals("Ruby")) {
this.stager = Transforms.toPython(this.stager);
} else if (var2.equals("COM Scriptlet")) {
if (var3) {
DialogUtils.showError(var2 + " is not compatible with x64 stagers");
return;
}

this.stager = (new ArtifactUtils(this.client)).buildSCT(this.stager);
} else if (var2.equals("Veil")) {
this.stager = Transforms.toVeil(this.stager);
} else if (var2.equals("VBA")) {
this.stager = CommonUtils.toBytes("myArray = " + Transforms.toVBA(this.stager));
}
}

CommonUtils.writeToFile(new File(var1), this.stager);
DialogUtils.showInfo("Saved " + var2 + " to\n" + var1);
}

​ 我们以C#为例来进行分析,主要代码如下

1
2
if (var2.equals("C#")) {
this.stager = Transforms.toCSharp(this.stager);

​ 跟进toCSharp方法,创建了一个Packer对象,添加了stager长度的字符串,再添加了stager字节数组的字符串。

image-20200901115643729

​ 最后再将生成的shellcode写入文件。

image-20200901115942287

​ 好了,关于payload generator的过程就分析到这里,可能由于个人水平有限,在静态代码分析的功底有限,有些地方可能分析的不对,不过这里大致的流程分析是没有问题的。主要是在stager生成这里,将httpstager.bin模板里的关于监听主机和端口以及uri的位置进行了替换和修改,然后根据不同的类型写入文件。所以如果要免杀,其实有一个思路也是可以将这个httpstager.bin的内容分析出来,将有特征的部分进行更改。

windows executable

​ windows executable是在WindowsExecutableDialog.class中进行处理的,其中关于stager生成的部分和payload generator相同,就不分析了,再看下dialogResult方法,先看下windows exe是怎么处理的,这里主要是调用了patchArtifact方法来进行处理,我们看下这个方法是做什么的。

image-20200901133939693

​ 跟进patchArtifact,发现内部还调用了patchArtifact方法

image-20200901134326360

​ 继续跟进

1
2
3
4
5
6
7
public byte[] patchArtifact(byte[] var1, String var2) {
Stack var3 = new Stack();
var3.push(SleepUtils.getScalar(var1));
var3.push(SleepUtils.getScalar(var2));
String var4 = this.client.getScriptEngine().format("EXECUTABLE_ARTIFACT_GENERATOR", var3);
return var4 == null ? this.fixChecksum(this._patchArtifact(var1, var2)) : this.fixChecksum(CommonUtils.toBytes(var4));
}

​ 主要看下_patchArtifact的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public byte[] _patchArtifact(byte[] var1, String var2) {
try {
InputStream var3 = CommonUtils.resource("resources/" + var2); //我这里var2是artifact64.exe,所以这里加载的是resources/artifact64.exe
byte[] var4 = CommonUtils.readAll(var3); //读取artifact64.exe的内容
var3.close();
byte[] var5 = new byte[]{(byte)CommonUtils.rand(254), (byte)CommonUtils.rand(254), (byte)CommonUtils.rand(254), (byte)CommonUtils.rand(254)};
byte[] var6 = new byte[var1.length];

for(int var7 = 0; var7 < var1.length; ++var7) {
var6[var7] = (byte)(var1[var7] ^ var5[var7 % 4]);
}

String var12 = CommonUtils.bString(var4);
int var8 = var12.indexOf(CommonUtils.repeat("A", 1024));//找到存在1024个A的位置
Packer var9 = new Packer();
var9.little();
var9.addInteger(var8 + 16);
var9.addInteger(var1.length); //写入stager的长度
var9.addString(var5, var5.length); // 写入随机字符
var9.addString("aaaa", 4);
var9.addString(var6, var6.length); //写入stager内容
if (License.isTrial()) {
var9.addString("X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*");
CommonUtils.print_trial("Added EICAR string to " + var2);
}

byte[] var10 = var9.getBytes();
var12 = CommonUtils.replaceAt(var12, CommonUtils.bString(var10), var8);
return CommonUtils.toBytes(var12);
} catch (IOException var11) {
MudgeSanity.logException("patchArtifact", var11, false);
return new byte[0];
}
}

​ 总结一下上面的过程,主要就是将1024个A的地址进行替换,替换为随机字符和stager。

​ 用010 editor打开artifact64.exe文件,发现确实是有很多A

image-20200901140702963

​ 我们再尝试生成一个x64的exe,用010 editor再看看里面的内容,我们可以看到在某处的开头是有aaaa,并且在最后还有一些大写的A,所以这两个点都可以当作CobaltStrike默认生成的exe的特征。

image-20200901142122258

image-20200901142214196

​ 我再看了下dll和service类型的生成方式,发现最终都调用了_patchArtifact方法,因此他们这些模板文件中也都包含了1024个A,并且在替换的过程中首先也会写入四个a,我们以dll为例再看看。

image-20200901143237412

image-20200901143245238

​ 那我们猜想一下,关于这个特征查杀是也可以根据首先出现4个a,在1024个字符以内,又同时出现10个A来进行检测呢?

windows executables

​ 还有一种形式,我们在使用privote生成shellcode常用到,那就是windows executables生成的是stagerless类型,这种形式的生成是在WindowsExecutableStageDialog.class文件中

​ 首先看下dialogAction中的代码,我们可以看到和payload generator和windows executable的形式不同,executables中并没有在这个方法中生成stager。

image-20200901144358493

​ 再看下dialogResult方法,和其他的方式不同,它也获取了SCListener,并通过调用export方法

image-20200901144705204

​ 我们跟进export方法,export的代码如下,他根据payload的类型,调用了不同的方法,我们以最基本的http_reverse为例进行分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public byte[] export(String var1, int var2) {
if ("windows/foreign/reverse_http".equals(this.getPayload())) {
return this.getPayloadStager(var1);
} else if ("windows/foreign/reverse_https".equals(this.getPayload())) {
return this.getPayloadStager(var1);
} else if ("windows/beacon_http/reverse_http".equals(this.getPayload())) {
return (new BeaconPayload(this, var2)).exportBeaconStageHTTP(this.getPort(), this.getCallbackHosts(), false, false, var1);
} else if ("windows/beacon_https/reverse_https".equals(this.getPayload())) {
return (new BeaconPayload(this, var2)).exportBeaconStageHTTP(this.getPort(), this.getCallbackHosts(), false, true, var1);
} else if ("windows/beacon_dns/reverse_dns_txt".equals(this.getPayload())) {
return (new BeaconPayload(this, var2)).exportBeaconStageDNS(this.getPort(), this.getCallbackHosts(), true, false, var1);
} else if ("windows/beacon_bind_pipe".equals(this.getPayload())) {
return (new BeaconPayload(this, var2)).exportSMBStage(var1);
} else if ("windows/beacon_bind_tcp".equals(this.getPayload())) {
return (new BeaconPayload(this, var2)).exportBindTCPStage(var1);
} else if ("windows/beacon_reverse_tcp".equals(this.getPayload())) {
return (new BeaconPayload(this, var2)).exportReverseTCPStage(var1);
} else {
AssertUtils.TestFail("Unknown payload '" + this.getPayload() + "'");
return new byte[0];
}
}

​ 我们发现其调用了exportBeaconStageHTTP方法,跟进这个方法,首先判断架构,再根据架构的不同给var6进行赋值,最后调用了exportBeaconStage方法

1
2
3
4
5
6
7
8
9
10
11
public byte[] exportBeaconStageHTTP(int var1, String var2, boolean var3, boolean var4, String var5) {
AssertUtils.TestSetValue(var5, "x86, x64");
String var6 = "";
if ("x86".equals(var5)) {
var6 = "resources/beacon.dll";
} else if ("x64".equals(var5)) {
var6 = "resources/beacon.x64.dll";
}

return this.pe.process(this.exportBeaconStage(var1, var2, var3, var4, var6), var5);
}

​ 跟进exportBeaconStage方法,这个方法比较长,我会把简单的分析写在注释中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
protected byte[] exportBeaconStage(int var1, String var2, boolean var3, boolean var4, String var5) {
try {
long var6 = System.currentTimeMillis();
byte[] var8 = SleevedResource.readResource(var5); //获取sleeve/beacon.dll的内容
if (var2.length() > 254) {
var2 = var2.substring(0, 254);
}

String[] var9 = this.c2profile.getString(".http-get.uri").split(" "); //加载配置文件中.http-get.uri的值
String[] var10 = var2.split(",\\s*");
LinkedList var11 = new LinkedList();

for(int var12 = 0; var12 < var10.length; ++var12) {
var11.add(var10[var12]);
var11.add(CommonUtils.pick(var9));
}

String var32;
while(var11.size() > 2 && CommonUtils.join(var11, ",").length() > 255) {
var32 = var11.removeLast() + "";
String var13 = var11.removeLast() + "";
CommonUtils.print_info("dropping " + var13 + var32 + " from Beacon profile for size");
}

var32 = randua(this.c2profile);
int var33 = Integer.parseInt(this.c2profile.getString(".sleeptime")); //加载配置文件的sleeptime的值
String var14 = CommonUtils.pick(this.c2profile.getString(".http-post.uri").split(" "));
byte[] var15 = this.c2profile.recover_binary(".http-get.server.output");
byte[] var16 = this.c2profile.apply_binary(".http-get.client");
byte[] var17 = this.c2profile.apply_binary(".http-post.client");
int var18 = this.c2profile.size(".http-get.server.output", 1048576);
int var19 = Integer.parseInt(this.c2profile.getString(".jitter"));
if (var19 < 0 || var19 > 99) {
var19 = 0;
}

int var20 = Integer.parseInt(this.c2profile.getString(".maxdns"));
if (var20 < 0 || var20 > 255) {
var20 = 255;
}

int var21 = 0;
if (var3) {
var21 |= 1;
}

if (var4) {
var21 |= 8;
}

long var22 = CommonUtils.ipToLong(this.c2profile.getString(".dns_idle"));
int var24 = Integer.parseInt(this.c2profile.getString(".dns_sleep"));
Settings var25 = new Settings();
var25.addShort(1, var21);
var25.addShort(2, var1);
var25.addInt(3, var33);
var25.addInt(4, var18);
var25.addShort(5, var19);
var25.addShort(6, var20);
var25.addData(7, this.publickey, 256);
var25.addString(8, CommonUtils.join(var11, ","), 256);
var25.addString(9, var32, 128);
var25.addString(10, var14, 64);
var25.addData(11, var15, 256);
var25.addData(12, var16, 256);
var25.addData(13, var17, 256);
var25.addData(14, CommonUtils.asBinary(this.c2profile.getString(".spawnto")), 16);
var25.addString(29, this.c2profile.getString(".post-ex.spawnto_x86"), 64);
var25.addString(30, this.c2profile.getString(".post-ex.spawnto_x64"), 64);
var25.addString(15, "", 128);
var25.addShort(31, QuickSecurity.getCryptoScheme());
var25.addInt(19, (int)var22);
var25.addInt(20, var24);
var25.addString(26, this.c2profile.getString(".http-get.verb"), 16);
var25.addString(27, this.c2profile.getString(".http-post.verb"), 16);
var25.addInt(28, this.c2profile.shouldChunkPosts() ? 96 : 0);
var25.addInt(37, this.c2profile.getInt(".watermark"));
var25.addShort(38, this.c2profile.option(".stage.cleanup") ? 1 : 0);
var25.addShort(39, this.c2profile.exerciseCFGCaution() ? 1 : 0);
String var26 = this.listener.getHostHeader();
if (var26 != null && var26.length() != 0) {
if (Profile.usesHostBeacon(this.c2profile)) {
var25.addString(54, "", 128); //获取host的值进行赋值
} else {
var25.addString(54, "Host: " + this.listener.getHostHeader() + "\r\n", 128);
}
} else {
var25.addString(54, "", 128);
}

if (Profile.usesCookieBeacon(this.c2profile)) {
var25.addShort(50, 1);
} else {
var25.addShort(50, 0);
}

ProxyServer var27 = ProxyServer.parse(this.listener.getProxyString());
var27.setup(var25);
this.setupKillDate(var25);
this.setupGargle(var25, var5);
(new ProcessInject(this.c2profile)).apply(var25);
byte[] var28 = var25.toPatch();
var28 = beacon_obfuscate(var28);
String var29 = CommonUtils.bString(var8);
int var30 = var29.indexOf("AAAABBBBCCCCDDDDEEEEFFFF"); //找到AAAABBBBCCCCDDDDEEEEFFFF的位置
var29 = CommonUtils.replaceAt(var29, CommonUtils.bString(var28), var30); //将模板的内容进行替换
return CommonUtils.toBytes(var29); //返回替换后的结果
} catch (IOException var31) {
MudgeSanity.logException("export Beacon stage: " + var5, var31, false);
return new byte[0];
}
}

​ 简单的分析后我们发现这段代码的主要作用是读取beacon.dll文件,以这个文件作为模板对一些值进行替换,并且是以找到AAAABBBBCCCCDDDDEEEEFFFF来找到要替换的位置的,但是比较奇怪的是我再beacon.dll中并没有发现AAAABBBBCCCCDDDDEEEEFFFF这个字符串。

image-20200901154145081

​ 后面根据不同的类型的类型,调用patchArtifact方法,虽然使用的模板不同,最终还是会有4个a的特征,不过可能使用这种方式的模板比较大,因此不会有大写A这种特征。因为本身替代Stager那部分比如beacon.dll的文件内容就比较大,有200多k。

image-20200901154601816

总结

​ 总结一下这几种生成方式的过程和一些区别

  • payload generator只是加载stager模板并且对里面host,port和uri部分进行替换,最终生成的文件只是将stager文件以字节数组的形式进行输出
  • windows executable首先生成stager,这个stager可能非常小,再去替换模板文件中出现1024个A的地址,这种方式由于stager比较小,而且在模板插入stager之前会写入4个a,因此可以把出现4个a和a出现后的1024字节内出现多个A为特征进行检测
  • windows executables这种方式不会再去生成stager而是使用其他的方式进行替换,而替换stager的部分模板文件过大,因此会在插入的开始出现4个a但是不会出现多个A。

参考文章

从剖析CS木马生成到开发免杀工具