- •Внимание!
- •Об авторах
- •О техническом редакторе
- •О соавторах
- •Предисловие
- •Благодарности
- •Отдельное спасибо
- •Введение
- •Необходимая квалификация
- •Изучение на примерах
- •Структура книги
- •Глава 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 |
|
|
|||
w |
|
|
to |
|
|
444 Часть 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 |
|
|
|
|
Инструкция fldz помещает вещественное число 0,0 в стек сопроцессора, который обновляет значение fpu_instruction_pointer, чтобы оно указывало на fldz.
Врезультате выполнения операции fnstenv структура FpuSaveState попадает
встек по адресу [esp-0ch], что позволяет коду командной оболочки загрузить значение fpu_instruction_pointer в регистр EBX, используя инструкцию pop. Когда pop завершится, EBX будет содержать указатель на местоположение инструкции fldz. Затем shell-код начнет использовать EBX в качестве базового регистра для доступа к данным, встроенным в процесс.
Как и в предыдущем примере на основе операций call/pop, мы вызываем функции MessageBoxA и ExitProcess, используя заранее заданные адреса. Однако теперь эти адреса хранятся в виде данных вместе со строкой в формате ASCII, которую
нужно вывести. Инструкция lea загружает адрес строки Hello World!, вычитая 0x0d из местоположения инструкции fldz, хранящегося в регистре EBX. Инструк-
ции mov в строках и загружают местоположение функций MessageBoxA и соответственно ExitProcess.
ПРИМЕЧАНИЕ
Пример в листинге 19.3 немного надуманный, но такой подход часто используется в shell-коде для хранения или создания массивов с указателями на функции. Мы выбрали инструкцию fldz, но здесь подошла бы любая неуправляющая операция математического сопроцессора.
Чтобы запустить этот пример с помощью утилиты shellcode_launcher.exe, введите следующую команду:
shellcode_launcher.exe -i hellofstenv.bin -bp -L user32
Поиск символов вручную
Код командной оболочки существует в виде набора двоичных данных, которые можно выполнить. При запуске он должен делать что-то осмысленное. Обычно это подразумевает взаимодействие с системой посредством API-вызовов.
Помните, что shell-код не может загружать необходимые ему библиотеки, проверять их доступность и находить внешние символы с помощью системного загрузчика. Он должен делать это самостоятельно. В предыдущих примерах для нахождения символов использовались заранее определенные адреса, но это очень ненадежный подход, который пригоден только для конкретной версии системы и обновлений. Чтобы надежно работать в разных средах, код командной оболочки должен динамически находить нужные функции, и для этой цели в нем обычно используются вызовы LoadLibraryA и GetProcAddress.
LoadLibraryA загружает определенную библиотеку и возвращает ее дескриптор. GetProcAddress ищет в библиотеке экспортные вызовы для символа с заданным именем или порядковым номером. Если shell-код имеет доступ к этим функциям,
|
|
|
|
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. Анализ кода командной оболочки 445 |
to |
|
|
|
|
|
||||
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
он может загрузить любую библиотеку в системе и найти символы, которые она экспортирует, что дает ему полный доступ к API.
Обе функции экспортируются из файла kernel32.dll, поэтому код командной оболочки должен сделать следующее.
1.Найти kernel32.dll в памяти.
2.Разобрать PE-заголовок kernel32.dll и найти в нем адреса функций LoadLibraryA
и GetProcAddress.
Поиск kernel32.dll в памяти
Чтобы найти kernel32.dll, нужно пройтись по цепочке из недокументированных системных структур, одна из которых и будет содержать загрузочный адрес нужного файла.
ПРИМЕЧАНИЕ
Большинство системных структур Windows указаны на сайте Microsoft Developer network (www.msdn.microsoft.com), но вы ненайдете там их полной документации. Многие из них содержат массивы байтов, помеченные как Reserved и содержащие следующее предупреждение: «Эта структура может измениться в будущих версиях Windows». Полный список этих структур можно найти по адресу www.undocumented.ntinternals.net.
На рис. 19.1 показаны структуры данных, по которым обычно определяют базовый адрес библиотеки kernel32.dll (в каждом случае показаны только важные для нас поля и сдвиги).
Рис. 19.1. Обход структур для поиска DllBase в kernel32.dll
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
w |
|
|
to |
|
|
446 Часть 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 |
|
|
|
|
Процедура обхода начинается со структуры TEB, доступной из сегментного регистра FS. Сдвиг 0x30 внутри TEB указывает на PEB. Сдвиг 0xc внутри PEB ведет к структуре PEB_LDR_DATA, которая содержит три списка с двойным связыванием. Их элементы представляют собой структуры LDR_DATA_TABLE — по одной для каждого загруженного модуля. Поле DllBase на входе в kernel32.dll — то значение, которое мы искали.
Три структуры LIST_ENTRY связывают по имени, но в разном порядке элементы LDR_DATA_TABLE. Код командной оболочки обычно идет за элементом InInitializationOrderLinks. В версиях Windows с 2000 по Vista среди всех библиотек kernel32.dll инициализируется второй, вслед за ntdll.dll: это означает, что второй элемент в списке InInitializationOrderLinks должен принадлежать kernel32.dll. Но, начиная с Windows 7, модуль kernel32.dll больше не инициализируется вторым по счету, в связи с чем этот простой алгоритм не работает. Переносимый shell-код должен проверить поле FullDllName типа UNICODE_STRING, чтобы подтвердить, что это kernel32.dll.
При обходе структур LIST_ENTRY важно понимать, что указатели Flink и Blink ссылаются на аналогичные поля LIST_ENTRY в следующей и предыдущей структурах типа LDR_ DATA_TABLE. Это означает, что при переходе к элементу InInitializationOrderLinks для получения из kernel32.dll записи LDR_DATA_TABLE_ENTRY (и затем DllBase) вам нужно добавить к указателю значение 8, а не 0x18, как если бы указатель ссылался на начало структуры.
Листинг 19.4 содержит пример ассемблерного кода, который находит базовый адрес kernel32.dll.
Листинг 19.4. Реализация функции findKernel32Base
; __stdcall |
DWORD findKernel32Base(void); |
|
findKernel32Base: |
|
|
push |
esi |
|
xor |
eax, eax |
|
mov |
eax, [fs:eax+0x30] |
; eax получает указатель на PEB |
test |
eax, eax |
; если старший бит установлен: Win9x |
js |
.kernel32_9x |
|
mov |
eax, [eax + 0x0c] |
; еах получает указатель на PEB_LDR_DATA |
;esi gets pointer to 1st ;LDR_DATA_TABLE_ENTRY.InInitializationOrderLinks.Flink
mov |
esi, [eax + 0x1c] |
|
;eax gets pointer to 2nd |
|
|
;LDR_DATA_TABLE_ENTRY.InInitializationOrderLinks.Flink |
||
lodsd |
|
|
mov |
eax, [eax + 8] |
; eax получает LDR_DATA_TABLE_ENTRY.DllBase |
jmp |
near .finished |
|
.kernel32_9x: |
|
|
jmp |
near .kernel32_9x |
; Win9x не поддерживается: бесконечный цикл |
.finished: |
|
|
Pop |
esi |
|
ret |
|
|
Чтобы получить указатель на структуру PEB, этот код обращается к TEB, используя сегментный регистр FS . Инструкция js (переход со знаком) в строке
|
|
|
|
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. Анализ кода командной оболочки 447 |
to |
|
|
|
|
|
||||
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
проверяет, установлен ли старший бит в указателе на PEB: это делается для того, чтобы отличить систему Win9x от WinNT. В WinNT (включая Windows 2000, XP и Vista) старший бит в указателе на PEB обычно не устанавливается, поскольку верхние адреса зарезервированы для ОС. Определение семейства операционной системы с помощью знакового бита не работает, если ОС загружена с параметром /3GB, из-за которого разделение между пространствами пользователя и ядра происходит по адресу 0xC0000000 вместо 0x8000000. Но для простоты мы решили этим пренебречь. Данный shell-код намеренно не поддерживает Win9x, поэтому при обнаружении ОС этого семейства он входит в бесконечный цикл .
Дальше код командной оболочки переходит к PEB_LDR_DATA . Предполагается, что он работает в системе не новее Windows Vista, поэтому он может просто извлечь из связного списка InInitializationOrderLinks второй элемент LDR_DATA_TABLE_ ENTRY и вернуть его поле DllBase.
Разбор экспортных данных в PE-файле
Получив базовый адрес библиотеки kernel32.dll, вы должны найти символы, которые она экспортирует. Как и в предыдущем случае, этот процесс заключается в переборе цепочки из нескольких структур, загруженных в память.
При определении местоположения файла формат PE использует относительные виртуальные адреса (ОВА). Эти адреса можно рассматривать как сдвиги в образе PE-файла, поэтому, чтобы получить корректный указатель, к каждому ОВА следует добавлять базовый адрес образа.
Экспортные данные содержатся в структуре IMAGE_EXPORT_DIRECTORY, ОВА которой хранится в массиве элементов IMAGE_DATA_DIRECTORY в конце IMAGE_OPTIONAL_ HEADER. Местоположение массива зависит от разрядности PE-файла. Код командной оболочки обычно рассчитан на работу в 32-битной системе, поэтому на этапе компиляции он может вычислить расстояние между PE-сигнатурой и массивом элементов
IMAGE_DATA_DIRECTORY:
sizeof(PE_Signature) + sizeof(IMAGE_FILE_HEADER) + sizeof(IMAGE_OPTIONAL_HEADER) = 120 bytes
На рис. 19.2 показаны поля структуры IMAGE_EXPORT_DIRECTORY, которые нас интересуют. AddressOfFunctions — это массив ОВА, который ведет к реальным экспортным функциям. В качестве индекса в нем используется экспортный порядковый номер (альтернативный способ обращения к экспортным символам).
Чтобы использовать этот массив, shell-код должен привязать экспортное имя к порядковому номеру. Для этого он использует массивы AddressOfNames и Ad dressOfNameOrdinals, которые существуют параллельно. Они имеют одинаковое количество элементов, а их индексы напрямую связаны между собой. AddressOfNames содержит 32-битные ОВА, которые указывают на строки с именами символов. AddressOfNameOrdinals содержит 16-битные порядковые номера. Если взять idx за индекс, то символ по адресу AddressOfNames[idx] имеет экспортный порядковый номер AddressOfNameOrdinals[idx]. Массив AddressOfNames отсортирован в алфавитном порядке, поэтому мы можем быстро найти нужную строку, используя
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
w |
|
|
to |
|
|
448 Часть 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 |
|
|
|
|
двоичный поиск (хотя в большинстве случаев это делается простым последовательным перебором массива).
Рис. 19.2. Структура IMAGE_EXPORT_DIRECTORY в kernel32.dll
Чтобы найти экспортный адрес символа, выполните следующие шаги.
1.Пройдитесь по массиву AddressOfNames, сравнивая каждую запись char* с нужным вам символом, пока не найдете совпадение. Воспользуйтесь соответству ющим индексом, чтобы получить имя iName из AddressOfNames.
2.Возьмите из массива AddressOfNameOrdinals элемент по индексу iName. Это будет порядковый номер iOrdinal.
3.Используйте iOrdinal в качестве индекса в массиве AddressOfFunctions. Полученное значение будет содержать ОВА экспортного символа. Верните его запрашивающему коду.
4.Реализация этого алгоритма будет показана в этой главе при демонстрации пол-
ной версии примера 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. Анализ кода командной оболочки 449 |
to |
|
|
|
|
|
||||
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
Как только код командной оболочки найдет функцию LoadLibraryA, он сможет загружать произвольные библиотеки. Значение, возвращаемое из LoadLibraryA, имеет в Win32 API тип HANDLE. Изучив значения этого типа, вы увидите, что на самом деле это 32-битный указатель на функцию dllBase в загруженной библиотеке. Это означает, что shell-код может обойтись без вызова GetProcAddress и дальше использовать свой собственный код в сочетании с указателями на dllBase, полученными из функции LoadLibraryA. В следующем разделе вы увидите, что это полезно в случае использования хешированных имен.
Использование хешированных экспортных имен
Алгоритм, который мы только что рассмотрели, имеет один недостаток: он выполняет операцию strcmp для каждого экспортного имени, пока не найдет подходящее. Для этого имя каждой API-функции, используемой в shell-коде, должно храниться
вформате ASCII. Однако это может сделать размер кода непозволительно большим.
Распространенное решение этой проблемы состоит в вычислении хеша строки каждого символа и сравнении результата с заранее подготовленным значением, хранящимся в коде командной оболочки. Хеш-функция не должна быть слишком сложной — достаточно, чтобы она гарантировала уникальность хешей в пределах отдельной DLL. Коллизии между хешами символов, которые не используются
вshell-коде или находятся в разных динамических библиотеках, считаются допустимыми.
Самой распространенной функцией хеширования является добавочный алгоритм с вращением вправо, представленный в листинге 19.5.
Листинг 19.5. Реализация функции hashString
; __stdcall |
DWORD hashString(char* symbol); |
|
hashString: |
|
|
push |
esi |
|
push |
edi |
|
mov |
esi, dword [esp+0x0c] |
; загружаем аргумент функции в esi |
.calc_hash: |
|
|
xor |
edi, edi |
|
cld |
|
|
.hash_iter: |
|
|
xor |
eax, eax |
|
lodsb |
|
; загружаем следующий байт входящей строки |
cmp |
al, ah |
|
je |
.hash_done |
; проверяем, последний ли это символ |
ror |
edi, 0x0d |
; поворачиваем вправо на 13 (0x0d) |
add |
edi, eax |
|
jmp |
near .hash_iter |
|
.hash_done: |
|
|
mov |
eax, edi |
|
pop |
edi |
|
pop |
esi |
|
retn |
4 |
|