В прошлом 2022 сразу несколько компаний по созданию антивирусного ПО опубликовали статьи про бекдор из семейства PlugX, который называется Talisman. В сегодняшней статье, на примере этого трояна мы рассмотрим способ выполнения динамического анализа вредоносного ПО.
Еще по теме: Взлом программы macOS с помощью IDA и Hiew
Динамический анализа кода с помощью x64dbg и IDA Pro
Приступим к подробному исследованию кода. Процесс динамического анализа будем проводить с использованием утилиты x64dbg. Анализ псевдокода выполним в IDA Pro с установленным плагином HexRays. В процессе анализа вредоносных программ необходимо комбинировать используемые инструменты для получения наиболее полного результата.
Программа SNAC.EXE представляет собой безопасный исполняемый файл, который имеет валидную цифровую подпись. Его основная задача — загрузка динамической библиотеки WGXMAN.DLL методом DLL Side-loading. После загрузки библиотеки выполнение передается на функцию экспорта DllMain. Далее динамическая библиотека расшифровывает исполняемый код в файле SNAC.LOG и передает выполнение на него.
Приступим к анализу, для этого загрузим исполняемый файл SNAC.EXE в утилиту x64dbg, в которой будем проводить отладку. Также загрузим динамическую библиотеку WGXMAN.DLL в IDA.
Для проведения динамического анализа необходимо найти точку останова, с которой начинается выполнение основных функций программы. Для этого проанализируем код загружаемой библиотеки и найдем функцию, подходящую для этой цели.
В IDA открываем вкладку File → Open и выбираем файл WGXMAN.DLL. После загрузки файла мы попадаем на функцию DllMain. Далее декомпилируем код, используя плагин HexRays, для этого нажимаем клавишу F5. И синхронизируем анализ кода во вкладке IDA View A и Pseudocode A, для чего перенесем вкладку Pseudocode A в правую часть вкладки IDA View A. Нажатием правой кнопки мыши выберем Syncronize with → IDA View A, теперь при выборе участка кода он будет подсвечиваться в каждой вкладке.
Переходим в функцию sub_6FE443C, эта функция и будет точкой входа во время динамической отладки. Проанализируем ее.
Функция sub_6FE420 служит для получения списка функций экспорта динамической библиотеки kernel32.dll.
Начнем отладку в x64dbg. Для этого перейдем к функции sub_6FE443C нажатием сочетания клавиш Ctrl-G, затем наберем адрес функции и разберем алгоритм расшифровки констант.
Поставим точку останова на вход функции. Нажмем F7 для захода в функцию и увидим интересующие нас константы.
Зайдем в функцию call wgxman.6FE42D4 и разберем механизм поиска экспортных функций библиотеки kernel32.dll.
В функции sub_6FE42D4 расположен алгоритм преобразования функции экспорта библиотеки kernel32.dll. Имена экспортируемых функций не хранятся в открытом виде, поэтому трояну следует сначала привести их в пригодный для использования вид. Для этого на вход функции sub_6DE42D4 поступает константа и список функций экспорта библиотеки kernek32.dll, далее из названия функции экспорта считывается символ, преобразуется по алгоритму ROR со смещением 7, а затем сумма всех преобразованных символов складывается.
Напишем этот алгоритм преобразования в виде программы на Python. Список функций экспорта возьмем с сайта geoffchappell.com.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
name_const = ['0xc917432','0xf8062593','0xef64a41e','0xf4e2f2b2','0xb4ffafed','0xbbafdf85'] ror = lambda val, r_bits, max_bits: \ ((val & (2**max_bits-1)) >> r_bits%max_bits) | \ (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1)) f = open('list_export_kernel32.txt','r') w = open('out_export.txt','w+') def decode(s: str): result = 0 for j in s: result = ror(result,7,32) result += ord(j) return hex(result) for i in f: s = i.replace('\n','') d = decode(s) if d in func: w.write(s + '-' + d +'\n') f.close() w.close() |
Запустив программу, мы получим следующий результат.
Преобразуем псевдокод в читаемый формат, воспользовавшись возможностями IDA Pro. Горячая клавиша N позволяет переименовать переменные в псевдокоде IDA.
После проверки того, что имя запущенного исполняемого файла содержит .NAC., управление передается по адресу 06FE44E1.
Мы разобрали метод запутывания названий функций вредоносом. Теперь перейдем к участку кода, где расшифровывается файл SNAC.LOG. Для этого найдем функцию CreateFileA, воспользовавшись сочетанием клавиш Ctrl-G.
По адресу 0x6FE43C8 происходит вызов функции CreateFileA, а в регистре EAX содержится путь к файлу SNAC.LOG.
Далее вредонос получает размер файла SNAC.LOG с использованием функции GetFileSize. Затем происходит выделение памяти с помощью функции VirtualAlloc.
Продолжаем отлаживать код: нажимаем F8 и спускаемся к адресу функции ReadFile.
Перейдем к значению в памяти ds:[edi+0x3C] (параметр lpBuffer функции ReadFile). Так как по этому адресу хранятся значения, нажимаем правой кнопкой мыши на [edi+0x3c] и переходим на вкладку «Перейти к дампу → Значение [edi+0x3C]». После выполнения функции по тому адресу сохранится содержимое файла SNAC.LOG.
По адресу 0x00550000 хранится содержимое файла SNAC.LOG. Нажимаем F8 и двигаемся дальше. Находим участок кода, в котором расшифровывается этот файл.
В регистр ESI передаются значения по адресу [edi+3c] (там хранится содержимое файла SNAC.LOG), далее с каждым байтом файла происходит следующее преобразование. Сначала из него вычитается значение 0x48, далее выполняется операция XOR со значением 0x19, и, наконец, полученный результат складывается со значением 0xA7. Далее выполнение кода передается на расшифрованные значения и вызывается функция call esi. Поставим точку останова на этом вызове.
Давай напишем на Python код расшифровки файла SNAC.LOG и загрузим расшифрованный файл в IDA Pro.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
f = open('SNAC.LOG','rb') w = open('SNAC_DECODE.LOG','wb+') cont = f.read() result = bytearray() for i in cont: #print(i) b = i - 0x48 b ^= 0x19 b += 0xa7 result.append(b & 0xff) w.write(result) f.close() w.close() |
Полученный расшифрованный файл SNAC_DECODE.LOG загрузим в IDA Pro и разберем его код.
Как видно из рисунка, происходит запуск функции sub_269DD, в качестве второго аргумента передается размер файла SNAC.LOG, равный 159981 байту. При выполнении этого кода происходит поиск WinAPI-функции GetProcAddress.
Далее вредонос получает адреса следующих функций: LoadLibraryA, VirtualAlloc, VirtualFree, ExitThread, RtlDecompressBuffer, memcpy.
Затем происходят следующие преобразования.
На рисунке выше показан алгоритм расшифровки полезной нагрузки, спрятанной внутри динамической библиотеки. С целью замедления расшифровки пять раз вызывается функция Sleep, затем расшифрованный буфер распаковывается с помощью функции RtlDecompressBuffer. Преобразованные данные будут сохранены в переменной v29, для которой выделяется память с помощью функции VirtualAlloc. Наша задача при отладке попасть на функцию RtlDecompressBuffer, перейти про адресу переменной v29 и сохранить область памяти. Так мы получим основную полезную нагрузку вредоноса PlugX.
Приступаем к отладке. Мы останавливались на операнде call esi. Заходим в функцию, нажимаем клавишу F7, а затем F8. Проходим этап получения адресов функций, далее, минуя этап расшифровки полезной нагрузки, остановимся перед запуском функции RtlDecompressBuffer. Эту функцию можно отыскать, спускаясь вниз по участку кода либо перейдя на ее вызов. Для этого можно использовать сочетание клавиш Ctrl-G в x64dbg, после чего следует ввести название функции, на которую мы хотим перейти.
В регистре ESI хранятся преобразованные данные (параметр UncompressBuffer). Щелкнем правой кнопкой мыши на этом регистре, затем нажмем «Перейти к дампу → esi» и получим основную нагрузку вредоноса PlugX.
Сохраним полученный код, для этого щелкнем правой кнопкой мыши на регистре ESI и выберем в открывшемся меню пункт «Перейти к карте памяти».
Нажмем правую кнопку мыши, чтобы сохранить дамп в файл. Мы выгрузили основную нагрузку вредоноса. Чуть позже мы загрузим ее в IDA Pro и разберем функционал, а для начала посмотрим, что происходит после расшифровки полезной нагрузки. В отладчике спускаемся немного ниже (F8) и попадаем на формирование внутренней структуры вредоноса.
Спустимся до участка кода mov dword ptr ds:[esi],CF455089 и перейдем к дампу по адресу в регистре ESI. Для этого наводим курсор на регистр [esi], нажимаем правую кнопку мыши и открываем дамп.
Компании Trellix и «Доктор Веб» описывали схожие структуры PlugX, но мы разберем их самостоятельно.
Первые 4 байта содержат значение 0xCF455089, которое является сигнатурой структуры. Размер конфигурации Talisman равен 0x1924 байт, а зашифрованный участок конфигурации хранится по адресу 0x610013 (в твоем отладчике адрес будет отличаться).
Перейдем по адресу 0x610013, для этого нажмем правую кнопку мыши и в открывшемся меню выберем пункт «Перейти к дампу DWORD».
Скопируем полученное значение и сохраним в файл encrypt_talisman.txt.
Мы нашли конфигурацию Talisman, теперь отыщем участок кода, который его расшифровывает.
Для этого загрузим сохраненный дамп полезной нагрузки PlugX Talisman в IDA Pro. Нам известно, что длина конфигурации составляет 0x1924 байт, а сигнатура конфигурации имеет вид 0xCF455089. Найдем участок кода, где происходит сравнение сигнатуры и объема памяти конфигурации.
В IDA Pro открываем вкладку Search → Sequence of bytes и вводим значение 0x1924.
Переходим в функцию sub_100243A0 и находим сравнение сигнатуры Talisman, а также размера файла конфигурации.
Функция sub_1000A710, которую мы переименовали в Decrypt_Config_Talisman, содержит участок кода расшифровки структуры бекдора.
Данный участок кода схож с модулем PlugX.38. Давай напишем на Python алгоритм расшифровки конфигурации, которую мы сохранили в файл encrypt_talisman.txt.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
def DWORD(i): return i & 0xFFFFFFFF def LOBYTE(i): return i & 0x000000FF def dec(key, in_data): k1 = k2 = k3 = k4 = key result = bytearray() for x in in_data: k1 = DWORD(k1 + (k1 >> 3) - 0x11111111) k2 = DWORD(k2 + (k2 >> 5) - 0x22222222) k3 = DWORD(k3 + 0x33333333 - (k3 << 7)) k4 = DWORD(k4 + (0x44444444 - (k4 << 9))) k = LOBYTE(k1 + k2 + k3 + k4) result.append(ord(x) ^ k) return result f = open('encrypt_config.txt','r') w = open('decrypt_config_talisman','wb') encrypt_config = [chr(int(i,base=16)) for i in f.read().split(' ')] key = 0x4CB8B675 w.write(dec(key,encrypt_config)) f.close() w.close() |
Запустив скрипт, мы получаем следующие значения.
Размер конфигурации составляет 0x1924 байт, она содержит список панелей управления и C2-серверов (dhsg123.jkub.com). А вот и другие полученные нами полезные данные:
- домашний каталог вредоноса — %ALLUSERSPROFILE%\SymantecSNAC;
- мьютекс создаваемого процесса — Global\ReStart0;
- целевой процесс, в который будет внедряться вредонос — %SystemRoot%\system32\nslookup.exe;
- имя отображаемой службы — SymantecSNAC;
- идентификатор компании — TEST.
На текущем этапе мы расшифровали конфигурацию PlugX Talisman, теперь разберем основную нагрузку DLL PlugX, которую извлекли из памяти.
Анализируя код основной нагрузки PlugX, можно обнаружить следующие используемые плагины, расположенные в функции sub_1000B9F0.
После запуска полезной нагрузки вредонос создает следующие токены:
- SeDebugPrivilege — эта привилегия позволяет процессу подключаться к любому процессу или ядру как отладчик;
- SeTcbPrivilege — позволяет процессу олицетворить любого пользователя без проверки подлинности, таким образом процесс может получить доступ к тем же локальным ресурсам, что и этот пользователь.
Далее вредонос создает основной поток, который называется bootProc.
Также в основной нагрузке PlugX все функции Windows API вызываются динамически на основе алгоритма CRC32 от названий функций. Но в алгоритме необходимо добавить завершающий нулевой байт. Алгоритм поиска функций представлен ниже.
1 2 3 4 5 6 7 |
import zlib f = open('list_ws2_32.txt','r') m = ['0x49f9b50b','0xeaed580c'] for i in f: h=hex(zlib.crc32(bytes(i.replace('\n',''),'utf-8')+b'\x00')) if h in m: print(i,h) |
В ходе анализа мы расшифровали файл SNAC.LOG, конфигурацию PlugX Talisman, в которой хранятся сведения о C2-серверах, а также расшифровали основную нагрузку PlugX: она представляет собой DLL, загружаемую в память процесса SNAC.exe.
ПОЛЕЗНЫЕ ССЫЛКИ: