- •Внимание!
- •Об авторах
- •О техническом редакторе
- •О соавторах
- •Предисловие
- •Благодарности
- •Отдельное спасибо
- •Введение
- •Необходимая квалификация
- •Изучение на примерах
- •Структура книги
- •Глава 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 |
|
|
. |
|
|
|
|
21 |
|||||
|
|
|
|
|
|
|
.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 |
|
|
|
|
Шестидесятичетырехбитные вредоносные программы
Почти все современные вредоносные программы являются 32-битными, но бывают и такие, которые написаны под 64-битную архитектуру с расчетом на взаимодействие с 64-битной ОС. Популярность последних растет по мере распространения 64-битных операционных систем.
Существует несколько 64-битных архитектур. Одна из них, Itanium, стала первой поддерживаться в Windows; она создана для производительных вычислений и несовместима с x86. Позже компания AMD представила 64-битную архитектуру под названием AMD64, которая способна выполнять код формата x86. Компания Intel переняла эту технологию и назвала свою реализацию EM64T. Теперь эта архитектура известна как x64 или x86-64, и на сегодняшний день это самая популярная реализация 64-битного кода в Windows. Все современные версии Windows имеют 64-битный вариант с поддержкой как 64-, так и 32-битных приложений.
Архитектура x64 разрабатывалась в качестве надстройки над x86, поэтому наборы их инструкций не сильно различаются. Если вы откроете 64-битный исполняемый файл в IDA Pro, вам должно быть знакомо большинство инструкций. Одна из трудностей, связанных с анализом 64-битного вредоносного ПО, заключается в том, что не все инструменты поддерживают ассемблер в формате x64. Например, на момент написания этой книги OllyDbg (в отличие от WinDbg) не поддерживал 64-битные приложения. В IDA Pro такая поддержка имеется, но для этого вам потребуется версия Advanced.
Данная глава посвящена различиям между 32- и 64-битными системами. Здесь вы также найдете несколько советов относительно анализа 64-битного кода.
Какой смысл в 64-битном вредоносном ПО?
Тридцатидвухбитное вредоносное ПО может атаковать как 32-, так и 64-битные системы. Зачем же тогда тратить время на написание отдельной 64-битной версии?
Одна и та же система поддерживает приложения с любой разрядностью, однако вы не можете выполнять 32-битный код внутри 64-битного процесса. Когда процессор выполняет 32-битные инструкции, он находится в 32-битном режиме и не может обрабатывать 64-битный код. Поэтому, если вредоносу нужно работать в рамках адресного пространства 64-битного процесса, он должен сам быть 64-битным.
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
w |
|
|
to |
|
|
472 Часть VI • Специальные темы |
||||
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 |
|
|
|
|
Ниже приводится несколько ситуаций, в которых вредоносную программу нужно компилировать под архитектуру x64.
Режим ядра. Весь код ядра операционной системы находится в одном и том же адресном пространстве, и его разрядность соответствует разрядности ОС. Руткиты часто выполняются внутри ядра, поэтому, если они атакуют 64-битные ОС, они должны быть скомпилированы в 64-битный машинный код. Кроме того, антивирусы и локальные системы безопасности часто содержат модули ядра, и, если вредоносное ПО противодействует таким приложениям, оно тоже должно быть 64-битным или как минимум иметь 64-битные компоненты. В 64-бит- ных версиях Windows есть механизм обнаружения неавторизованных изменений ядра, что усложняет его заражение и исключает загрузку драйверов без цифровой подписи (эти нововведения были подробно рассмотрены в конце главы 10).
Плагины и внедрение кода. Чтобы корректно выполняться в 64-битном процессе, плагины и внедряемый код тоже должны быть 64-битными. Например, 64-битная версия Internet Explorer поддерживает только 64-битные плагины и компоненты ActiveX. Код, который внедряется так, как это было описано в главе 12, тоже работает в рамках другого процесса. И, если этот процесс 64-битный, код должен иметь ту же разрядность.
Код командной оболочки. Код командной оболочки обычно является частью эксплойта внутри зараженного процесса. Например, чтобы воспользоваться уязвимостью в 64-битной версии Internet Explorer, злоумышленнику придется написать 64-битный shell-код. Чем больше пользователей запускает одновременно 64- и 32-битные приложения, тем чаще авторам вредоносного ПО приходится писать отдельный код командной оболочки для каждой из архитектур.
Особенности архитектуры x64
Ниже перечислены наиболее важные отличия архитектуры x64 от x32 в контексте Windows.
Все адреса и указатели x64 являются 64-битными.
Все регистры общего назначения (включая RAX, RBX, RCX и т. д.) имеют увеличенный размер, хотя при этом доступны их 32-битные версии. Например, RAX является 64-битным вариантом регистра EAX.
Некоторые регистры общего назначения (RDI, RSI, RBP и RSP) были расширены для поддержки побайтового доступа путем добавления суффикса L к 16-битной версии. Например, BP обычно содержит 16 младших бит регистра RBP, а BPL — 8 младших бит регистра RBP.
Специальные регистры являются 64-битными и имеют другие названия. Например, RIP — это 64-битный указатель на инструкцию.
|
|
|
|
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 |
|
|
|||
Глава 21. Шестидесятичетырехбитные вредоносные программы 473 |
to |
|
|
|
|
|
||||
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
Регистров общего назначения стало в два раза больше. Новые регистры получили имена R8–R15. Их версии типа DWORD (32-битные) доступны как R8D, R9D и т. д. Для доступа к версиям типа WORD (16-битным) используется суффикс W (R8W, R9W и т. д.), а для байтовых версий предусмотрен суффикс L (R8L, R9L и т. д.).
Архитектура x64 также поддерживает относительную адресацию данных с помощью указателя на инструкции. Это важное отличие от x86 в контексте контроллера прерываний и кода командной оболочки. В частности, чтобы получить в ассемблере формата x86 доступ к данным по адресу, который не является сдвигом относительно регистра, приходится хранить в инструкции весь адрес целиком. Это называется абсолютной адресацией. Но в архитектуре x64 ассемблер позволяет обращаться к данным со сдвигом относительно указателя на текущую инструкцию. В литературе, посвященной технологии x64, это называется адресацией относительно регистра RIP. В листинге 21.1 показана простая программа на языке C, которая обращается к адресу в памяти.
Листинг 21.1. Простая программа на языке C с доступом к данным
int x;
void foo() { int y = x;
...
}
Если перевести листинг 21.1 в ассемблерный код формата x86, он будет обращаться к глобальным данным (то есть к переменной x). Для этого инструкция mov кодирует 4 байта, представляющих собой адрес данных. Эта инструкция зависит от размещения, поскольку она всегда получает доступ к адресу 0x00403374, но, если загрузить исполняемый файл в другом месте, ее придется изменить, чтобы она обращалась к корректному участку памяти (листинг 21.2).
Листинг 21.2. Ассемблер формата x86 для программы из листинга 21.1
00401004 A1 |
74 |
33 |
40 |
00 mov |
eax, dword_403374 |
Можно заметить, что байты адреса хранятся вместе с инструкцией в позициях , , и . Как вы помните, порядок их размещения направлен от младшего байта к старшему. Байты 74, 33, 40 и 00 соответствуют адресу 0x00403374.
После перекомпиляции кода для платформы x64 мы получим ту же инструкцию mov, что и в листинге 21.2.
Листинг 21.3. Ассемблер формата x64 для листинга 21.1
0000000140001058 8B 05 |
A2 |
D3 |
00 |
00 mov |
eax, dword_14000E400 |
На ассемблерном уровне никаких изменений вроде бы нет. Инструкция попрежнему имеет вид mov eax, dword_адрес, и IDA Pro автоматически вычисляет ее адрес. Но благодаря различию на уровне опкодов данный код на архитектуре x64 является позиционно-независимым (чего нельзя сказать в случае с x86).
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
w |
|
|
to |
|
|
474 Часть VI • Специальные темы |
||||
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 |
|
|
|
|
В 64-битной версии байты инструкции не содержат фиксированного адреса данных. Адрес равен 14000E400, но байты выглядят как A2 , D3 , 00 и 00 , что соответствует значению 0x0000D3A2.
Шестидесятичетырехбитная инструкция хранит адрес данных не в виде абсолютного значения (как в 32-битной версии), а как сдвиг относительно указателя на текущую инструкцию. Если загрузить файл в другом месте, инструкция по-прежнему будет ссылаться на корректный адрес. В архитектуре x86 в этой ситуации придется менять ссылку.
Адресация относительно указателя на инструкцию — это важная особенность платформы x64, которая существенно уменьшает количество адресов, нуждающихся в перемещении при загрузке DLL. Она также упрощает написание shell-кода, поскольку для обращения к данным больше не нужно получать указатель на EIP. Эта возможность устраняет необходимость в инструкциях call/pop, чем затрудняет распознавание кода командной оболочки (см. раздел «Позиционно-независимый код» в главе 19). При работе с вредоносным ПО, написанным для архитектуры х64, многие методики скрытия shell-кода становятся излишними или теряют свою актуальность.
Особенности вызова кода и использования стека на платформе х64
Формат вызова кода в 64-битных версиях Windows больше всего напоминает использование 32-битного соглашения fastcall, рассмотренного в главе 6. Первые четыре параметра вызова загружаются в регистры RCX, RDX, R8 и R9, а еще один попадает в стек.
ПРИМЕЧАНИЕ
Большинство соглашений и приемов, описанных в этом разделе, относятся к коду, сгенерированному компилятором для работы в ОС Windows. Они не являются обязательными с точки зрения процессора, но, чтобы обеспечить согласованность и стабильность кода, компания Microsoft рекомендует разработчикам компиляторов следовать определенным правилам. Имейте это в виду, потому что вредоносный или написанный вручную ассемблерный код может пренебрегать этими рекомендациями и делать неожиданные вещи. И, как обычно, обращайте внимание на код, который не следует общепринятым правилам.
При работе с 32-битным кодом пространство стека можно выделять и освобождать посреди функции, используя инструкции push и pop. Но в архитектуре x64 функция не может выделять какое-либо пространство в процессе выполнения, даже если она использует инструкцию push или другую операцию для изменения стека.
На рис. 21.1 сравнивается управление стеком в 32- и 64-битном коде. Обратите внимание на то, что на левом графике размер стека растет по мере записи в него
|
|
|
|
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 |
|
|
|||
Глава 21. Шестидесятичетырехбитные вредоносные программы 475 |
to |
|
|
|
|
|
||||
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
аргументов и уменьшается при очистке. Пространство стека выделяется в начале функции, меняя свой объем по ходу ее работы. Когда функция вызывается, стек расширяется; когда функция завершается, стек возвращается к обычному размеру. Сравните это с 64-битной версией, в которой стек растет в начале функции, но остается неизменным, пока та не закончит свою работу.
Рис. 21.1. Размер стека одной и той же функции, скомпилированной для архитектур x86 и x64 |
Иногда 32-битный компилятор может сгенерировать код, который не меняет размер стека посреди функции, но для 64-битного кода это обязательное требование. И хотя данное ограничение не требуется на уровне процессора, от него зависит корректная работа модели обработки исключений, используемой в Windows x64. Столкнувшись с исключением, функции, которые не соблюдают это соглашение, могут привести к сбою программы или вызвать другие проблемы.
Из-за отсутствия инструкций push и pop посреди функции аналитику может быть сложнее выяснить, сколько аргументов она принимает: нет простого способа определить, для чего используется адрес в памяти — для локальной переменной или входящего параметра. Мы также не можем узнать, хранится ли параметр в регистре. Например, если прямо перед вызовом функции загрузить в ECX какоенибудь значение, вы не сможете сказать, является ли оно параметром или чем-то другим.
В листинге 21.4 показан пример дизассемблированного вызова функции, который был скомпилирован для 32-битного процессора.
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
w |
|
|
to |
|
|
476 Часть VI • Специальные темы |
||||
w Click |
|
|
|
|
|
|
||||
|
|
|
|
|
o |
m |
||||
|
w |
|
|
|
|
|
|
|
|
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-xcha |
|
|
|
|
Листинг 21.4. Вызов функции printf, скомпилированный для 32-битного процессора
004113C0 |
mov |
eax, [ebp+arg_0] |
004113C3 |
push |
eax |
004113C4 |
mov |
ecx, [ebp+arg_C] |
004113C7 |
push |
ecx |
004113C8 |
mov |
edx, [ebp+arg_8] |
004113CB |
push |
edx |
004113CC |
mov |
eax, [ebp+arg_4] |
004113CF |
push |
eax |
004113D0 |
push |
offset aDDDD_ |
004113D5 |
call |
printf |
004113DB |
add |
esp, 14h |
|
|
|
|
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 |
|
|
|
|
Прежде чем вызвать printf, 32-битный ассемблерный код выполняет пять инструкций push. Сразу после вызова стек очищается путем добавления в него значения 0x14. Это явно говорит о том, что функции printf передается пять параметров.
В листинге 21.5 показан ассемблерный код того же вызова, скомпилированного для 64-битного процессора.
Листинг 21.5. Вызов функции printf, скомпилированный для 64-битного процессора
0000000140002C96 |
mov |
ecx, [rsp+38h+arg_0] |
0000000140002C9A |
mov |
eax, [rsp+38h+arg_0] |
0000000140002C9E |
mov |
[rsp+38h+var_18], eax |
0000000140002CA2 |
mov |
r9d, [rsp+38h+arg_18] |
0000000140002CA7 |
mov |
r8d, [rsp+38h+arg_10] |
0000000140002CAC |
mov |
edx, [rsp+38h+arg_8] |
0000000140002CB0 |
lea |
rcx, aDDDD_ |
0000000140002CB7 |
call |
cs:printf |
В 64-битном варианте количество параметров, передаваемых в printf, является менее очевидным. Загрузка инструкций в регистры RCX, RDX, R8 и R9 указывает на перемещение аргументов функции, но инструкция mov в строке менее очевидна. IDA Pro маркирует это значение как локальную переменную, но у нас нет четкого способа отличить его от аргумента вызываемой функции. В данном случае мы можем просто проверить, сколько параметров передается в строку форматирования, но в других ситуациях все может оказаться гораздо сложнее.
Листовые и нелистовые функции
Соглашение об использовании 64-битного стека делит функции на листовые и нелистовые. Нелистовая функция содержит внутри себя другие вызовы, а листовая — нет.
Нелистовые функции иногда называют многослойными, поскольку для каждого вызова требуется слой стека. При любом вызове в стеке должно выделяться 0x20 байт. В это пространство вызываемая функция может при необходимости сохранить параметры, загруженные в регистры (RCX, RDX, R8 и R9).
Листовые и нелистовые функции модифицируют стек только в начале и в конце своей работы. Эти участки, отвечающие за изменение стека, рассматриваются далее.
|
|
|
|
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 |
|
|
|||
Глава 21. Шестидесятичетырехбитные вредоносные программы 477 |
to |
|
|
|
|
|
||||
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
Пролог и эпилог в 64-битном коде
В Windows 64-битный ассемблерный код имеет четкие разделы в начале и конце функций. Они называются прологом и эпилогом и могут содержать полезную информацию. Любая инструкция mov в прологе всегда используется для сохранения аргументов, переданных в функцию (компилятор не может вставить в пролог инструкцию mov, которая занимается чем-то другим). Пример пролога в небольшой функции показан в листинге 21.6.
Листинг 21.6. Код пролога в небольшой функции
00000001400010A0 |
mov |
[rsp+arg_8], rdx |
00000001400010A5 |
mov |
[rsp+arg_0], ecx |
00000001400010A9 |
push |
rdi |
00000001400010AA |
sub |
rsp, 20h |
Здесь видно, что функция принимает два аргумента: один 32-битный и один 64-битный. В качестве хранилища для аргументов она выделяет в стеке 0x20 байт, как это должна делать любая нелистовая функция. Если у нее есть какие-либо локальные переменные, ей придется выделить для них дополнительное пространство того же размера. В данном случае можно с уверенностью сказать, что локальных переменных нет, потому что в стеке выделено лишь 0x20 байт.
Обработка исключений в 64-битном коде
В отличие от 32-битных систем, архитектура x64 не использует стек для структурированной обработки исключений. В 32-битном коде fs:[0] выступает в роли указателя на текущий слой обработки, который хранится в стеке; это позволяет каждой функции определить свой собственный обработчик исключений. Как следствие,
вначале функции часто можно увидеть инструкции, изменяющие значение fs:[0]. Существуют также эксплойты, которые модифицируют эту информацию в стеке, чтобы получить контроль над кодом во время исключительной ситуации.
На платформе x64 в структурированной обработке исключений используется статическая таблица, которая хранится в PE-файле. В стек не сохраняется никакая информация, связанная с этим процессом. Для каждой функции в исполняемом файле предусмотрена структура IMAGE_RUNTIME_FUNCTION_ENTRY. Она находится
вразделе .pdata и хранит адреса начала и конца функции, а также указатель на информацию об обработчиках исключений, которые ей принадлежат.
WOW64
Компания Microsoft разработала подсистему WOW64 (Windows 32-bit on Windows 64-bit), которая позволяет корректно выполнять 32-битные приложения на 64-бит- ном компьютере. У этой подсистемы есть несколько особенностей, которыми могут воспользоваться злоумышленники.