- •Внимание!
- •Об авторах
- •О техническом редакторе
- •О соавторах
- •Предисловие
- •Благодарности
- •Отдельное спасибо
- •Введение
- •Необходимая квалификация
- •Изучение на примерах
- •Структура книги
- •Глава 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 |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-xcha |
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
Глава 20. Анализ кода на C++ 463 |
to |
|
|
|
|
|
||||
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
Класс Socket содержит функцию для задания конечного адреса, но у него нет метода sendData, поскольку он не представляет собой какой-то конкретный тип сокета. Дочерний класс UDPSocket реализует функцию sendData , поэтому он способен отправлять данные. Кроме того, он может вызывать метод setDestinationAddr, определенный в классе Socket.
Влистинге 20.5 sendData вызывает функцию setDestinationAddr , хотя та
ине указана в классе UDPSocket. Это возможно благодаря тому, что родительский класс автоматически становится частью дочернего.
Наследование делает многократное использование кода более эффективным, при этом не требует никаких структур данных на этапе выполнения и в целом является незаметным в ассемблерном коде.
Обычные и виртуальные функции
Виртуальной называется функция, которую можно переопределить в подклассе и работа которой определяется на этапе выполнения. Если родительский и дочерний классы содержат функцию с одним и тем же именем, дочерняя функция перезапишет родительскую.
Несколько популярных моделей программирования используют эту концепцию для существенного упрощения сложных задач. Чтобы проиллюстрировать полезность такого подхода, вернемся к примеру с сокетом из листинга 20.5. У нас есть код, который должен отправить данные по сети (sendData), но мы хотим иметь возможность выбирать между протоколами TCP и UDP. Чтобы этого добиться, можно просто создать родительский класс Socket с виртуальным методом sendData и два дочерних класса, UDPSocket и TCPSocket, которые переопределяют этот метод для отправки данных по соответствующему протоколу.
Внашем коде мы создаем объект типа Socket и указываем тот сокет, который будем использовать в том или ином случае. Функция sendData всегда будет вызываться из подходящего подкласса, будь то UDPSocket или TCPSocket; выбор будет делаться в зависимости от типа исходного объекта Socket.
Основное преимущество данного подхода состоит в том, что при изобретении нового протокола (скажем, QDP) вам достаточно будет лишь создать новый класс QDPSocket и изменить строку кода, в которой создается объект. После этого все вызовы будут направлены к новой версии sendData из класса QDPSocket, и вам не придется менять их вручную.
Если речь идет об обычных функциях, версия выбирается на этапе компиляции. Если объект является экземпляром родительского класса, будет вызвана родительская функция, даже если на момент выполнения этот объект принадлежит дочернему классу. Если функция виртуальная, в аналогичной ситуации выбор делается
впользу дочерней версии.
Втабл. 20.1 показан фрагмент кода, выполнение которого зависит от того, явля-
ется ли функция виртуальной.
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|
|
|
|||
|
|
X |
|
|
|
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
|
|
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
||
|
D |
|
|
|
|
|
|
|
i |
r |
|
|
|
P |
|
|
|
|
|
|
|
|
o |
|
|
||
|
|
|
|
|
NOW! |
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|
|
||||
w |
|
|
to |
|
|
464 Часть VI • Специальные темы |
|
|
|||||
w Click |
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
o |
m |
|
|
||||
|
w |
|
|
|
|
|
|
|
|
|
|
|
|
|
. |
|
|
|
|
|
|
.c |
|
|
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
||
|
|
|
df |
|
|
n |
e |
|
|
|
|
||
|
|
|
|
-xcha |
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
Таблица 20.1. Пример исходного кода с виртуальными функциями |
|||||
|
|
|
|
|
|
|
|
|
Обычная функция |
|
Виртуальная функция |
||
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
class A { |
|
class A { |
||
|
|
|
|
|
|
|
|
|
public: |
|
public: |
||
|
|
|
|
|
|
|
|
|
|
|
void foo() { |
|
virtual void foo() { |
|
|
|
|
|
|
|
|
|
|
|
printf(“Class A\n”); |
|
printf(«Class A\n»); |
|
|
|
|
|
|
|
|
|
|
|
} |
|
} |
|
|
|
|
|
|
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
class B : public A { |
|
class B : public A { |
||
|
|
|
|
|
|
|
|
|
public: |
|
public: |
||
|
|
|
|
|
|
|
|
|
|
|
void foo() { |
|
virtual void foo() { |
|
|
|
|
|
|
|
|
|
|
|
printf(“Class B\n”); |
|
printf(«Class B\n»); |
|
|
|
|
|
|
|
|
|
|
|
} |
|
} |
|
|
|
|
|
|
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
void g(A& arg) { |
|
void g(A& arg) { |
||
|
|
|
|
|
|
|
|
|
|
|
arg.foo(); |
|
arg.foo(); |
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
int _tmain(int argc, _TCHAR* argv[]) |
|
int _tmain(int argc, _TCHAR* argv[]) |
||
|
|
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
B b; |
|
B b; |
|
|
|
|
|
|
|
|
|
|
|
A a; |
|
A a; |
|
|
|
|
|
|
|
|
|
|
|
g(b); |
|
g(b); |
|
|
|
|
|
|
|
|
|
|
|
return 0; |
|
return 0; |
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
Код содержит два класса: A и B. Класс B переопределяет метод foo из класса A. В коде также есть функция для вызова метода foo за пределами любого из этих классов. Если эту функцию не сделать виртуальной, программа выведет строку Class A; в противном случае результатом будет строка Class B. Если не считать ключевых слов virtual в строках и , оба столбца содержат идентичный код.
В случае с обычными функциями выбор конкретного вызова происходит во время компиляции. Когда компилируются два фрагмента кода, представленных в табл 20.1, объект получает класс A. Теоретически компилятор мог выбрать подкласс A, но на этом этапе нам уже известен тип объекта, поэтому функция foo будет вызываться из класса A. По этой причине код в левом столбце выводит строку Class A.
Если речь идет о виртуальных функциях, решение о том, какая из них будет вызвана, принимается во время выполнения. Если используется объект класса A, код вызовет функцию именно из этого класса. То же самое относится и к классу B. Поэтому код в правом столбце выводит строку Class B.
Такое поведение часто называют полиморфизмом. Основное его преимущество — возможность создавать объекты с разными функциями, но с общим интерфейсом.
|
|
|
|
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 |
|
|
|||
Глава 20. Анализ кода на C++ 465 |
to |
|
|
|
|
|
||||
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
Использование таблиц виртуальных методов
При обработке кода на языке C++ компилятор добавляет специальные структуры данных для поддержки виртуальных функций. Эти структуры называются таблицами виртуальных методов или просто vtable. Они представляют собой обычные массивы указателей на функции. У каждого класса, который использует виртуальные методы, есть своя таблица, и каждый метод в ней имеет отдельную запись.
Втабл. 20.2 показан ассемблерный код двух версий функции g, представленной
втабл. 20.1. Слева находится обычная версия для вызова foo, а справа — виртуальная.
Таблица 20.2. Ассемблерный код примера из табл. 20.1
Вызов обычной функции |
Вызов виртуальной функции |
||||
|
|
|
|
|
|
00401000 |
push |
ebp |
00401000 |
push |
ebp |
00401001 |
mov |
ebp, esp |
00401001 |
mov |
ebp, esp |
00401003 |
mov |
ecx, [ebp+arg_0] |
00401003 |
mov |
eax, [ebp+arg_0] |
00401006 |
call |
sub_401030 |
00401006 |
mov |
edx, [eax] |
0040100B |
pop |
ebp |
00401008 |
mov |
ecx, [ebp+arg_0] |
0040100C |
retn |
|
0040100B |
mov |
eax, [edx] |
|
|
|
0040100D |
call |
eax |
|
|
|
0040100F |
pop |
ebp |
|
|
|
00401010 |
retn |
|
|
|
|
|
|
|
Исходный код изменился незначительно, однако его дизассемблированный вариант выглядит совершенно иначе. Слева вызов происходит так, как мы уже видели ранее в языке C. Вызов виртуальной функции существенно отличается. Основная разница состоит в том, что мы не видим, куда ведет инструкция call. Это может оказаться большой проблемой при анализе дизассемблированных программ, написанных на C++, поскольку мы должны знать, что именно вызывается.
В качестве аргумента функция g принимает ссылку на объект класса A (или любого его подкласса), с которой можно работать как с указателем. Ассемблерный код обращается к указателю, расположенному в начале объекта . Затем происходит доступ к первым четырем байтам объекта .
На рис. 20.2 показано, как с помощью виртуальной функции делается выбор
впользу того или иного участка кода в табл. 20.2. Первые четыре байта объекта являются указателем на vtable. Первые четыре байта внутри vtable представляют собой указатель на код первой виртуальной функции.
Чтобы понять, какая функция вызывается, нужно определить, к какому участку таблицы происходит обращение: так вы узнаете, с каким сдвигом делается вызов. В табл. 20.2 мы видим, что доступ происходит к первой записи. Чтобы обнаружить вызываемый код, мы должны найти в памяти vtable и пройти к первой функции
всписке.
Обычные функции не попадают в таблицу виртуальных методов, поскольку в этом нет необходимости. Вызов таких функций фиксируется на этапе компиляции.
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|
|
|
|
|
|
|
|
|||
|
|
X |
|
|
|
|
|
|
|
|
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
|
|
|
|
|
|
|
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
|
|
|
|
|
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
r |
||||||||||
P |
|
|
|
|
|
NOW! |
o |
|||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|
|
|
|
|
|
|
|
|||
w |
|
|
to |
|
|
466 Часть 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 |
|
|
|
|
Рис. 20.2. Объект на языке C++ с таблицей виртуальных методов (vtable)
Распознавание таблицы виртуальных методов
Чтобы понять, куда ведет вызов, нам нужно определить тип объекта и установить местоположение vtable. Доступ к адресу vtable обычно происходит недалеко от оператора new, относящегося к конструктору (об этом понятии мы поговорим в следующем разделе).
Таблица виртуальных методов выглядит как массив указателей на функции. В листинге 20.6 показан пример vtable для класса с тремя виртуальными функция ми. При просмотре такой таблицы перекрестную ссылку должна иметь только первая ее запись. Доступ к остальным элементам выполняется по сдвигу относительно начала таблицы: прямого доступа к ним нет.
ПРИМЕЧАНИЕ
Вэтом примере строка с меткой off_4020F0 является началом vtable; не спутайте это с таблицами переключения сдвигов, рассмотренными в главе 6.
Втаблице переключения сдвиги не связаны с ответвлениями и помечены как loc_###### вместо sub_######.
Листинг 20.6. Таблица виртуальных методов в IDA Pro
004020F0 |
off_4020F0 |
dd offset sub_4010A0 |
004020F4 |
|
dd offset sub_4010C0 |
004020F8 |
|
dd offset sub_4010E0 |
Виртуальные функции можно распознать по их перекрестным ссылкам. Они не вызываются напрямую с других участков кода, поэтому при исследовании перекрестных ссылок вы не должны обнаружить их вызовы. На рис. 20.3 показан пример двух перекрестных ссылок для виртуальной функции. Обе они представляют собой ее сдвиг, и ни одна из них не является инструкцией call. Виртуальные функции почти всегда выглядят подобным образом, в то время как инструкция call обычно применяется для невиртуальных вызовов.
Установив местоположение vtable и виртуальных функций, можно использовать эту информацию для их анализа. Вы должны знать, что все методы внутри найденной вами таблицы принадлежат одному и тому же классу и что все они каким-то образом связаны между собой. На основе vtable можно также определить, являются ли два метода родственными.