QuasarRAT 恶意软件分析报告

admin 2024年3月13日14:37:05评论6 views字数 17399阅读57分59秒阅读模式

介绍

QuasarRAT 是一种开源 RAT(远程访问工具/特洛伊木马)。这些工具是为合法目的而构建的,例如访问远程计算机,例如,系统管理员访问办公室计算机。但是,RAT 软件通常用于在未经客户端许可的情况下访问远程计算机。所以,我们称之为网络攻击,对吧?这就是为什么 RAT 通常被称为恶意软件的原因。QuasarRAT 是我们在本报告中将研究的例子之一。您可以找到许多 QuasarRAT 样本,但由于它是开源的,我们可以下载并构建自己的样本。构建样本后,我们将分析我们自己的样本。

在开始报告之前,我非常感谢@jeFF0Falltrades,因为像这样写这份报告的想法是从他的视频(https://www.youtube.com/watch?v=xV0x7kNZ_Yc)中获得的。与视频不同的是,我更改了恶意软件系列,配置提取器的某些部分(试图保持更简单一点),并在此报告中完全分析了恶意软件。

  • 155

  • QuasarRAT 恶意软件分析报告

继续报告,如果不想生成示例,可以下载此示例并按照报告进行操作:

SHA256:40f93888414cd5eb808e66a0abb9a207f5d070b569e9d30c3e56d24d5a05cc6e

下载、构建示例和使用 RAT

QuasarRAT 可以通过以下链接从 github.com 下载: [1]:

https://github.com/puniaze/QuasarRAT

没有任何版本,因为我们将自己编译代码并构建示例。因此,请下载所有代码并双击build-debug.bat。我们将构建一个测试用例示例,因为调试示例可以与 C2 在同一台计算机上工作。在我们的案例中,C2 和受害者都将是我们。

创建了一个 bin 文件夹,您可以看到Client.exe(示例)和Quasar.exe(RAT)。在 RAT 中,您可以设置设置并开始收听。然后运行示例,开始了,您现在自己听。

QuasarRAT 恶意软件分析报告

图1

您可以通过右键单击页面上的列表项来探索 QuasarRAT 可以执行的许多功能。对手可能会喜欢许多功能。其中一些是:

  • 获取系统信息

  • 浏览文件和文件夹。您可以删除并运行该文件,也可以将该文件添加到启动中,以便在受害者每次启动计算机时启动它

  • 实时观看受害者的屏幕

  • 从网络摄像头观看受害者(当然,如果存在)

  • 键盘记录

  • 下载/上传文件

  • 与受害者互动

QuasarRAT 恶意软件分析报告

图2

从现在开始,我们将继续发布版本,因为受害者和恶意软件分析师,您将遇到恶意软件的发布版本。debug-build 和 release-build 之间的主要区别在于 release-build 对配置值进行加密。

编译步骤与 debug-build 相同,但需要从Quasar.exe生成示例并设置配置。最重要的配置设置是指示主机名。这是 C2 的主机名。您可能还希望将客户端安装在不同的文件夹(默认为 %APPDATA%)上,启用自动启动和键盘记录。

检查样品

我将把 RAT 留给您并继续分析恶意软件。通过使用 Detect-it-Easy 查看示例,我们可以看到它是一种 .NET 恶意软件。您还可以通过在 github 页面中看到恶意软件是用 C# 编写的来猜测这一点。

QuasarRAT 恶意软件分析报告

图3

在 dnSpy 上反编译恶意软件并打开入口点会给我们一个混乱的视图。恶意软件在构建后会自动混淆自身,使分析变得困难。

QuasarRAT 恶意软件分析报告

图4

如果很难使用混乱的视图来跟踪报告,您还可以打开恶意软件的非构建版本,以便您可以清楚地了解正在发生的事情。我们将继续使用非构建版本来轻松解释恶意软件,但请记住,配置是写在恶意软件的构建版本中。

QuasarRAT 恶意软件分析报告

图5

在 Settings.Initialize() 中,恶意软件解密配置设置。

QuasarRAT 恶意软件分析报告

图6

AES。SetDefaultKey() 方法,创建一个键和 AES。Decrypt() 方法使用该密钥来解密设置。在 Settings.FixDirectory() 方法中,恶意软件会确保它在 64 位系统上运行。

解密配置设置

我们需要一个密钥来使用解密方法。我们可以从名为 AES 的 Settings.Initialize() 方法中的第一个方法获取它。SetDefaultKey()。

QuasarRAT 恶意软件分析报告

图7

Rfc2898DeriveBytes() 方法通过使用其他参数 salt 和迭代计数对密码进行哈希处理来派生密钥。它在我们的示例中使用 SHA1 哈希算法。您可以通过单击 Rfc2898DeriveBytes 类来了解它使用的哈希算法。我们可以通过在 CyberChef 上手动输入输入来获取密钥。输入是从生成版本示例中收集的,因此需要转到生成版本上的确切方法并查找输入。

盐输入以十进制形式写入,需要转换为十六进制值。

QuasarRAT 恶意软件分析报告

图8

然后,使用 salt 值派生密钥,如下图所示。你可以从 GetBytes(16) 方法中看到密钥的大小,它是 16 个字节。这就是为什么我们在下面的密钥大小部分写了 128(位)。每次从 AES 解密配置设置时,我们都会使用此密钥。

QuasarRAT 恶意软件分析报告

图9

恶意软件最初设置解密密钥,然后逐个解密设置。在 AES 内部。Decrypt() 方法,我们可以看到它首先从 Base64 解密,然后从 AES 加密解密。最后,它将结果转换为字符串。

QuasarRAT 恶意软件分析报告

图10

从恶意软件的构建版本中获取配置设置(例如主机),并使用 Base64 对其进行解密。我们将使用输出作为 Decrypt() 方法的输入。

QuasarRAT 恶意软件分析报告

图11

在 Decrypt() 方法中,我们可以看到配置。当我们尝试自己解密配置设置时,我们将使用这些配置。我们可以看到输入被写入内存流,每当输入作时,它就会从内存流中读取。

QuasarRAT 恶意软件分析报告

图12

它通过比较前 32 个字节和其余字节来检查恶意软件是否获得了正确的值。在比较之前,前 32 个字节之后的其余字节使用 SHA256 进行哈希处理。我们可以理解,前 32 个字节已经被哈希处理了。如果前 32 个字节和哈希方法的结果不同,则返回一个空数组。这部分与解密没有任何关系,它只是检查输入的完整性。

不要忘记 memoryStream.Read() 方法读取内存流,但在方法的末尾,变量 memoryStream 指向第 33 个字节。因此,内存流将从中断的地方继续读取,而不是从头开始。

QuasarRAT 恶意软件分析报告

图13

恶意软件会解密输入。它获取前 16 个字节(请记住,32 个字节之后的前 16 个字节)作为 IV。然后,它创建一个解密器,并用它解密内存流的其余部分。

QuasarRAT 恶意软件分析报告

图14

从图 11 中可以看出,前 32 个字节被忽略(直到输出中的黄色部分),接下来的 16 个字节用于 IV(黄色部分),其余用于输入(在黄色部分之后)。当我们将参数和输入放在它们的位置时,我们可以看到配置已被解密。您也可以尝试其他参数,但需要相应地更改输入和 IV。

QuasarRAT 恶意软件分析报告

图15

当然,你可以通过调试我们经历过的代码来获得解密结果。

程序初始化

恶意软件做的第一件事是获取解密的配置值。它获取主机,创建互斥锁,通过从 Base64 解密来设置 AES 默认密钥,并获取恶意软件将初始化的文件路径。

QuasarRAT 恶意软件分析报告

图16

获取位置

在 GeoLocationHelper.Initialize() 方法中,有一个 TryLocate() 方法,恶意软件试图找到受害者。正如你在下面看到的,它从 https://ip-api.com/json/ 获取位置,直接提供json数据,包括你的国家,国家代码,城市,纬度和经度,ISP和IP地址。设置用户代理,以便访问的服务器不会认为请求来自非法客户端。

QuasarRAT 恶意软件分析报告

图17

如果恶意软件无法找到受害者,它将转到 GeoLocationHelper.TryLocateFallback() 方法。在这里,恶意软件试图从 http://freegeoip.net/xml/ 获取受害者的位置。由于输出是 XML 格式,因此恶意软件会解析来自网站的响应。它获取 ip、国家名称、国家代码、地区名称、城市和时区。

QuasarRAT 恶意软件分析报告

图18

如果恶意软件无法再次找到受害者,它将转到 GetLocationHelper.TryGetWanIp() 方法。在这里,恶意软件再次尝试定位受害者,但这次它只是尝试获取 IP 地址,因为它访问的网站仅返回客户端的 IP 地址。恶意软件访问的地址是 http://api.ipify.org/ 。

QuasarRAT 恶意软件分析报告

图19

简单的防 Windows 检测

获取受害者的位置信息后,恶意软件会在 FileHelper.DeleteZoneIdentifier() 方法中删除自身的区域标识符。区域标识符具有一个值,该值指示文件的来源。恶意软件试图删除从 Internet 下载的信息,也可能是从受限制的站点下载的,以便 Windows 无法识别下载的文件及其来源。

QuasarRAT 恶意软件分析报告

图20 [2]

检查用户状态

在 WindowsAccountHelper.StartUserIdleCheckThread() 方法中,恶意软件会创建一个线程,该线程每五秒钟检查受害者是空闲还是处于活动状态。

QuasarRAT 恶意软件分析报告

图21

坚持

在 Startup.AddtoStartup() 方法中,如果用户是管理员,恶意软件会创建一个名为 schtasks 的进程,该进程是一个 Windows 程序,可在预定时间内自动执行程序。使用 schtasks.exe 执行的命令会在受害者每次登录时创建启动密钥任务(使用 /tn)(使用 /sc)和要运行的任务(使用 /tr)。它还在命令上设置高权限(使用 /rl)。由于用户是管理员,因此恶意软件会强制执行此操作(使用 /f)。

恶意软件还会向注册表项 HKCU\Software\Microsoft\Windows\CurrentVersion\Run 添加一个值,以便每次受害者启动计算机时,恶意软件都会运行。将恶意软件的文件路径添加到注册表项不需要恶意软件是管理员。

QuasarRAT 恶意软件分析报告

图22

键盘记录器

恶意软件会记录键盘和鼠标。

QuasarRAT 恶意软件分析报告

图23

恶意软件将键盘和鼠标中的数据保存在 Keylogger.Subscribe() 方法中。当按下该键时,恶意软件会以 HTML 格式获取活动窗口的标题(按下该键的窗口)和当前时间。然后,它添加按下的键。

QuasarRAT 恶意软件分析报告

图24

当程序的初始化结束时,恶意软件连接到C2,关闭shell和互斥锁,并在Main()中退出程序。

命令和信息窃取

在命名空间 xClient.Core.Commands 中,有一个名为 CommandHandler 的类,该类具有攻击者可以在受感染的计算机上执行的所有功能。我们不会得到太多细节,因为每个人都可以通过从 github 下载 QuasarRAT 工具来找到这些功能,该工具在报告开头提到。但是,如果需要提及功能是什么,攻击者可以操纵:

  • 注册表项

  • 网络摄像头

  • 文件和文件夹

  • 互联网连接

  • 互动的受害者

  • 通过控制鼠标和键盘,计算机状态(关机,重启等)来控制受害者的活动

  • 正在运行的进程

  • Shell(执行命令)

攻击者还会获取有关受感染计算机的信息:

  • 处理器

  • 记忆

  • 显卡

  • 用户名

  • 电脑名称

  • 域名

  • 主机名

  • 系统驱动

  • 系统目录

  • 正常运行时间

  • MAC地址

  • LAN IP 地址

  • WAN IP 地址

  • 防毒

  • 防火墙

  • 时区

  • 国家

  • 互联网服务提供商

  • 正在运行的进程的名称

  • 正在运行的进程的 ID

  • 运行进程的窗口标题

QuasarRAT 恶意软件分析报告

图25

YARA

rule quasarRAT_detector {
meta:
author = "psy_maestro"
date = "10/Feb/2024"
description = "Detects QuasarRAT"
strings:
$str1 = "<p class="h">[{0}" wide
$str2 = "<p class="h">[Enter]</p><br>" wide
$str3 = "<p class="h">[Esc]</p>" wide
$str4 = "<meta http-equiv='Content-Type' content='text/html; charset=utf-8' />Log created on " wide
$str5 = "<style>.h { color: 0000ff; display: inline; }</style>" wide
$str6 = ":Zone.Identifier" wide
$str7 = "echo DONT CLOSE THIS WINDOW!" wide
$str8 = "Uninstalling... bye ;(" wide
condition:
uint16(0) == 0x5A4D and //looks for MZ at 0x00
uint32(uint32(0x3C)) == 0x00004550 and // PE at 0x3C
all of them
}

配置提取器 [3]

# this config extractor can only work with QuasarRAT with version 1.3.0.0
import sys
import os
from pathlib import Path
from re import search, DOTALL, findall
import ast
from base64 import b64decode
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.ciphers.modes import CBC
from cryptography.hazmat.primitives.padding import PKCS7
from cryptography.hazmat.primitives.hashes import SHA1
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.backends import default_backend

class QuasarRATParser:
# regex to find the ldstr opcodes and 4 bytes after them, stsfld opcode and 4 bytes after them.
PATTERN_CONFIG_START = b"x72(.{4})x80(.{4})"
# pattern to find the start of the metadata table
PATTERN_CLR_METADATA_START = b"x42x53x4ax42"
# metadata tables list and their row size (each row size differentiates for each sample)
MAP_TABLE = {
'Module': {
'row_size': 12
},
'TypeRef': {
'row_size': 10
},
'TypeDef': {
'row_size': 18
},
'FieldPtr': {
'row_size': 2
},
'Field': {
'row_size': 8
},
'MethodPtr': {
'row_size': 2
},
'Method': {
'row_size': 16
},
'ParamPtr': {
'row_size': 2
},
'Param': {
'row_size': 8
},
'InterfaceImpl': {
'row_size': 4
},
'MemberRef': {
'row_size': 8
},
'Constant': {
'row_size': 6
},
'CustomAttribute': {
'row_size': 8
},
'FieldMarshal': {
'row_size': 4
},
'DeclSecurity': {
'row_size': 6
},
'ClassLayout': {
'row_size': 8
},
'FieldLayout': {
'row_size': 6
},
'StandAloneSig': {
'row_size': 2
},
'EventMap': {
'row_size': 4
},
'EventPtr': {
'row_size': 2
},
'Event': {
'row_size': 8
},
'PropertyMap': {
'row_size': 4
},
'PropertyPtr': {
'row_size': 2
},
'Property': {
'row_size': 8
},
'MethodSemantics': {
'row_size': 6
},
'MethodImpl': {
'row_size': 6
},
'ModuleRef': {
'row_size': 4
},
'TypeSpec': {
'row_size': 2
},
'ImplMap': {
'row_size': 10
},
'FieldRVA': {
'row_size': 6
},
'ENCLog': {},
'ENCMap': {},
'Assembly': {},
'AssemblyProcessor': {},
'AssemblyOS': {},
'AssemblyRef': {},
'AssemblyRefProcessor': {},
'AssemblyRefOS': {},
'File': {},
'ExportedType': {},
'ManifestResource': {},
'NestedClass': {},
'GenericParam': {},
'MethodSpec': {},
'GenericParamConstraint': {},
'Reserved 2D': {},
'Reserved 2E': {},
'Reserved 2F': {},
'Document': {},
'MethodDebugInformation': {},
'LocalScope': {},
'LocalVariable': {},
'LocalConstant': {},
'ImportScope': {},
'StateMachineMethod': {},
'CustomDebugInformation': {},
'Reserved 38': {},
'Reserved 39': {},
'Reserved 3A': {},
'Reserved 3B': {},
'Reserved 3C': {},
'Reserved 3D': {},
'Reserved 3E': {},
'Reserved 3F': {}
}

class QuasarRATAESDecryptor:
# pattern for getting RVA of the AES.Salt variable
PATTERN_AES_METADATA = b"x02x7e(.{4})x20(.{4})"
# pattern for getting the AES key and block size
PATTERN_AES_KEY_AND_BLOCK_SIZE = b"x08x20(.{4})x6F"
# pattern for field table RVA of the salt initializer (salt initializer initializes a byte array)
PATTERN_SALT_ARRAY_ID = b"x1Fx20x8D.{4}x25xD0(.{2})"
# pattern for getting the passphrase's RVA
PATTERN_DERIVE_KEY_LDSFLD = b"x7E(.{4})x80.{4}x7E"
PATTERN_DERIVE_KEY_LDSTR = b"x72(.{4})x80.{4}(x72.{4}x80.{4}){2}"

def __init__(self, parent_parser):
self.parent = parent_parser
self.salt_flag, self.iterations = self.get_aes_metadata()
self.aes_salt = self.get_salt()
self.key_size, self.block_size = self.get_key_and_block_size()
self.aes_key = self.derive_aes_key()

# get the metadata flag of the AES salt and iteration value
def get_aes_metadata(self):
metadata_flag = search(self.PATTERN_AES_METADATA, self.parent.data, DOTALL)
salt_flag = int.from_bytes(metadata_flag.group(1), byteorder="little")
iterations_byte = search(self.PATTERN_AES_METADATA, self.parent.data, DOTALL)
iterations = int.from_bytes(metadata_flag.group(2), byteorder="little")
return salt_flag, iterations

# get salt initialized as a byte array
def get_salt_rva(self):
salt_array_id = search(self.PATTERN_SALT_ARRAY_ID, self.parent.data, DOTALL).group(1)
salt_array_id_int = int.from_bytes(salt_array_id, byteorder="little")
# go to Field RVA
field_rva_cursor = self.parent.get_subtable_map("FieldRVA")
# In the Field RVA, find the relevant row with field value matches with the last three bytes
rva_value_found = False
for i in range(self.parent.table_map["FieldRVA"]["row_num"]):
if int.from_bytes(self.parent.data[field_rva_cursor + 4:field_rva_cursor + 6], byteorder="little") == salt_array_id_int:
rva_value = self.parent.data[field_rva_cursor:field_rva_cursor + 4]
rva_value_found = True
break
field_rva_cursor += self.parent.table_map["FieldRVA"]["row_size"]
if not rva_value_found:
print("FieldRVA value of the AES Salt CANNOT BE FOUND")
sys.exit(7)
rva_value_int = int.from_bytes(rva_value, byteorder="little")
return rva_value_int

# get the offset of rva
def rva_to_file_offset(self, rva_value):
text_section_start = self.parent.data.find(b".text")
va_pointer = int.from_bytes(self.parent.data[text_section_start + 12:text_section_start + 16], byteorder="little")
file_offset = int.from_bytes(self.parent.data[text_section_start + 20:text_section_start + 24], byteorder="little")
result = rva_value - va_pointer + file_offset
return result

# get salt value
def get_salt(self):
salt_offset = self.rva_to_file_offset(self.get_salt_rva())
salt = b"0"
for i in range(32):
salt_item = self.parent.data[salt_offset + i]
salt += salt_item.to_bytes(1, byteorder="big")
return salt[1:]

# get the AES key and block size
def get_key_and_block_size(self):
key_size_byte = findall(self.PATTERN_AES_KEY_AND_BLOCK_SIZE, self.parent.data, DOTALL)[0]
block_size_byte = findall(self.PATTERN_AES_KEY_AND_BLOCK_SIZE, self.parent.data, DOTALL)[1]
key_size = int.from_bytes(key_size_byte, byteorder="little") // 8
block_size = int.from_bytes(block_size_byte, byteorder="little")
return key_size, block_size

# get aes passphrase
def get_aes_passphrase(self):
config_map = self.parent.config_map
try:
passphrase_rva = search(self.PATTERN_DERIVE_KEY_LDSFLD, self.parent.data, DOTALL).group(1)
for config in config_map:
if config[0] == hex(int.from_bytes(passphrase_rva, byteorder="little")):
return config[2]
except:
passphrase_rva = search(self.PATTERN_DERIVE_KEY_LDSTR, self.parent.data, DOTALL).group(1)
config_addr_map = self.parent.config_addr_map
for config in config_addr_map:
if config[0] == hex(int.from_bytes(passphrase_rva, byteorder="little")):
hold_strings_rva = config[1]
for config in config_map:
if config[0] == hold_strings_rva:
return config[2]


def derive_aes_key(self):
passphrase = self.get_aes_passphrase()
new_passphrase = ""
for byte in passphrase:
if byte != 0:
new_passphrase += str(chr(byte))
new_passphrase = bytes(new_passphrase.encode("utf-8"))
kdf = PBKDF2HMAC(SHA1(), length=self.key_size, salt=self.aes_salt, iterations=self.iterations)
aes_key = kdf.derive(new_passphrase)
return aes_key

def decrypt(self, iv, ciphertext):
aes_cipher = Cipher(AES(self.aes_key), CBC(iv), backend=default_backend())
decryptor = aes_cipher.decryptor()
unpadder = PKCS7(self.block_size).unpadder()
padded_text = decryptor.update(ciphertext) + decryptor.finalize()
unpadded_text = unpadder.update(padded_text) + unpadder.finalize()
return unpadded_text

def __init__(self, file_path):
self.file_path = file_path
self.data = self.get_file_data()
self.config_addr_map = self.get_config_addr_map()
self.table_map = self.get_table_map()
self.strings_stream_rva, self.variable_name = self.get_strings_name()
self.variable_value = self.get_US_name()
self.config_map = self.get_config_map()
self.aes_decryptor = self.QuasarRATAESDecryptor(self)
self.config = self.decrypt_config()

# read the file binary data
def get_file_data(self):
try:
with open(self.file_path, "rb") as fp:
data = fp.read()
except FileNotFoundError as e:
print(f"ERROR: File {self.file_path} cannot be found.")
sys.exit(2)
return data

# get where the config values are loaded
def get_config_addr_map(self):
# search the regex through the binary
hit = findall(self.PATTERN_CONFIG_START, self.data, DOTALL)
if hit is None:
print("CANNOT FIND THE CONFIG PATTERN")
sys.exit(3)

# convert the byte values to hex
strings_offset_list = []
for us_rva, strings_rva in hit:
us_rva2 = hex(int.from_bytes(us_rva, byteorder="little"))
strings_rva2 = hex(int.from_bytes(strings_rva, byteorder="little"))
strings_offset_list.append((us_rva2, strings_rva2))
return strings_offset_list

# get the start of the metadata (Store Signature). This is where the metadata table begins
def get_metadata_header_offset(self):
metadata_start = self.data.find(self.PATTERN_CLR_METADATA_START)
if metadata_start == -1:
print("CANNOT FIND THE METADATA STARTING OFFSET")
sys.exit(4)
return metadata_start

# get the start of the #~ stream
def get_stream_start(self, stream_id):
# get the relative address of the stream #~
stream_offset_bin = self.data.find(stream_id)
if stream_offset_bin == -1:
print(f"CANNOT FIND STREAM {stream_id} OFFSET")
sys.exit(5)

# convert the address written 8 bytes before and it's 4 bytes long into integer
stream_offset = int.from_bytes(self.data[stream_offset_bin-8:stream_offset_bin-4], byteorder="little")

# to get the actual address of the next table, add the relative address to the start of the metadata table
return stream_offset + self.get_metadata_header_offset()

# get the mask_valid value so that we can map the metadata stream
def get_mask_valid(self):
# mask_valid is 8 bytes ahead of the table it's in
mask_valid_addr = self.get_stream_start(b"#~") + 8
# mask_valid value is 8 bytes long
mask_valid = int.from_bytes(self.data[mask_valid_addr:mask_valid_addr+8], byteorder="little")
return mask_valid

# map the table with its row number, row size and whether they are contained
def get_table_map(self):
mask_valid = self.get_mask_valid()
table_map = self.MAP_TABLE.copy()
storage_stream_offset = self.get_stream_start(b"#~")
table_start = storage_stream_offset + 24
cur_offset = table_start
try:
for table in table_map:
if mask_valid & 2**list(table_map.keys()).index(table):
row_count = int.from_bytes(self.data[cur_offset:cur_offset + 4], byteorder="little")
table_map[table]["row_num"] = row_count
cur_offset += 4
else:
table_map[table]["row_num"] = 0
except:
print("CANNOT GET TABLE MAP")
sys.exit(6)
return table_map

# get the offset where the fields table starts
def get_subtable_map(self, table_name):
storage_stream_offset = self.get_stream_start(b"#~")
table_cursor = storage_stream_offset + 24
field_start = 0
temp_cursor = 0
field_found = False
for table in self.table_map:
if self.table_map[table]["row_num"] == 0:
continue
else:
table_cursor += 4

if table == table_name:
field_found = True
elif not field_found:
table_cursor += self.table_map[table]["row_num"] * self.table_map[table]["row_size"]
field_start = table_cursor

return field_start

# iterate over the fields table and get the offset of names which is RVA in #Strings stream
def get_offset_from_fields(self):
field_start = self.get_subtable_map("Field")
string_field_name = []
for config_us_rva, config_strings_rva in self.config_addr_map:
string_row_start = field_start + (int(config_strings_rva[-3:], 16) - 1) * 8
string_field_name.append(int.from_bytes(self.data[string_row_start + 2:string_row_start + 6], byteorder="little"))
return string_field_name

# look at the #strings stream and get the variable name
def get_strings_name(self):
strings_start = self.get_stream_start(b"#Strings")
variable_list = []
variable_fields_rva = []
for name in self.get_offset_from_fields():
variable_list.append(self.data[strings_start + name:strings_start + name + 100].partition(b"")[0])
for config_us_rva, config_strings_rva in self.config_addr_map:
variable_fields_rva.append(config_strings_rva)
return variable_fields_rva, variable_list

# extract the decrypted user strings from #US stream
def get_US_name(self):
us_start = self.get_stream_start(b"#US")
config_value = []
for us_rva, strings_rva in self.config_addr_map:
config_value.append(self.data[us_start + int(us_rva[-4:], 16) + 1:].partition(b"x00x00")[0])
return config_value

# concatanate variable name RVA, variable name and variable value into a triple
def get_config_map(self):
config_list = []
for i in range(len(self.variable_value)):
config_list.append((self.strings_stream_rva[i], self.variable_name[i], self.variable_value[i]))
return config_list

def decrypt_config(self):
decrypted_config = {}
for rva, key, val in self.config_map:
try:
decoded_val = b64decode(val)
iv = decoded_val[32:48]
ciphertext = decoded_val[48:]
decrypted_config[key] = self.aes_decryptor.decrypt(iv, ciphertext)
#print(decrypted_config[key])
except:
continue
return decrypted_config

def report(self):
report_dict = {}
variable_name_list = ["VERSION:","HOSTS:","SUBDIRECTORY:","INSTALL_NAME:","MUTEX:","STARTUP_NAME:","TAG:","LOG_FOLDER:"]
counter = 0
for config in self.config:
report_dict[counter] = self.config[config]
counter += 1
for i in range(counter):
print(variable_name_list[i], report_dict[i])


def main():
arg = sys.argv[1]
abs_fp = os.path.abspath(arg)
file_path_obj = Path(abs_fp)

file_paths = []
if not file_path_obj.exists():
print(f"FILE {arg} DOES NOT EXISTS")
sys.exit(1)
elif file_path_obj.is_file():
print(QuasarRATParser(abs_fp).report())
else:
os.chdir(abs_fp)
for file_path in os.listdir(Path.cwd()):
print(QuasarRATParser(file_path).report())

if __name__ == "__main__":
main()

包含示例的配置提取器示例:SHA256:40f93888414cd5eb808e66a0abb9a207f5d070b569e9d30c3e56d24d5a05cc6e

QuasarRAT 恶意软件分析报告

图26

提取的主机 (rick63.publicvm.com) 在 VirusTotal 中被 17 家供应商标记为恶意主机:

https://www.virustotal.com/gui/url/e13a4f29ec1d48495a983a51425a0d96eb753c5aa3fb3df39294c708e9912383

引用

[1]:https://github.com/puniaze/QuasarRAT

[2]:https://www.cloudsek.com/blog/malicious-macros-and-zone-identifier-alternate-data-stream-information-bypass

[3]:https://www.youtube.com/watch?v=xV0x7kNZ_Yc

其它教程
二进制漏洞 
  • QuasarRAT 恶意软件分析报告

  • windows

  • QuasarRAT 恶意软件分析报告

  • windows()

  • QuasarRAT 恶意软件分析报告

  • USB()

  • QuasarRAT 恶意软件分析报告

  • ()

  • QuasarRAT 恶意软件分析报告

  • ios

  • QuasarRAT 恶意软件分析报告

  • windbg

  • QuasarRAT 恶意软件分析报告

  • ()

  • QuasarRAT 恶意软件分析报告QuasarRAT 恶意软件分析报告QuasarRAT 恶意软件分析报告

  • QuasarRAT 恶意软件分析报告

  • QuasarRAT 恶意软件分析报告

  • QuasarRAT 恶意软件分析报告

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年3月13日14:37:05
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   QuasarRAT 恶意软件分析报告http://cn-sec.com/archives/2565721.html

发表评论

匿名网友 填写信息