Упаковщик: отсутствует
Дата компиляции: 16:43:21 02.02.2015
SHA1-хеш:
- f6bf976a2fdef5a5a44c60cbfb0c8fcbdc0bae02
Описание
Многокомпонентный троян-бэкдор, написанный на языке С++ и предназначенный для работы в 64-разрядных операционных системах семейства Microsoft Windows. Устанавливается при помощи модуля-загрузчика, после чего функционирует в оперативной памяти зараженного компьютера. Используется в целевых атаках на информационные системы для несанкционированного доступа к данным и их передачи на управляющие серверы. Характерной особенностью является наличие плагинов, с помощью которых реализована основная функциональность бэкдора.
Принцип действия
Загружается посредством BackDoor.PlugX.27. Соглашения о вызовах разнятся от функции к функции и зачастую являются нестандартными — передача аргументов выполняется через произвольные регистры и/или через стек, что может говорить о компиляции вредоносной программы с опцией оптимизации.
Для работы определяется и используется множество различных объектов. Для передачи информации используются абстрактные объекты, которые просто реализуют интерфейс (прием/передача). Таким образом, функция не привязана к внутренней реализации объекта соединения, будь то TCP-сокет, RAW-сокет, HTTP-соединение или пайп. Класс объекта может явно определяться в коде, а также задаваться в конфигурации типом сервера или полученными данными от известных серверов.
Практически все строки в коде трояна зашифрованы. Скрипт дешифровки:
import idaapi
import struct
def dec(d):
s = ''
k = struct.unpack('<I', d[:4])[0]
d = d[4:]
a = k ^ 0x13377BA
b = k ^ 0x1B1
for i in range(len(d)):
a = (a + 4337) & 0xffffffff
b = (b - 28867) & 0xffffffff
a0 = (a & 0x000000ff)
a1 = (a & 0x0000ff00) >> 8
a2 = (a & 0x00ff0000) >> 16
a3 = (a & 0xff000000) >> 24
b0 = (b & 0x000000ff)
b1 = (b & 0x0000ff00) >> 8
b2 = (b & 0x00ff0000) >> 16
b3 = (b & 0xff000000) >> 24
s += chr(ord(d[i]) ^ (((b2 ^ (((b0 ^ (((a2 ^ ((a0 - a1)&0xff)) - a3)&0xff)) - b1)&0xff)) - b3) & 0xff))
return s
def decrypt(addr, size):
d = ''
for i in range(size):
d += chr(idaapi.get_byte(addr + i))
s = dec(d)
print s
for i in range(len(s)):
idaapi.patch_byte(addr + i, ord(s[i]))
idaapi.patch_dword(addr + i + 1, 0)
Подготовка к работе
BackDoor.PlugX28 может получить конфигурацию несколькими способами. Загрузчик передает аргумент, представляющий собой указатель на структуру shellarg:
#pragma pack(push,1)
struct st_data
{
_DWORD size;
_BYTE *data;
};
struct shellarg
{
_DWORD shellcode_ep;
_DWORD field_4;
st_data* mod;
_DWORD comp_size;
st_data* cfg;
_DWORD field_14;
_DWORD op_mode;
};
#pragma pack(pop)
Затем проверяется значение по указателю shellarg->cfg:
- если первые 8 байтов по данному адресу равны XXXXXXXX, то бэкдор самостоятельно подготавливает так называемую базовую конфигурацию, использующуюся по умолчанию;
- в противном случае используется расшифрованная и разжатая конфигурация, полученная от загрузчика.
Во втором варианте также выполняется проверка наличия конфигурационного файла в домашней (указывается в конфигурации) директории трояна. Имя файла конфигурации, как и многие другие имена файлов, генерируются по определенному алгоритму:
int __usercall gen_string@<eax>(DWORD seed@<eax>, s *result, LPCWSTR base)
{
DWORD v3; // edi
DWORD v4; // eax
signed int v5; // ecx
signed int i; // edi
DWORD v7; // eax
WCHAR Buffer; // [esp+10h] [ebp-250h]
__int16 v10; // [esp+16h] [ebp-24Ah]
__int16 name[34]; // [esp+210h] [ebp-50h]
DWORD FileSystemFlags; // [esp+254h] [ebp-Ch]
DWORD MaximumComponentLength; // [esp+258h] [ebp-8h]
DWORD serial; // [esp+25Ch] [ebp-4h]
v3 = a1;
GetSystemDirectoryW(&Buffer, 0x200u);
v10 = 0;
if ( GetVolumeInformationW(
&Buffer,
&Buffer,
0x200u,
&serial,
&MaximumComponentLength,
&FileSystemFlags,
&Buffer,
0x200u) )
{
v4 = 0;
}
else
{
v4 = GetLastError();
}
if ( v4 )
serial = v3;
else
serial ^= v3;
v5 = (serial & 0xF) + 3;
for ( i = 0; i < v5; serial = 8 * (v7 - (serial >> 3) + 20140121) - ((v7 - (serial >> 3) + 20140121) >> 7) - 20140121 )
{
v7 = serial << 7;
name[i++] = serial % 0x1A + 'a';
}
name[v5] = 0;
string::wcopy(a2, base);
string::wconcat(a2, (LPCWSTR)name);
return 0;
}
Инициализирующим значением для имени конфигурационного файла служит значение 0x4358 ("CX").
В путях для файлов и директорий (в том числе домашней) может использоваться переменная %AUTO%, которая в зависимости от версии ОС преобразуется в:
- "<drive>:\\Documents and Settings\\All Users\\DRM" — для ОС Windows 2000, Windows XP;
- "<drive>:\\Documents and Settings\\All Users\\Application Data" — для ОС Windows Server 2003;
- "<drive>:\\ProgramData" — для более поздних версий ОС Windows.
При этом <drive> берется из пути до каталога Windows.
Полученная конфигурация представляет собой следующую структуру ("st_config"):
struct config_timestamp
{
BYTE days;
BYTE hours;
BYTE minutes;
BYTE seconds;
};
struct srv
{
WORD type;
WORD port;
BYTE address[64];
};
struct proxy_info
{
WORD type;
WORD port;
BYTE serv_addr_str[64];
BYTE username_str[64];
BYTE password[64];
};
struct st_config
{
DWORD hide_service;
DWORD gap_0[4];
DWORD cleanup_files_flag;
DWORD keylog_log_event;
DWORD dword_0;
DWORD skip_persistence;
DWORD dword_1;
DWORD sleep_timeout;
config_timestamp timestamp;
BYTE timetable[672];
DWORD DNS_1;
DWORD DNS_2;
DWORD DNS_3;
DWORD DNS_4;
srv srv1_basic_type3;
srv srv2_basic_type;
srv srv3_basic_type_6;
srv srv3_basic_type_7;
srv srv5_basic_type_8;
srv srv6_basic_type_5;
srv srv7;
srv srv8;
srv srv9;
srv srv10;
srv srv11;
srv srv12;
srv srv13;
srv srv14;
srv srv15;
srv srv16;
BYTE url_1[128];
BYTE url_2[128];
BYTE url_3[128];
BYTE url_4[128];
BYTE url_5[128];
BYTE url_6[128];
BYTE url_7[128];
BYTE url_8[128];
BYTE url_9[128];
BYTE url_10[128];
BYTE url_11[128];
BYTE url_12[128];
BYTE url_13[128];
BYTE url_14[128];
BYTE url_15[128];
BYTE url_16[128];
proxy_info proxy_data_1;
proxy_info proxy_data_2;
proxy_info proxy_data_3;
proxy_info proxy_data_4;
DWORD persist_mode;
DWORD env_var_AUTO_X[128];
DWORD service_name[128];
DWORD ServiceDisplayName[128];
BYTE ServiceDescription[512];
DWORD reg_predefined_key;
BYTE reg_sub_key[512];
BYTE reg_value_name[512];
DWORD process_injection_flag;
BYTE inject_target_dummy_proc_1[512];
BYTE inject_target_dummy_proc_2[512];
BYTE inject_target_dummy_proc_3[512];
BYTE inject_target_dummy_proc_4[512];
DWORD elevated_process_injection_flag;
BYTE elevated_inject_target_dummy_proc_1[512];
BYTE elevated_inject_target_dummy_proc_2[512];
BYTE elevated_inject_target_dummy_proc_3[512];
BYTE elevated_inject_target_dummy_proc_4[512];
BYTE campaign_id[512];
BYTE str_X_4[512];
BYTE mutex_name[512];
DWORD make_screenshot_flag;
DWORD make_screenshot_time_interval;
DWORD screen_scale_coefficient;
DWORD bits_per_pixel;
DWORD encoder_quality;
DWORD screen_age;
BYTE screenshots_path[512];
DWORD subnet_scan_flag_mb;
DWORD port_54D_1;
DWORD raw_socket_subnet_flag;
DWORD port_54D_2;
DWORD type_7_subnet_flag;
DWORD portl_54D_3;
DWORD flag_1_6;
DWORD port_54D_4;
DWORD flag_1_7;
DWORD first_IP_range_addr_beg;
DWORD first_IP_range_addr_end;
DWORD first_IP_range_addr_beg_2;
DWORD first_IP_range_addr_beg_3;
DWORD last_IP_range_addr_beg;
DWORD last_IP_range_addr_end;
DWORD last_IP_range_addr_beg_2;
DWORD last_IP_range_addr_beg_3;
BYTE mac_addr[6];
BYTE gap_2[2];
} config;
В случае использования конфигурации по умолчанию значения некоторых полей следующие:
- домашний каталог трояна: %AUTO%\\X;
- имя службы: X;
- отображаемое имя службы: X;
- описание службы: X;
- имя процесса для запуска и внедрения шелл-кода: %windir%\\system32\\svchost.exe;
- имя процесса для запуска с привилегиями администратора и внедрения шелл-кода: %windir%\\system32\\svchost.exe;
- каталог хранения скриншотов экрана: %AUTO%\\XS;
- имя мьютекса: X.
Выполнение
Получает привилегии SeDebugPrivilege и SeTcbPrivilege, после чего запускает отдельный поток, в котором будут происходить дальнейшие действия.
Проверяет наличие у зараженного компьютера сетевого адаптера с зашитым в трояна MAC-адресом. Если адаптер присутствует, троян завершает свою работу (в исследованном образце адрес не задан).
Проверяет значение shellarg->op_mode. Список возможных значений:
- больше 4 — проверка закрепления в системе, далее — основной режим работы;
- 4 — перехват функции WinInet.dll и выход;
- 3 — внедрение в процесс Internet Explorer и выход;
- 2 — основной режим работы без проверки закрепления.
Проверка и закрепление в системе (op_mode>4)
В конфигурации проверяется флаг config.skip_persistence и, если он установлен, то этапы проверки пути, создания мьютексов и закрепления пропускаются, и управление переходит к инициализации службы.
Затем проверяет путь, откуда запущен текущий процесс. Если он совпадает с %AUTO%\\EHSrv.exe, то пропускает фазу установки в систему.
Создает два мьютекса вида Global\\<rndname>. Для имени первого мьютекса инициализирующим значением является PID текущего процесса, а для второго — PID родительского процесса. Далее — переход к установке в систему.
В конфигурации проверяется параметр config.persist_mode, определяющий вариант закрепления:
- 0, 1 — установка службы;
- 2 — создание значения в реестре.
В обоих случаях копирует в домашнюю директорию (%AUTO%\\X в конфигурации по умолчанию) свои файлы http_dll.dll и EHSrv.exe, а также сохраняет зашифрованный шелл-код в ключи реестра [HKLM\\SOFTWARE\\BINARY] 'ESETSrv' и [HKCU\\SOFTWARE\\BINARY] 'ESETSrv'. Подделывает временные атрибуты файлов, меняя их на атрибуты системного файла ntdll.dll.
Для установки службы троян создает и запускает службу типа SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS с автоматическим запуском. Имя, отображаемое имя, а также описание службы берутся из конфигурации (параметры config.service_name, config.ServiceDisplayName, config.ServiceDescription). Параметр lpBinaryPathName в функции CreateService задается как <path>\EHSrv.exe -app.
Для записи в реестре создает значение <path>\EHSrv.exe -app в ключе реестра. Дескриптор, имя ключа и значения заданы в конфигурации:
Затем троян запускает новый процесс.
После этого происходит инициализация службы путем вызова функции StartServiceCtrlDispatcherW.
В конфигурации проверяется флаг config.process_injection_flag. Если флаг установлен, то из конфигурации извлекается путь до файла процесса, в который будет внедряться шелл-код. Имя может содержать переменные окружения. Предусмотрено до четырех имен, каждое из которых проверяется последовательно до первого ненулевого, после чего создается процесс с флагом CREATE_SUSPENDED, в который внедряется шелл-код.
В случае успешного внедрения процесс завершается, в противном случае управление переходит на основной функционал.
Установка хуков "Wininet.dll" (op_mode==4)
Предположительно, данный режим предусмотрен после внедрения в процесс Internet Explorer.
Хуки устанавливаются на функции:
HttpSendRequestA
HttpSendRequestW
HttpSendRequestExA
HttpSendRequestExW
Их назначение состоит в перехвате HTTP-запросов для извлечения логинов и паролей для подключения к прокси-серверу. Полученные реквизиты записываются в структуру:
struct proxy_info
{
WORD type;
WORD port;
BYTE serv_addr_str[64];
BYTE username_str[64];
BYTE password[64];
};
где параметр type может принимать следующие значения:
- 1 — socks4
- 2 — socks5
- 3 — http
- 5 — https
- 6 — ftp
В формате этой структуры данные сохраняются в глобальной переменной и в файле в каталоге бэкдора. Имя файла генерируется с инициализирующим значением 0x485A ("HZ").
Внедрение в процесс IE (op_mode==3)
BackDoor.PlugX28 внедряет шелл-код через функцию CreateRemoteThread в процесс IE. Поиск процесса выполняется через поиск окна IEFrame. Перед этим происходит инициализация плагинов бэкдора. Генерируется имя пайпа \\.\PIPE\<rndname>. Инициализатором для <rndname> является PID текущего процесса. Затем происходит повторная инициализация конфигурации.
Создается объект, реализующий интерфейс асинхронного взаимодействия с пайпом, затем создается и инициализируется сам пайп. В отдельном потоке создается обработчик, который в качестве параметра принимает указатель на экземпляр объекта подключения к пайпу. Обработчик через интерфейс объекта читает команды, предназначенные для выполнения плагинами. Результаты их исполнения возвращает в пайп.
Основной фукнционал (op_mode==2 или op_mode==4 после закрепления)
Проверяет наличие административных привилегий у пользователя, от имени которого запущен текущий процесс. Затем проверяет в конфигурации флаг config.hide_service. Если флаг установлен, и пользователь не является локальным администратором, то ищет %WINDIR%\SYSTEM32\SERVICES.EXE среди запущенных процессов, после чего перечисляет его модули. Обращается к первому модулю в списке, читает адрес первой секции и копирует ее в свой буфер. Затем ищет в этом буфере последовательность инструкций, которую можно представить в виде следующего регулярного выражения:
\xA3.{4}\xA3.{4}\xA3.{4}\xA3.{4}\xE8
Это соответствует последовательности в функции ScInitDatabase()
Далее считывается адрес ServiceDatabase — связный список структур, описывающих работающие службы. Затем ищет запись, соответствующую имени службы бэкдора, и «удаляет» ее, изменяя указатели предыдущей и следующей записи в списке.
После сокрытия службы создается мьютекс с именем, указанным в конфигурации в параметре config.mutex_name.
Создает RAW-сокет, прослушивающий все IP-пакеты на локальном хосте. Разбирает входящие пакеты, выбирает из них TCP, а затем проверяет на соответствие пакетам протоколов SOCKS4, SOCKS5 и HTTP. Затем извлекает из них реквизиты прокси-серверов, после чего формирует структуры proxy_info и сохраняет их в файл, аналогично алгоритму перехвата функций Wininet.
В конфигурации проверяется флаг config.elevated_process_injection_flag. Если флаг установлен, то запускается поток, в котором последовательно перебираются запущенные процессы в поисках запущенного от имени локального администратора. У этого процесса клонируется маркер доступа, и дубликату маркера присваивается HighIntegrity класс. Затем создается блок окружения от имени локального администратора, который используется для создания процесса с маркером доступа класса HighIntegrity. Имя процесса также извлекается из параметра конфигурации config.elevated_inject_target_dummy_proc_<n>", n 1..4. Проверяются все четыре варианта до первого ненулевого. Процесс создается с флагом CREATE_SUSPENDED, и в него внедряется шелл-код. Информация о каждом процессе сохраняется в специальной структуре:
struct injected_proc
{
DWORD session_ID;
DWORD pid;
DWORD hProcess;
BYTE admin_account_name[40];
};
struct injected_elevated__procs
{
injected_proc injected_process_info[32];
DWORD hThread;
DWORD hEvent;
};
Далее инициализируются плагины, запускается цикл приема и обработки команд от управляющего сервера. Перед подключением к серверу извлекаются и сохраняются глобальные настройки прокси, используемые функциями WIninet, и записанные в конфигурации браузера Firefox.
Следует отметить, что серверы хранятся в конфигурации в виде структур:
struct srv
{
WORD type;
WORD port;
BYTE address[64];
};
Для взаимодействия с сервером создается объект соединения, тип которого определяется значением srv.type. Возможные типы соединений:
1, 2 — pipe-соединение;
3 — TCP-сокет (подразумевает возможность использования SOCKS4, SOCKS5, HTTP-прокси);
4 — HTTP-соединение;
5 — открытие UDP-сокета для прослушивания, работа в режиме DNS-сервера;
6, 7, 8 — RAW-сокет.
Поддерживаемые типы протоколов позволяют взять на себя функции сервера как удаленной машине, так и другому процессу с использованием пайпа.
Первая попытка подключения к управляющему серверу из конфигурации выполняется без использования прокси. В случае неудачи используются данные прокси, которые хранятся в глобальной переменной. Конфигурация предусматривает слоты для 16 серверов. Если ни к одному из них не удалось подключиться, то из поля config.url_1 извлекается URL, который сначала разбирается на составляющие (такие как хост, URI, параметры), а затем по этим данным формируется и отправляется GET-запрос. В теле ответа на этот запрос выполняется поиск закодированной строки, заключенной между тегами DZKS и DZJS. После декодирования строка представляет собой тип, порт и адрес управляющего сервера в виде структуры srv. Предусмотрено до 16 таких URL-указателей, при помощи которых можно получить новый адрес управляющего сервера.
Алгоритм декодирования адреса управляющего сервера:
int __usercall find_and_decode_string@(BYTE *decoded_response@, BYTE *response_data@, int response_data_len@)
{
int v3; // edx
int i; // ecx
int v6; // esi
int j; // edi
int v8; // ecx
char v9; // dl
BYTE *v10; // edx
int v11; // eax
int v12; // esi
v3 = response_data_len - 4;
for ( i = 0; i < v3; ++i )
{
if ( response_data[i] == 'D'
&& response_data[i + 1] == 'Z'
&& response_data[i + 2] == 'K'
&& response_data[i + 3] == 'S' )
{
break;
}
}
if ( i >= v3 )
return 1168;
v6 = i + 4;
for ( j = i + 4;
j < v3
&& (response_data[j] != 'D'
|| response_data[j + 1] != 'Z'
|| response_data[j + 2] != 'J'
|| response_data[j + 3] != 'S');
++j )
{
;
}
if ( j > v3 )
return 1168;
v8 = 0;
while ( v6 < j )
{
v9 = response_data[v6] + 16 * (response_data[v6 + 1] - 65);
response_data[v8 + 1] = 0;
response_data[v8++] = v9 - 65;
v6 += 2;
}
*(_WORD *)decoded_response = *response_data + (response_data[1] << 8);
*((_WORD *)decoded_response + 1) = response_data[2] + (response_data[3] << 8);
if ( v8 > 0 )
{
v10 = decoded_response + 4;
v11 = response_data - decoded_response;
v12 = v8;
do
{
*v10 = v10[v11];
++v10;
--v12;
}
while ( v12 );
}
return 0;
}
Первичное обращение к управляющему серверу
BackDoor.PlugX28 получает текущие время и дату при помощи функции GetLocalTime. Затем проверяет день недели: если это воскресенье (0), то в структуре SYSTEMTIME меняет его на субботу (6), в ином случае уменьшает значение на 1. Далее обращается к массиву config.timetable, чтобы проверить значение элемента под индексом, зависящим от текущего времени. Размер массива составляет _BYTE[672]. Каждый элемент представляет собой флаг каждой четверти часа в неделе (24 * 7 * 4 = 672). Если значение элемента ненулевое, то выполнение продолжается, в противном случае троян переходит в режим ожидания.
Проверка флага расписания работы:
while ( 1 )
{
GetLocalTime(&local_time);
if ( local_time.wDayOfWeek )
--local_time.wDayOfWeek;
else
local_time.wDayOfWeek = 6;
if ( config.timetable[4 * (local_time.wHour + 24 * local_time.wDayOfWeek) + local_time.wMinute / 15] )
break;
Sleep(1000);
}
В конфигурации рассмотренного образца все элементы массива равны 1. При подготовке конфигурации по умолчанию массив также заполняется единицами. После проверки расписания троян читает значение поля config.timestamp, определяемого следующим образом:
struct config_timestamp
{
BYTE days;
BYTE hours;
BYTE minutes;
BYTE seconds;
};
Затем переводит его значение в секунды, умножает на 0x10000000 и складывает с текущим значением системного времени в формате FILETIME. Затем проверяет наличие параметра <config.service_name> в ключе реестра HKCU\Software\<config.service_name>. Если параметр существует, то сравнивает его значение с системной временной меткой. Если значение метки больше хранимого, то выполнение продолжается, в противном случае происходит ожидание в течение секунды, после чего производится повторная проверка. Если значение в реестре отсутствует, то вычисленная временная метка помещается в этот параметр и сравнивается с системной временной меткой. В итоге режим ожидания остается активным до тех пор, пока системная временная метка не окажется больше метки, вычисленной в начале проверки, либо метки, хранимой в параметре.
result_ts помещается в реестр при необходимости.
Перемножение значений временных меток представлено на иллюстрации:
После временны́х проверок создается объект соединения, соответствующий типу, указанному для текущего сервера. В случае использования протокола HTTP для взаимодействия с управляющим сервером обработка соединения (прием и передача данных) выполняется в отдельном потоке. На первом этапе устанавливается соединение в режиме keep-alive, и отправляется первый GET-запрос. URL формируется по формату /%p%p%p из трех случайных значений DWORD. Затем готовится структура следующего формата:
struct prefix
{
DWORD unknown;
DWORD sync_ctr;
DWORD conn_state;
DWORD available_buffer_size;
};
Данная структура используется только при передаче по протоколу HTTP и выполняет служебную функцию синхронизации и поддержания соединения. При установке соединения поля структуры заполняются следующими значениями из внутренних полей объекта соединения:
- unknown = 0;
- sync_ctr = 1 — представляет собой некий счетчик, при каждой отправке увеличивающийся на единицу;
- conn_state = 20130923 — представляет собой флаг состояния соединения. В данном случае начальное значение 20130923 используется клиентом для запроса на установку соединения;
- available_buffer_size = 0xF010 — начальный размер внутреннего буфера объекта для хранения входящих данных.
Данная структура шифруется тем же алгоритмом, что используется для шифрования строк. Структура на выходе имеет следующий вид:
struct http_encrypted_data
{
DWORD key;
BYTE data[0x10];
}
После шифрования данные кодируются в Base64 и помещаются в заголовок Cookie:, затем запрос отправляется на управляющий сервер. В теле ответа приходит структура http_encrypted_data, после расшифровки которой получается структура prefix. Проверяется значение prefix.conn_state, которое должно быть равно 20130924. Данное значение может сообщать о готовности сервера к приему данных. Также предусмотрено значение 20130926, которое обозначает окончание соединения.
Проверяется поле prefix.sync_ctr, значение которого должно быть на единицу больше значения prefix.sync_ctr, отправленного клиентом. Такой формат диалога с сервером используется и при отправке реальных данных. Они помещаются после структуры prefix.
После того как соединение с сервером установлено, подготавливается непосредственный запрос команды. Для этого троян генерирует от 0 до 0x1F случайных байт и формирует структуру пакета из заголовка и тела:
struct packet_hdr
{
DWORD key;
DWORD command_id;
DWORD len;
DWORD errc;
};
struct packet
{
packet_hdr header;
BYTE data[61440] //0xF000;
};
Данная структура используется для отправки данных на сервер и обработки команд. Значения полей заголовка ("packet_hdr"):
- "key" — ключ, использованный для шифрования данных;
- "command_id" — идентификатор команды или ответа на команду. При ответе идентификатор не меняется;
- "len" — длина данных без учета заголовка;
- "errc" — код ошибки выполнения команды. В большинстве случаев это поле содержит код ошибки (GetLastError), если не удалось полностью выполнить команду, либо содержит 0, если команда выполнена успешно. В ряде случаев содержит дополнительные параметры для выполнения команды клиентом.
При первой отправке значения command_id и errc приравниваются к 0, а значение len приравнивается к длине случайной последовательности (0-0x1F). В packet.data помещается сама случайная последовательность. Затем данные пакета ("data") сжимаются алгоритмом LZNT1 через RtlCompressBuffer и шифруются алгоритмом шифрования строк со случайным ключом. В поле packet.header.len помещается значение (uncompressed_len << 0x10) | compressed_len, где uncompressed_len и comressed_len содержат размер данных до сжатия и после сжатия соответственно (без учета длины заголовка). Далее шифруется заголовок, и в поле packet.header.key помещается случайный ключ.
Полученные шифрованные данные отправляются на сервер в поле Cookie: HTTP-запроса. Перед данными помещается структура prefix, вся полученная последовательность шифруется, затем кодируется по стандарту Base64 и отправляется в запросе. В ответ приходит команда от сервера. Пакет с командой представляет собой структуру, аналогичную packet, при этом формат data и, в ряде случаев, назначение header.errc может меняться в зависимости от команды.
Обработка команд управляющего сервера
После получения пакет расшифровывается и распаковывается. Троян проверяет значение идентификатора команды packet.header.command_id. Предусмотрены следующие значения команд:
- 1 — сбор и отправка сведений о системе;
- 3 — работа с плагинами;
- 4 — сброс соединения;
- 5 — самоудаление;
- 6 — отправка имеющейся конфигурации на управляющий сервер;
- 7 — получить новую конфигурацию;
- 8 — отправить информацию о процессах с внедренным шелл-кодом;
- 2, 9, 10 —действий не предусмотрено;
- >10 — работа с плагинами.
В поле packet.header.command_id принятых ответов проставляется то же значение, что получено в команде.
Команда 1 (информация о системе)
Троян сравнивает строку в поле packet.data со строкой из конфигурации config.campaign_id (TEST в конфигурации по умолчанию). Если строки равны, то начинается сбор информации о системе, в противном случае возникает ошибка. После этого троян пытается прочитать в своем каталоге файл с именем, которое сгенерировано с инициализатором 0x4343 ("CC"). Если файл существует — его содержимое считывается и кодируется.
Если файла нет, то таковой создается, и в него записывается последовательность из 8 случайных байтов. После чего эта случайная последовательность кодируется таким же образом. Получившаяся закодированная строка будет помещена в ответ на команду. Затем программа выполняет сбор следующей информации:
- имя компьютера;
- имя пользователя;
- частота процессора (из HKLM\HARDWARE\DESCRIPTION\SYSTEM\CENTRALPROCESSOR\0);
- является ли процесс Wow64;
- информация о домене;
- наличие у текущего пользователя привилегий локального администратора;
- IP-адрес компьютера;
- общий объем оперативной памяти в килобайтах;
- информация о версии ОС;
- системное время;
- разрешение экрана;
- региональные настройки;
- количество процессоров.
Ответ с результатами отправляется на сервер в виде структуры:
struct command_1_response
{
packet_hdr header;
sysinfo data;
};
где sysinfo представляет собой структуру с информацией о системе:
struct sysinfo
{
DWORD date_stamp; //20150202
DWORD zero_0;
DWORD self_IP;
DWORD total_PhysMem;
DWORD cpu_MHz;
DWORD screen_width;
DWORD screen_height;
DWORD winserv2003_build_num;
DWORD default_LCID;
DWORD tick_count;
DWORD systeminfo_processor_architecture;
DWORD systeminfo_number_of_processors;
DWORD systeminfo_processor_type;
DWORD zero_1;
DWORD os_MajorVersion;
DWORD os_MinorVersion;
DWORD os_BuildNumber;
DWORD os_PlatformId;
WORD os_ServicePackMajor;
WORD os_ServicePackMinor;
WORD os_SuiteMask;
WORD os_ProductType;
DWORD isWow64Process;
DWORD if_domain;
DWORD if_admin;
DWORD process_run_as_admin;
WORD systime_Year;
WORD systime_Month;
WORD systime_Day;
WORD systime_Hour;
WORD systime_Minute;
WORD systime_Second;
DWORD server_type;
WORD off_CCseed_file_data; //offset from 0
WORD off_compname_string;
WORD off_username_string;
WORD off_verinfo_szCSDVersion;
WORD off_str_X_4_from_config;
BYTE string_CCseed_file_data[16];
.......
//strings
};
Члены структуры off_CCseed_file_data", "off_compname_string", "off_username_string", "off_verinfo_szCSDVersion", "off_str_X_4_from_config" являются смещениями относительно начала структуры sysinfo. off_str_X_4_from_config — смещение до строки, скопированной из config.str_X_4 (в конфигурации по умолчанию — X). Затем подготавливается пакет для отправки информации на сервер. В заголовке указывается идентификатор пакета, равный 1. Затем пакет сжимается, шифруется и отправляется на сервер.
command_id == 3 (работа с плагинами)
При получении пакета с command_id 3 в отдельном потоке запускается обработчик задач для плагинов, в котором создается новое соединение с управляющим сервером. Входящий пакет с командой имеет следующий вид:
struct command_3_packet
{
packet_hdr header;
DWORD dword_0;
DWORD index;
};
Если значение index равно 0xFFFFFFFF, то в этом случае обработка задач для плагинов будет производиться в этом же процессе. В ином случае это значение используется как индекс в массиве структуры injected_elevated_procs. Из массива по заданному индексу получается нужная структура, из нее извлекается идентификатор процесса, который служит в качестве инициализирующего значения для генерации имени пайпа. Затем создается объект соединения с пайпом, реализующий интерфейс пересылки команд для плагинов. Эти команды будут исполняться в контексте другого процесса (например, Internet Explorer), внедрение в который будет выполнено при "shellarg.op_mode" == 3, или же в контексте одного из процессов, заданных в конфигурации и запускаемых с повышенными привилегиями ("config.elevated_inject_target_dummy_proc_<n>", n 1..4). После инициализации пайпа на сервер отправляется ответ, который представляет собой тот же пакет, что и команда. После этого запускается пересылка пакетов, содержащих задачи для плагинов, между двумя объектами — HTTP-соединением и пайпом.
Если значение index задано как 0xFFFFFFFF, то полученный пакет отправляется обратно, и выполняется запуск цикла обработки задач для плагинов.
command_id == 4 — сброс соединения. Никаких специальных действий не совершается; производится выход из цикла обработки команд для дальнейшего подключения к другим серверам.
command_id == 5 — самоудаление. Удаляет из реестра ключ своей службы и удаляет все файлы в своем каталоге.
command_id == 6 — отправка конфигурации. Шифрует имеющуюся конфигурацию и отправляет ее в теле пакета.
command_id == 7 — получение новой конфигурации. В теле пакета содержится новая конфигурация. Троян сжимает ее, шифрует и сохраняет в файл, присваивая имя с инициализирующим значением 0x4358 ("CX"). Затем считывает ее и заменяет старую конфигурацию.
command_id == 8 — отправка информации и процессах с внедрениями. Подготавливает пакет с информацией о процессах, в которые был внедрен шелл-код, затем шифрует его и отравляет на сервер. Структура пакета:
struct command_8_response
{
packet_hdr header;
DWORD number_of_procs;
injected_proc injected_processes_info[number_of_procs];
};
command_id > 10 — работа с плагинами. В отличие от режима "command_id" == 3, в этом случае предусмотрена работа только в контексте текущего процесса.
Работа с плагинами
После того как была получена команда с id 3 или >10, и полученный пакет был отправлен обратно, в ответ от сервера приходит пакет с задачей для какого-либо плагина. Команда для плагина обрабатывается отдельно от основного цикла обработки команд и в отдельном соединении.
BackDoor.PlugX28 имеет следующие плагины:
- DISK (2 вида);
- Keylogger;
- Nethood;
- Netstat;
- Option;
- PortMap;
- Process;
- Regedit;
- Screen;
- Service;
- Shell;
- SQL;
- Telnet.
Имена плагинов зашиты в зашифрованном виде и используются при инициализации соответствующих объектов.
Каждый плагин представлен объектом pluginfo:
struct pluginfo
{
wchar_t name[64];
DWORD timestamp;
PROC pfnInit;
PROC pfnJob;
PROC pfnFree;
};
Временная метка timestamp для всех плагинов равна 20130707.
Объекты плагинов объединены в глобальный объект, через который осуществляется доступ к функциям плагинов.
Во время инициализации для каждого плагина последовательно вызываются функции pluginfo.pfnInit. Инициализация заключается в создании таблицы вспомогательных функций. Кроме того, для плагинов Keylogger и Screen выполняются некоторые дополнительные действия.
Инициализация плагина "Keylogger"
После инициализации таблицы вспомогательных функций троян создает отдельный поток для функции pluginfo.pfnJob, которая устанавливает хук типа WH_KEYBOARD_LL. Имя файла для журнала событий генерируется с инициализатором 0x4B4C ("KL"). Временные атрибуты файла подделываются после каждой записи. Формат строчки записи журнала имеет следующий вид:
<yyyy-mm-dd hh:mm:ss> <username> <process_path> <window_title> <event></event></window_title>
Записи в журнал событий записываются последовательно. Каждая запись имеет формат:
struct keylog_rec
{
DWORD recsize;
BYTE encdata[recsize];
};
Инициализация плагина "Screen"
Во время инициализации плагина Screen в отдельном потоке запускается функция создания скриншота. В потоке сначала инициализируется библиотека gdiplus.dll, затем в конфигурации проверяется флаг config.make_screenshot_flag. Если флаг не установлен, то поток переходит в режим ожидания, периодически проверяя флаг. Если флаг установлен, то из конфигурации извлекается значение config.screen_age, которое задает максимальный срок хранения скриншотов в днях. При этом из каталога config.screenshots_path (в конфигурации по умолчанию %AUTO>%\\XS) рекурсивно удаляются все JPEG-файлы, даты создания которых меньше заданной. Очистка происходит раз в день. Далее выполняется создание скриншота, кодирование его в JPEG-формат и сохранение в директорию <config.screenshots_path>\<username>\<screen_filename>.jpg. Имя созданного скриншота записывается в формате < ГГГГ-ММ-ДД ЧЧ:ММ:СС >.
Параметры кодирования JPEG заданы в конфигурации в параметрах "config.screen_scale_coefficient", "config.encoder_quality", "config.bits_per_pixel".
Для каждого плагина предусмотрен отдельный набор command_id. При ответе на команду в заголовке проставляется тот же command_id. Строки передаются в теле пакета, смещение указывается явно, отсчитывается от начала тела пакета, при этом пропускается заголовок packet_hdr.
Плагин DISK
command_id для плагина DISK имеет формат 0x300X, X - 0,1,2,4,7,0xA,0xC,0xD,0xE.
Команда | Описание | Ввод | Вывод |
---|---|---|---|
0x3000 | Собирает информацию о логических дисках по буквам A-Z, заполняет массив структур disk_info и отправляет на сервер. | - |
|
0x3001 | Формирует список файлов и поддиректорий в заданной директории, которая указывается в параметре target_dir команды; по каждому файлу отправляет отдельный пакет. |
|
|
0x3002 | Формирует список файлов из директории, заданной в параметре target_dir команды. Имена файлов задаются маской, которая может использовать символы ? и * для замещения одного или нескольких любых символов; по каждому файлу отправляет отдельный пакет. |
|
|
0x3004 | Читает запрашиваемый файл блоками по 0x1000 байт с заданного смещения от начала файла. Имя файла и смещение определяются в команде. Сначала отправляет информацию о файле (временные атрибуты, размер файла) со значением поля command_id, равным 0x3004, в заголовке ответа, затем начинает читать файл и отправляет блоками с "command_id"==0x3005. Блоки содержимого файла помещает в тело пакета. По завершении отправляет пакет нулевой длины с 0x3005 в заголовке. |
|
|
0x3007 | Создает новый или открывает существующий файл с конца для записи и записывает в него данные, начиная с указанного смещения. Подменяет временны́е атрибуты. В команде с "command_id" 0x3007 задается имя файла и смещение, в команде с command_id 0x3008 — буфер для записи. |
|
- |
0x300A | Создает папку, путь к которой указывается в теле пакета. Отвечает пакетом с нулевой длиной и "command_id" == 0x300A. | - | - |
0x300C | Создает процесс с использованием командной строки, переданной в теле команды. При этом, если в заголовке пакета значение поля «errc» ненулевое, создает рабочий стол HH и использует его в STARTUP_INFO создаваемого процесса. В ответ возвращает PROCESS_INFORMATION созданного процесса. |
|
|
0x300D | Выполнение функции SHFileOperationW с заданными в команде параметрами. В ответ — пакет с нулевой длиной. |
|
- |
0x300E | Разворачивает переменную среды и отправляет на сервер результат. Переменная содержится в теле команды, результат содержится в теле ответа. | - | - |
Плагин DISK (2)
Второй плагин также называется DISK, однако ничего общего с системными дисками не имеет. Предусмотрены следующие номера команд: 0xF010, 0xF011, 0xF012, 0xF013.
В команде получает структуру srv, в соответствии с которой создает объект подключения и начинает ретрансляцию пакетов из одного соединения во вновь созданное.
Плагин KeyLogger
Включает команду 0xE000. Читает файл с журналом событий плагина, затем отправляет его на сервер в теле ответа.
Плагин Nethood
Плагин для работы с сетевым окружением.
Команда | Описание | Ввод | Вывод |
---|---|---|---|
0xA000 | Перечисляет все доступные ресурсы сети. По каждому ресурсу заполняет структуру и отправляет на сервер. В команде содержатся параметры структуры NETRESOURCE, используемой в качестве аргумента при вызове функции WNetOpenEnumW. |
|
|
0xA001 | Отключает заданный в команде сетевой ресурс с флагом Force, затем заново подключает. В ответ отправляет пакет с нулевой длиной. |
|
- |
Плагин Netstat
Плагин собирает и отправляет сведения о сетевых соединениях.
Команда | Описание | Ввод | Вывод |
---|---|---|---|
0xD000 | Собирает и отправляет информацию о TCP-соединениях. В зависимости от версии ОС вызывает одну из функций для получения сведений о соединениях: AllocateAndGetTcpExTableFromStack (Windows XP); GetTcpTable (Windows 2000); GetExtendedTcpTable (Windows Vista-Windows 7). | - |
|
0xD001 | Собирает информацию о UDP-соединениях. Аналогична предыдущей команде. | - |
|
0xD002 | Меняет состояние TCP-соединения. В теле команды содержится аргумент для функции SetTcpEntry (MIB_TCPROW). |
|
- |
Плагин Option
Плагин может принимать следующие команды:
- 0x2000 — заблокировать систему через функцию LockWorkstation;
- 0x2001 — принудительно завершить сеанс пользователя;
- 0x2002 — перезагрузка;
- 0x2003 — завершение работы;
- 0x2005 — показать в отдельном потоке MessageBox с заданными параметрами:
struct command_0x2004_request { packet_hdr header; DWORD uType; WORD off_lpCaption; WORD off_lpText; }
Плагин Portmap
Содержит одну команду — 0xB000. Получает от управляющего сервера его адрес и порт:
struct command_0xB000_request
{
packet_hdr header;
WORD port;
BYTE srv_addr[40];
}
Затем создает объект TCP-соединения и подключается к полученному серверу, после чего работает в режиме туннельного соединения, передавая данные от управляющего сервера тому серверу, с которым установил соединение.
Плагин Process
Команда | Описание | Вывод |
---|---|---|
0x5000 | Получить список работающих процессов. Каждому процессу соответствует отдельный отправляемый пакет. |
|
0x5001 | Получить список модулей заданного процесса; идентификатор целевого процесса задан в поле header.errc команды. |
|
0x5002 | Завершить процесс; идентификатор задан в поле header.errc команды. | - |
Плагин Regedit
Плагин, предназначенный для работы с реестром.
Команда | Описание | Ввод | Вывод |
---|---|---|---|
0x9000 | Получить вложенные ключи реестра в заданном ключе. Дескриптор раздела содержится в поле header.errc, имя ключа содержится в теле команды. Отправляет по одному вложенному ключу. | - |
|
0x9001 | Создает вложенный ключ с заданным в теле команды именем. Дескриптор ключа содержится в поле header.errc. | - | - |
0x9002 | Удаляет заданный в теле команды вложенный ключ. Дескриптор ключа содержится в поле header.errc. | - | - |
0x9003 | Создает ключ с заданным именем, затем рекурсивно копирует в него значения из другого ключа, также заданного в команде. В случае успеха исходный ключ удаляет. В ином случае удаляет вновь созданный ключ. Дескриптор содержится в поле header.errc. |
|
- |
0x9004 | Получает значения заданного ключа. Имя ключа содержится в теле; дескриптор содержится в поле header.errc. | - |
|
0x9005 | Создает вложенный ключ и значение в нем. В зависимости от флага в команде может проверить, существует ли значение. Дескриптор содержится в поле header.errc. |
|
- |
0x9006 | Удаляет значение из ключа. |
|
- |
0x9007 | Проверяет, существует ли значение 1. Если его нет, то проверяет значение 2. Если оно существует, то создается значение 1, которое заменяется значением 2. После этого значение 2 удаляется. |
|
- |
Плагин Screen
Создает и отправляет скриншоты рабочего стола, имитирует работу по протоколу RDP.
Команда | Описание | Ввод | Вывод |
---|---|---|---|
0x4000 | Команда запускает 2 отдельных потока, которые имитируют работу по протоколу RDP. В одном потоке происходит отправка скриншотов интерактивного рабочего стола. Во втором — прием и выполнение команд, связанных с регистрацией событий мыши и клавиатуры. Изначально с командой 0x4000 приходит пакет с указанием на требуемое разрешение снимков экрана (бит на пиксель). Второй поток может получить одну из команд:
|
|
|
0x4100 | Делает снимок экрана с заданными параметрами и отправляет на сервер. |
|
- |
0x4200 | Отправляет заранее сделанный снимок экрана в формате JPEG из каталога config.screenshots_path. Сначала отправляет его имя, затем блоками по 0xE000 байт сам снимок. По окончании — пакет с нулевой длиной. | - | - |
Плагин Service
Плагин предназначен для работы со службами.
Команда | Описание | Ввод | Вывод |
---|---|---|---|
0x6000 | Получает информацию о сервисах и их файлах. | - |
|
0x6001 | Изменяет способ запуска заданной службы struct command_6000h_response. Имя целевой службы содержится в теле команды; новый параметр способа запуска содержится в поле header.errc. | - | - |
0x6002 | Запускает заданную службу, имя службы содержится в теле команды. | - | - |
0x6003 | Отправляет код управления заданной службе. Имя содержится в теле; код управления содержится в поле header.errc. | - | - |
0x6004 | Удаляет заданную в теле команды службу. | - | - |
Плагин Shell
Плагин предназначен для создания оболочки командного процессора cmd.exe; идентификатор команды плагина — command_id — 0x7002. В двух отдельных потоках создает пайпы для чтения и записи, затем создает процесс cmd.exe с перенаправлением ввода-вывода на пайпы. Ввод получает от объекта соединения с управляющим сервером, а вывод отправляет в ответ.
Плагин SQL
Плагин предназначен для работы с SQL-запросами.
Команда | Описание | Ввод | Вывод |
---|---|---|---|
0xC000 | Получает доступные источники SQL-данных через функцию odbc32!SQLDataSourcesW. | - |
|
0xC001 | Перечисляет доступные SQL-драйверы через функцию odbc32!SQLDriversW. | - |
|
0xC002 | Исполняет произвольный SQL-запрос. В теле первого пакета содержится строка подключения, используемая в качестве аргумента при вызове функции odbc32!SQLDriverConnect. Далее в пакетах с header.command_id, равным 0xC003, содержатся сами запросы. В ответ в пакетах с header.command_id, равным 0xC008, отправляются диагностические данные, полученные с помощью вызова функции SQLGetDiagRecW; в пакетах с header.command_id, равным 0xC004, отправляются результаты SQL-запроса. | - | - |
Плагин Telnet
Плагин предназначен для полной имитации работы по протоколу Telnet. Запускается при получении команды 0x7100. По этой команде создается процесс "cmd.exe /Q", на сервер отправляется пакет нулевой длины, затем в отдельных потоках запускаются 2 обработчика. Первый принимает пакеты с id 0x7101 и 0x7102:
- 0x7101 — открывает консоль по идентификатору CONIN$ и пишет в нее данные, полученные из команды. В теле пакета находится массив структур INPUT_RECORD;
- 0x7102 — отправляет идентификатор управляющего события (CtrlEvent) в консоль, открытую по идентификатору CONIN$. Код события находится в поле header.errc.
Второй дескриптор в пакетах с id 0x7103 отправляет информацию о консоли:
struct c2_command_7103h_telnet_cli2srv
{
packet_hdr header;
DWORD console_CP;
DWORD consoleOutput_CP;
DWORD console_input_mode;
DWORD console_output_mode;
DWORD console_display_mode;
CONSOLE_CURSOR_INFO console_cursor_info;
COORD console_position;
COORD console_size;
};
В пакетах с id 0x7104 отправляет считанный буфер консоли.