ESCalator
3.98K subscribers
227 photos
1 video
1 file
95 links
Tips and tricks от команды экспертного центра безопасности Positive Technologies (PT ESC)
加入频道
Рапид-расшифровка данных из NSIS-скрипта 🗄

Когда нет времени на идентификацию алгоритма шифрования и реализацию алгоритма дешифрования, на помощь приходит отладчик. Но как быть со скриптовым языком для установщика?

В качестве примера взят загрузчик XDigo

Для начала извлечем скрипт, расширение которого — .nsi. Воспользуемся специальной версией 7-Zip.

После этого нам нужен компилятор .nsi-скриптов — скачаем и установим NSIS. Откроем компилятор и загрузим в него извлеченный скрипт. Базовые возможности программы ограничены: он позволяет только скомпилировать скрипт в исполняемый файл и запустить его.

Для того чтобы расширить функциональность компилятора, нам понадобится Debug Plug-In. Набор его функций не очень широк, но их вполне хватит, чтобы динамически расшифровать данные.

После установки плагина откроем исходный вредоносный скрипт и в конце функции дешифрования, после операции передачи расшифрованных данных в стек, добавим строку:
Debug::Stack


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

#reverse #tips #malware #XDigo #TI
@ptescalator
👏 Раз-два — и готово. Генерируем FLIRT-сигнатуры

А делаем это, чтобы не тратить время на распознавание библиотечного кода 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
🔦 Как мы нашли ITW-эксплойт для CVE-2024-38178

В рамках ежемесячного просмотра свежезапатченных уязвимостей мы в команде 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



http://img[.]mobonad[.]com/images/20230912/43


#escvr #itw #jscript9 #reverse
@ptescalator
Please open Telegram to view this post
VIEW IN TELEGRAM
🛠 Реверсим Delphi без IDR

Когда активно занимаешься реверсом, рано или поздно возникает ситуация, когда на твоем пути появляется исполняемый файл на Delphi. Анализ объектов в Delphi требует особого подхода, и вручную это делать сложно. Однако такой анализ можно значительно ускорить с помощью автоматизации, если знать, как именно устроена структура объектов.

🕵️ Первый шаг при анализе Delphi — не забыть переключить параметр компилятора в OptionsCompiler OptionsCompiler, тогда 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

Заглянув в ClassFieldTable, можно найти большое количество информации о переменных (пример на скриншоте 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
Как починить CFG 🔧

В процессе реверса ВПО мы встречаемся со случаями, когда обфускация мешает понять общий алгоритм работы. Один из примеров — создание двух последовательных противоположных условных переходов в одну точку.

Схематично это выглядит так:


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
📑 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
Please open Telegram to view this post
VIEW IN TELEGRAM
Статический резолв импортов 👨‍💻

Динамический резолв импортов по хеш-суммам в ВПО — тема заезженная, но для проведения статического анализа необходимо разметить имена и прототипы API. Чтобы из того, что представлено на скрине 1, получить то, что на скрине 2, и не мучаться с ручной разметкой, можно написать скрипт IDAPython.

😠 На примере DodgeBox рассмотрим резолв, который заключается в вычислении адреса API-функции и помещении его в глобальную структуру. Реализация — на скрине 3.

Алгоритм хеширования опустим, поскольку здесь он не столь важен. Перед началом необходимо подготовить словарь с именами функций 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.member_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
Используете ли вы криптографию правильно? 🔓

Группе исследования сложных угроз департамента 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