ESCalator
6.61K subscribers
479 photos
1 video
18 files
190 links
Tips and tricks от команды экспертного центра безопасности Positive Technologies (PT ESC)
Download Telegram
⚠️ Эксперты PT ESC обнаружили попытки эксплуатации уязвимости CVE-2025-24071

Уязвимость CVE-2025-24071, затрагивающая широкий спектр операционных систем Windows, включая серверные и клиентские версии Windows 10 и Windows 11, была выявлена 11 марта.

CVE-2025-24071 связана с механизмом обработки файлов в Windows Explorer и системой индексирования — они автоматически анализируют файл .library-ms, извлекаемый при распаковке вредоносного архива и содержащий ссылку на SMB-ресурс.

Операционная система без взаимодействия с пользователем инициирует NTLM-аутентификацию на атакующем SMB-сервере — это приводит к утечке хеш-суммы NTLMv2 учетной записи жертвы, что может быть использовано для атаки.

Впервые описание и РoС уязвимости привел исследователь 0x6rss в своем блоге. Эксперты также заметили, что уязвимость может использоваться при сохранении файла с расширением .library-ms из электронного письма.

🕵️‍♀️ Несмотря на то что данные об уязвимости были опубликованы пару дней назад, злоумышленники не спят: мы уже обнаружили попытки эксплуатации CVE-2025-24071 в организациях в России и Республике Беларусь.

Атакующие распространяют архивы, в которых содержатся документ в формате PDF и файл .library-ms (скриншот 1). Жертва распаковывает архив и запускает PDF-приманку (скриншот 2), в то время как файл .library-ms автоматически и незаметно для пользователя отправляет данные на командный центр злоумышленников.

📈 Эксперты PT ESC прогнозируют всплеск атак с использованием этой уязвимости. Мы рекомендуем следующие методы защиты:

Ограничить соединения по SMB-протоколу к внешним серверам.
Обновить Windows до последней версии (установить мартовские обновления).
Запретить запуск файлов с расширением .library-ms.
Заблокировать получение файлов с расширением .library-ms по электронной почте.

IoCs

Письмо Минпромторга России от 17.03.2025 № 182544_21 о направлении сведений по кадровому потенциалу предприятий 2025.pdf.library-ms
MD5: c3f9813545b7f830183369dd649bd595
SHA-1: fcadd1a24f2fa6e0f5338ff0e8d186258c79a05d
SHA-256: a4205e773eee7f33d1bb776a2f7b36da4b3955284208c015257311b8ef23f721

Письмо Минпромторга России от 17.03.2025 № 182544_21.zip
MD5: 83a60de9faed1b0a0344eda108aee44f
SHA-1: 10c02f7a3214dc166f6a8ce19c3d0a988084b3ea
SHA-256: e7897176a7d226c82af27ff525399bd0c7d7b73fdfffac8d2d56b8707637aa99

01 Сопроводительное.pdf.library-ms
MD5: 74e2f206e99040868b60eef04781de8a
SHA-1: f27ecc7ec9c6425a41a3cbfa8bf74f24c32c6488
SHA-256: 8a3728ebdb64e69347c14356b250eb0720801ce367acd1b53510a8dea16f7001

01 Сопроводительное.zip
MD5: 78cd8d4481713fbd4beb790a127bd793
SHA-1: 595b68aaad8a705e78125735cdb7136b6f17b077
SHA-256: 07f6d81b5e3fba23f5de34038424ebec4710cbc16959de4389ceb7855e69bac2

spisoc.library-ms
MD5: 9bab71704cefac935546e09d12dfd2c1
SHA-1: 922c6ee612bd22a85cd1e84f50e18d21656c3f3d
SHA-256: cb9810b6492aad667554958332a3518aeed8d356dcd03b43e4116cd92c938d0f

154.205.148.56
38.60.247.250
94.250.249.129


#win #news #cve #detect #ioc
@ptescalator
🔥20👍168👏4🆒2❤‍🔥1
Как починить CFG. Часть вторая 🛠

Ранее мы рассказывали про то, как восстановить 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👍118👏3🆒2
Mmaallwwaarree iinn ooppeennssoouurrccee!

В сети развивается примечательная кампания одного исследователя. Ему принадлежат следующие пакеты:

Пользователь lastbright:
🟢yyttt
🟢bbllaacckkwwoollff
🟢bbllaacckkwwoollff-6ad8f762-1a91-45d7-a9c5-356bd858356a
🟢bbllaacckkwwoollff6ad8f762
🟢bbllaacckkwwoollff6ad8f751
🟢bbllaacckkwwoollff6ad8f752
🟢bbllaacckkwwoollff6ad8f753
Пользователь lifeyi2253:
🟢f2d5cfdc642c3d4
🟢f2d5cfdc642c3d5

Нагрузка отрабатывает в момент установки пакета.

Интересно в реальном времени наблюдать, как развивается кампания:

🔤 PoC с комментариями на китайском. Вероятно, тут приложил руку LLM-ассистент (скриншот 1, библиотека yyttt 0.1).

🔤 Результат работы Unix-команды id отправляется на удаленный сервер (скриншот 2, bbllaacckkwwoollff 0.1, 0.2).

🔤 Выполняется код, полученный с C2-сервера (скриншот 3, bbllaacckkwwoollff 0.3, 0.4, bbllaacckkwwoollff-6ad8f762-1a91-45d7-a9c5-356bd858356a 0.1).

🔤 Почему бы не прихватить листинг интересных директорий (/opt/, /run/), переменные окружения и прочую радость (скриншот 4, bbllaacckkwwoollff6ad8f753 0.1)?

🔤 Нет, так много не надо, из директорий достаточно /etc/ (скриншот 5, f2d5cfdc642c3d5 0.1).

На хосте либо на виртуалке для тестов злоумышленник использует имя пользователя mind, о чем говорит путь /home/mind/configuration/config.py в четвертой итерации (скриншот 4).

Примечателен принцип именования пакетов. Кроме того, всегда интересно следить, как в режиме реального времени злоумышленник борется с тем, что его пакеты удаляют по репортам 😈

В информационной безопасности есть термин «пирамида боли» (The Pyramid of Pain) — он описывает сложность ухода от обнаружения. Так вот, в рамках кампании злоумышленник использует один и тот же уникальный файл __init__.py, представленный на скриншоте 6. Система PT PyAnalysis легко это подсвечивает 😑

Опасайтесь всяких волков.
#ti #scs #pyanalysis
@ptescalator
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
😁20👍13🔥93
PT ESC обнаружил новую кампанию Joking Wolf 🐺

Юмористы проникают в популярные (и не очень) Telegram-каналы и публикуют несуществующие новости. Основная цель — ввести граждан в заблуждение, последствия — легкая растерянность и желание проверить информацию.

Попытки проникновения зафиксированы и в канал @ptescalator, но были успешно отбиты. Для защиты рекомендуем прокачивать навыки критического мышления. Эксклюзивное фото злоумышленников прикрепляем к посту.

IoCs опубликовали в Telegraph.

Будьте бдительны!
Please open Telegram to view this post
VIEW IN TELEGRAM
🤣81😁29🔥163👍1🎉1
@Вредонос попал в систему...
@Вредонос хочет сделать пакость...
@Вредонос вызывает 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
ESCalator
@Вредонос попал в систему... @Вредонос хочет сделать пакость... @Вредонос вызывает WinAPI и... @EDR-система обнаруживает его и начинает очень громко орать... 😱 Не самое приятное развитие событий для вирусописателей. Как же они борются с подобными ситуациями?…
Код скрипта:

import idaapi
import ida_ua
import ida_ida
import ida_name

def get_syscall_by_hash(hash: int) -> str:
syscall_id = idaapi.Appcall.SW2_GetSyscallNumber(hash).value

# Получаем имена функций
dn = ida_name.get_debug_names(
ida_ida.inf_get_min_ea(),
ida_ida.inf_get_max_ea())

for addr in dn:
# Ищем функцию с префиксом ntdll_Zw - это обёртки над syscall`ми
if "ntdll_Zw" in dn[addr]:
instruction_addr = addr + 3

