Рапид-расшифровка данных из NSIS-скрипта 🗄
Когда нет времени на идентификацию алгоритма шифрования и реализацию алгоритма дешифрования, на помощь приходит отладчик. Но как быть со скриптовым языком для установщика?
В качестве примера взят загрузчик XDigo
• Для начала извлечем скрипт, расширение которого — .nsi. Воспользуемся специальной версией 7-Zip.
• После этого нам нужен компилятор .nsi-скриптов — скачаем и установим NSIS. Откроем компилятор и загрузим в него извлеченный скрипт. Базовые возможности программы ограничены: он позволяет только скомпилировать скрипт в исполняемый файл и запустить его.
• Для того чтобы расширить функциональность компилятора, нам понадобится Debug Plug-In. Набор его функций не очень широк, но их вполне хватит, чтобы динамически расшифровать данные.
• После установки плагина откроем исходный вредоносный скрипт и в конце функции дешифрования, после операции передачи расшифрованных данных в стек, добавим строку:
• В результате перекомпиляции скрипта и тестового запуска исполняемого файла каждый раз после выполнения функции дешифрования в отдельном окне будет демонстрироваться состояние стека на момент выполнения строки Debug::Stack, в данных которого и будут находиться расшифрованные данные.
#reverse #tips #malware #XDigo #TI
@ptescalator
Когда нет времени на идентификацию алгоритма шифрования и реализацию алгоритма дешифрования, на помощь приходит отладчик. Но как быть со скриптовым языком для установщика?
В качестве примера взят загрузчик XDigo
• Для начала извлечем скрипт, расширение которого — .nsi. Воспользуемся специальной версией 7-Zip.
• После этого нам нужен компилятор .nsi-скриптов — скачаем и установим NSIS. Откроем компилятор и загрузим в него извлеченный скрипт. Базовые возможности программы ограничены: он позволяет только скомпилировать скрипт в исполняемый файл и запустить его.
• Для того чтобы расширить функциональность компилятора, нам понадобится Debug Plug-In. Набор его функций не очень широк, но их вполне хватит, чтобы динамически расшифровать данные.
• После установки плагина откроем исходный вредоносный скрипт и в конце функции дешифрования, после операции передачи расшифрованных данных в стек, добавим строку:
Debug::Stack
• В результате перекомпиляции скрипта и тестового запуска исполняемого файла каждый раз после выполнения функции дешифрования в отдельном окне будет демонстрироваться состояние стека на момент выполнения строки Debug::Stack, в данных которого и будут находиться расшифрованные данные.
#reverse #tips #malware #XDigo #TI
@ptescalator
🔥10👍3❤2
👏 Раз-два — и готово. Генерируем FLIRT-сигнатуры
А делаем это, чтобы не тратить время на распознавание библиотечного кода PureBasic, на котором написан COM-DLL-Dropper группировки ExCobalt.
Для начала нам понадобится компилятор PureBasic. Скачав его и установив или распаковав, найдем все .lib-файлы. Далее нам понадобятся входящие в состав Flair инструменты:
—
—
С их помощью мы и сгенерируем сигнатуры. Чтобы автоматизировать создание PAT-файла, будем использовать BAT-файл со следующим содержимым:
После получения PAT-файла нам нужно преобразовать его в SIG-файл. Для этого выполним следующую команду:
Так как произошли коллизии при генерации сигнатур, получаем следующий список файлов:
— PureBasic_x86.err,
— PureBasic_x86.exc,
— PureBasic_x86.pat.
При отсутствии коллизий сразу получили бы готовый SIG-файл. Для их устранения нужно отредактировать EXC-файл. Пример коллизии:
Видим, что три функции имеют одну и ту же сигнатуру, — нам необходимо выбрать, какую из них использовать.
Слева, рядом с именем нужной функции, ставим +. В этом случае коллизия коснулась функций, идентичных по назначению, и мы можем выбрать любую:
Но бывает, когда она касается функций с совершенно противоположным назначением, — можно выбрать любую, но запомнить или записать ее: это пригодится, когда будет получен результат.
Нужно также удалить строку --------- в EXC-файле, чтобы выбор учитывался при повторной генерации сигнатур:
После того как действие будет проделано для всех коллизий, нужно повторить генерацию. Если все сделано правильно, мы получим SIG-файл. Его следует положить в:
#reverse #tips #ComDllDropper #ExCobalt #APT #TI
@ptescalator
А делаем это, чтобы не тратить время на распознавание библиотечного кода PureBasic, на котором написан COM-DLL-Dropper группировки ExCobalt.
Для начала нам понадобится компилятор PureBasic. Скачав его и установив или распаковав, найдем все .lib-файлы. Далее нам понадобятся входящие в состав Flair инструменты:
—
pcf
— парсер файлов .lib и .obj, создает PAT-файл из COFF-файлов.—
sigmake
— конвертирует ранее созданный PAT-файл в SIG-файл для IDA.С их помощью мы и сгенерируем сигнатуры. Чтобы автоматизировать создание PAT-файла, будем использовать BAT-файл со следующим содержимым:
@echo off
\path\pcf.exe -a \path\Debugger.lib
\path\PureBasic_x86.pat
\path\pcf.exe -a \path\libmariadb.lib
\path\PureBasic_x86.pat
...
После получения PAT-файла нам нужно преобразовать его в SIG-файл. Для этого выполним следующую команду:
\path\sigmake.exe -n"PureBasic_Windows_X86_LTS_6.03" \path\PureBasic_x86.pat \path\PureBasic_x86.sig
Так как произошли коллизии при генерации сигнатур, получаем следующий список файлов:
— PureBasic_x86.err,
— PureBasic_x86.exc,
— PureBasic_x86.pat.
При отсутствии коллизий сразу получили бы готовый SIG-файл. Для их устранения нужно отредактировать EXC-файл. Пример коллизии:
_PB_WriteFloat@8 0B 5366........E8........85C0742D83780400
_PB_WriteInteger@8 0B 5366........E8........85C0742D83780400
_PB_WriteLong@8 0B 5366........E8........85C0742D83780400
Видим, что три функции имеют одну и ту же сигнатуру, — нам необходимо выбрать, какую из них использовать.
Слева, рядом с именем нужной функции, ставим +. В этом случае коллизия коснулась функций, идентичных по назначению, и мы можем выбрать любую:
_PB_WriteFloat@8 0B 5366........E8........85C0742D83780400
+_PB_WriteInteger@8 0B 5366........E8........85C0742D83780400
_PB_WriteLong@8 0B 5366........E8........85C0742D83780400
Но бывает, когда она касается функций с совершенно противоположным назначением, — можно выбрать любую, но запомнить или записать ее: это пригодится, когда будет получен результат.
Нужно также удалить строку --------- в EXC-файле, чтобы выбор учитывался при повторной генерации сигнатур:
;--------- (delete these lines to allow sigmake to read this fil
; add '+' at the start of a line to select a module
; add '-' if you are not sure about the selection
; do nothing if you want to exclude all modules
После того как действие будет проделано для всех коллизий, нужно повторить генерацию. Если все сделано правильно, мы получим SIG-файл. Его следует положить в:
%IDA Home%\sig\pc
#reverse #tips #ComDllDropper #ExCobalt #APT #TI
@ptescalator
🔥11👍5👏3
В рамках ежемесячного просмотра свежезапатченных уязвимостей мы в команде ESC-VR обращаем пристальное внимание на уязвимости, помеченные как эксплуатируемые в дикой природе. Такие уязвимости становятся нашей главной целью, особенно если отсутствует какая бы то ни было информация о публичных эксплойтах.
Уязвимость CVE-2024-38178 — это повреждение памяти типа Type Confusion (CWE-843). Говоря по-простому: ситуация, когда область памяти, занимаемая объектом типа A, интерпретируется кодом как объект типа B.
Проанализировав патч, мы обнаружили, что изменения сделаны в функции, отвечающей за оптимизацию работы с массивами, в частности в функции
GlobOpt::OptArraySrc
. После исправления добавилась обработка ситуации, когда оптимизатор не замечает, что иногда тип переменной может изменяться в runtime
.Если вы следите за деятельностью
Google ProjectZero
так же активно, как и мы, то вы уже обо всем догадались 😉Функция
GlobOpt::OptArraySrc
уже фигурировала в ITW-эксплойте, а именно в посте, описывающем CVE-2022-41128. В посте есть
PoC
, который демонстрирует эксплуатацию CVE-2022–41128. Взяв из него ключевые строки, мы провели поиск в публичных и приватных источниках по файлам, загруженным недавно, используя следующие подстроки:•
6E6577204F626A656374287B0D0A20
•
206E657720496E7433324172726179
Мы нашли всего один файл. Он был загружен из
KR
, и эксплойт, вероятно, использовался в атаках в этой стране, о чем косвенно свидетельствует информация из бюллетени Microsoft. Прогнав файл в системах с патчем и без него, мы быстро поняли, что это именно то, что мы искали. В связи с большой схожестью с CVE-2022-41128 мы считаем, что и эта уязвимость была найдена через фаззинг, который проводился с использованием
PoC
для CVE-2022-41128 и CVE-2021-34480.Эксплойт создает ситуацию, когда JIT-компилятор убежден, что переменная X имеет тип
js::TypedArray<int,0>
, но на самом деле X содержит значение Y типа js::DynamicObj
. Далее эксплойт использует доступ по индексу 4, 11, 12, чтобы модифицировать внутренние поля массива js::JavaScriptNativeArray
, находящегося в одном из свойств значения Y. Модифицируемые поля хранят размер массива. В результате эксплойт дает возможность для доступа за пределы этого массива для того, чтобы получить примитивы на относительную запись и чтение. Дальнейшее описание заняло бы неприлично много места в рамках поста, поэтому stay tunned и happy hunting 🙂
YARA-правило (на файл):
rule exploit_CVE_2024_38178 {
strings:
$a = { 6E6577204F626A656374287B0D0A20 }
$b = { 206E657720496E7433324172726179 }
condition:
all of them
}
IoCs:
SHA256: 736092B71A9686FDE43D3C4ABD941A6774721B90B17D946C9D05AF19C84DF0A4
https://img[.]mobonad[.]com/images/20230912/43
#escvr #itw #jscript9 #reverse
@ptescalator
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥17⚡4👍4👏2💩1🤡1🆒1
Когда активно занимаешься реверсом, рано или поздно возникает ситуация, когда на твоем пути появляется исполняемый файл на Delphi. Анализ объектов в Delphi требует особого подхода, и вручную это делать сложно. Однако такой анализ можно значительно ускорить с помощью автоматизации, если знать, как именно устроена структура объектов.
🕵️ Первый шаг при анализе Delphi — не забыть переключить параметр компилятора в
Options
→ Compiler Options
→ Compiler
, тогда IDA будет лучше обрабатывать вызовы функций.Классы в Delphi создаются через функцию
ClassCreate
, которая получает на вход указатель на структуру класса и вызывает в нем функцию Tobject_NewInstance
путем вычитания оффсета из указателя на VMT. 👀 Сама структура класса в Delphi выглядит следующим образом (пример на скриншоте 1):
struct DelphiClassInternal
{
DWORD* vmt;
DWORD* InterfaceTable; используется только для интерфейсов
DWORD* PAutoTable;
DWORD* PInitTable;
DWORD* TypeInfo;
DWORD* FieldTable;
DWORD* MethodTable;
DWORD* DynamicMethodTable;
}
💼 Рассмотрим содержимое полей
TypeInfo
, MethodTable
и FieldTable
подробнее, поскольку в них больше всего полезной для анализа информации.➡️ TypeInfo
В Delphi каждый тип объекта имеет свой идентификатор. Как видно на скриншоте 2, для нашего объекта выставлен идентификатор 7 — тип
Class
. В зависимости от этого типа, для объекта указывается соответствующий контекст. Для классов это информация о Property
и указатель на родительский тип. Зная имя Property
, нетрудно понять и разметить Get/Set-функции
. Восстановление этих имен позволяет сильно упростить восприятие некоторых блоков кода. Пример на скриншоте 3.➡️ MethodTable
Внимательный читатель заметит, что на скриншоте 1 присутствуют имена некоторых методов. Достать их можно, заглянув в таблицу опубликованных (Published) методов (текущего и родительского классов). Delphi не хранит информацию о других типах методов: приватных, защищенных и публичных. Помимо имени, там приводятся: возвращаемый тип, количество аргументов, их типы и имена.
Псевдоструктуру метода можно представить так (пример на скриншоте 4):
struct CMArg
{
DWORD* TypeInfo;
WORD UNK;
BYTE NameLen;
char Name[];
BYTE UNK2[3];
}
struct ClassPubMethod
{
WORD EntrySize;
DWORD* MethodPtr;
BYTE NameLen;
char Name[];
WORD W_UNK1;
DWORD* ReturnType;
WORD W_UNK2;
BYTE ArgCount;
CMArg Args[];
}
Из-за механизма наследования методов в Delphi восстановление даже части имен методов имеет большую пользу, если сделать это глобально, для всех классов. Затем это можно использовать среди прочего и для генерации структуры VMT класса с осмысленными именами. Пример — на скриншоте 5.
➡️ FieldTable
Заглянув в
Class
→ FieldTable
, можно найти большое количество информации о переменных (пример на скриншоте 6). Представлена она может быть в двух вариантах: 1. В виде имени, оффсета и номера типа переменной (из таблицы типов). Первые два байта в
FieldTable
— количество элементов в этой таблице, следующие четыре — указатель на таблицу типов.2. В виде имени, оффсета и указателя на тип переменной (таблица начинается сразу после таблицы из п. 1).
Структура переменных имеет следующий вид:
struct VarTypeTable
{
WORD Count;
DWORD* Entries[];
}
struct ClassVar
{
DWORD* TypeInfo;
WORD VarOffset;
WORD UNK;
BYTE NameLen;
char Name[];
}
struct TableClassVar
{
WORD VarOffet;
WORD UNK;
WORD TableTypeNum;
BYTE NameLen;
char Name[];
}
Исходя из полученной информации о переменных можно составить структуру класса (нужно не забыть, что переменные также наследуются из родительских классов). Пример — на скриншоте 7.
🤔 Резюме: в Delphi присутствует большое количество RTTI-информации, за счет которой можно относительно просто разметить большое количество функций или восстановить структуру классов для упрощения статического анализа.
#TI #Delphi #Reverse
@ptescalator
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥25👏6👍5👎1😱1💩1🤡1
Как починить CFG 🔧
В процессе реверса ВПО мы встречаемся со случаями, когда обфускация мешает понять общий алгоритм работы. Один из примеров — создание двух последовательных противоположных условных переходов в одну точку.
Схематично это выглядит так:
То есть IDA при анализе кода идет сначала по ветке
Далее, встретив противоположный условный переход, она снова идет по ветке
При живом исполнении в независимости от состояния флагов будет выполнен переход на операнд, то есть выполнение будет как на скриншоте 2, если мы подправим control flow.
Трудность при исследовании таких шеллкодов в том, что переходов может быть много и каждый раз анализ будет о них спотыкаться. Можно попробовать починить вручную, но если таких блоков будут сотни?
✍️ Чтобы справиться с этим, напишем несложный скрипт на
Сначала составим список противоположных условных переходов и положим его в функцию, которая будет сверять две последовательные инструкции со списком:
Будем последовательно проходить каждую инструкцию до тех пор, пока не встретим нужные либо не упремся в лимит.
🧐 С помощью метода
С помощью
Затем исправляем условные переходы и просим IDA проанализировать новый код. Это необходимо, чтобы можно было найти дальнейшие блоки обфускации:
🗂 Методом
Таким образом, этот скрипт позволяет из обфусцированного кода получить нормальный ASM-код, как на скриншоте 3.
Иногда после него остаются единичные нераспознанные байты, но их легко поправить. Из этого кода можно создать функцию, декомпилировать ее и проанализировать (как на скриншоте 4).
Изучайте IDAPython, пишите скрипты. Happy reversing!
#tip #reverse #idapython
@ptescalator
В процессе реверса ВПО мы встречаемся со случаями, когда обфускация мешает понять общий алгоритм работы. Один из примеров — создание двух последовательных противоположных условных переходов в одну точку.
Схематично это выглядит так:
start:
jnX labelA
jX labelA
labelA:
<bytes>
То есть IDA при анализе кода идет сначала по ветке
False
и создает код там, откладывая ветку True
на потом. Встретив инструкцию jnX
, IDA создает код сразу после текущей.Далее, встретив противоположный условный переход, она снова идет по ветке
False
и создает код на следующем адресе, построив мусорную инструкцию, после которой дизассемблировать уже нельзя. Тогда IDA возвращается к отложенной на потом очереди и берет адрес оттуда, но беда в том, что там код уже создан, а значит анализ завершается (хорошо видно на скриншоте 1).При живом исполнении в независимости от состояния флагов будет выполнен переход на операнд, то есть выполнение будет как на скриншоте 2, если мы подправим control flow.
Трудность при исследовании таких шеллкодов в том, что переходов может быть много и каждый раз анализ будет о них спотыкаться. Можно попробовать починить вручную, но если таких блоков будут сотни?
✍️ Чтобы справиться с этим, напишем несложный скрипт на
IDAPython
, который исправит проблему автоматически. Задача — найти эти блоки и пропатчить их.Сначала составим список противоположных условных переходов и положим его в функцию, которая будет сверять две последовательные инструкции со списком:
def c_jumps(addr, n_addr):
ops = [
("jz", "jnz"),
("jnz", "jz"),
("je", "jne"),
("jne", "je"),
...
]
if (ida_ua.print_insn_mnem(addr), ida_ua.print_insn_mnem(n_addr)) in ops:
return True
return False
Будем последовательно проходить каждую инструкцию до тех пор, пока не встретим нужные либо не упремся в лимит.
def deobf(start, limit=BADADDR):
while addr != BADADDR:
n_addr = ida_search.find_code(addr, ida_search.SEARCH_DOWN)
if n_addr == BADADDR:
break
if not c_jumps(addr, n_addr):
addr = n_addr
continue
🧐 С помощью метода
find_code
из модуля ida_search
находим следующий адрес, на котором есть код, а с помощью функции c_jumps
проверяем, являются ли инструкции на этом и следующем адресе противоположными прыжками. Найдя их, мы должны проверить, указывают ли эти прыжки на одну точку (то есть равны ли их операнды):
o1 = get_operand_value(addr, 0)
o2 = get_operand_value(n_addr, 0)
if o1 != o2:
addr = n_addr
continue
insn = ida_ua.insn_t()
l1 = ida_ua.decode_insn(insn, addr)
l2 = ida_ua.decode_insn(insn, n_addr)
С помощью
get_operand_value
получаем значение операндов (у jX и jXX он один) и проверяем их равенство. Чтобы определить длину отрезка, который нужно пропатчить, с помощью decode_insn
из ida_ua
находим длины инструкций.Затем исправляем условные переходы и просим IDA проанализировать новый код. Это необходимо, чтобы можно было найти дальнейшие блоки обфускации:
after_addr = n_addr + l2
ida_bytes.patch_bytes(addr, bytes([0x90] * (l1 + l2 + 1)))
ida_auto.auto_wait()
ida_bytes.del_items(after_addr, ida_bytes.DELIT_EXPAND)
ida_auto.auto_wait()
ida_ua.create_insn(o1)
addr = o1
ida_auto.auto_wait()
🗂 Методом
patch_bytes
из ida_bytes
мы патчим инструкции, с помощью auto_wait из ida_auto
просим IDA проанализировать новый код, затем, используя del_items
, удаляем мусорные инструкции, созданные при первичном анализе, и снова анализируем. С помощью create_insn
создаем валидную инструкцию там, куда указывали условные переходы, и переанализируем в последний раз.Таким образом, этот скрипт позволяет из обфусцированного кода получить нормальный ASM-код, как на скриншоте 3.
Иногда после него остаются единичные нераспознанные байты, но их легко поправить. Из этого кода можно создать функцию, декомпилировать ее и проанализировать (как на скриншоте 4).
Изучайте IDAPython, пишите скрипты. Happy reversing!
#tip #reverse #idapython
@ptescalator
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥18👍7💯6👾3
📑 TaxOff: кажется, у вас… бэкдор
В третьем квартале специалисты TI-департамента экспертного центра безопасности Positive Technologies (PT Expert Security Center, PT ESC) обнаружили серию атак, направленных на государственные структуры России. Связей с уже известными группами, использующими такие же техники, нам установить не удалось.
😐 Основными целями киберпреступников были шпионаж и закрепление в системе для развития последующих атак. Эту группировку мы назвали TaxOff из-за использования писем на правовые и финансовые темы в качестве приманок. В своих атаках злоумышленники применяли написанный минимум на C++17 бэкдор, который мы назвали Trinper из-за артефакта, используемого при соединении с C2-сервером.
📩 Начальный вектор заражения — фишинговые письма. Мы обнаружили несколько таких: в одном была ссылка на Яндекс Диск с вредоносным содержимым, связанным с «1С», в другом — фальшивый установщик ПО для заполнения справок о доходах и расходах, которые госслужащим необходимо подавать каждый год. И ежегодно это ПО обновляется и становится целью злоумышленников, распространяющих вредоносы под видом обновлений.
Trinper — написанный на C++ многопоточный бэкдор с гибкой конфигурацией, в котором используются шаблонный метод в качестве паттерна проектирования, контейнеры STL, буферный кэш для повышения производительности.
🧐 Подробный анализ бэкдора и действий группировки TaxOff, а также индикаторы компрометации вы можете найти в отчете.
#TI #APT #IOC #Reverse
@ptescalator
В третьем квартале специалисты TI-департамента экспертного центра безопасности Positive Technologies (PT Expert Security Center, PT ESC) обнаружили серию атак, направленных на государственные структуры России. Связей с уже известными группами, использующими такие же техники, нам установить не удалось.
Trinper — написанный на C++ многопоточный бэкдор с гибкой конфигурацией, в котором используются шаблонный метод в качестве паттерна проектирования, контейнеры STL, буферный кэш для повышения производительности.
🧐 Подробный анализ бэкдора и действий группировки TaxOff, а также индикаторы компрометации вы можете найти в отчете.
#TI #APT #IOC #Reverse
@ptescalator
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥16👍7👌4💯2
Статический резолв импортов 👨💻
Динамический резолв импортов по хеш-суммам в ВПО — тема заезженная, но для проведения статического анализа необходимо разметить имена и прототипы API. Чтобы из того, что представлено на скрине 1, получить то, что на скрине 2, и не мучаться с ручной разметкой, можно написать скрипт IDAPython.
😠 На примере DodgeBox рассмотрим резолв, который заключается в вычислении адреса API-функции и помещении его в глобальную структуру. Реализация — на скрине 3.
Алгоритм хеширования опустим, поскольку здесь он не столь важен. Перед началом необходимо подготовить словарь с именами функций WinAPI и их хеш-суммами. Для этого выберем те библиотеки, что используются в бинаре. Здесь есть имена DLL в открытом виде, но иногда — только хеш-суммы, в этом случае можно составить словарь из всех системных DLL. Наш словарь — на скрине 4.
Далее убеждаемся, что члены в структуре заполняются последовательно, чтобы автоматически извлечь из кода хеш-суммы, имена модулей. Внимание — первый член структуры пропускается. Создаем структуру нужного размера (скрин 5), чтобы она вместила все функции.
Чтобы извлечь хеш-суммы и имена библиотек, пишем функцию, которая пройдет по всем вызовам
Функция
На выходе
Функция
Функция
Однако на самом деле функция возвращает объект типа
Структура API после вызова скрипта представлена на скрине 6. Если применить ее к глобальной переменной, резолв превратится в то, что видно на скрине 7, и можно будет удобно анализировать бинарь статически, не запуская отладчик.
Есть, конечно, соблазн написать функцию
#tip #reverse #idapython
@ptescalator
Динамический резолв импортов по хеш-суммам в ВПО — тема заезженная, но для проведения статического анализа необходимо разметить имена и прототипы API. Чтобы из того, что представлено на скрине 1, получить то, что на скрине 2, и не мучаться с ручной разметкой, можно написать скрипт IDAPython.
Алгоритм хеширования опустим, поскольку здесь он не столь важен. Перед началом необходимо подготовить словарь с именами функций WinAPI и их хеш-суммами. Для этого выберем те библиотеки, что используются в бинаре. Здесь есть имена DLL в открытом виде, но иногда — только хеш-суммы, в этом случае можно составить словарь из всех системных DLL. Наш словарь — на скрине 4.
Далее убеждаемся, что члены в структуре заполняются последовательно, чтобы автоматически извлечь из кода хеш-суммы, имена модулей. Внимание — первый член структуры пропускается. Создаем структуру нужного размера (скрин 5), чтобы она вместила все функции.
Чтобы извлечь хеш-суммы и имена библиотек, пишем функцию, которая пройдет по всем вызовам
get_proc_by_hash
и извлечет ее аргументы.def get_hashes(resolve_API_addr, get_proc_by_hash_addr):
result = []
func: ida_funcs.func_t
func = ida_funcs.get_func(resolve_API_addr)
cur = func.start_ea
while cur < func.end_ea:
if get_operand_value(cur, 0) == get_proc_by_hash_addr:
result.append(get_args(cur))
cur = ida_search.find_code(cur, SEARCH_DOWN)
return result
Функция
get_args
поднимается на несколько шагов вверх от операции call
и извлекает аргументы.def get_args(call_addr):
func_name_hash = None
lib_name = None
cur = call_addr
True:
if print_insn_mnem(cur) == "mov" and print_operand(cur, 0) == "r8d":
func_name_hash = get_operand_value(cur, 1) & 0xFFFFFFFF
elif print_insn_mnem(cur) == "lea" and print_operand(cur, 0) == "rcx":
lib_name = ida_bytes.get_strlit_contents(get_operand_value(cur, 1), -1, STRTYPE_C_16).decode()
if func_name_hash and lib_name:
return lib_name, func_name_hash
cur = ida_search.find_code(cur, SEARCH_UP)
На выходе
get_hashes
получим список, который используем для заполнения структуры API. Основная функция будет выглядеть так:struc: ida_struct.struc_t = ida_struct.get_struc(ida_struct.get_struc_id("API"))
funcs = get_hashes(0x180007A90, 0x1800078E0)
for i in range(1, len(funcs) + 1):
lib_name, func_name_hash = funcs[i - 1]
member: ida_struct.iss.onember_t = struc.members[i]
func_name = get_func_name(lib_name, func_name_hash, winapi_hashes_dict)
if func_name:
ida_struct.set_member_name(struc, member.soff, func_name)
func_tinfo = get_func_tinfo(func_name)
if func_tinfo:
ida_struct.set_member_tinfo(struc, member, 0, func_tinfo, 0)
Функция
get_func_name
проста в реализации, она находит в словаре имя API по хеш-сумме. А вот get_func_tinfo
более интересна: она создает объект, содержащий прототип функции, который мы также применим к члену структуры.def get_func_tinfo(func_name):
tinfo = ida_typeinf.get_named_type(None, func_name, 0)
if tinfo:
type_s = tinfo[1]
field_s = tinfo[2]
t = ida_typeinf.tinfo_t()
t.deserialize(None, type_s, field_s)
t.create_ptr(t)
return t
else:
return None
Функция
ida_typeinf.get_named_type
получает информацию о типе, который содержится в Type Library (*.til)
. Вызов выглядит так:Python>get_func_tinfo("GetWindowsDirectoryW")
UINT (__stdcall *)(LPWSTR lpBuffer, UINT uSize)
Однако на самом деле функция возвращает объект типа
ida_typeinf.tinfo_t
.Структура API после вызова скрипта представлена на скрине 6. Если применить ее к глобальной переменной, резолв превратится в то, что видно на скрине 7, и можно будет удобно анализировать бинарь статически, не запуская отладчик.
Есть, конечно, соблазн написать функцию
make_beautifully
, которая сама вычитает офсеты, создаст структуру и члены внутри нее, но об этом в другой раз.#tip #reverse #idapython
@ptescalator
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥23❤10👍10
Используете ли вы криптографию правильно? 🔓
Группе исследования сложных угроз департамента Threat Intelligence часто приходится решать интересные задачи в процессе реверса ВПО. Этот раз не стал исключением. Существует такая техника, как Execution Guardrails: Environmental Keying, — она нужна для ограничения выполнения ВПО только в конкретной целевой среде. Например, ее использует группировка Decoy Dog в своих операциях.
В попавшем к нам на исследование семпле применялась эта же техника: в качестве ключа для используемых строк использовалось имя компьютера, которого у нас не было, дополнительно семпл был обфусцирован. В случае, если ВПО запускалось не на нужном злоумышленнику устройстве, оно крашилось — отсюда и стартует наше исследование.
☠️ Выяснилось, что ВПО «падает» при попытке вызова функции
Чтобы вычислить промежуточный ключ (который формируется в результате логических операций), мы прибегнули к атаке на основе открытого текста. Возвращаясь к функции
Группа исследования сложных угроз рекомендует одну из платформ для изучения криптоанализа — cryptohack.org.
#tips #reverse #malware #cryptography #TI
@ptescalator
Группе исследования сложных угроз департамента Threat Intelligence часто приходится решать интересные задачи в процессе реверса ВПО. Этот раз не стал исключением. Существует такая техника, как Execution Guardrails: Environmental Keying, — она нужна для ограничения выполнения ВПО только в конкретной целевой среде. Например, ее использует группировка Decoy Dog в своих операциях.
В попавшем к нам на исследование семпле применялась эта же техника: в качестве ключа для используемых строк использовалось имя компьютера, которого у нас не было, дополнительно семпл был обфусцирован. В случае, если ВПО запускалось не на нужном злоумышленнику устройстве, оно крашилось — отсюда и стартует наше исследование.
LoadLibraryA
с переданным именем библиотеки в качестве неотображаемых байтов. Этот набор байтов должен представлять собой имя библиотеки, которая расшифровывается в цикле; при каждой итерации берется символ от имени компьютера, с помощью логических операций приводится в необратимый вид, а после уже гаммируется с зашифрованным текстом и его однобайтовой константой. Чтобы вычислить промежуточный ключ (который формируется в результате логических операций), мы прибегнули к атаке на основе открытого текста. Возвращаясь к функции
LoadLibraryA
: мы знаем, что имя библиотеки будет составлять 13 байт в кодировке ASCII + '\x00'
; так мы смогли восстановить треть ключа, а далее по аналогии уже с другими строками восстановили оставшуюся часть ключа.Дешифрование строки:
F(sym_comp_name) ^ sym_enc ^ cnst_enc = sym_dec
F(sym_comp_name) — логические операции
Получение промежуточного ключа:
sym_dll_name ^ sym_enc ^ cnst_enc = irr_sym_key
irr_sym_key — промежуточный байт ключа, необратимый
Группа исследования сложных угроз рекомендует одну из платформ для изучения криптоанализа — cryptohack.org.
#tips #reverse #malware #cryptography #TI
@ptescalator
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18❤17🔥12🆒1
Как починить CFG. Часть вторая 🛠
Ранее мы рассказывали про то, как восстановить Control Flow Graph (CFG) в случае его обфускации. Однако часто при анализе даже необфусцированного ВПО можно встретить случаи, когда CFG некоторых функций генерируется с ошибками. Одним из таких примеров является написанное на Delphi ВПО, где из-за особенностей обработки исключений часто можно встретить картину, как на скриншоте 1.
🤔 Если присмотреться внимательнее (скриншот 2), то можно заметить, что в блоке (1) на стек сохраняется адрес одного из последующих блоков (3), после чего выполняется некоторая логика (зачастую — освобождение ресурсов или объектов) и происходит прыжок на сохраненный ранее адрес (2).
Из-за того что в блоке 2 присутствует дополнительная ссылка, IDA не может однозначно определить адрес, куда будет осуществлен прыжок в
Создадим класс хука, который будет ожидать событие
Напишем функцию поиска сохраняемого на стек адреса
Добавим инициализацию хука при запуске скрипта и загрузим его в IDA. Запустим повторный анализ бинарного файла. В результате получим исправленный граф (скриншот 3).
Оставшиеся одиночные блоки — это вызовы обработчиков исключений; в данном случае они не влияют на ход работы программы. Стоит помнить, что пока хук активен, он будет автоматически вызываться даже при разметке нового кода, который не был размечен ранее.
Happy reversing! 💫
#tip #reverse #idapython
@ptescalator
Ранее мы рассказывали про то, как восстановить Control Flow Graph (CFG) в случае его обфускации. Однако часто при анализе даже необфусцированного ВПО можно встретить случаи, когда CFG некоторых функций генерируется с ошибками. Одним из таких примеров является написанное на Delphi ВПО, где из-за особенностей обработки исключений часто можно встретить картину, как на скриншоте 1.
🤔 Если присмотреться внимательнее (скриншот 2), то можно заметить, что в блоке (1) на стек сохраняется адрес одного из последующих блоков (3), после чего выполняется некоторая логика (зачастую — освобождение ресурсов или объектов) и происходит прыжок на сохраненный ранее адрес (2).
Из-за того что в блоке 2 присутствует дополнительная ссылка, IDA не может однозначно определить адрес, куда будет осуществлен прыжок в
jmp eax
. Чтобы исправить эту проблему, напишем небольшой хук, который будет проверять и автоматически патчить подобные места в коде.Создадим класс хука, который будет ожидать событие
ev_ana_insn
. Для начала необходимо убедиться, что это действительно интересующая нас последовательность, после чего пройтись «вверх» и попытаться найти сохраненный на стек адрес. Затем пропатчить jmp eax
на jmp short address
.class DelphiJmpEaxFixer(idaapi.IDP_Hooks):
def lookup_push_insn(self, start: int, limit: int = 30) -> int | None:
...
def ev_ana_insn(self, insn: idaapi.insn_t) -> bool:
#
b = bytes(idaapi.get_bytes(insn.ea - 1, 3))
if idaapi.is_tail(idaapi.get_flags(insn.ea)):
return True
# pop eax | 58
# jmp eax | ff e0
# ensure all pop & jmp seq
if b[0] != 0x58 or b[1] != 0xFF or b[2] != 0xE0:
return False
print(f"Got jmp short eax at {insn.ea:x}")
pushed_address = self.lookup_push_insn(insn.ea)
if pushed_address is None:
return False
delta = pushed_address - insn.ea
if delta < 0 or delta > 128:
return False
print(f"{delta=}")
asm_call = f"jmp short {delta}"
assembled = idaapi.AssembleLine(insn.ea, 0, 0, True, asm_call)
if assembled is None:
return False
return idaapi.patch_bytes(insn.ea, assembled)
Напишем функцию поиска сохраняемого на стек адреса
lookup_push_insn
. В ней найдем предположительную верхнюю границу блока 2 и проверим, что блок имеет единственную ссылку. Дополнительно ограничим диапазон поиска, в целях оптимизации.def lookup_push_insn(self, start: int, limit: int = 30) -> int | None:
ptr: int = start
insn = idaapi.insn_t()
jmp_ref = idaapi.BADADDR
for _ in range(limit, 0, -1):
prev_addr = idaapi.decode_prev_insn(insn, ptr)
if prev_addr == idaapi.BADADDR:
break
ptr = prev_addr
_refs = [xref for xref in idautils.CodeRefsTo(ptr, False)]
# If we found refs it's likely an upper basic block address
# it must be a single jmp ref
if _refs:
if len(_refs) != 1:
return None
jmp_ref = next(iter(_refs))
break
if jmp_ref == idaapi.BADADDR:
return None
ref_insn_sz = idaapi.decode_insn(insn, jmp_ref)
if ref_insn_sz == 0 or insn.itype != idaapi.NN_jmp:
return None
addr = idaapi.decode_prev_insn(insn, ptr)
if addr == idaapi.BADADDR or insn.itype != idaapi.NN_push:
return None
return insn.Op1.value
Добавим инициализацию хука при запуске скрипта и загрузим его в IDA. Запустим повторный анализ бинарного файла. В результате получим исправленный граф (скриншот 3).
Оставшиеся одиночные блоки — это вызовы обработчиков исключений; в данном случае они не влияют на ход работы программы. Стоит помнить, что пока хук активен, он будет автоматически вызываться даже при разметке нового кода, который не был размечен ранее.
hook_instance = DelphiJmpEaxFixer()
hook_instance.hook()
Happy reversing! 💫
#tip #reverse #idapython
@ptescalator
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥21👍11❤8👏3🆒2
@Вредонос попал в систему...
@Вредонос хочет сделать пакость...
@Вредонос вызывает WinAPI и...
@EDR-система обнаруживает его и начинает очень громко орать...
😱 Не самое приятное развитие событий для вирусописателей. Как же они борются с подобными ситуациями?
Один из способов — вызывать необходимую функцию Windows не через WinAPI, а через системные вызовы (system calls). Многие EDR-системы перехватывают вызовы WinAPI-функций, но далеко не всегда они определяют использование system calls. Этой особенностью можно воспользоваться для обхода защитных механизмов.
SysWhispers
Зачем самому реализовывать механизм вызова
Выглядит это так: вызываем функцию
🧐 И вот теперь приходит исследователь, которому нужно это отреверсить. Как же ему понять, какой syscall используется?
Напрямую получить номер нужной системной функции мы не можем: он захеширован, поэтому придется что-то придумать. Можно написать скрипт, который будет реализовывать тот же алгоритм хеширования, что и SysWhispers (по сути, в нем используется XOR имени функции по случайному значению). Но можно пойти другим путем: с помощью механизма
Просто сравните — написать скрипт, который будет воссоздавать хеш-функцию, или одну строчку кода на Python:
👋 Угу, номер syscall у нас есть, а что дальше, как узнать его имя?
Можем найти его по номеру в табличке, а можем обойтись без нее и податься в автоматизацию процесса. Как ранее было сказано,
Код скрипта, реализующего эту логику, представлен в публикации ниже. Основная идея заключается в том, что все функции-обертки над системными вызовами однотипны и выглядят примерно так, как показано на скриншоте 2. Перед вызовом инструкции
😮💨 Вот таким незамысловатым образом реверсер может облегчить себе жизнь, когда ему попадется вредонос с SysWhispers. Напоследок еще раз (на всякий случай) скажем, что представленный скрипт будет работать только в режиме отладки, поэтому, если речь заходит про анализ чего-то зловредного, то лучше делать это в виртуальной машине. Так что стоит отметить, что IDA работает со многими отладчиками, в нашем же случае все разрабатывалось и проверялось с использованием WinDbg.
#reverse #malware #tip
@ptescalator
@Вредонос хочет сделать пакость...
@Вредонос вызывает WinAPI и...
@EDR-система обнаруживает его и начинает очень громко орать...
Один из способов — вызывать необходимую функцию Windows не через WinAPI, а через системные вызовы (system calls). Многие EDR-системы перехватывают вызовы WinAPI-функций, но далеко не всегда они определяют использование system calls. Этой особенностью можно воспользоваться для обхода защитных механизмов.
SysWhispers
Зачем самому реализовывать механизм вызова
syscall
, если добрые люди уже это сделали за тебя? Правильно, будем использовать готовое решение — SysWhispers (в этом посте будем говорить про его вторую версию). Стоит отметить, что помимо предоставления удобного интерфейса, с помощью которого можно вызывать syscall
, SysWhispers также хеширует их.Выглядит это так: вызываем функцию
SW2_GetSyscallNumber
, в которую передаем хеш необходимого syscall
. В ответ функция возвращает номер системной функции, которую мы и вызываем с помощью инструкции syscall
. Более подробно этот процесс показан на скриншоте 1 (на нем приведен пример вызова функции NtWriteVirtualMemory
).🧐 И вот теперь приходит исследователь, которому нужно это отреверсить. Как же ему понять, какой syscall используется?
Напрямую получить номер нужной системной функции мы не можем: он захеширован, поэтому придется что-то придумать. Можно написать скрипт, который будет реализовывать тот же алгоритм хеширования, что и SysWhispers (по сути, в нем используется XOR имени функции по случайному значению). Но можно пойти другим путем: с помощью механизма
Appcall
в IDA Pro (Appcall работает только в режиме отладки, поэтому с вредоносами лучше работать в виртуальной машине) напрямую вызвать SW2_GetSyscallNumber
, передав ей хеш необходимой функции. Мы все также получим номер функции, но при этом процесс пройдет значительно быстрее. Просто сравните — написать скрипт, который будет воссоздавать хеш-функцию, или одну строчку кода на Python:
syscall_id = idaapi.Appcall.SW2_GetSyscallNumber(hash).value
👋 Угу, номер syscall у нас есть, а что дальше, как узнать его имя?
Можем найти его по номеру в табличке, а можем обойтись без нее и податься в автоматизацию процесса. Как ранее было сказано,
Appcall
работает именно в режиме отладки, в этом режиме мы можем получить имена функций, определенных в различных модулях, загружаемых во время исполнения бинаря. Среди этих модулей самым интересным для нас является ntdll.dll
: в нем находятся определения оберток над системными вызовами, которые имеют идентичные названия. По этим оберткам мы и получим имя системной функции. Код скрипта, реализующего эту логику, представлен в публикации ниже. Основная идея заключается в том, что все функции-обертки над системными вызовами однотипны и выглядят примерно так, как показано на скриншоте 2. Перед вызовом инструкции
syscall
происходит передача номера нужной функции в регистр EAX
(вторая инструкция). Мы можем взять вторую инструкцию функции-обертки, получить ее операнд — это и будет номер системного вызова. Далее остается сравнить его с тем, что ищем мы, и вывести результат (скриншот 3).😮💨 Вот таким незамысловатым образом реверсер может облегчить себе жизнь, когда ему попадется вредонос с SysWhispers. Напоследок еще раз (на всякий случай) скажем, что представленный скрипт будет работать только в режиме отладки, поэтому, если речь заходит про анализ чего-то зловредного, то лучше делать это в виртуальной машине. Так что стоит отметить, что IDA работает со многими отладчиками, в нашем же случае все разрабатывалось и проверялось с использованием WinDbg.
#reverse #malware #tip
@ptescalator
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍19🔥10👌5
Реально тонкое взаимодействие 🕊
В ходе реверс-инжиниринга протокола одного из бразильских банковских троянов обнаружилось использование интересного сетевого фреймворка — RealThinClient. Процесс общения между клиентом и сервером в этом фреймворке построен на сериализации структур вызова функции в строковый формат и их последующей десериализации на другой стороне. Это делает RTC не только мощным инструментом разработки, но и удобной платформой для скрытого обмена командами — например, в случае малварного поведения.
Сначала клиент и сервер проходят через кастомное рукопожатие, в ходе которого устанавливаются ключи шифрования и дешифрования для обеих сторон. После этого фреймворк предоставляет возможность выполнять функции на стороне как клиента, так и сервера.
Перейдем сразу к примеру и на нем разберемся в логике работы инструмента. Клиент хочет залогиниться на сервере и отправляет запрос, как на скриншоте.
Что здесь происходит
1. Клиент формирует запрос:
• Устанавливает параметр
• Добавляет параметры: логин, пароль и id. Они передаются в виде структуры-словаря
• На стороне Delphi это будет примерно так:
А при сериализации все это превратится в следующее:
📁 Когда мы вызываем удаленную функцию через RTC SDK, мы создаем объект
• Перед отправкой запроса клиент регистрирует хендлер OnLoginResult для обработки результата выполнения функции на сервере.
2. Запрос отправляется на сервер RTC:
• Сервер принимает HTTP-запрос и интерпретирует параметр
• На стороне сервера вызывается обработчик для функции
3. Сервер отвечает:
• Результат функции возвращается клиенту, и он вызывает свой хендлер
• Ответ сервера десериализуется обратно в
Например:
👾 Это может успешно использоваться в малвари: сервер возвращает зашифрованное описание команды, которую клиент должен выполнить. Таким образом, в легитимный RTC-поток можно прятать вполне себе вредоносное поведение.
#reverse #hacktool
@ptescalator
В ходе реверс-инжиниринга протокола одного из бразильских банковских троянов обнаружилось использование интересного сетевого фреймворка — RealThinClient. Процесс общения между клиентом и сервером в этом фреймворке построен на сериализации структур вызова функции в строковый формат и их последующей десериализации на другой стороне. Это делает RTC не только мощным инструментом разработки, но и удобной платформой для скрытого обмена командами — например, в случае малварного поведения.
Сначала клиент и сервер проходят через кастомное рукопожатие, в ходе которого устанавливаются ключи шифрования и дешифрования для обеих сторон. После этого фреймворк предоставляет возможность выполнять функции на стороне как клиента, так и сервера.
Перейдем сразу к примеру и на нем разберемся в логике работы инструмента. Клиент хочет залогиниться на сервере и отправляет запрос, как на скриншоте.
Что здесь происходит
1. Клиент формирует запрос:
• Устанавливает параметр
FC
(function call) в значение Login
.• Добавляет параметры: логин, пароль и id. Они передаются в виде структуры-словаря
RE=3
(record), содержащей ключи user
, pwd
, id
и т. п. В user
можно передать зашифрованный малварный запрос. RE обозначает тип rtc_Record
— это структура вида «ключ:значение», аналогичная словарю или JSON-объекту.• На стороне Delphi это будет примерно так:
with FunctionCall.Param do
begin
asText['user'] := '...'; // rtc_Text
asText['pwd'] := ''; // rtc_Text
asString['id'] := '8DF313279CD34B81A3FB438708B4E8F1'; // rtc_String
end;
А при сериализации все это превратится в следующее:
RE=3;
user:T=...;
pwd:T=...;
id:S=...
📁 Когда мы вызываем удаленную функцию через RTC SDK, мы создаем объект
TRtcFunctionInfo
— это класс, который описывает удаленную вызываемую функцию, ее имя, параметры и результат выполнения. Он используется как на клиенте (для упаковки вызова), так и на сервере (для распаковки и выполнения). Объект TRtcFunctionInfo
упаковывается в контейнер TRtcValue
и сериализуется в строку или поток байтов. TRtcValue
может хранить любой поддерживаемый тип данных: строку, число, массив, запись, дату, вложенную функцию и т. п. Фактически он используется везде, где нужно передать или получить данные по сети, например в параметрах удаленной функции (Param.asValue[...]
) или результатах выполнения функций (Result.asValue
). Это делает его гибким, но и потенциально непрозрачным, что хорошо для скрытой передачи команд, особенно если используются вложенные структуры типа rtc_Function
.• Перед отправкой запроса клиент регистрирует хендлер OnLoginResult для обработки результата выполнения функции на сервере.
2. Запрос отправляется на сервер RTC:
• Сервер принимает HTTP-запрос и интерпретирует параметр
FC
как указание на то, какую функцию нужно вызвать.• На стороне сервера вызывается обработчик для функции
Login
. Выполняется Delphi-процедура, связанная с этим именем.3. Сервер отвечает:
• Результат функции возвращается клиенту, и он вызывает свой хендлер
OnLoginResult
для обработки полученного ответа.• Ответ сервера десериализуется обратно в
TRtcValue
, чтобы мы смогли снова получить доступ к его структуре. Сервер может вернуть не просто данные, а вложенный вызов функции. Это структура, поле в которой содержит rtc_Function
, rtc_Record
или rtc_Array
, которые клиент десериализует и выполняет. Например:
if xData.isType = rtc_Record then
ExecuteRec(xData.asRecord)
else if xData.isType = rtc_Array then
ExecuteArr(xData.asArray)
👾 Это может успешно использоваться в малвари: сервер возвращает зашифрованное описание команды, которую клиент должен выполнить. Таким образом, в легитимный RTC-поток можно прятать вполне себе вредоносное поведение.
#reverse #hacktool
@ptescalator
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥15🤯12❤7👍2