BlackMatter
BlackMatter 勒索软件分析
1. 前言
一个新的勒索软件团伙BlackMatter
于 2021 年 7 月在地下论坛/Exploit/
和/XSS/
上招募附属机构。他们填补了在 Colonial Pipeline 攻击后DarkSide
关闭以及REvil
在 7 月中旬击败 Kaseya 后消失所留下的空白。该团伙表示:他们既不是DarkSide
也不是REvil
的继任者。宣称BlackMatter
是将勒索软件LockBit
、REvil
和DarkSide
各自的优势结合起来的最好的勒索软件。
IOC
SHA256 : 22d7d67c3af10b1a37f277ebabe2d1eb4fd25afbd6437d4377400e148bcc08d6
你可以在MalwareBazaar下载。
赎金票据
勒索信的内容在BlackMatter
的配置中进行了加密,并动态解密写入到每个目录的勒索信文件中。赎金票据文件名的形式为:**.README.txt
2. 动态解析API
因为结合了诸多勒索软件的优点,那么动态解析API和基本的字符串加密是必不可少的,接下来就让我们来看看它到底是怎么做的。
IDA打开样本,首先便是动态解析API,函数如下:
2.1. 哈希计算
实际调用是使用Get_Func_Addr_By_ROR13_Hash
导入API函数的hash进行函数加载。Get_Func_Addr_By_ROR13_Hash
函数中可以看到首先确保LoadLibraryA
和GetProcAddress
已加载,然后再进行hash比对:
hash
计算采用了循环右移0xD
位的方式,Get_Dll_Name_Hash
函数内容如下:
采用python
代码重写就美观了许多:
def Calc_Dll_Hash(dll_name):
mask = 0xFFFFFFFF
result = 0
for each in dll_name+'\x00':
each = ord(each)
if(each > 0x40 and each < 0x5b):
each = each | 0x20
result = (result >> 0xd) | (result << 0x13)
result = (result+each) & mask
return result
Get_Func_Name_Hash
函数略有不同,但大同小异:
python
代码如下:
def Calc_Func_Hash(dll_name, func_name):
mask = 0xFFFFFFFF
result = dll_name
for each in func_name+'\x00':
each = ord(each)
result = (result >> 0xd) | (result << 0x13)
result = (result+each) & mask
return result
检验代码的有效性
实际上要调用的函数散列可以如下这般进行计算:
dll_name = "kernel32.dll"
func_name = "LoadLibraryA"
print(hex(Calc_Func_Hash(Calc_Dll_Hash(dll_name), func_name)))
dll_name = "kernel32.dll"
func_name = "GetProcAddress"
print(hex(Calc_Func_Hash(Calc_Dll_Hash(dll_name), func_name)))
返回结果如下:
0x27d05eb2
0xbb93705c
实际上加载LoadLibraryA
和GetProcAddress
的hash
确实如此:
2.2. 加载函数
深入到Resolve_API_Hash
函数中去,可以发现其逻辑传入两个地址,第一个地址是加载后函数存放地址,第二个地址是待解密加载的hash
,解密密钥也很清晰:0x22065FED
,当获取到的地址是0xCCCCCCCC
时退出加载循环。
加载函数前有两个push
,通过动态调试可以知道第一个push
是加载函数存放起始地址,第二个push
是需要解密的函数hash
存放起始地址。简单调试后可以得到知道每个dll
加载时的hash
可以检索一下所有的本地dll文件,计算hash进行比对,
dict = {}
for filename in os.listdir("C:\\Windows\\System32"):
if '.dll' in filename:
dll_hash = Calc_Dll_Hash(filename)
dll_name = filename
dict[hex(dll_hash).upper() ] = filename
print('411677B7:'+dict['0X411677B7'])
print('B1FC7F66:'+dict['0XB1FC7F66'])
print('BCFA1667:'+dict['0XBCFA1667'])
print('7132A177:'+dict['0X7132A177'])
print('3032403A:'+dict['0X3032403A'])
print('391830B4:'+dict['0X391830B4'])
print('38327FBA:'+dict['0X38327FBA'])
print('C50C676F:'+dict['0XC50C676F'])
print('820A18A3:'+dict['0X820A18A3'])
print('FD1A17C6:'+dict['0XFD1A17C6'])
print('41E8A017:'+dict['0X41E8A017'])
print('3CFC1737:'+dict['0X3CFC1737'])
print('C3BC5607:'+dict['0XC3BC5607'])
print('76E87915:'+dict['0X76E87915'])
最终计算结果如下:
411677B7:ntdll.dll
B1FC7F66:kernel32.dll
BCFA1667:advapi32.dll
7132A177:user32.dll
3032403A:gdi32.dll
391830B4:shell32.dll
38327FBA:ole32.dll
C50C676F:shlwapi.dll
820A18A3:oleaut32.dll
FD1A17C6:wtsapi32.dll
41E8A017:RstrtMgr.dll
3CFC1737:netapi32.dll
C3BC5607:activeds.dll
76E87915:wininet.dll
查看第二个参数地址可以找到需要进行加载的hash
再未进行异或前的hash
。
2.3. IDAPython解密
根据上面的结论,可以进行简单的逻辑实现:
# 传入Resolve_API_Hash函数的的地址
def Resolve_All_APIs(resolve_ea):
# 获取dll加载函数的交叉引用地址
for ref in idautils.CodeRefsTo(resolve_ea,1):
current_ea = ref
api_addr_ea = 0
api_hashes_ea = 0
while True:
# 获取上一条汇编语言的地址
prev_instruction_ea = idc.prev_head(current_ea)
# 判断助记符是否为push
if idc.print_insn_mnem(prev_instruction_ea) == 'push':
if api_addr_ea == 0:
# 向上第一条push是api写入地址
api_addr_ea = idc.get_operand_value(prev_instruction_ea,0)
else:
# 再向上一条push是apihash存储的地址
api_hashes_ea = idc.get_operand_value(prev_instruction_ea,0)
break
current_ea = prev_instruction_ea
api_addr_ea += 4
api_hashes_ea += 4
index = 0
while True:
# 根据地址获取hash
api_hash = idc.get_wide_dword(api_hashes_ea + 4*index)
if api_hash == 0xCCCCCCCC:
break
# 进行异或解密
api_hash = api_hash ^ 0x22065FED
if api_hash in export_hashes:
print(export_hashes[api_hash])
# 写入地址对应的api函数名
idc.set_name(api_addr_ea + 4*index, 'mw_' + export_hashes[api_hash], idaapi.SN_CHECK)
else:
print(hex(api_addr_ea),'NOTFOUND')
index += 1
3. BypassUAC
当成功加载dll
后,开始检查当前用户的RID
是否是administrators
:
接着通过对比OSMajorVersion
与OSMinorVersion
检查操作系统版本:
从TOKEN_GROUPS
中判断进程令牌是否是admin
权限。
如果不是admin
权限运行,则进行提权,使用LdrEnumerateLoadedModules
将C:\\Windows\\System32\\dllhost.exe
注入到进程的PEB
中,这样可以使用dllhost.exe
接管COM
对象。
调用CoGetObject
创建以下对象:
Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}
通过COM
库重载自身,从而达到权限提升的目的,整个BypassUAC
过程如下:
最后再调用NtTerminateProcess
终止自身。
4. 勒索设备ID号生成
获取注册表项SOFTWARE\Microsoft\Cryptography
中的MachineGuid
,经过三次ror13
,再进行字节反转,最后base64
编码一次,再将+
、/
、=
符号进行转义,生成勒索设备ID号。
解密出勒索信文件名%s.README.txt
:
5. 窃取登录凭证
接着会尝试使用LogonUserW
窃取登录凭证:
然后检查该用户是否属于DOMAINNAME\Domain Admins
:
6. 勒索主体
该功能模块主要是根据不同的命令参数进行功能分化,不同的参数执行不同的功能:
6.1. 命令参数解析
可以看到BlackMatter
支持以下四个命令参数:
-path <pwd> 加密<pwd>路径的文件
<pwd> 加密<pwd>路径的文件
-safe 安全模式重启
-wall 设置壁纸
由于存储在样本中的是hash
,所以只能通过生成明文字典(very luncky),再计算出字符串的hash
进行比对,最终确认出明文命令:
6.2. 指定路径模式
当使用-path <pwd>
或<pwd>
参数时,将创建I/O
接口进行加密,然后再重写生成加密文件。
当-path <pwd>
中的<pwd>
是服务器路径时,将检索共享资源进行加密:
如果-path <pwd>
中的<pwd>
是文件路径,或仅使用<pwd>
参数,则直接加密目录文件:
6.3. 安全模式
当使用-safe
参数时,BlackMatter
会首先检测是否是RID
是否属于BUILTIN\Administrators
,如果是,则进行以下操作。
6.3.1. 创建自登录账户
随机生成一个12位的密码:由3个随机大写字母、1个随机字符(#
或&
)、3个随机数字、1个随机字符(#
或&
)、4个随机小写字母组成。然后使用NetUserSetInfo
创建administrator
,密码则是刚才生成的12位字符串。
然后创建注册表:SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon
,设置以下键值:
AutoAdminLogon:1
DefaultUserName:Administrator
DefaultDomainName:[GetComputerNameW()]
DefaultPassword:[Random_Password()]
整体结构如下:
6.3.2. RunOnce自启动
为了实现持久化,BlackMatter
会随机生成一个9位数值名称:由3个随机大写字母、3个随机数字和3个随机小写字母组成,数值内容为当前路径,写入到注册表:SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce
中。
6.3.3. 设置生成桌面壁纸
根据当前屏幕大小,修改注册表项中的hScreen
和wScreen
:
当操作系统是windows 10
时,会在注册表SOFTWARE\\Policies\\Microsoft\\Windows\\OOBE
中设置DisablePrivacyExperience:1
:
壁纸生成的具体细节如下:
创建Times New Roman
字体的句柄,从内存中获取壁纸突显的字符串:
BlackMatter Ransomware encrypted all your files!
To get your data back and keep your privacy safe,
you must find [readme.txt] file
and follow the instructions!
然后以二进制形式将图片写入到C:\ProgramData\[id].bmp
,从而生成壁纸文件:
修改注册表HKEY_CURRENT_USER\Control Panel\Desktop
的键值,设置桌面壁纸:
WallPaper:C:\ProgramData\[id].bmp
WallpaperStyle:10
6.3.4. 重启进入安全模式
BlackMatter
会根据当前运行的操作系统版本,选择相应的命令使用WinExec
执行,并重启操作系统。
当传递的参数为True
时(从上方可以得知,当前模式传递的是True
),启用安全模式:
winsows vista 之前的操作系统:bcdedit /set {{current}} safeboot network
winsows vista 之后的操作系统:bootcfg /raw /a /safeboot:network /id 1
当传递的参数为False
时,该函数还提供了关闭功能:
winsows vista 之前的操作系统:bcdedit /deletevalue {{current}} safeboot
winsows vista 之后的操作系统:bootcfg /raw /fastdetect /id 1
6.4. 壁纸模式
当使用-wall
参数时,BlackMatter
仅生成壁纸,具体可参考壁纸生成的具体细节。
6.5. 默认模式
当什么都不添加时,就进入到该模式,该模式涵盖了大多数的功能。
6.5.1. 互斥锁
当Mutex_Flag
为真时,将运行一次互斥。首先是获取MachineGuid
,再通过MD4
加密生成hash
,再使用OpenMutexW
结果来测试是否已存在。
如果存在,则关闭并退出,如果不存在则创建并继续:
6.5.2. 网络请求
如果配置文件中的Post_Flag
为真,则会收集数据并发送到C2
服务器。
在加密前会进行一次基本信息的收集,数据结构的json
格式如下:
{
"bot_version":"%s",
"bot_id":"%s",
"bot_company":"%.8x%.8x%.8x%.8x%",
"host_hostname":"%s",
"host_user":"%s",
"host_os":"%s",
"host_domain":"%s",
"host_arch":"%s",
"host_lang":"%s",
"disks_info":[
{ //当存在多个磁盘,该字典将变多
"disk_name":"%s",
"disk_size":"%u",
"free_size":"%u"
}
]
}
以下是我在虚拟机中获取的加密前发送数据:
{
"bot_version":"1.2",
"bot_id":"26a3ebea3de1f2c535eb81c5456fc830",
"bot_company":"512478c08dada2af19e49808fbda5b0b",
"host_hostname":"WIN-B1ESUN1R9U8",
"host_user":"alee",
"host_os":"Windows 7 Ultimate",
"host_domain":"WORKGROUP",
"host_arch":"x64",
"host_lang":"zh-CN",
"disks_info":[
{
"disk_name":"C",
"disk_size":"102397",
"free_size":"70005"
}
]
}
当加密完成以后,还会进行一次结果统计,数据结构的json
格式如下:
{
"bot_version":"%s",
"bot_id":"%s",
"bot_company":"%.8x%.8x%.8x%.8x%",
"stat_all_files":"%u",
"stat_not_encrypted":"%u",
"stat_size":"%s",
"execution_time":"%u",
"start_time":"%u",
"stop_time":"%u"
}
以下是我在虚拟机中获取的加密后发送数据
{
"bot_version":"1.2",
"bot_id":"26a3ebea3de1f2c535eb81c5456fc830",
"bot_company":"512478c08dada2af19e49808fbda5b0b",
"stat_all_files":"16499",
"stat_not_encrypted":"3",
"stat_size":"1282",
"execution_time":"829",
"start_time":"1633799826",
"stop_time":"1633800656"
}
获取到数据后,会通过AES
加密,再通过base64
编码:
AES
加密的密钥为:A6F330B09CD47B4FB9214F7836AA46AD
最终的C2
服务器为:https[:]//paymenthacks.com
6.5.3. 清除回收站数据
在加密前,会遍历每一个磁盘驱动器上的第一个recycle
文件:
然后删除所有S-
开头的文件:
6.5.4. 删除卷影副本
使用CoCreateInstance
创建两个对象,然后使用IWbemServices::ExecQuery
方法执行WQL
查询SELECT * FROM Win32_ShadowCopy
检索卷影副本对象:
调用IEnumWbemClassObject::Next
枚举系统上的所有卷影副本,调用IEnumWbemClassObject::Get
获取每个卷影副本的 ID,并调用IWbemServices::DeleteInstance
删除它们。
6.5.5. 终止服务
当Kill_Service_Flag
为True
时,使用OpenSCManagerW
获取服务控制管理器句柄,然后使用EnumServicesStatusExW
枚举所有服务,对比是否在终止名单中,如果是,则使用ControlService
发送控制代码,使用DeleteService
删除服务,服务列表如下:
"mepocs"、"memtas"、"veeam"、"svc$"、"backup"、"sql"、"vss"
6.5.6. 杀死进程
当Kill_Process_Flag
为True
时,使用NtQuerySystemInformation
检索进程列表,对比是否在杀死名单中,如果是,使用NtOpenProcess
确认句柄,使用NtTerminateProcess
杀死进程,进程列表如下:
"encsvc"、"thebat"、"mydesktopqos"、"xfssvccon"、"firefox"、"infopath"、"winword"、"steam"、"synctime"、"notepad"、"ocomm"、"onenote"、"mspub"、"thunderbird"、"agntsvc"、"sql"、"excel"、"powerpnt"、"outlook"、"wordpad"、"dbeng50"、"isqlplussvc"、"sqbcoreservice"、"oracle"、"ocautoupds"、"dbsnmp"、"msaccess"、"tbirdconfig"、"ocssd"、"mydesktopservice"、"visio"
6.5.7. I/O多线程加密
创建用于处理I/O
数据包的子线程,这些子线程使用GetQueuedCompletionStatus
与PostQueuedCompletionStatus
与主线程进行通信。接收到的数据包,都是需要处理的文件。
整个加密过程存在四个 case
case 0
:读取文件;
case 1
:加密和写入文件。加密采用了自定义的加密算法,待加密结束后,将文件重新写入磁盘;
case 2
:写入文件页脚标识。当完成加密后,会在文件末端写入标识,标记该文件已被加密;
case 3
:当加密完成并写入页脚后,调用NtClose
关闭文件句柄,使用RtlFreeHeap
释放缓冲区
6.5.8. 驱动器检索
当Drive_Flag
为True
时,采用FindFirstVolumeW
和FindNextVolumeW
检索全部的卷,然后调用GetVolumePathNamesForVolumeNameW
检索驱动器号和已安装文件夹路径的列表:
当操作系统版本是Windows 7
以前的版本时,将bootmgr
挂载到最后一个驱动器后:
6.5.9. 域内资源检索
当Network_Flag
为True
时,将通过DsGetDcNameW
获取域控制器信息,然后使用DsGetDcOpenW
打开控制器,通过DsGetDcNextW
枚举出所有的域控制器:
然后采用ADsEnumerateNext
来枚举域控制器内的DNS
主机:
然后使用NetShareEnum
多线程寻找域内主机:
剩下的就是目录过滤和加密了:
6.5.10. 检索文件并加密
当Drive_Flag
为True
时,使用FindFirstVolumeW
和FindNextVolumeW
获取所有卷的驱动器号和已安装文件夹路径的列表;扫描每个驱动器目录,在目录中放置赎金票据,并使用FindFirstFileExW
和FindNextFileW
枚举目录,避开所有名为.
或..
的文件或目录:
在对文件进行加密时,首先会解除服务或进程对文件的占用:
然后会检查文件是否已加密:
将文件更名为带加密拓展名的文件:
然后使用CreateIoCompletionPort
向全局I/O
注册文件句柄,让I/O
子线程进行加密:
参考链接
RID 说明:https://docs.microsoft.com/zh-tw/dotnet/api/system.security.principal.windowsprincipal.isinrole?redirectedfrom=MSDN&view=windowsdesktop-5.0#overloads
OSMajorVersion 与 OSMinorVersion 对照表:https://docs.microsoft.com/en-us/windows-hardware/drivers/install/inf-manufacturer-section
样本配置文件提取器:https://github.com/advanced-threat-research/DarkSide-Config-Extract