Agent Tesla. Учимся реверсить боевую малварь в Ghidra

Содержание статьи

  • Подготовительные работы
  • Реверсим
  • Определяем функцию main
  • Расшифровываем шелл-код
  • Вытаскиваем шелл-код отдельным файлом
  • Выводы

Не­дав­но мне попал­ся инте­рес­ный экзем­пляр мал­вари под наз­вани­ем Agent Tesla. Он рас­простра­нен и исполь­зует­ся по сей день (семпл 2023 года). Пред­лагаю поп­робовать иссле­довать его и пос­мотреть, что внут­ри у боевой мал­вари.

Кро­ме того, мы стол­кнем­ся:

  • с рас­паков­кой инстал­лятора NSIS и ана­лизом получен­ного инстал­ляци­онно­го скрип­та, который нам поможет в рас­паков­ке;
  • по­иском фун­кции main при ее обвязке в CRT (мож­но будет уди­вить­ся, сколь­ко кода неяв­но закиды­вает ком­пилятор в .exe);
  • рас­шифров­кой и дам­пингом шелл‑кода и пред­варитель­ным поис­ком его по фун­кции выделе­ния памяти;
  • пра­виль­ной заг­рузкой получен­ного шелл‑кода в дизас­сем­блер.

В этот раз я отой­ду от сво­ей тра­диции исполь­зовать IDA Pro для ревер­синга: вмес­то это­го возь­мем Ghidra. С момен­та ее выпус­ка прош­ло уже нес­коль­ко лет, она обза­велась вну­шитель­ным спис­ком баг­фиксов и новых фич, к тому же она бес­плат­на и пос­тоян­но обновля­ется.

info

В прош­лый раз я пи­сал о Ghidra в 2019 году, ког­да этот инс­тру­мент толь­ко‑толь­ко стал дос­тупен широкой пуб­лике.

 

Подготовительные работы

На­чина­ем этап пред­варитель­ной раз­ведки: закиды­ваем семпл в детек­тор пакеров и про­тек­торов DiE. Выяс­няем, что мал­варь пос­тавля­ется в виде инстал­лятора NSIS.

Ре­зуль­тат ска­ниро­вания DiE инстал­лятора Agent Tesla

Из­вле­каем содер­жимое инстал­лятора и получа­ем нес­коль­ко фай­лов. Обра­ти вни­мание, что сре­ди рас­пакован­ных фай­лов дол­жен быть скрипт NSIS, который содер­жит полез­ную информа­цию. Для рас­паков­ки я исполь­зовал уста­рев­шую вер­сию 7-Zip (под­дер­жка извле­чения скрип­тов начина­ется с вер­сии 4.42 и прек­раща­ется в вер­сии 15.06).

Ин­терес­ная часть NSIS-скрип­та

В этой час­ти скрип­та мы видим спи­сок фай­лов в инстал­ляторе и парамет­ры запус­ка единс­твен­ного .exe (это инте­рес­но и при­годит­ся нам в даль­нейшем). Сре­ди про­чих дан­ных скрип­та есть путь уста­нов­ки InstallDir $TEMP. Теперь пос­мотрим на PE-файл в DiE.

Смот­рим рас­пакован­ный PE в DiE

Вид­но, что файл написан на C/C++, ском­пилиро­ван для 32-бит­ных сис­тем и, судя по не осо­бен­но высокой энтро­пии, не запако­ван. Приш­ло вре­мя заг­рузить его в Ghidra.

 

Реверсим

Сре­ди фун­кций, перечис­ленных в таб­лице импорта, есть упо­мина­ние VirtualAlloc. Она‑то нас и инте­ресу­ет, потому что мал­варь час­то исполь­зует ее для выделе­ния памяти под рас­паков­ку. Вос­ста­нав­лива­ем перек­рес­тную ссыл­ку и видим фун­кцию, в которой она вызыва­ется.

Мы мог­ли бы поп­робовать пой­ти «быс­трым» путем: заг­рузить вре­донос в отладчик, пос­тавить бряк на VirtualAlloc и… обло­мать­ся, потому что Agent Tesla завер­шится рань­ше бря­ка. Поэто­му всег­да советую в пер­вую оче­редь осмотреть инте­рес­ные вызовы и при­лега­ющий к ним код в ста­тике.

Фун­кция неболь­шая, при­веду лис­тинг деком­пилято­ра Ghidra пол­ностью. Тем более что она нам инте­рес­на поч­ти вся.

BOOL FUN_00401300(undefined4 param_1,undefined4 param_2,LPCSTR param_3){ DWORD DVar1; DWORD DVar2; BOOL BVar3; HANDLE hFile; HANDLE hFileMappingObject; LPVOID _Src; code *_Dst; int local_8; DVar1 = GetTickCount(); Sleep(702); DVar2 = GetTickCount(); if (DVar2 - DVar1 < 700) { BVar3 = 0; } else { hFile = CreateFileA(param_3,0x80000000,1,0x0,3,0x80,0x0); if (hFile == 0xffffffff) { BVar3 = 0; } else { hFileMappingObject = CreateFileMappingA(hFile,0x0,2,0,0,0x0); if (hFileMappingObject == 0x0) { CloseHandle(hFile); BVar3 = 0; } else { _Src = MapViewOfFile(hFileMappingObject,4,0,0,0x1de0); if (_Src == 0x0) { CloseHandle(hFileMappingObject); CloseHandle(hFile); BVar3 = 0; } else { _Dst = VirtualAlloc(0x0,0x1de0,0x1000,0x40); if (_Dst == 0x0) { UnmapViewOfFile(_Src); CloseHandle(hFileMappingObject); CloseHandle(hFile); BVar3 = 0; } else { FID_conflict:_memcpy(_Dst,_Src,0x1de0); for (local_8 = 0; local_8 < 0x16c2; local_8 = local_8 + 1) { _Dst[local_8] = _Dst[local_8] ^ s_248058040134_0041c2a4[local_8 % 0xc]; } (*_Dst)(); VirtualFree(_Dst,0,0x8000); UnmapViewOfFile(_Src); CloseHandle(hFileMappingObject); BVar3 = CloseHandle(hFile); } } } } } return BVar3;}

Здесь сра­зу бро­сают­ся в гла­за стро­ки кода, содер­жащие прос­тую анти­отладку:

DVar1 = GetTickCount();Sleep(702);DVar2 = GetTickCount();if (DVar2 - DVar1 < 700) { BVar3 = 0;}else {

Это извес­тный анти­отла­доч­ный при­ем, который про­веря­ет ско­рость выпол­нения кода. Если код выпол­няет­ся слиш­ком мед­ленно (замеря­ем вре­мя выпол­нения в мил­лисекун­дах при помощи двух вызовов GetTickCount), перемен­ная BVar3 при­нима­ет зна­чение 0, и прог­рамма завер­шает­ся.

Источник: xakep.ru

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *