Упаковщик:
NSPack
Дата компиляции:
02.07.2017 05:59:15
SHA1-хеш:
4bba897ee81240b10f9cca41ec010a26586e8c09
Описание
Многокомпонентный троян-бэкдор, написанный с использованием языков С и Assembler, предназначенный для работы в 32- и 64-разрядных операционных системах семейства Microsoft Windows. Используется для целевых атак на информационные системы и несанкционированного доступа к данным для их передачи на управляющие серверы. Основная функциональность бэкдора реализована при помощи встроенных в его тело модулей — плагинов.
Принцип действия
DLL-модуль бэкдора загружается в оперативную память методом DLL Hijacking с помощью подлинного исполняемого файла TosBtKbd.exe от издателя TOSHIBA CORPORATION. На зараженном компьютере файл располагался под именем msmsgs.exe.
.>sigcheck -a msmsgs.exe_
Verified: Signed
Signing date: 5:24 24.07.2008
Publisher: TOSHIBA CORPORATION
Company: TOSHIBA CORPORATION.
Description: TosBtKbd
Product: Bluetooth Stack for Windows by TOSHIBA
Prod version: 6, 2, 0, 0
File version: 6, 2, 0, 0
MachineType: 32-bit
Binary Version: 6.2.0.0
Original Name: TosBtKbd.exe
Internal Name: n/a
Copyright: Copyright (C) 2005-2008 TOSHIBA CORPORATION, All rights reserved.
Comments: n/a
Entropy: 5.287
Бэкдор может быть связан с BackDoor.Farfli.125, так как обе вредоносные программы используют один и тот же управляющий сервер — www[.]pneword[.]net.
Рассмотренный образец находился на зараженном компьютере в директории C:\ProgramData\Messenger\ и был установлен в качестве службы Messenger.
Стоит отметить, что в BackDoor.Farfli.125 предусмотрена команда 0x7532, по которой бэкдор пытается установить службу с аналогичным названием — Messenger.
Начало работы
Вредоносная библиотека имеет две экспортируемые функции:
SetTosBtKbdHook
UnHookTosBtKbd
Имя модуля, указанное в таблице экспорта, — TosBtKbd.dll.
Функция DLLMain и экспортируемая функция UnHookTosBtKbd являются заглушками.
Функция SetTosBtKbdHook перебирает дескрипторы в поиске объектов, чьи имена содержат TosBtKbd.exe, а затем закрывает их.
int __stdcall check_handles()
{
ULONG v0; // ecx
HMODULE v1; // eax
int result; // eax
int iter; // esi
int v4; // eax
ULONG ReturnLength; // [esp+0h] [ebp-4h] BYREF
ReturnLength = v0;
if ( *(_DWORD *)NtQueryObject
|| (v1 = GetModuleHandleA(aNtdllDll),
result = (int)GetProcAddress(v1, aNtqueryobject),
(*(_DWORD *)NtQueryObject = result) != 0) )
{
iter = 0;
while ( 1 )
{
if ( NtQueryObject((HANDLE)(4 * iter), ObjectNameInformation, &object__name_info, 0x1000u, &ReturnLength) >= 0 )
{
v4 = lstrlenW(object__name_info.Name.Buffer);
do
--v4;
while ( v4 > 0 && object__name_info.Name.Buffer[v4] != 92 );
if ( !lstrcmpiW(&object__name_info.Name.Buffer[v4 + 1], String2) )
break;
}
if ( ++iter >= 100000 )
return 0;
}
result = CloseHandle((HANDLE)(4 * iter));
}
return result;
}
После этого при помощи SetTosBtKbdHook расшифровывается шелл-код, который хранится в теле бэкдора.
Алгоритм расшифровки шелл-кода:
def LOBYTE(v):
return v & 0xFF
def dump_shellcode(addr, size, key):
buffer = get_bytes(addr, size)
result = b""
for x in buffer:
result += bytes([x ^ LOBYTE(key)])
key = ((key * 0x6A730000) - (((key >> 0x10) * 0x39F3958D)) - 0x5C0BB335) & 0xFFFFFFFF
i = 0
for x in result:
patch_byte(addr + i, x)
i += 1
Расшифрованный шелл-код сопротивляется анализу с помощью двух последовательных условных JMP-инструкций в одну точку.
После обхода обфускации функция приобретает корректный вид:
Шелл-код предназначен для загрузки основной полезной нагрузки, которая представляет собой разобранный PE-модуль без заголовков MZ и PE. Для загрузки используется специальный заголовок, состоящий из отдельных частей стандартных заголовков.
struct section
{
DWORD RVA;
DWORD raw_data_offset;
DWORD raw_data_len;
};
struct module_header
{
DWORD key;
DWORD key_check;
DWORD import_table_RVA;
DWORD original_ImageBase;
DWORD relocation_table_RVA;
DWORD relocation_table_size;
DWORD IAT_RVA;
DWORD IAT_size;
DWORD EP_RVA;
WORD HDR32_MAGIC;
WORD word;
DWORD number_of_sections;
DWORD timestamp;
section section_1;
section section_2;
section section_3;
section section_4;
};
Заголовок хранится в шелл-коде после первого блока инструкций.
Затем функция module_loader непосредственно загружает полезную нагрузку. Вначале через структуру PEB бэкдор получает адреса следующих функций из kernel32:
LoadLibraryA
GetProcAddress
VirtualAlloc
Sleep
Имя библиотеки Kernel32 и указанные API программа ищет по хешу имени, который вычисляется по алгоритму:
def rol(val, r_bits, max_bits=32):
return (val << r_bits%max_bits) & (2**max_bits-1) | ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))
def ror(val, r_bits, max_bits=32):
return ((val & (2**max_bits-1)) >> r_bits%max_bits) | (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))
def libnamehash(lib_name):
result = 0
b = lib_name.encode()
for x in b:
result = ror(result, 8)
x |= 0x20
result = (result + x) & 0xFFFFFFFF
result ^= 0x7C35D9A3
return result
def procnamehash(proc_name):
result = 0
b = proc_name.encode()
for x in n:
result = ror(result, 8)
result = (result + x) & 0xFFFFFFFF
result ^= 0x7C35D9A3
return result
После получения адресов API бэкдор проверяет целостность значений из заголовка алгоритмом на основе XOR-операции — module_header.key ^ module_header.key_check. Значение должно быть равно 0x7C35D9A3; то же значение, что используется при хешировании имен функций из kernel32. После этого проверяет значение сигнатуры module_header.HDR32_MAGIC — оно должно быть равно 0x10B. Затем выделяет исполняемый буфер размером module_header.import_table_RVA и прибавляет 0x4000 под модуль.
После этого заполняет блок размером 0x1000 байт в начале выделенного буфера module_header.section_1.RVA — места, где должен был располагаться PE-заголовок загруженного модуля.
В регистре ECX при этом изначально лежит адрес выделенного исполняемого буфера.
Затем бэкдор загружает секции модуля в соответствии с их RVA (Relative Virtual Address). Данные секций хранятся в шелл-коде после заголовка, смещение к данным (section.raw_data_offset) отсчитывается от начала заголовка.
После секций программа обрабатывает релоки, которые хранятся в виде структур IMAGE_BASE_RELOCATION, однако каждый WORD, который отвечает за тип релока и смещение от начала блока, зашифрован. Начальный ключ берется из module_header.key, после каждой итерации он изменяется. Стоит отметить, что полученный после всех итераций ключ будет использоваться при обработке импортируемых функций.
Алгоритм обработки релоков:
import struct
def relocations(image_address, original_image_base, relocation_table_RVA):
global key
relocation_table_addr = image_address + relocation_table_RVA
reloc_hdr_data = get_bytes(relocation_table_addr, 8)
block_address, size_of_block = struct.unpack('<II', reloc_hdr_data)
while size_of_block:
if ((size_of_block - 8) >> 1) > 0:
block = get_bytes(relocation_table_addr + 8, size_of_block - 8)
i = 0
while i < ((size_of_block - 8) >> 1):
reloc = struct.unpack('<H', block[i*2:i*2+2])[0]
reloc_type = ((reloc ^ key) & 0xFFFF) >> 0x0C
offset = (reloc ^ key) & 0xFFF
offset_high = (((key >> 0x10) + reloc) & 0xFFFFFFFF) | ((key << 0x10) & 0xFFFFFFFF)
key = offset_high
if reloc_type == 3:
patch_addr = offset + image_address + block_address
delta = (image_address - original_image_base) & 0xFFFFFFFF
value = get_wide_dword(patch_addr)
patch_dword(patch_addr, (value + delta) & 0xFFFFFFFF)
elif reloc_type == 0x0A:
patch_addr = image_address + offset + + block_address
delta = (image_address - original_image_base) & 0xFFFFFFFF
old_low = get_wide_dword(patch_addr)
old_high = get_wide_dword(patch_addr + 4)
patch_dword(patch_addr, (old_low + offset) & 0xFFFFFFFF)
patch_dword(patch_addr + 4, (old_high + offset_high) & 0xFFFFFFFF)
i += 1
relocation_table_addr += size_of_block
reloc_hdr_data = get_bytes(relocation_table_addr, 8)
block_address, size_of_block = struct.unpack('<II', reloc_hdr_data)
После того как все релоки обработаны, структура заполняется нулями.
Далее BackDoor.ShadowPad.1 приступает к обработке импортируемых функций. В целом процедура соответствует стандартной, однако имена библиотек и функций зашифрованы. Используется ключ, модифицированный после обработки релоков, и также после каждой итерации шифрования он изменяется. После обработки очередной импортируемой функции ее адрес не помещается непосредственно в ячейку, заданную относительно IMAGE_IMPORT_DESCRIPTOR.FirstThunk. Вместо этого формируется блок инструкций, передающий управление на API вида:
mov eax, <addr>
neg eax
jmp eax
Алгоритм обработки импортов:
def imports(image_address, IAT_RVA,):
global key
IAT_address = image_address + IAT_RVA
import_table_address = image_address + 0x1A000
import_descriptor_address = IAT_address
while True:
OriginalThunkData, TimeDateStamp, ForwarderChain, Name, FirstThunk = struct.unpack('<IIIII', get_bytes(import_descriptor_address, 0x14))
TimeDateStamp = 0
ForwarderChain = 0
OriginalThunkData_address = image_address + OriginalThunkData
FirstThunk_address = image_address + FirstThunk
libname_address = image_address + Name
n1 = get_wide_byte(libname_address)
libname_decrypted = bytes([(n1 ^ key) & 0xFF])
key = ((key >> 0x08) + c_byte(n1).value) | ((key << 0x18) & 0xFFFFFFFF)
i = 1
nb = get_wide_byte(libname_address + i)
while libname_decrypted[-1]:
libname_decrypted += bytes([(nb ^ key) & 0xFF])
key = ((key >> 0x08) + c_byte(nb).value) | ((key << 0x18) & 0xFFFFFFFF)
i += 1
nb = get_wide_byte(libname_address + i)
libname_decrypted = libname_decrypted[:-1]
print("Imports from {0}".format(libname_decrypted[:-1]))
thunk = get_wide_dword(OriginalThunkData_address)
it_ptr = 0
j = 0
while thunk:
name_address = image_address + thunk + 2
nb1 = get_wide_byte(name_address)
func_name = bytes([(nb1 ^ key) & 0xFF])
key = ((key >> 0x08) + c_byte(nb1).value) | ((key << 0x18) & 0xFFFFFFFF)
i = 1
nb = get_wide_byte(name_address + i)
while func_name[-1]:
func_name += bytes([(nb ^ key) & 0xFF])
key = ((key >> 0x08) + c_byte(nb).value) | ((key << 0x18) & 0xFFFFFFFF)
i += 1
nb = get_wide_byte(name_address + i)
func_name = func_name[:-1]
print("Function {0}".format(func_name))
j_type = key % 5
if j_type == 0:
patch_byte(import_table_address, 0xE8)
elif j_type == 1:
patch_byte(import_table_address, 0xE9)
elif j_type == 2:
patch_byte(import_table_address, 0xFF)
elif j_type == 3:
patch_byte(import_table_address, 0x48)
elif j_type == 4:
patch_byte(import_table_address, 0x75)
else:
patch_byte(import_table_address, 0x00)
import_table_address += 1
patch_dword(FirstThunk_address + it_ptr, import_table_address) #addr to trampoline
func_addr = binascii.crc32(func_name) & 0xFFFFFFFF
patch_byte(import_table_address, 0xB8)
patch_byte(import_table_address + 1, func_addr)
patch_word(import_table_address + 5, 0xD8F7)
patch_word(import_table_address + 7, 0xE0FF)
import_table_address += 9
j += 1
it_ptr = j << 2
thunk = get_wide_dword(OriginalThunkData_address + it_ptr)
import_descriptor_address += 0x14
if not get_wide_dword(import_descriptor_address):
break
Таблица импортов также заполняется нулями после обработки.
Далее управление передается загруженному модулю. В качестве аргументов передаются:
- адрес начала буфера, в который загружен модуль,
- значение 1 (код),
- указатель на структуру shellarg.
В точке входа загруженный модуль проверяет код, переданный от загрузчика:
- 1 — выполнение основных функций,
- 0x64, 0x65 — нет действия,
- 0x66 — возвращает код 0x64 в 3 аргументе,
- 0x67 — расшифровывает и возвращает строку Root (в дальнейшем Root — имя модуля),
- 0x68 — возвращает в 3 аргументе указатель на таблицу функций, реализованных в данном модуле.
Алгоритм расшифровки строк:
def decrypt_str(addr):
key = get_wide_word(addr)
result = b""
i = 2
b = get_wide_byte(addr + i)
while i < 0xFFA:
result += bytes([b ^ (key & 0xFF)])
key = ((( key >> 0x10) * 0x1447208B) + (key * 0x208B0000) - 0x4875A15) & 0xFFFFFFFF
i += 1
b = get_wide_byte(addr + i)
if not result[-1]:
break
result = result[:-1]
return result
Стоит отметить, что содержащиеся в этом модуле фрагменты кода, а также некоторые объекты характерны для семейства бэкдоров BackDoor.PlugX.
При вызове с кодом 1 модуль переходит к выполнению основных функций. Вначале программа регистрирует обработчик исключений верхнего уровня. При получении управления обработчик формирует отладочную строку с информацией об исключении.
Затем программа выводит ее с помощью функции OutputDebugString, а также записывает ее в журнал, расположенный в %ALLUSERPROFILE%\error.log.
В бэкдорах семейства BackDoor.PlugX также регистрируются обработчики исключений. В частности, в BackDoor.PlugX.38 формируется строка с информацией об исключении, при этом незначительно отличается формат:
После регистрации обработчика формируется таблица вспомогательных функций, используемая для взаимодействия между модулями. Далее Root переходит к загрузке встроенных дополнительных модулей.
Каждый модуль хранится сжатым по алгоритму QuickLZ, а также зашифрованным. В начале модуль имеет заголовок размером 0x14 байт. На первом шаге заголовок расшифровывается. Алгоритм шифрования:
import struct
def LOBYTE(v):
return v & 0x000000FF
def BYTE1(v):
return (v & 0x0000FF00) >> 8
def BYTE2(v):
return (v & 0x00FF0000) >> 16
def HIBYTE(v):
return (v & 0xFF000000) >> 24
def decrypt_module(data, data_len, init_key):
key = []
for i in range(4):
key.append(init_key)
k = 0
result = b""
if data_len > 0:
i = 0
while i < data_len:
if i & 3 == 0:
t = key[0]
key[0] = (0x9150017B - (t * 0xD45A840)) & 0xFFFFFFFF
elif i & 3 == 1:
t = key[1]
key[1] = (0x95D6A3A8 - (t * 0x645EE710)) & 0xFFFFFFFF
elif i & 3 == 2:
t = key[2]
key[2] = (0xD608D41B - (t * 0x1ED33670)) & 0xFFFFFFFF
elif i & 3 == 3:
t = key[3]
key[3] = (0xD94925D3 - (t * 0x68208D35)) & 0xFFFFFFFF
k = (k - LOBYTE(key[i & 3])) & 0xFF
k = k ^ BYTE1(key[i & 3])
k = (k - BYTE2(key[i & 3])) & 0xFF
k = k ^ HIBYTE(key[i & 3])
result += bytes([data[i] ^ k])
i += 1
return result
Начальное значения ключа шифрования хранится в заголовке модуля. Структура заголовка имеет следующий вид:
struct plugin_header
{
DWORD key;
DWORD flags;
DWORD dword;
DWORD compressed_len;
DWORD decompressed_len;
};
После расшифровки заголовка бэкдор проверяет значение flags. Если в нем установлен флаг 0x8000, это означает, что модуль состоит из одного лишь заголовка. Затем проверяется значение нулевого бита первого байта расшифрованного блока. Если нулевой бит имеет значение 1, то это означает, что тело модуля упаковано алгоритмом QuickLZ.
После распаковки сверяет размеры получившихся данных со значениями в заголовке и переходит непосредственно к загрузке модуля. Для этого выделяет исполняемый буфер памяти, в который копирует функцию загрузки, затем передает на нее управление. Каждый модуль имеет тот же формат, что и модуль Root, то есть имеет собственный заголовок и зашифрованные импорты и релоки, поэтому загрузка происходит аналогичным образом. После того как модуль загружен, функция-загрузчик вызывает его точку входа с кодом 1. Каждый модуль, аналогично Root, инициализирует таблицу своих функций по этому коду. Затем Root вызывает точку входа загруженного модуля последовательно с кодами 0x64, 0x66, 0x68. Таким образом бэкдор инициализирует модуль и передает ему указатели на необходимые объекты.
Модули представляются объектами, объединенными в связный список. Обращение к конкретному модулю выполняется с помощью кода, который плагин помещает в свой объект после вызова его точки входа с к кодом 0x66.
struct loaded_module
{
LIST_ENTRY list;
DWORD run_count;
DWORD timestamp;
DWORD code_id;
DWORD field_14;
BOOL loaded;
BOOL unk;
BOOL module_is_PE;
DWORD module_size;
LPVOID module_base;
Root_helper *func_tab; //указатель на таблицу функций модуля Root
}
При обращении к точке входа модуля с кодом 0x67 расшифровывается и возвращается строка, которую можно обозначить как имя модуля:
- 1 — Plugins
- 2 — Online
- 3 — Config
- 4 — Install
- 5 — TCP
- 6 — HTTP
- 7 — UDP
- 8 — DNS
Если перевести поля временной метки из заголовков каждого плагина в даты, то получаются корректные значения даты и времени:
- Plugins — 2017-07-02 05:52:53
- Online — 2017-07-02 05:53:08
- Config — 2017-07-02 05:52:58
- Install — 2017-07-02 05:53:30
- TCP — 2017-07-02 05:51:36
- HTTP — 2017-07-02 05:51:44
- UDP — 2017-07-02 05:51:50
- DNS — 2017-07-02 05:51:55
После загрузки всех модулей Root ищет в списке модуль Install и вызывает вторую из двух функций, размещенных в его таблице функций.
Install
В первую очередь бэкдор получает привилегии SeTcbPrivilege и SeDebugPrivilege. Затем получает конфигурацию с помощью модуля Config. Для обращения к функциям используются функции-переходники следующего типа:
Через объект, который хранит список загруженных модулей, по коду находится необходимый, затем через таблицу вызывается нужная функция.
При инициализации конфигурации на первом этапе проверяется буфер, хранящийся в модуле Root. Если первые четыре байта этого буфера равны X, это значит, что бэкдору необходимо сформировать конфигурацию по умолчанию. В ином случае данный буфер является закодированной конфигурацией. Конфигурация хранится в аналогичном плагинам виде — сжатая при помощи алгоритма QuickLZ и зашифрованная тем же алгоритмом, что и плагины. Под расшифрованную и распакованную конфигурацию резервируется 0x858 байт. Ее структуру можно представить следующим образом:
struct config
{
WORD off_id; //lpBvQbt7iYZE2YcwN
WORD offset_1; //Messenger
WORD off_bin_path; //%ALLUSERSPROFILE%\Messenger\msmsgs.exe
WORD off_svc_name; //Messenger
WORD off_svc_display_name; //Messenger
WORD off_svc_description; //Messenger
WORD off_reg_key_install; //SOFTWARE\Microsoft\Windows\CurrentVersion\Run
WORD off_reg_value_name; //Messenger
WORD off_inject_target_1; //%windir%\system32\svchost.exe
WORD off_inject_target_2; //%windir%\system32\winlogon.exe
WORD off_inject_target_3; //%windir%\system32\taskhost.exe
WORD off_inject_target_4; //%windir%\system32\svchost.exe
WORD off_srv_0; //HTTP://www.pneword.net:80
WORD off_srv_1; //HTTP://www.pneword.net:443
WORD off_srv_2; //HTTP://www.pneword.net:53
WORD off_srv_3; //UDP://www.pneword.net:53
WORD off_srv_4; //UDP://www.pneword.net:80
WORD off_srv_5; //UDP://www.pneword.net:443
WORD off_srv_6; //TCP://www.pneword.net:53
WORD off_srv_7; //TCP://www.pneword.net:80
WORD off_srv_8; //TCP://www.pneword.net:443
WORD zero_2A;
WORD zero_2C;
WORD zero_2E;
WORD zero_30;
WORD zero_32;
WORD zero_34;
WORD zero_36;
WORD off_proxy_1; //HTTP\n\n\n\n\n
WORD off_proxy_2; //HTTP\n\n\n\n\n
WORD off_proxy_3; //HTTP\n\n\n\n\n
WORD off_proxy_4; //HTTP\n\n\n\n\n
DWORD DNS_1; //8.8.8.8
DWORD DNS_2; //8.8.8.8
DWORD DNS_3; //8.8.8.8
DWORD DNS_4; //8.8.8.8
DWORD timeout_multiplier; //0x0A
DWORD field_54; //zero
//data
};
Поля с именами off_* содержат смещения к зашифрованным строкам от начала конфигурации. Алгоритм шифрования строк тот же, что используется для шифрования имен плагинов. После инициализации бэкдор также пытается получить конфигурацию из файла, расположенного в директории %ALLUSERSPROFILE%\<rnd1>\<rnd2>\<rnd3>\<rnd4>. Элементы пути и имя файла генерируются в процессе выполнения и зависят от серийного номера системного раздела.
После инициализации конфигурации проверяется параметр mode, хранящийся в структуре shellarg, которая заполняется загрузчиком (шелл-кодом) и хранится в модуле stage_1.
struct shellarg
{
module_header *p_module_header;
DWORD module_size;
DWORD mode;
DWORD unk;
}
Алгоритмом предусмотрен ряд возможных значений параметра mode — 2, 3, 4, 5, 6, 7. При значении, отличном от перечисленных, запускается установка бэкдора в систему, затем происходит переход к выполнению основных функций.
Ряд значений 2, 3 ,4 — переход к взаимодействию с сервером, минуя установку.
Ряд значений 5, 6 — работа с плагином, с кодом 0x6A, хранящимся в реестре.
Значение 7 — с помощью интерфейса IFileOperation исходный модуль копируется в %TEMP%, а также в System32 или SysWOW64, в зависимости от разрядности системы. Это необходимо для перезапуска бэкдора с обходом UAC с помощью файла wusa.exe.
Установка в систему
При установке бэкдор проверяет текущий путь исполняемого файла, сравнивая его со значением off_bin_path из конфигурации (%ALLUSERSPROFILE%\Messenger\msmsgs.exe). Если путь не совпадает и при этом бэкдор запущен впервые, то создается мьютекс, имя которого генерируется следующим образом:
Формат имени мьютекса для wsprintfW — Global\%d%d%d.
Затем проверяет, включен ли контроль учетных записей UAC. Если контроль отключен, то бэкдор создает процесс control.exe (из System32 или SysWOW64, в зависимости от разрядности системы) с флагом CREATE_SUSPENDED. Затем с помощью WriteProcessMemory внедряет в него модуль Root. Перед этим бэкдор также внедряет функцию, которая загрузит модуль и передаст на него управление. Если же UAC включен, то этот этап пропускается.
Основной исполняемый файл (msmsgs.exe) и файл TosBtKbd.dll копируются в директорию, заданную в параметре off_bin_path и затем устанавливаются как служба. Имя службы, отображаемое имя и описание содержатся в конфигурации (параметры off_svc_name, off_svc_display_name, off_svc_description). В рассмотренном образце все три параметра имеют значение Messenger. Если создать службу не удалось, бэкдор прописывается в реестре. Ключ и имя параметра для данного случая также хранятся в конфигурации (параметры off_reg_key_install, off_reg_value_name).
После установки бэкдор пытается выполнить инжект модуля Root в один из процессов указанных в конфигурации (off_inject_target_<1..4>). В случае успеха текущий процесс завершается, а новый процесс (или служба) переходит к взаимодействию с управляющим сервером.
Для этого создается отдельный поток. После этого создается новый или открывается существующий ключ реестра, используемый в качестве виртуальной файловой системы вредоносной программы. Ключ располагается в ветке реестра Software\Microsoft\<key>, при этом значение <key> тоже генерируется в зависимости от серийного номера системного тома. Ключ также может располагаться в разделах HKLM и HKCU, в зависимости от привилегий процесса. Далее с помощью функции RegNotifyChangeKey отслеживаются изменения в этом ключе. Каждый параметр является сжатым и зашифрованным плагином. Бэкдор извлекает каждое значение и загружает его как модуль, добавляя в список к имеющимся.
Представленная функциональность выполняется в отдельном потоке.
На следующем шаге генерируется псевдослучайная последовательность длиной от 3 до 9 байтов, которая записывается в реестр в ключе SOFTWARE\, расположенном в разделах HKLM или HKCU. Имя параметра также генерируется и является уникальным для каждого компьютера. Это значение используется в качестве идентификатора зараженного устройства.
Далее бэкдор извлекает из конфигурации адрес первого управляющего сервера. Формат хранения серверов представлен следующим образом: <protocol>://<address>:<port>. Помимо значений, явно определяющих используемый протокол (HTTP, TCP, UDP), может также указываться значение URL. В этом случае бэкдор обращается по этому URL и в ответ получает новый адрес управляющего сервера, при этом используется алгоритм генерации домена (DGA). С помощью алгоритма генерируется строка:
wstr *__stdcall dga(wstr *p_wstr)
{
unsigned int v1; // ecx
unsigned int v2; // edi
unsigned int v3; // esi
unsigned int v4; // edx
char v5; // dl
wstr *v6; // eax
wstr *v7; // esi
wstr tmp_str; // [esp+10h] [ebp-34h] BYREF
char generated_char_str[16]; // [esp+20h] [ebp-24h] BYREF
struct _SYSTEMTIME SystemTime; // [esp+30h] [ebp-14h] BYREF
GetSystemTime_0(&SystemTime);
if ( SystemTime.wDay > 0xAu )
{
if ( SystemTime.wDay > 0x14u )
v1 = 0xE52F65F3 * SystemTime.wYear - 0x2527D2DD * SystemTime.wMonth - 0x4BA7EAF5;
else
v1 = 0xF108D240 * SystemTime.wMonth - 0x78C6249D * SystemTime.wYear - 0x17AB943D;
}
else
{
v1 = 0xF5D6C030 * SystemTime.wMonth - 0x5FBD1755 * SystemTime.wYear - 0x5540E1B0;
}
v2 = 0;
v3 = v1 % 7;
do
{
v4 = v1 % 0x34;
if ( v1 % 0x34 >= 0x1A )
v5 = v4 + 39;
else
v5 = v4 + 97;
v1 = 13 * v1 + 7;
generated_char_str[v2++] = v5;
}
while ( v2 <= v3 + 7 );
generated_char_str[v3 + 8] = 0;
v6 = wstr::assign_char_str_pl2(&tmp_str, generated_char_str);
v7 = (wstr *)wstr::init_by_wchar_pl2(p_wstr, (LPCWSTR)v6->buffer_wchar);
wstr::clean_pl2(&tmp_str);
return v7;
}
Полученная строка объединяется со строкой, хранимой в конфигурации, при этом используется часть до символа @. По полученному URL выполняется HTTP-запрос, в ответ на который приходит закодированный адрес управляющего сервера.
Затем создается объект соединения, соответствующий указанному для данного сервера протоколу.
TCP
При подключении по TCP имеется поддержка протоколов SOCKS4, SOCKS5 и HTTP-прокси. Вначале создается сокет и устанавливается соединение с сервером в режиме keep-alive. Для обмена с сервером используется пакет с заголовком следующего формата:
struct packet_header
{
DWORD key;
DWORD id;
DWORD module_code;
DWORD compressed_len;
DWORD decompressed_len;
};
HTTP
При использовании протокола HTTP данные отправляются POST-запросом:
Передача данных по HTTP выполняется функцией-обработчиком в отдельном потоке. Механизм похож на таковой у BackDoor.PlugX.
Для разрешения адресов управляющих серверов используются DNS-серверы из конфигурации (в рассмотренном образце все 4 адреса - 8.8.8.8). Первый пакет, отправляемый на сервер, представляет собой последовательность нулей длиной от 0 до 0x3f байт. Длина выбирается случайным образом.
От сервера поступает ответ, который расшифровывается и распаковывается. Затем в заголовке пакета проверяется значение module_code, содержащее код плагина, для которого поступила команда. Бэкдор обращается к плагину, код которого указан в команде, и вызывает функцию обработки команд из его таблицы. Идентификатор самой команды содержится в поле id заголовка.
Pабота с плагинами
Идентификаторы команд для модуля Plugins могут иметь следующие значения id — 0x650000, 0x650001, 0x650002, 0x650003, 0x650004. По сути модуль Plugins является менеджером плагинов, позволяя регистрировать новые плагины и удалять имеющиеся.
ID Команды | Описание | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x650003 | Удаляет заданный плагин из хранилища в реестре. | ||||||||||||||||||||
0x650000 | Отправляет информацию об имеющихся плагинах.
|
||||||||||||||||||||
0x650001 | В теле команды содержится новый плагин. Формат плагина такой же, как и у встроенных. Бэкдор упаковывает его алгоритмом QuickLZ, шифрует и сохраняет в хранилище в реестре, после чего приостанавливает текущий поток, чтобы поток обработки плагинов загрузил новый плагин из хранилища в реестре. | ||||||||||||||||||||
0x650002 | В команде содержится имя DLL, которую бэкдор пытается загрузить, после чего последовательно вызывает ее точку входа с dwReason 0x64, 0x66, 0x68. | ||||||||||||||||||||
0x650004 | В команде содержится код модуля. Если плагин с заданным кодом присутствует в списке, бэкдор деинициализирует его. |
Online
Идентификаторы команд для плагина Online могут иметь значения 0x680002, 0x680003, 0x680004, 0x680005.
ID Команды | Описание |
---|---|
0x680002 | Запускает в отдельном потоке обработку команд для плагинов с инициализацией нового подключения к текущему серверу. |
0x680003 | Отправляет информацию о системе. Можно представить в виде структуры:
Значение id — уникальный идентификатор зараженного компьютера, хранимый в реестре. Стоит заметить, что значения полей datestamp1 и datestamp2 зашиты и равны 20150810 и 20170330 соответственно. Подобные константы в виде дат использовались также в плагинах бэкдоров PlugX. |
0x680004 | Отправляет пакетом с телом случайной длины (от 0 до 0x1F байт). Тело пакета заполнено нулями. |
0x680005 | Отправляет пустой пакет (только заголовок) после чего 3 раза подряд вызывает Sleep(1000). |
Config
Плагин для работы с конфигурацией.
ID Команды | Описание |
---|---|
0x660000 | Отправляет на сервер текущую конфигурацию. |
0x660001 | Получает и применяет новую конфигурацию. |
0x660002 | Удаляет файл с сохраненной конфигурацией. |
Install
ID Команды | Описание |
---|---|
0x670000 | Выполняет установку бэкдора в качестве службы или устанавливает в реестр. |
0x670001 | Трижды вызывает Sleep(1000), после чего проверяет параметр shellarg.mode, если его значение равно 4, то завершает текущий процесс. |
Артефакты
В исторической WHOIS-записи домена управляющего сервера можно увидеть электронный адрес регистратора: ddggcc@189[.]cn.
Этот же адрес находится в записях доменов icefirebest[.]com и www[.]arestc[.]net, которые содержались в конфигурациях образцов бэкдоров PlugX, установленных на том же компьютере.
Domain Name: ICEFIREBEST.COM
Registry Domain ID: 2042439159_DOMAIN_COM-VRSN
Registrar WHOIS Server: whois.1api.net
Registrar URL: http://www.1api.net
Updated Date: 2016-07-28T16:55:13Z
Creation Date: 2016-07-13T01:39:31Z
Registrar Registration Expiration Date: 2017-07-13T01:39:31Z
Registrar: 1API GmbH
Registrar IANA ID: 1387
Registrar Abuse Contact Email: abuse@1api.net
Registrar Abuse Contact Phone: +49.68416984x200
Domain Status: ok - http://www.icann.org/epp#OK
Registry Registrant ID:
Registrant Name: edward davis
Registrant Organization: Edward Davis
Registrant Street: Tianhe District Sports West Road 111
Registrant City: HONG KONG
Registrant State/Province: Hongkong
Registrant Postal Code: 510000
Registrant Country: HK
Registrant Phone: +86.2029171680
Registrant Phone Ext:
Registrant Fax: +86.2029171680
Registrant Fax Ext:
Registrant Email: ddggcc@189.cn
Registry Admin ID:
Admin Name: edward davis
Admin Organization: Edward Davis
Admin Street: Tianhe District Sports West Road 111
Admin City: HONG KONG
Admin State/Province: Hongkong
Admin Postal Code: 510000
Admin Country: HK
Admin Phone: +86.2029171680
Admin Phone Ext:
Admin Fax: +86.2029171680
Admin Fax Ext:
Admin Email: ddggcc@189.cn
Registry Tech ID:
Tech Name: edward davis
Tech Organization: Edward Davis
Tech Street: Tianhe District Sports West Road 111
Tech City: HONG KONG
Tech State/Province: Hongkong
Tech Postal Code: 510000
Tech Country: HK
Tech Phone: +86.2029171680
Tech Phone Ext:
Tech Fax: +86.2029171680
Tech Fax Ext:
Tech Email: ddggcc@189.cn
Name Server: ns1.ispapi.net 194.50.187.134
Name Server: ns2.ispapi.net 194.0.182.1
Name Server: ns3.ispapi.net 193.227.117.124
DNSSEC: unsigned
URL of the ICANN WHOIS Data Problem Reporting System:
http://wdprs[.]internic[.]net/
Domain Name: ARESTC.NET
Registry Domain ID: 2196389400_DOMAIN_NET-VRSN
Registrar WHOIS Server: whois.1api.net
Registrar URL: http://www.1api.net
Updated Date: 2017-12-06T08:43:04Z
Creation Date: 2017-12-06T08:43:04Z
Registrar Registration Expiration Date: 2018-12-06T08:43:04Z
Registrar: 1API GmbH
Registrar IANA ID: 1387
Registrar Abuse Contact Email: abuse@1api.net
Registrar Abuse Contact Phone: +49.68416984x200
Domain Status: ok - http://www.icann.org/epp#OK
Registry Registrant ID:
Registrant Name: li yiyi
Registrant Organization: li yiyi
Registrant Street: Tianhe District Sports West Road 111
Registrant City: GuangZhou
Registrant State/Province: Guangdong
Registrant Postal Code: 510000
Registrant Country: CN
Registrant Phone: +86.2029179999
Registrant Phone Ext:
Registrant Fax: +86.2029179999
Registrant Fax Ext:
Registrant Email: ddggcc@189.cn
Registry Admin ID:
Admin Name: li yiyi
Admin Organization: li yiyi
Admin Street: Tianhe District Sports West Road 111
Admin City: GuangZhou
Admin State/Province: Guangdong
Admin Postal Code: 510000
Admin Country: CN
Admin Phone: +86.2029179999
Admin Phone Ext:
Admin Fax: +86.2029179999
Admin Fax Ext:
Admin Email: ddggcc@189.cn
Registry Tech ID:
Tech Name: li yiyi
Tech Organization: li yiyi
Tech Street: Tianhe District Sports West Road 111
Tech City: GuangZhou
Tech State/Province: Guangdong
Tech Postal Code: 510000
Tech Country: CN
Tech Phone: +86.2029179999
Tech Phone Ext:
Tech Fax: +86.2029179999
Tech Fax Ext:
Tech Email: ddggcc@189.cn
Name Server: ns1.ispapi.net 194.50.187.134
Name Server: ns2.ispapi.net 194.0.182.1
Name Server: ns3.ispapi.net 193.227.117.124
DNSSEC: unsigned
URL of the ICANN WHOIS Data Problem Reporting System:
http://wdprs[.]internic[.]net/