CobInt. Разбираем известный бэкдор и практикуемся в реверсе

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

  • PowerShell loader
  • Расшифровка тела шелл-кода
  • Исследование загрузчика
  • Unhashing functions
  • HTTP Request to C2
  • Расшифровка полезной нагрузки
  • Исследование загруженной DLL
  • Выводы

CobInt — это бэк­дор, который активно исполь­зует груп­пиров­ка Cobalt/(Ex)Cobalt при ата­ках на рос­сий­ские ком­пании. В статье мы по шагам выпол­ним реверс CobInt и изу­чим полез­ные тех­ники ана­лиза этой мал­вари. www

Под­робнос­ти про груп­пиров­ку, инс­тру­мен­ты и TTP ищи по ссыл­кам:

  • Cobalt: обновле­ние так­тик и инс­тру­мен­тов
  • Уже не те: ата­ки (Ex)Cobalt на рос­сий­ские ком­пании в 2023 году

 

PowerShell loader

В начале 2024 года наша коман­да по рас­сле­дова­нию инци­ден­тов (PT ESC Positive Technoligies) выяви­ла при­мене­ние мал­вари CobInt в инфраструк­туре заказ­чика. Про­ана­лизи­ровав логи EVTX на ском­про­мети­рован­ной машине, мы извлек­ли силь­но обфусци­рован­ный вре­донос­ный скрипт PowerShell.

Об­фусци­рован­ный код PowerShell

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

Вы­зов фун­кции

Код фун­кции выг­лядит сле­дующим обра­зом:

If ([IntPtr]::size -eq 8){ $L7Q=aQGIf $d90qSK=1331}else{ $L7Q=tNKdTPG $d90qSK=1166}$YodSQB=[System.Convert]::FromBase64String((vXQvbyjhW $L7Q))RgEGGBTGL $YodSQB $d90qSK}

В этой фун­кции на осно­ве раз­мера ука­зате­ля опре­деля­ется раз­рядность ОС, в зависи­мос­ти от которой перемен­ной $d90qSK прис­ваивает­ся соот­ветс­тву­ющее зна­чение. Оно опре­деля­ет сме­щение к фун­кции рас­шифров­ки шелл‑кода. Шелл‑код для опре­делен­ной архи­тек­туры декоди­рует­ся из Base64, пос­ле чего переда­ется в фун­кцию RgEGGBTGL вмес­те со сме­щени­ем. Что­бы сох­ранить рас­кодиро­ван­ный шелл‑код на диск, сра­зу пос­ле вызова фун­кции FromBase64String добавим в код сле­дующую стро­ку:

[IO.File]::WriteAllBytes('shellcode.bin',$YodSQB)

Те­перь убе­дим­ся, что шелл‑код сох­ранил­ся кор­рек­тно. Закиды­ваем его в IDA, перехо­дим по сме­щению 1331 (0x533) в слу­чае с x64 и пре­обра­зуем бай­ты в код.

Код рас­шифров­ки по сме­щению 0x533 

Расшифровка тела шелл-кода

Код, который отве­чает за рас­шифров­ку самого себя в памяти, выг­лядит сле­дующим обра­зом.

Код для дешиф­ровки тела шелл‑кода

Нам нуж­но пре­обра­зовать шелл‑код в исполня­емый файл. Я это делаю с помощью shellcode2exe:

shellcode2exe.bat 64 shellcode.bin cob_shellx64.exe

Те­перь мы можем заг­рузить получив­ший­ся исполня­емый файл в x64dbg. Пос­ле заг­рузки жмем RUN, что­бы перей­ти к EntryPoint.

Ин­тер­пре­тация зашиф­рован­ных бай­тов шелл‑кода дебаг­гером

Код на текущий момент еще не рас­шифро­ван и пред­став­ляет собой воль­ную интер­пре­тацию мешани­ны из бай­тов, выдан­ной дебаг­гером. Нуж­но перей­ти по извес­тно­му сме­щению 0x533 + ImageBase, то есть в нашем слу­чае по адре­су 0x401533.

Точ­ка вхо­да для рас­шифров­ки кода

Мы видим уже зна­комую фун­кцию для рас­шифров­ки тела шелл‑кода. Выпол­нять код мы нач­нем с текущей стро­ки. Для это­го жмем Set RIP Here, пос­ле чего адрес Instruction Pointer при­мет необ­ходимое зна­чение.

С помощью F8 нуж­но прой­тись по инс­трук­циям рас­шифров­ки и най­ти ту, которая отве­чает за выход из цик­ла. Эта инс­трук­ция находит­ся по адре­су 0x401538.

Инс­трук­ция для выхода из цик­ла рас­шифров­ки

Ста­вим брейк‑пой­нт на сле­дующей за jne инс­трук­ции и нажима­ем F9, что­бы вруч­ную не ходить по каж­дой ите­рации цик­ла рас­шифров­ки. Пос­ле оста­нов­ки на нашем брейк‑пой­нте про­ходим по коду нем­ного даль­ше и пры­гаем на начало уже рас­шифро­ван­ного кода в ори­гиналь­ной EntryPoint.

