Упаковщики: что это и как распознать
Упаковщик сжимает или шифрует исполняемый файл и распаковывает его в памяти при запуске — учимся это распознавать.
Упаковщик (packer) — инструмент, который сжимает или шифрует код и данные исполняемого файла, добавляя маленькую программу-распаковщик (stub), восстанавливающую оригинал в памяти при старте.
Когда аналитик загружает файл и видит крошечный список импортов, нечитаемые секции и почти случайные байты — перед ним, скорее всего, упакованный образ. Статический анализ такого файла бесполезен: настоящий код проявится только во время выполнения, когда stub распакует его в память. Распознавание упаковки — обязательный первый шаг разбора, потому что без него вы анализируете не программу, а её сжатый контейнер.
Зачем это знать защитнику и аналитику
Упаковщики применяются совершенно легально: уменьшение размера дистрибутива, базовая защита от поверхностного реверса, объединение ресурсов. UPX — самый известный пример, его открыто используют в легитимном ПО. Но та же техника крайне популярна у вредоносного ПО: упаковка ломает сигнатурное детектирование (на диске байты другие) и прячет настоящую логику. Поэтому для аналитика факт упаковки сам по себе — не приговор, а сигнал «здесь нужен динамический анализ». Умение распознать упаковщик и его тип экономит часы и направляет разбор по верному пути.
Как устроен упакованный файл
Концептуально упаковщик берёт оригинальный исполняемый файл, сжимает (или шифрует) его секции кода и данных и формирует новый файл. В нём остаётся минимальный «обёрточный» код — stub. При запуске управление получает stub: он распаковывает оригинальные секции в выделенную память, восстанавливает таблицу импортов и передаёт управление на настоящую точку входа исходной программы — её называют OEP (Original Entry Point). До этого момента в памяти лежит лишь распаковщик.
[ упакованный файл на диске ]
+-----------------------------+
| stub (распаковщик) | <- сюда указывает Entry Point
| сжатые/зашифрованные |
| секции оригинала |
+-----------------------------+
| запуск
v
[ память после распаковки ]
оригинальный код -> переход на OEP -> работает настоящая программа
Признак 1: высокая энтропия
Энтропия — мера «случайности» байт. У обычного кода и текста она умеренная (повторяющиеся опкоды, ASCII-строки), у сжатых и зашифрованных данных — близка к максимуму (~8 бит на байт), потому что хорошие сжатие и шифрование делают вывод статистически неотличимым от случайного. Резко высокая энтропия секций — классический индикатор упаковки или шифрования. Покажем идею на маленьком примере: сравним энтропию повторяющихся данных и «случайных».
import math
from collections import Counter
def entropy(data: bytes) -> float:
if not data:
return 0.0
counts = Counter(data)
n = len(data)
return -sum((c / n) * math.log2(c / n) for c in counts.values())
plain = bytes([65] * 256) # одинаковые байты (как простой код)
packed = bytes(range(256)) # все значения по разу (как сжатые данные)
print("Обычные данные:", round(entropy(plain), 2))
print("Похоже на упаковку:", round(entropy(packed), 2))
Вывод:
Обычные данные: 0.0 Похоже на упаковку: 8.0
В реальности аналитик смотрит энтропию по секциям (инструменты вроде detect-it-easy показывают её графиком). Значение близко к 8 у секции кода — сильный намёк на упаковку. Это эвристика, а не доказательство: легитимно сжатые ресурсы тоже дают высокую энтропию.
Признак 2: мало импортов
Обычная программа импортирует десятки функций из системных библиотек (работа с файлами, окнами, сетью). У упакованного файла таблица импортов часто подозрительно мала — буквально несколько функций, нужных самому stub: выделить память, изменить права страниц, загрузить библиотеку. Полная таблица импортов восстановится только в рантайме после распаковки. Короткий список вида «выделение памяти + загрузка модулей и больше почти ничего» — характерный портрет упаковщика.
Другие признаки
- Нестандартные имена секций. UPX называет секции
UPX0,UPX1; другие упаковщики оставляют свои метки. Имена вроде.text/.dataзаменены на необычные — повод насторожиться. - Секция с нулевым «сырым» размером, но большим виртуальным. Место под распакованный код выделяется в памяти, на диске оно пустое — типичная картина упакованной секции.
- Сигнатуры упаковщика. Многие упаковщики оставляют узнаваемые строки или байтовые шаблоны в stub; статические сканеры (detect-it-easy, сигнатурные базы) определяют упаковщик и версию по ним.
Как это работает под капотом
Упаковка опирается на способность ОС выполнять код, сгенерированный во время работы программы: stub выделяет память, помещает туда распакованные инструкции, делает страницы исполняемыми и прыгает на них. Именно это поведение — «программа на лету создаёт и запускает новый код» — и есть то, что аналитик ловит динамически. На диске же остаётся высокоэнтропийный контейнер с минимумом импортов. Эти два слоя (статический контейнер и динамическая распаковка) объясняют, почему упакованное ПО нужно анализировать в движении, а не по статике.
Как защититься
С точки зрения обороны и грамотного анализа:
- Для защитников: упаковка собственного релиза — слабая защита (распознаётся и снимается), но осмысленная как уменьшение размера. Не полагайтесь на неё как на барьер от реверса.
- Для Blue Team: высокая энтропия + крошечные импорты + нестандартные секции — это сигнал к расследованию, а не вердикт. Многие легитимные программы упакованы; решение принимают по совокупности признаков и поведению при запуске в изолированной среде.
- Инструменты: статические детекторы упаковщиков (detect-it-easy и аналоги) быстро дают тип и версию; дальше — динамический анализ в безопасной лаборатории (изолированная ВМ, снапшоты).
- Безопасность лаборатории: подозрительные образцы запускают только в изолированной виртуальной машине без доступа к рабочей сети, на свежем снапшоте.
Анализ упаковки законен на своих файлах, в учебной лаборатории и на CTF. Распространение вредоносного ПО и несанкционированный доступ к чужим системам наказуемы (ст. 272–273 УК РФ).
Итоги
- Упаковщик сжимает/шифрует код и добавляет stub, который распаковывает оригинал в память при запуске.
- Главные признаки: высокая энтропия секций (близко к 8) и подозрительно малое число импортов.
- Дополнительно: нестандартные имена секций (например, UPX0/UPX1), пустые на диске, но большие в памяти секции, сигнатуры упаковщика.
- Применение бывает и легитимным (UPX в нормальном ПО), и вредоносным; факт упаковки — сигнал к динамическому анализу, не приговор.
- Настоящий код упакованного файла проявляется только в рантайме, на OEP — анализировать нужно в движении и в изоляции.