# Дисассемблируем инструкцию
insn = idaapi.insn_t()
ida_ua.create_insn(instruction_addr)
idaapi.decode_insn(insn, instruction_addr)

# Сраниваем номер функции с тем, что мы ищем
if syscall_id == insn.ops[1].value:
return dn[addr]


@ptescalator
👍28🔥9🫡7
А вы знали, что злые люди не любят парсеры LNK-файлов? 👿

В марте мы писали об атаках XDSpy на российские организации. LNK-файлы, которые использовались в этой атаке, были модифицированы так, что при их обработке многие парсеры «падали». Например, это касается LECmd, LnkParse3 и ExifTool, который используется на VirusTotal (скриншот 1).

Разберемся, в чем кроется проблема и как ее исправить 🔧

В общих чертах структура LNK-файла выглядит следующим образом:

1. SHELL_LINK_HEADER с интересующим нас значением LinkFlags. Оно задает присутствие тех или иных структур в LNK-файле и выглядит так:

typedef struct
{
uint32 HasLinkTargetIDList : 1; // есть LINKTARGET_IDLIST
uint32 HasLinkInfo : 1; // есть LINKINFO
uint32 HasName : 1; // есть STRING_DATA
uint32 HasRelativePath : 1; // есть STRING_DATA
uint32 HasWorkingDir : 1; // есть STRING_DATA
uint32 HasArguments : 1; // есть STRING_DATA
uint32 HasIconLocation : 1; // есть STRING_DATA
uint32 IsUnicode : 1; // строки в UTF-16
// ... skipped
} LinkFlags;


2. [Optional] LINKTARGET_IDLIST.
3. [Optional] LINKINFO.
4. [Optional] STRING_DATA.

STRING_DATA — это массив структур StringData, каждая из которых имеет следующий формат:

CountCharacters (количество символов в строке, поэтому для юникода количество байт будет CountCharacters × 2).
String (строка, которая в соответствии с документацией MUST NOT be NULL-terminated).

Структура STRING_DATA содержит Description (Name), RelativePath, WorkingDir, Arguments и IconLocation в том порядке, который указан в LinkFlags.

🫱 Именно в CountCharacters и кроется проблема для парсеров, которые на него полагаются. Чтобы их «уронить» добрые дяденьки устанавливают большое значение в CountCharacters, например 0xFFFF. String делают длиной до 260 символов, а следующую StringData располагают ровно через 260 символов (в UTF-16 это 520 байт).

Как оказалось, Windows «под капотом» ограничивает длину строк Description, RelativePath и WorkingDir 260 символами. В итоге мы имеем следующее:

Парсеры «падают» из-за некорректных значений (считают смещение 0xFFFF и попадают на бессмысленные байты).

Windows ограничивается 260 символами, правильно попадая на следующую StringData, и запускает LNK-файл без проблем.

Ограничение в 260 символов точно подразумевается для Description, что было неочевидно, RelativePath и WorkingDir.

Для Arguments такого ограничения нет или порог сильно выше, что уже логично. При этом в свойствах LNK-файла при нажатии правой кнопкой будет отображаться только 260 первых символов команды.

Windows считает последний байт нулевым, даже если это не так, хотя из документации можно было предположить, что последний символ тоже учитывается.

🕵️‍♀️ В варианте LNK-файла от XDSpy атакующие «сломали» таким образом строку WorkingDir и установили ее значение в C:\Windows\System32\<пробелы> с длиной 260 символов (скриншот 2). Здесь можно посмотреть, как выглядит такой файл на VT (скриншот 3).

---- WorkingDir ----
0x0 CountCharacters (0XFFFF)
0x2 String (до 0x208) // C:\Windows\System32_______
---- Arguments ----
0x20A CountCharacters // Парсер Windows идет сюда
0x20C String // Arguments
...
0xFFFF RandomData // Обычные парсеры идут сюда


Чтобы исправить это недоразумение, нужно проверять размер CountCharacters для релевантных строк и ограничивать смещение до следующей StringData 260 символами с учетом флага isUnicode.

#tip #win
@ptescalator
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥20👍155👏5😁2😢1