Рас­шифро­ван­ный код в EntryPoint

На этом эта­пе мы можем сдам­пить PE в рас­шифро­ван­ном виде для удобс­тва даль­нейше­го дебага. Я исполь­зую в этих целях пла­гин OllyDumpEx.

Дамп про­цес­са

Код для рас­шифров­ки себя в памяти содер­жит сле­дующий алго­ритм:

  • XOR пер­вых четырех бай­тов (Little Endian) с зашитым клю­чом 0x4498D9DE.
  • По­бито­вый сдвиг вле­во на один бит для клю­ча (rol xor_key, 1).
  • XOR сле­дующих четырех бай­тов с модифи­циро­ван­ным клю­чом и так далее.
  • www

    Ин­тер­пре­тация алго­рит­ма рас­шифров­ки на Go с вши­тым клю­чом

     

    Исследование загрузчика

     

    Unhashing functions

    Нас­тало вре­мя поковы­рять­ся в заг­рузчи­ке. Откры­ваем сдам­плен­ный файл в x64dbg. В коде встре­чает­ся мно­жес­тво вызовов фун­кции 401380, в которую переда­ются раз­личные Hex-зна­чения. Обыч­но такой пат­терн свой­стве­нен фун­кци­ям, отве­чающим за вос­ста­нов­ление имен биб­лиотек и фун­кций из хеш‑зна­чений. Как вид­но на скрин­шоте, пер­вый вызов вер­нул имя фун­кции LoadLibraryA биб­лиоте­ки Kernel32, а так­же ее адрес в RCX.

    Вы­зов unhashing-фун­кции

    Рас­смот­рим эту фун­кцию под­робнее. Обра­ти вни­мание на сле­дующий код.

    Инс­трук­ции для получе­ния информа­ции из PEB

    Этот модуль сна­чала получа­ет ука­затель на PEB (Process Environment Block) через сме­щение 0x60 в TEB (Thread Environment Block), дос­туп к которо­му осу­щест­вля­ется через регистр gs. Затем код перехо­дит по сме­щени­ям 0x18 и 0x20. Что­бы понять, что это за сме­щения, мож­но обра­тить­ся к опи­санию струк­туры PEB или про­верить их в WinDbg. Для это­го:

    • заг­ружа­ем исполня­емый файл в WinDbg;
    • вво­дим коман­ду bp $exentry для уста­нов­ки брейк‑пой­нта на EntryPoint;
    • за­пус­каем исполне­ние коман­дой g;
    • пе­реме­щаем­ся нажати­ем F8 к рас­смат­рива­емо­му учас­тку кода.

    Ко­ман­да dt ntdll!_PEB @$peb покажет струк­туру PEB со сме­щени­ями.

    Струк­тура PEB в WinDbg

    Пер­вое сме­щение ука­зыва­ет на струк­туру PEB_LDR_DATA, которая содер­жит в себе све­дения о заг­ружен­ных модулях для про­цес­са. Изу­чим ее пов­ниматель­нее, для это­го в WinDbg вве­дем коман­ду dt -r1 _PEB_LDR_DATA.

    Струк­тура PEB_LDR_DATA в WinDbg

    Вто­рое сме­щение ука­зыва­ет на двус­вязный спи­сок InMemoryOrderModuleList, который содер­жит заг­ружен­ные модули для про­цес­са. Каж­дый эле­мент в этом спис­ке явля­ется ука­зате­лем на струк­туру LDR_DATA_TABLE_ENTRY. Сме­щение на него лежит в rbx, поэто­му берем адрес из rbx и нак­ладыва­ем на него струк­туру _LDR_DATA_TABLE_ENTRY.

    Со­дер­жание струк­туры LDR_DATA_TABLE_ENTRY, на которую получен ука­затель

    Та­ким обра­зом, по сме­щению 0x50 код получа­ет дос­туп к полю buffer с име­нем модуля. Далее вызыва­ется фун­кция, в которую аргу­мен­тами переда­ется ука­затель на стро­ку с име­нем модуля и дли­на стро­ки.

    Пе­реда­ча име­ни модуля и дли­ны стро­ки в фун­кцию

    Эта фун­кция выпол­няет хеширо­вание стро­ки. Алго­ритм выг­лядит сле­дующим обра­зом.

    Ал­горитм хеширо­вания стро­ки

    По­лучив хеш, алго­ритм про­веря­ет получен­ное зна­чение с передан­ным ранее аргу­мен­том. Если зна­чения не сов­пада­ют, выпол­няет­ся переход к сле­дующе­му име­ни модуля. Если сов­пада­ют, то сох­раня­ется ука­затель на адрес биб­лиоте­ки, к которо­му при­бав­ляют­ся сме­щения.

    Дос­туп к дан­ным биб­лиоте­ки по сме­щени­ям

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

    Ответить

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