Распознаем STL-код легко 😐
Нередко в процессе реверс-инжиниринга мы сталкиваемся с STL-кодом, анализ которого на первый взгляд кажется затруднительным. Неопытный глаз может принять этот код за полезный и потратить время на анализ какого-нибудь конструктора.
На самом деле здесь важно потратить время на то, чтобы распознать STL-контейнер по косвенным признакам, быстро понять, где какие данные лежат, типизировать их и идти дальше. В новой серии постов мы расскажем о самых распространенных контейнерах STL и начнем с
Паттерн1️⃣ : три последовательных чтения/записи указателей
Именно эта тройка и определяет схему памяти вектора. Компилятор расставляет их в памяти последовательно (смещения 0, 8, 16 байт в 64-битном коде). Поэтому в ассемблере мы всегда будем видеть обращения по этим смещениям — это наш главный ориентир.
Сразу посмотрим на простой пример на скриншоте 2. Не имея исходника, видно, что общая картина выглядит скомкано. Слева в ассемблерном коде мы видим кусок кода:
Компилятор обращается к членам вектора как к смещениям относительно базового адреса. Базовый адрес здесь в локальной переменной var_68. Первые 16 байт обнуляются: по смещению
Таким образом, мы видим инициализацию пустого вектора, выполняемую вместо вызова конструктора по умолчанию. Проверяем размер вектора — 16 + 8 = 24 байта. На скриншоте 3 видно, как выглядит вектор на стеке.
Это облегчает нам задачу. Мы пока предполагаем, что работаем с вектором, но все еще есть шанс, что мы захотим зареверсить
Паттерн2️⃣ :
В C++ вектор
Обычно, если используется данный метод, реализация в псевдокоде будет выглядеть примерно так:
Код проверяет, есть ли место для нового элемента в векторе. Если нет, то оно выделяется и элемент добавится в конец вектора. Если есть, значение добавляется по адресу, на который сейчас указывает
👀 Видим, что паттерн просматривается. Есть сравнение указателей
🤝 Теперь, когда понятно, что перед нами вектор, можем воссоздать его структуру (скриншот 6) и структуру, которая его использует, и разметить их в IDA (скриншот 7). Выглядит немного лучше и понятнее.
Несмотря на наличие и других конструкторов и методов у
Stay tuned!
#tip #reverse
@ptescalator (X, Max)
Нередко в процессе реверс-инжиниринга мы сталкиваемся с STL-кодом, анализ которого на первый взгляд кажется затруднительным. Неопытный глаз может принять этот код за полезный и потратить время на анализ какого-нибудь конструктора.
На самом деле здесь важно потратить время на то, чтобы распознать STL-контейнер по косвенным признакам, быстро понять, где какие данные лежат, типизировать их и идти дальше. В новой серии постов мы расскажем о самых распространенных контейнерах STL и начнем с
std::vector.Паттерн
std::vector — один из самых простых контейнеров. Он занимает в памяти 24 байта в 64-битных сборках. Если открыть исходный код std::vector в MSVC STL, мы увидим класс _Vector_val (скриншот 1), который хранит ровно три указателя — начало (_Myfirst) и конец данных (_Mylast), а также конец выделенной памяти (_Myend).Именно эта тройка и определяет схему памяти вектора. Компилятор расставляет их в памяти последовательно (смещения 0, 8, 16 байт в 64-битном коде). Поэтому в ассемблере мы всегда будем видеть обращения по этим смещениям — это наш главный ориентир.
Сразу посмотрим на простой пример на скриншоте 2. Не имея исходника, видно, что общая картина выглядит скомкано. Слева в ассемблерном коде мы видим кусок кода:
xorps xmm1, xmm1 ; и xmm1
movdqu xmmword ptr [rbp+57h+var_68], xmm1 ; записываем 16 нулевых байт по адресу var_68
xor r15d, r15d ; обнуляем r15d
mov [rbp+57h+var_58], r15 ; записываем 8 нулевых байт по адресу var_58
Компилятор обращается к членам вектора как к смещениям относительно базового адреса. Базовый адрес здесь в локальной переменной var_68. Первые 16 байт обнуляются: по смещению
var_68 лежит _Myfirst, по смещению var_60 — _Mylast. Затем обнуляется _Myend, который находится по смещению var_58.Таким образом, мы видим инициализацию пустого вектора, выполняемую вместо вызова конструктора по умолчанию. Проверяем размер вектора — 16 + 8 = 24 байта. На скриншоте 3 видно, как выглядит вектор на стеке.
Это облегчает нам задачу. Мы пока предполагаем, что работаем с вектором, но все еще есть шанс, что мы захотим зареверсить
sub_140002860. Посмотрим на второй паттерн, осознаем себя и не станем этого делать.Паттерн
push_back и проверка на заполненность вектораВ C++ вектор
push_back() — это встроенный метод, используемый для добавления нового элемента в конец вектора. Он автоматически изменяет размер вектора, если для размещения нового элемента недостаточно места. Стоит отметить, что это не единственный и не основной способ заполнения вектора, но в данном посте мы рассмотрим его как конкретную операцию, в которой проявляется основной паттерн.Обычно, если используется данный метод, реализация в псевдокоде будет выглядеть примерно так:
if (_Mylast == _Myend)
push_back_func();
else
construct(_Mylast, value);
++_Mylast;
Код проверяет, есть ли место для нового элемента в векторе. Если нет, то оно выделяется и элемент добавится в конец вектора. Если есть, значение добавляется по адресу, на который сейчас указывает
_Mylast. Посмотрим в наш пример на скриншоте 4.👀 Видим, что паттерн просматривается. Есть сравнение указателей
_Mylast и _Myend, за которым следует вызов функции sub_140002860, в которую передаются наши указатели и целевое значение. Если мы зайдем в sub_140002860, первое, что мы увидим, будет вычисление размера вектора — size() = (_Mylast - _Myfirst) / sizeof(T) (скриншот 5). То есть функции, чтобы понять, нужно ли выделять новую память, требуется вычислить новый размер вектора и сравнить его с текущей вместимостью. Для нас это подсказка — мы смотрим в функцию push_back.🤝 Теперь, когда понятно, что перед нами вектор, можем воссоздать его структуру (скриншот 6) и структуру, которая его использует, и разметить их в IDA (скриншот 7). Выглядит немного лучше и понятнее.
Несмотря на наличие и других конструкторов и методов у
std::vector, знание его внутреннего расположения в памяти позволяет легко идентифицировать вектор в скомпилированном коде.Stay tuned!
#tip #reverse
@ptescalator (X, Max)
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
❤16👍11🔥10🤯3✍1
Путаница в уязвимостях WSUS: ставим все на свои места 🕷
Одной из самых актуальных уязвимостей в Windows Server Update Services (WSUS) стала критическая ошибка с идентификатором CVE-2025-59287 и оценкой CVSS 9.8. Она связана с десериализацией недоверенных данных в службе обновления Windows Server и позволяет неавторизованному удаленному злоумышленнику выполнить код на сервере, отправив специально сформированное событие.
В разборах эксплуатации шаги были указаны некорректно, так как их взяли из статьи. Однако позже авторы сами ее исправили и указали, что разбор относится к CVE-2023-35317, тогда как анализ CVE-2025-59287 перенесли в отдельную статью.
Это вызвало путаницу в многочисленных репостах, поэтому мы решили расставить все точки над i и заодно показать, как атакующий может восстановить оснастку после эксплуатации уязвимости.
❗️ Напомним технические детали эксплуатации
Условия для эксплуатации:
• Сервер Windows с включенной ролью WSUS Server (по умолчанию отключена)
• Неустановленные обновления
• Сетевой доступ к портам
• Учетные данные не требуются
Техническая цепочка эксплуатации:
1️⃣ Получение конфигурации — злоумышленник отправляет запрос к
2️⃣ Извлечение cookies — используя ServerID, выполняется запрос к
3️⃣ Получение криптографических данных — запрос к
4️⃣ Доставка полезной нагрузки — финальный запрос отправляет событие с вредоносным сериализованным объектом, созданным через
При обработке события с ID 389 (
Также в процессе эксплуатации регистрируется ложный узел с указанным DNS, в рамках которого отправляется событие. Этот процесс легко автоматизировать — например, с помощью шаблона Nuclei (скриншот 5). Восстановление оснастки после тестирования — на скриншоте 6.
🗿 После успешной эксплуатации оснастка WSUS ломается. Это происходит потому, что GUI при отображении узлов парсит их события, а у ложного узла событие повреждено и не поддается десериализации. В результате отображение узлов вызывает исключение и интерфейс перестает работать.
Для восстановления необходимо удалить вредоносное событие из таблицы
При этом служба WSUS имеет доступ к функциям, используемым в оснастке, поэтому пользователь, от имени которого получена сессия (даже
Эта команда выполняет две важные функции:
• Удаляет зарегистрированный ложный узел из базы данных WSUS
• Автоматически очищает связанное с ним событие с ID 389, использованное для доставки полезной нагрузки
🧐 Почему это важно: без очистки в WSUS остаются артефакты — ложный узел и события в логах, что может нарушить нормальную работу службы.
Эта уязвимость демонстрирует, как критические компоненты инфраструктуры могут становиться точкой входа для злоумышленников. При проведении тестирования на проникновение важно не только атаковать, но и корректно восстанавливать систему после эксплуатации.
Рекомендации — в посте ниже 🔽
#offensive #nuclei #wsus #cve
@ptescalator (X, Max)
Одной из самых актуальных уязвимостей в Windows Server Update Services (WSUS) стала критическая ошибка с идентификатором CVE-2025-59287 и оценкой CVSS 9.8. Она связана с десериализацией недоверенных данных в службе обновления Windows Server и позволяет неавторизованному удаленному злоумышленнику выполнить код на сервере, отправив специально сформированное событие.
В разборах эксплуатации шаги были указаны некорректно, так как их взяли из статьи. Однако позже авторы сами ее исправили и указали, что разбор относится к CVE-2023-35317, тогда как анализ CVE-2025-59287 перенесли в отдельную статью.
Это вызвало путаницу в многочисленных репостах, поэтому мы решили расставить все точки над i и заодно показать, как атакующий может восстановить оснастку после эксплуатации уязвимости.
Условия для эксплуатации:
• Сервер Windows с включенной ролью WSUS Server (по умолчанию отключена)
• Неустановленные обновления
KB5070879 / KB5070881 / KB5070882 / KB5070883 / KB5070884 / KB5070886 / KB5070887• Сетевой доступ к портам
8530 (HTTP) или 8531 (HTTPS)• Учетные данные не требуются
Техническая цепочка эксплуатации:
1️⃣ Получение конфигурации — злоумышленник отправляет запрос к
/ReportingWebService/ReportingWebService.asmx для получения ServerID (скриншот 1)2️⃣ Извлечение cookies — используя ServerID, выполняется запрос к
/SimpleAuthWebService/SimpleAuth.asmx для получения AuthorizationCookie (скриншот 2)3️⃣ Получение криптографических данных — запрос к
/ClientWebService/Client.asmx для извлечения временных меток и зашифрованной нагрузки (скриншот 3)4️⃣ Доставка полезной нагрузки — финальный запрос отправляет событие с вредоносным сериализованным объектом, созданным через
ysoserial.net с гаджетом TextFormattingRunProperties, с использованием события SynchronizationCompletedCancel (скриншот 4)При обработке события с ID 389 (
SynchronizationCompletedCancel) WSUS пытается десериализовать XML с телом ошибки, что приводит к выполнению произвольного кода.Также в процессе эксплуатации регистрируется ложный узел с указанным DNS, в рамках которого отправляется событие. Этот процесс легко автоматизировать — например, с помощью шаблона Nuclei (скриншот 5). Восстановление оснастки после тестирования — на скриншоте 6.
Для восстановления необходимо удалить вредоносное событие из таблицы
tbEventInstance в базе данных SUSDB. Однако прямой доступ к БД есть только у привилегированных пользователей, а служба WSUS часто запущена от имени Network Service.При этом служба WSUS имеет доступ к функциям, используемым в оснастке, поэтому пользователь, от имени которого получена сессия (даже
Network Service), может управлять зарегистрированными узлами. Это позволяет безопасно завершить эксплуатацию и сразу восстановить оснастку.$wsus = Get-WsusServer
Get-WsusComputer -NameIncludes "test1337.test.local" | ForEach-Object {
$wsus.GetComputerTarget($_.Id).Delete()
}
Эта команда выполняет две важные функции:
• Удаляет зарегистрированный ложный узел из базы данных WSUS
• Автоматически очищает связанное с ним событие с ID 389, использованное для доставки полезной нагрузки
🧐 Почему это важно: без очистки в WSUS остаются артефакты — ложный узел и события в логах, что может нарушить нормальную работу службы.
Эта уязвимость демонстрирует, как критические компоненты инфраструктуры могут становиться точкой входа для злоумышленников. При проведении тестирования на проникновение важно не только атаковать, но и корректно восстанавливать систему после эксплуатации.
Рекомендации — в посте ниже 🔽
#offensive #nuclei #wsus #cve
@ptescalator (X, Max)
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14🔥7💯6❤3💋1
ESCalator
Путаница в уязвимостях WSUS: ставим все на свои места 🕷 Одной из самых актуальных уязвимостей в Windows Server Update Services (WSUS) стала критическая ошибка с идентификатором CVE-2025-59287 и оценкой CVSS 9.8. Она связана с десериализацией недоверенных…
Рекомендации по защите к посту выше 🆙
• Установите обновления —
• Ограничьте сетевой доступ — WSUS не должен быть доступен из интернета. Используйте сегментацию сети для ограничения доступа только доверенным подсетям
• Если невозможно обновить — временно отключите роль WSUS Server или заблокируйте порты
#offensive #nuclei #wsus #cve
@ptescalator (X, Max)
• Установите обновления —
KB5070879 / KB5070881 / KB5070882 / KB5070883 / KB5070884 / KB5070886 / KB5070887 в зависимости от версии Windows Server• Ограничьте сетевой доступ — WSUS не должен быть доступен из интернета. Используйте сегментацию сети для ограничения доступа только доверенным подсетям
• Если невозможно обновить — временно отключите роль WSUS Server или заблокируйте порты
8530 / 8531 до установки патчей#offensive #nuclei #wsus #cve
@ptescalator (X, Max)
✍6👍6❤1🔥1💋1
Команда PT ESC IR представила новое исследование, посвященное деятельности хакерской группы PhantomCore. Оно базируется на реальных кейсах расследования инцидентов, произошедших в период с осени 2025 года по первый квартал 2026 года, и содержит детальный разбор атак.
В материале поэтапно восстанавливается вся цепочка компрометации: от эксплуатации критических уязвимостей в TrueConf Server для получения первоначального доступа и методов продвижения по сети с кражей учетных данных с помощью легитимных DFIR-инструментов — до механизмов закрепления в инфраструктуре. На этом этапе атакующие комбинируют собственные бэкдоры (MacTunnelRAT, PhantomSscp, PhantomProxyLite) с инструментом для расследований Velociraptor, что позволяет им эффективно маскировать вредоносную активность под легитимные действия специалистов по информационной безопасности.
🚇 Одним из новых и интересных инструментов является MacTunnelRAT — PowerShell-скрипт, предназначенный для создания обратного SSH-туннеля. Он работает только на скомпрометированных хостах и расшифровывает список URL C2-серверов, используя MAC-адрес первого физического сетевого интерфейса.
function Get-MacAddressAsUUID {
try {
$adapter = Get-NetAdapter | Where-Object { $_.InterfaceType -eq 6 } | Select-Object -First 1
if ($adapter) {
return $adapter.MacAddress
}
return ""
} catch {
return ""
}
}В обновленной версии появилась возможность обновления URL C2-серверов (на скриншоте).
Подробно про MacTunnelRAT и другие актуальные техники группы PhantomCore читайте в нашем блоге
#dfir #ir #malware
@ptescalator
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥17❤9👍7😁1
Почему IDA не сворачивает константы и как это исправить 👨💻
В последнее время в ВПО все чаще встречается обфускация, где константы заменяют на арифметические выражения. Подобный прием, например, есть в OLLVM.
😑 В чем тут проблема: часть констант, из которых собирается итоговое выражение, хранится в памяти. Из-за этого IDA по умолчанию не может нормально применить
Особенно неприятно, когда таким способом маскируют еще и адреса вызываемых функций. На практике это выливается в конструкции вида:
Для статического анализа это, конечно, лишняя головная боль🧱
Но есть нюанс: если явно указать, что данные по адресу
Перед тем как явно проставлять таким данным
Заодно полезно отфильтровать значения, которые на самом деле являются указателями, чтобы не испортить типизацию (скриншот 3).
😎 Все это можно автоматизировать небольшим IDAPython-скриптом (IDA 9.0+), который проходит по сегменту
В результате псевдокод становится заметно чище и понятнее (скриншот 4).
#tips #reverse
@ptescalator (X, Max)
В последнее время в ВПО все чаще встречается обфускация, где константы заменяют на арифметические выражения. Подобный прием, например, есть в OLLVM.
constant propagation и автоматически «свернуть» такие конструкции в исходные значения (скриншот 1).Особенно неприятно, когда таким способом маскируют еще и адреса вызываемых функций. На практике это выливается в конструкции вида:
mov <reg>, <const>
add <reg>, cs:<offset_*>
call <reg>
Для статического анализа это, конечно, лишняя головная боль
Но есть нюанс: если явно указать, что данные по адресу
cs:<offset_*> — это константные qword/dword, то во время декомпиляции IDA сама подтянет значение из памяти, пересчитает выражение и свернет его обратно в исходную константу.Перед тем как явно проставлять таким данным
const, стоит убедиться, что значение действительно readonly. Один из простых способов — посмотреть на перекрестные ссылки: если все обращения к адресу идут только на чтение, то это хороший кандидат на const (скриншот 2).Заодно полезно отфильтровать значения, которые на самом деле являются указателями, чтобы не испортить типизацию (скриншот 3).
😎 Все это можно автоматизировать небольшим IDAPython-скриптом (IDA 9.0+), который проходит по сегменту
.data (или любому другому нужному сегменту), находит значения, используемые только на чтение, исключает те, что похожи на адреса, и явно проставляет им const byte/word/dword/qword в зависимости от размера.В результате псевдокод становится заметно чище и понятнее (скриншот 4).
#tips #reverse
@ptescalator (X, Max)
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10👏6👍5❤1
ESCalator
Почему IDA не сворачивает константы и как это исправить 👨💻 В последнее время в ВПО все чаще встречается обфускация, где константы заменяют на арифметические выражения. Подобный прием, например, есть в OLLVM. 😑 В чем тут проблема: часть констант, из которых…
А вот и этот IDAPython-скрипт:
#tips #reverse
@ptescalator (X, Max)
import ida_bytes
import ida_segment
import ida_typeinf
import idautils
import ida_xref
import idaapi
from collections import namedtuple
PTR_SIZE = 8 if idaapi.inf_is_64bit() else 4
TypeInfo = namedtuple('SizeInfo', ['get_data', 'typename', 'badaddr'])
SIZE_TO_TYPE_INFO_MAP = {
1: (TypeInfo(ida_bytes.get_byte, "byte", 0xFF)),
2: (TypeInfo(ida_bytes.get_word, "word", 0xFFFF)),
4: (TypeInfo(ida_bytes.get_dword, "dword", 0xFFFFFFFF)),
8: (TypeInfo(ida_bytes.get_qword, "qword", 0xFFFFFFFFFFFFFFFF))
}
def apply_const_type(ea: int) -> bool:
flags = ida_bytes.get_flags(ea)
if not ida_bytes.is_data(flags):
return False
item_size = ida_bytes.get_item_size(ea)
type_info = SIZE_TO_TYPE_INFO_MAP.get(item_size)
if type_info is None:
return False
if type_info.get_data(ea) == type_info.badaddr:
return False
typeinfo_str = "const " + type_info.typename
tif = ida_typeinf.tinfo_t(typeinfo_str)
ida_bytes.clr_op_type(ea, 0)
return ida_typeinf.apply_tinfo(ea, tif, 1)
def is_addr_readonly(target_addr: int) -> bool:
flags = ida_bytes.get_full_flags(target_addr)
return ida_bytes.has_xref(flags) and \
all(ref.type == ida_xref.dr_R for ref in idautils.XrefsTo(target_addr))
def apply_const_type_if_readonly(target_addr: int) -> bool:
return apply_const_type(target_addr) if is_addr_readonly(target_addr) else False
def data_is_offset(ea:int) -> bool:
item_size = ida_bytes.get_item_size(ea)
if item_size != PTR_SIZE:
return False
type_info = SIZE_TO_TYPE_INFO_MAP.get(item_size)
if type_info is None:
return False
value = type_info.get_data(ea)
return (idaapi.getseg(value)) is not None
def main():
print("Casting readonly memory values to consts ")
print("---------------------------------")
segment_name = ".data"
seg = ida_segment.get_segm_by_name(segment_name)
if not seg:
print("No {segment_name} segment")
return
ea = seg.start_ea
fixed_consts_num = 0
while ea < seg.end_ea:
ea_size = ida_bytes.get_item_size(ea)
if ea_size <= 0:
ea += 1
continue
if not data_is_offset(ea) and apply_const_type_if_readonly(ea):
fixed_consts_num += 1
ea += ea_size
print("---------------------------------")
print(f"Done. fixed = {fixed_consts_num} consts")
print("Decompile again to view results: F5")
if __name__ == "__main__":
main()
#tips #reverse
@ptescalator (X, Max)
👍11🤯6🔥3😁2❤1