- •Внимание!
- •Об авторах
- •О техническом редакторе
- •О соавторах
- •Предисловие
- •Благодарности
- •Отдельное спасибо
- •Введение
- •Необходимая квалификация
- •Изучение на примерах
- •Структура книги
- •Глава 0. Анализ вредоносных программ для начинающих
- •Цель анализа вредоносных программ
- •Методики анализа вредоносного ПО
- •Общие правила анализа вредоносного ПО
- •Глава 1. Основные статические методики
- •Сканирование антивирусом: первый шаг
- •Хеширование: отпечатки пальцев злоумышленника
- •Поиск строк
- •Упакованное и обфусцированное вредоносное ПО
- •Формат переносимых исполняемых файлов
- •Компонуемые библиотеки и функции
- •Статический анализ на практике
- •Заголовки и разделы PE-файла
- •Итоги главы
- •Глава 2. Анализ вредоносных программ в виртуальных машинах
- •Структура виртуальной машины
- •Запуск виртуальной машины для анализа вредоносного ПО
- •Использование виртуальной машины для анализа безопасности
- •Риски при использовании VMware для анализа безопасности
- •Запись/воспроизведение работы компьютера
- •Итоги главы
- •Глава 3. Основы динамического анализа
- •Песочницы: решение на скорую руку
- •Запуск вредоносных программ
- •Мониторинг с помощью Process Monitor
- •Сравнение снимков реестра с помощью Regshot
- •Симуляция сети
- •Перехват пакетов с помощью Wireshark
- •Использование INetSim
- •Применение основных инструментов для динамического анализа
- •Итоги главы
- •Уровни абстракции
- •Архитектура x86
- •Итоги главы
- •Глава 5. IDA Pro
- •Загрузка исполняемого файла
- •Интерфейс IDA Pro
- •Использование перекрестных ссылок
- •Анализ функций
- •Схематическое представление
- •Повышение эффективности дизассемблирования
- •Плагины к IDA Pro
- •Итоги главы
- •Глава 6. Распознавание конструкций языка C в ассемблере
- •Переменные: локальные и глобальные
- •Дизассемблирование арифметических операций
- •Распознавание выражений if
- •Распознавание циклов
- •Соглашения, касающиеся вызова функций
- •Анализ выражений switch
- •Дизассемблирование массивов
- •Распознавание структур
- •Анализ обхода связного списка
- •Итоги главы
- •Глава 7. Анализ вредоносных программ для Windows
- •Windows API
- •Реестр Windows
- •API для работы с сетью
- •Отслеживание запущенной вредоносной программы
- •Сравнение режимов ядра и пользователя
- •Native API
- •Итоги главы
- •Глава 8. Отладка
- •Сравнение отладки на уровне исходного и дизассемблированного кода
- •Отладка на уровне ядра и пользователя
- •Использование отладчика
- •Исключения
- •Управление выполнением с помощью отладчика
- •Изменение хода выполнения программы на практике
- •Итоги главы
- •Глава 9. OllyDbg
- •Загрузка вредоносного ПО
- •Пользовательский интерфейс OllyDbg
- •Карта памяти
- •Просмотр потоков и стеков
- •Выполнение кода
- •Точки останова
- •Трассировка
- •Обработка исключений
- •Редактирование кода
- •Анализ кода командной оболочки
- •Вспомогательные возможности
- •Подключаемые модули
- •Отладка с использованием скриптов
- •Итоги главы
- •Драйверы и код ядра
- •Подготовка к отладке ядра
- •Использование WinDbg
- •Отладочные символы Microsoft
- •Отладка ядра на практике
- •Руткиты
- •Загрузка драйверов
- •Итоги главы
- •Глава 11. Поведение вредоносных программ
- •Программы для загрузки и запуска ПО
- •Бэкдоры
- •Похищение учетных данных
- •Механизм постоянного присутствия
- •Повышение привилегий
- •Заметая следы: руткиты, работающие в пользовательском режиме
- •Итоги главы
- •Глава 12. Скрытый запуск вредоносного ПО
- •Загрузчики
- •Внедрение в процесс
- •Подмена процесса
- •Внедрение перехватчиков
- •Detours
- •Внедрение асинхронных процедур
- •Итоги главы
- •Глава 13. Кодирование данных
- •Простые шифры
- •Распространенные криптографические алгоритмы
- •Нестандартное кодирование
- •Декодирование
- •Итоги главы
- •Глава 14. Сетевые сигнатуры, нацеленные на вредоносное ПО
- •Сетевые контрмеры
- •Безопасное расследование вредоносной деятельности в Интернете
- •Контрмеры, основанные на сетевом трафике
- •Углубленный анализ
- •Сочетание динамических и статических методик анализа
- •Понимание психологии злоумышленника
- •Итоги главы
- •Искажение алгоритмов дизассемблирования
- •Срыв анализа слоя стека
- •Итоги главы
- •Глава 16. Антиотладка
- •Обнаружение отладчика в Windows
- •Распознавание поведения отладчика
- •Искажение работы отладчика
- •Уязвимости отладчиков
- •Итоги главы
- •Глава 17. Методы противодействия виртуальным машинам
- •Признаки присутствия VMware
- •Уязвимые инструкции
- •Изменение настроек
- •Побег из виртуальной машины
- •Итоги главы
- •Глава 18. Упаковщики и распаковка
- •Анатомия упаковщика
- •Распознавание упакованных программ
- •Способы распаковки
- •Автоматизированная распаковка
- •Ручная распаковка
- •Советы и приемы для работы с распространенными упаковщиками
- •Анализ без полной распаковки
- •Итоги главы
- •Глава 19. Анализ кода командной оболочки
- •Загрузка кода командной оболочки для анализа
- •Позиционно-независимый код
- •Определение адреса выполнения
- •Поиск символов вручную
- •Окончательная версия программы Hello World
- •Кодировки кода командной оболочки
- •NOP-цепочки
- •Поиск кода командной оболочки
- •Итоги главы
- •Глава 20. Анализ кода на C++
- •Объектно-ориентированное программирование
- •Обычные и виртуальные функции
- •Создание и уничтожение объектов
- •Итоги главы
- •Какой смысл в 64-битном вредоносном ПО?
- •Особенности архитектуры x64
- •Признаки вредоносного кода на платформе x64
- •Итоги главы
- •Приложения
|
|
|
|
hang |
e |
|
|
|
|
||
|
|
|
C |
|
E |
|
|
||||
|
|
X |
|
|
|
|
|
|
|||
|
- |
|
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
||||
|
|
|
|
|
|
|
|||||
|
|
|
|
|
BUY |
|
|
||||
|
|
|
|
to |
|
|
|
|
|
|
|
w Click |
|
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
18 |
|||||
|
|
|
|
|
|
|
.c |
|
|||
|
|
p |
|
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
|||
|
|
|
|
-xcha |
|
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
Упаковщики и распаковка
Программы для упаковки, известные как упаковщики, стали чрезвычайно популярными среди авторов вредоносного ПО, потому что они помогают спрятать код от антивирусов, усложняют анализ безопасности и уменьшают размер зараженного исполняемого файла. Большинство упаковщиков просты в использовании и находятся в свободном доступе. Упакованная программа защищена от базового статического анализа: чтобы статические методики принесли какую-то пользу, программу сначала нужно распаковать, что делает анализ более сложным.
Упаковщики применяются к исполняемым файлам в основном с двумя целями: чтобы уменьшить программу и усложнить ее обнаружение или анализ. Несмотря на широкое разнообразие упаковщиков, все они работают по схожему принципу: программа помещается в раздел с данными внутри нового исполняемого файла, содержащего заглушку-распаковщик, которая вызывается операционной системой.
Мы начнем эту главу с общих сведений о принципах работы упаковщиков и способах их распознавания. Затем мы пройдемся по стратегиям распаковки — от простых к сложным.
Анатомия упаковщика
Когда аналитик сталкивается с упакованным вредоносом, он обычно имеет доступ только к внешнему файлу и не может исследовать оригинальную программу или утилиту, которая использовалась для упаковки. Чтобы распаковать исполняемый файл, мы должны сделать то же, что и упаковщик, но наоборот. А для этого нужно понимать, как он работает.
Любой упаковщик принимает на входе один исполняемый файл, а на выходе возвращает другой. Упакованная программа сжимается, шифруется или трансформируется еще каким-то образом, что усложняет ее распознавание и разбор ее внутренностей.
Большинство упаковщиков используют алгоритм сжатия для уменьшения размера оригинального файла. Если целью является усложнение анализа, к файлу может применяться шифрование, а также методики защиты от обратного проектирования, такие как антидизассемблирование, антиотладка или анти-ВМ. Ресурсы могут упаковываться вместе с кодом и данными или сохраняться отдельно.
Чтобы сохранить функциональность оригинальной программы, упаковщик должен записать информацию о том, что она импортирует. Эта информация может
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
w |
|
|
to |
|
|
416 Часть V • Противодействие обратному проектированию |
||||
w Click |
|
|
|
|
|
|
||||
|
|
|
|
|
o |
m |
||||
|
w |
|
|
|
|
|
|
|
|
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-xcha |
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
храниться в любом формате (позже в этой главе будет приведено несколько распространенных стратегий). В ходе распаковки программы восстановление импортированного раздела может оказаться долгим и затруднительным, но этот этап обязателен при анализе возможностей исполняемого файла.
Заглушка-распаковщик
Обычная программа загружается непосредственно операционной системой. Если же файл запакован, ОС загружает только его заглушку-распаковщик, а та уже занимается загрузкой исходной программы. Точка входа в исполняемый файл указывает на заглушку, а не на оригинальный код. Сама программа обычно хранится в одном или нескольких дополнительных разделах файла.
Заглушка-распаковщик доступна для анализа. Понимание разных ее частей является ключом к распаковке программы. Часто заглушка имеет небольшой размер, так как ее код не связан с основной функциональностью. Ее назначение простое: распаковать оригинальный исполняемый файл. Результаты, полученные при статическом анализе упакованной программы, будут относиться не к ней самой, а всего лишь к заглушке.
Заглушка-распаковщик действует в три этапа.
1.Распаковывает оригинальный исполняемый файл в память.
2.Находит все функции, которые он импортирует.
3.Переносит управление в оригинальную точку входа (ОТВ).
Загрузка исполняемого файла
При запуске обычного исполняемого файла загрузчик считывает его PE-заголовок, хранящийся на диске, и выделяет память для каждого его раздела на основе полученной информации. Затем загрузчик копирует эти разделы в выделенные участки памяти.
Упакованные исполняемые файлы тоже содержат PE-заголовок, что позволяет загрузчику выделить память для разделов, которые могут принадлежать оригинальной программе; заглушка-распаковщик также может создавать эти разделы. Затем заглушка распаковывает код каждого раздела и копирует его в выделенное пространство. Метод распаковки, который при этом используется, зависит от целей упаковщика и обычно реализован внутри заглушки.
Поиск разрешений импортов
Как уже упоминалось в главе 1, незапакованные PE-файлы содержат два раздела: из одного загрузчик может узнать обо всех функциях импорта, а в другом содержатся адреса их имен. В Windows загрузчик считывает данные об импорте,
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-xcha |
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
Глава 18. Упаковщики и распаковка 417 |
to |
|
|
|
|
|
||||
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
определяет, какие вызовы понадобятся программе, и затем записывает соответствующие адреса.
Загрузчик не может прочитать сведения об импорте, если они запакованы. В запакованном исполняемом файле поиском разрешений импортов занимается заглуш- ка-распаковщик. Конкретный подход зависит от упаковщика.
Самая распространенная стратегия состоит в том, что заглушка импортирует только функции LoadLibrary и GetProcAddress. После распаковки оригинальной программы она считывает исходные данные об импорте. Затем заглушка загружает в память каждую DLL, вызывая LoadLibrary, и использует GetProcAddress для получения адресов всех функций.
Существует еще один подход: оригинальную таблицу импорта можно оставить без изменений, чтобы системный загрузчик мог загрузить DLL и импорты функций. Это самый простой способ, так как заглушке-распаковщику не нужно искать разрешения импортов. Однако статический анализ упакованной программы покажет всю исходную информацию об импорте, что делает файл более заметным. Кроме того, импорты функций хранятся внутри исполняемого файла в виде простого те кста, поэтому данный подход не позволяет достигнуть оптимального сжатия.
Третий вариант заключается в том, чтобы сохранить одну функцию импорта из каждой библиотеки в исходной таблице импорта. Во время анализа вы будете видеть только по одной функции из каждой библиотеки, что делает файл менее заметным, чем в предыдущем случае, но все равно раскрывает все импортируемые библиотеки. Этот подход также проще реализовать на уровне упаковщика, ведь библиотеки не должны загружаться заглушкой, но сама заглушка по-прежнему берет на себя бо' льшую часть работы.
Последний подход состоит в удалении всех импортов (включая LoadLibrary
иGetProcAddress). Упаковщик должен либо самостоятельно найти все необходимые функции из других библиотек, либо сначала найти вызовы LoadLibrary
иGetProcAddress, а затем с их помощью определить местоположение других функций. Эта процедура обсуждается в главе 19, поскольку она похожа на то, чем должен заниматься код командной оболочки. Преимуществом этого варианта является то, что упакованная программа вообще ничего не импортирует, и это делает ее малозаметной. Однако заглушка-распаковщик в данном случае должна быть сложной.
Хвостовая рекурсия
Завершив распаковку, заглушка должна передать управление в ОТВ. Операцию, которая это делает, обычно называют хвостовой рекурсией.
Самым простым и популярным способом передачи управления является инструкция jump. Ввиду ее распространенности многие вредоносные упаковщики пытаются ее скрыть, используя инструкции ret или call. Иногда хвостовая рекурсия скрывается с помощью системных вызовов для передачи управления, таких как NtContinue
или ZwContinue.
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
w |
|
|
to |
|
|
418 Часть V • Противодействие обратному проектированию |
||||
w Click |
|
|
|
|
|
|
||||
|
|
|
|
|
o |
m |
||||
|
w |
|
|
|
|
|
|
|
|
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-xcha |
|
|
|
|
Демонстрация процедуры распаковывания
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
На рис. 18.1–18.4 проиллюстрирован процесс упаковывания/распаковывания.
На рис. 18.1 изображен оригинальный исполняемый файл. Мы можем увидеть его заголовок и разделы, а его точка входа ведет в ОТВ.
На рис. 18.2 показан запакованный исполняемый файл в том виде, в котором он хранится на диске. Мы можем видеть лишь новый заголовок, заглушку-распа- ковщик и упакованный оригинальный код.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Рис. 18.1. Исходный исполняемый файл |
Рис. 18.2. Упакованный исполняемый файл |
||||||||
|
до упаковывания |
после упаковывания оригинального кода |
|||||||
|
|
|
|
|
|
и добавления заглушки-распаковщика |
На рис. 18.3 показан упакованный исполняемый файл после его загрузки в память. Заглушка-распаковщик распаковала оригинальный код, в результате чего стали видны настоящие разделы .text и .data. Точка входа в исполняемый файл все еще ведет к заглушке-распаковщику, а таблица импорта на этом этапе обычно не является корректной.
На рис. 18.4 представлен полностью распакованный исполняемый файл. Таблица импорта была восстановлена, а точка входа теперь ведет к ОТВ.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Рис. 18.3. Структура программы |
Рис. 18.4. Полностью распакованная |
||||||||
после распаковки и загрузки в память. Заглушка |
программа. Таблица импорта восстановлена, |
||||||||
распаковывает все необходимое для выполнения |
а точка входа ведет в прежнее место (ОТВ) |
||||||||
кода. Точка входа в программу все еще ведет |
|
|
|
|
|
||||
к заглушке, а импорты отсутствуют |
|
|
|
|
|