- •Внимание!
- •Об авторах
- •О техническом редакторе
- •О соавторах
- •Предисловие
- •Благодарности
- •Отдельное спасибо
- •Введение
- •Необходимая квалификация
- •Изучение на примерах
- •Структура книги
- •Глава 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 |
|
|
. |
|
|
|
|
|
19 |
||||
|
|
|
|
|
|
.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 |
|
|
|
|
Под кодом командной оболочки понимают содержимое раздела с обычным исполняемым кодом. Такое название связано с тем, что злоумышленники обычно пытаются использовать этот код для получения доступа к интерактивной командной оболочке во взломанной системе. Однако со временем этот термин стали применять к любому автономному исполняемому коду.
Код командной оболочки (или shell-код) часто используется в связке с эксплойтом для получения контроля над активной программой или внутри вредоносного ПО, которое производит внедрение в процесс. Эксплуатация и внедрение похожи
втом смысле, что код командной оболочки добавляется в программу и выполняется от ее имени уже после того, как она была запущена.
Авторы кода командной оболочки должны выполнить несколько действий, о которых разработчики программ обычно никогда не задумываются. Например, вредоносный пакет не может полагаться на стандартную процедуру загрузки
вWindows — в частности, на следующие ее этапы:
размещение программы на предпочтительном участке памяти;
замену адреса, если программа не может быть загружена в предпочтительном участке памяти;
загрузку необходимых библиотек и поиск внешних зависимостей.
В этой главе вы познакомитесь с принципами работы кода командной оболочки на настоящих и полностью рабочих примерах.
Загрузка кода командной оболочки для анализа
Загрузка и выполнение shell-кода в отладчике — непростая задача, поскольку такой код обычно представляет собой набор двоичных данных, которые не могут работать так, как это делает обычный исполняемый файл. Чтобы вам было проще, для загрузки и сбрасывания на диск фрагментов shell-кода мы будем использовать утилиту shellcode_launcher.exe (которая вместе с лабораторными работами доступна по адресу www.practicalmalwareanalysis.com).
|
|
|
|
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 |
|
|
|||
Глава 19. Анализ кода командной оболочки 439 |
to |
|
|
|
|
|
||||
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
Как уже говорилось в главе 5, загрузка кода командной оболочки в IDA Pro для его статического анализа является относительно простой процедурой. Однако при этом вы должны предоставить вводные данные, ведь у вас нет стандартного исполняемого файла, который бы описывал содержимое этого кода. Первым делом следует убедиться в том, что в диалоговом окне загрузки выбран подходящий тип процессора. Для примеров в этой главе вы можете выбрать процессор Intel 80x86 processors: metapc и указать параметр 32-bit disassembly (32-битное дизассемблирование). IDA Pro загружает двоичные данные, но не выполняет автоматического анализа (это вы должны сделать самостоятельно).
Позиционно-независимый код
Позиционно-независимый код (position-independent code, PIC) использует адреса, которые не определены заранее (то же самое относится и к данным). Shell-код тоже имеет формат PIC. Во время выполнения он не может полагаться на то, что его загрузят по определенному адресу, поскольку на этом этапе он может выполняться от имени разных программ на разных участках памяти. Код командной оболочки должен следить за тем, чтобы вся работа с кодом и данными в памяти производилась по методу PIC.
В табл. 19.1 представлено несколько способов доступа к коду и данным на платформе x86. При этом указано, отвечают ли они требованиям PIC.
Таблица 19.1. Разные способы доступа к коду и данным на платформе x86
Формат инструкции |
Байты инструкции |
Зависит от размещения? |
|||||
|
|
|
|
|
|
|
|
call |
sub_401000 |
E8 |
C1 |
FF |
FF |
FF |
Нет |
|
|
|
|
|
|
|
|
jnz |
short loc_401044 |
75 |
0E |
|
|
|
Нет |
|
|
|
|
|
|
|
|
mov |
edx, dword_407030 |
8B |
15 |
30 |
70 |
40 00 |
Да |
|
|
|
|
|
|
|
|
mov |
eax, [ebp-4] |
8B |
45 |
FC |
|
|
Нет |
|
|
|
|
|
|
|
|
В этой таблице инструкция call содержит 32-битный относительный сдвиг со знаком, который идет сразу за адресом, чтобы вычислить итоговое местоположение. В данном случае инструкция call имеет адрес 0x0040103A, поэтому, если сложить ее размер (5 байт) и значение сдвига 0xFFFFFFC1 , получится вызов кода по адресу 0x00401000.
Инструкция jnz очень похожа на call, но ее относительный сдвиг со знаком равен лишь 8 битам. Она находится по адресу 0x00401034 — если добавить к нему сдвиг, хранящийся в инструкции (0xe) , и ее размер (2 байта), то получится переход на участок памяти 0x00401044.
Инструкции управления потоком, такие как call и jnz, изначально не зависят от размещения. Для вычисления итогового адреса они добавляют относительный сдвиг,
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
w |
|
|
to |
|
|
440 Часть 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 |
|
|
|
|
который в них хранится, к их текущему местоположению, указанному в регистре EIP. Некоторые виды инструкций call и jump позволяют программистам использовать абсолютные или неотносительные адреса, которые привязаны к определенному месту, но без них можно легко обойтись.
Инструкция mov содержит операцию доступа к глобальной переменной dword_407030. Ее последние 4 байта представляют собой адрес 0x00407030. Эта конкретная инструкция зависит от размещения, и авторы кода командной оболочки должны ее избегать.
Сравните инструкции mov в строках и . Последняя обращается к переменной DWORD в стеке. В ней находится относительный сдвиг со знаком 0xFC (-4), а в качестве базового адреса она использует регистр EBP. Этот способ доступа к данным не зависит от размещения и должен использоваться авторами shell-кода в качестве эталона: вычисление адресов на этапе выполнения и обращение к данным следует производить исключительно с помощью относительных сдвигов. В следующем разделе мы обсудим поиск подходящего адреса в памяти.
Определение адреса выполнения
При доступе к данным без привязки к конкретному местоположению код командной оболочки должен разыменовать базовый указатель. Добавление или вычитание значений из этого адреса позволит вам безопасно обращаться к данным, встроенным в shell-код. Архитектура x86 не поддерживает инструкции для доступа к данным относительно регистра EIP (в отличие от инструкций для управления потоком), и, чтобы использовать этот регистр в качестве базового указателя, в него нужно предварительно загрузить указатель на текущую инструкцию.
Процедура получения такого указателя может выглядеть не совсем понятной, поскольку на платформе x86 программа не может обратиться к нему напрямую. Собственно говоря, мы не можем собрать инструкцию вида mov eax, eip, которая бы загружала указатель на текущую инструкцию в регистр общего назначения. Но для обхода этого ограничения в shell-коде используются две популярные методики: инструкции call/pop и fnstenv.
Использование инструкций call/pop
При выполнении инструкции call процессор помещает в стек адрес следующей за ней инструкции и затем переходит к запрашиваемому участку. По завершении своей работы функция выполняет инструкцию ret, чтобы снять обратный адрес с вершины стека и загрузить его в указатель на текущую инструкцию. В итоге управление возвращается к инструкции, которая идет сразу за call.
Код командной оболочки может этим воспользоваться, выполнив инструкцию pop сразу после call, что приведет к загрузке адреса следующей инструкции в указанный регистр. В листинге 19.1 этот метод показан на примере программы Hello World.
|
|
|
|
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 |
|
|
|||
Глава 19. Анализ кода командной оболочки 441 |
to |
|
|
|
|
|
||||
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
Листинг 19.1. Программа Hello World с использованием инструкций call/pop
Bytes |
|
|
|
Disassembly |
|
|
||
83 |
EC |
20 |
|
|
sub |
esp, 20h |
|
|
31 |
D2 |
|
|
|
xor |
edx, edx |
|
|
E8 |
0D |
00 |
00 |
00 |
call |
sub_17 |
|
|
48 |
65 |
6C |
6C |
6F |
db 'Hello World!',0 |
|
|
|
20 |
57 |
6F |
72 |
6C |
|
|
|
|
64 |
21 |
00 |
|
|
|
|
|
|
sub_17: |
|
|
|
|
|
|
||
5F |
|
|
|
|
pop |
edi |
; |
edi получает указатель на строку |
52 |
|
|
|
|
push |
edx |
; |
uType: MB_OK |
57 |
|
|
|
|
push |
edi |
; |
lpCaption |
57 |
|
|
|
|
push |
edi |
; |
lpText |
52 |
|
|
|
|
push |
edx |
; |
hWnd: NULL |
B8 |
EA |
07 |
45 |
7E |
mov |
eax, 7E4507EAh ; MessageBoxA |
||
FF D0 |
|
|
|
call |
eax |
|
|
|
52 |
|
|
|
|
push |
edx |
; uExitCode |
|
B8 |
FA |
CA 81 7C |
mov |
eax, 7C81CAFAh ; ExitProcess |
||||
FF D0 |
|
|
|
call |
eax |
|
|
Инструкция call передает управление функции sub_17. Этот код является позиционно-независимым, поскольку call использует относительное значение EIP (0x0000000D) для вычисления адреса, который нужно вызвать. Инструкция pop загружает адрес, хранящийся на вершине стека, в регистр EDI.
Как вы помните, значение EIP, сохраненное инструкцией call, указывает на участок, который идет сразу после этой инструкции, поэтому после выполнения pop регистр EDI будет содержать указатель на объявление db . В ассемблере это объявление означает создание последовательности байтов, которые формируют строку Hello World!. После инструкции pop EDI будет указывать на эту самую строку.
Такое смешивание инструкций и данных является нормальным для shell-кода, но может легко запутать дизассемблер, который попытается интерпретировать данные, идущие вслед за вызовом call, как код и в результате сгенерирует бессмыслицу или просто прервет процесс дизассемблирования, если встретит некорректную комбинацию опкодов. Как было показано в главе 15, применение связки call/pop для получения указателя на данные можно интегрировать в более объемные программы в качестве средства защиты от методик обратного проектирования.
Оставшийся код вызывает функции MessageBoxA и ExitProcess , чтобы вывести сообщение Hello World! и корректно завершить работу. В примере для обоих вызовов используются заранее определенные адреса, поскольку местоположение функций импорта в коде командной оболочки не ищется автоматически с помощью загрузчика. Но это же делает данный код хрупким, так как прописанные адреса подобраны для Windows XP SP3 и могут отличаться от ваших.
Чтобы найти адреса этих функций в OllyDbg, откройте любой процесс и нажмите Ctrl+G. На экране появится диалоговое окно Enter Expression to Follow (Введите искомое выражение). Введите MessageBoxA и нажмите Enter. Если отлаживаемый процесс
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
w |
|
|
to |
|
|
442 Часть 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 |
|
|
|
|
загрузил библиотеки с этой экспортной функцией (user32.dll), отладчик должен показать ее местоположение.
Чтобы загрузить и пошагово выполнить этот пример с помощью утилиты shellcode_launcher.exe, введите следующую команду:
shellcode_launcher.exe -i helloworld.bin -bp -L user32
Параметр -L user32 является обязательным, потому что shell-код не вызывает LoadLibraryA, — следовательно, загрузкой этой библиотеки должна заниматься утилита shellcode_launcher.exe. Параметр -bp вставляет точку останова прямо перед переходом к двоичному файлу с кодом командной оболочки, указанному с помощью параметра -i. Как вы помните, отладчик можно зарегистрировать так, чтобы он запускался автоматически (или по запросу), когда программа сталкивается с точкой останова. OllyDbg в этом случае откроет соответствующий процесс и подключится к нему. Это позволит вам пропустить содержимое программы shellcode_launcher.exe и сразу начать с shell-кода.
Чтобы настроить OllyDbg для отладки в реальном времени (то есть в процессе выполнения кода), выберите пункт меню Options Just-in-time Debugging Make OllyDbg Just-in-time Debugger (Параметры Отладка в реальном времени Сделать OllyDbg отладчиком в реальном времени).
ПРИМЕЧАНИЕ
Если вы хотите запустить этот пример, вам, возможно, придется изменить заранее заданные адреса функций MessageBoxA и ExitProcess. Мы уже показали, как можно найти их местоположение. Получив подходящий адрес, вы можете модифицировать файл helloworld.bin прямо в OllyDbg. Для этого поместите курсор на инструкцию, которая загружает адрес функции врегистр EAX, и нажмите Пробел. Перед вами появится диалоговое окно Assemble At (Вставить в), которое позволит вам ввести свой собственный ассемблерный код. OllyDbg интерпретирует изменения, перезаписав исходные инструкции. Просто замените 7E4507EAh на значение, актуальное для вашей системы, и OllyDbg модифицирует программу в памяти, обеспечивая корректное выполнение кода командной оболочки.
Использование инструкции fnstenv
Специальная архитектура под названием x87 (или математический сопроцессор) предоставляет отдельную среду выполнения в рамках платформы x86. Она содержит отдельный набор регистров особого назначения, которые должны сохраняться операционной системой при переключении контекста, когда программа выполняет на математическом сопроцессоре операции с плавающей запятой. В листинге 19.2 показана 28-байтовая структура, которая используется инструкциями fstenv и fnstenv для хранения в памяти состояния сопроцессора при работе в защищенном 32-битном режиме.
|
|
|
|
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 |
|
|
|||
Глава 19. Анализ кода командной оболочки 443 |
to |
|
|
|
|
|
||||
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
Листинг 19.2. Определение структуры FpuSaveState struct FpuSaveState {
uint32_t |
control_word; |
uint32_t |
status_word; |
uint32_t |
tag_word; |
uint32_t |
fpu_instruction_pointer; |
uint16_t |
fpu_instruction_selector; |
uint16_t |
fpu_opcode; |
uint32_t |
fpu_operand_pointer; |
uint16_t |
fpu_operand_selector; |
uint16_t |
reserved; |
}; |
|
Здесь нас интересует только поле fpu_instruction_pointer со сдвигом 12. Оно будет хранить адрес последней инструкции, которая использовала математический сопроцессор. Таким образом предоставляется контекстная информация для обработчиков исключений, чтобы те могли понять, какая именно арифметическая операция привела к сбою. Необходимость этого поля обусловлена тем, что ЦПУ и математический сопроцессор работают параллельно. И если операция с плавающей точкой генерирует исключение, обработчик не может ее идентифицировать по обратному адресу прерывания.
В листинге 19.3 показан ассемблерный код еще одной программы Hello World, которая использует вызов fnstenv для получения содержимого EIP.
Листинг 19.3. Программа Hello World с использованием fnstenv
Bytes |
|
|
|
Disassembly |
|
||
83 |
EC |
20 |
|
|
sub |
esp, 20h |
|
31 |
D2 |
|
|
|
xor |
edx, edx |
|
EB 15 |
|
|
|
jmp |
short loc_1C |
|
|
EA 07 |
45 |
7E |
|
dd 7E4507EAh |
; MessageBoxA |
||
FA CA |
81 |
7C |
|
dd 7C81CAFAh |
; ExitProcess |
||
48 |
65 |
6C |
6C 6F |
db 'Hello World!',0 |
|
||
20 |
57 |
6F |
72 |
6C |
|
|
|
64 |
21 |
00 |
|
|
|
|
|
loc_1C: |
|
|
|
|
|
||
D9 |
EE |
|
|
|
fldz |
|
|
D9 |
74 |
24 |
F4 |
|
fnstenv byte ptr [esp-0Ch] |
|
|
5B |
|
|
|
|
pop |
ebx |
; ebx указывает на fldz |
8D |
7B |
F3 |
|
|
lea |
edi, [ebx-0Dh] |
; загружаем указатель на Hello World |
52 |
|
|
|
|
push |
edx |
; uType: MB_OK |
57 |
|
|
|
|
push |
edi |
; lpCaption |
57 |
|
|
|
|
push |
edi |
; lpText |
52 |
|
|
|
|
push |
edx |
; hWnd: NULL |
8B |
43 |
EB |
|
|
mov |
eax, [ebx-15h] |
; загружаем MessageBoxA |
FF D0 |
|
|
|
call |
eax |
; вызываем MessageBoxA |
|
52 |
|
|
|
|
push |
edx |
; uExitCode |
8B |
43 |
EF |
|
|
mov |
eax, [ebx-11h] |
; загружаем ExitProcess |
FF D0 |
|
|
|
call |
eax |
; вызываем ExitProcess |