- •Внимание!
- •Об авторах
- •О техническом редакторе
- •О соавторах
- •Предисловие
- •Благодарности
- •Отдельное спасибо
- •Введение
- •Необходимая квалификация
- •Изучение на примерах
- •Структура книги
- •Глава 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 |
|
|
144 Часть II • Продвинутый статический анализ |
||||
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 |
|
|
|
|
Стрелки, ведущие вверх после инкремента, являются признаком цикла. Благодаря им цикл легче увидеть на схеме, чем в стандартном окне дизассемблирования. Схема состоит из пяти блоков. Четыре верхних представляют собой этапы цикла for, размещенные в определенном порядке (инициализация, сравнение, выполнение и инкремент). Блок, находящийся в правой нижней части, является эпилогом функции, который в главе 4 был описан как часть функции, ответственная за очистку стека и возвращение значения.
Поиск циклов while
Цикл while часто используется авторами вредоносного ПО при ожидании, пока не будет выполнено какое-то условие, например получение команды или пакета. В ассемблере циклы while похожи на for, но их легче понять. В листинге 6.14 показан цикл while, который продолжает работу, пока checkResult возвращает 0.
Листинг 6.14. Цикл while в языке C
int status=0; int result = 0;
while(status == 0){
result = performAction(); status = checkResult(result);
}
В ассемблере это выражение похоже на цикл for, но без инкремента (листинг 6.15). Присутствуют условный и безусловный переходы, однако выполнение первого является единственным условием прекращения работы цикла.
Листинг 6.15. Код на ассемблере для цикла while из листинга 6.14
00401036 |
mov |
[ebp+var_4], 0 |
0040103D |
mov |
[ebp+var_8], 0 |
00401044 |
loc_401044: |
|
00401044 |
cmp |
[ebp+var_4], 0 |
00401048 |
jnz |
short loc_401063 |
0040104A |
call |
performAction |
0040104F |
mov |
[ebp+var_8], eax |
00401052 |
mov |
eax, [ebp+var_8] |
00401055 |
push |
eax |
00401056 |
call |
checkResult |
0040105B |
add |
esp, 4 |
0040105E |
mov |
[ebp+var_4], eax |
00401061 |
jmp |
short loc_401044 |
Соглашения, касающиеся вызова функций
В главе 4 мы обсудили то, как для вызова функций используются стек и инструкция call. В ассемблере функции могут вызываться по-разному. Эта процедура регули-
|
|
|
|
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 |
|
|
|||
Глава 6. Распознавание конструкций языка C в ассемблере 145 |
to |
|
|
|
|
|
||||
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
руется отдельными соглашениями, которые определяют, какие параметры помещаются в стек или регистры и кто ответственен за очистку стека после завершения функции — вызывающая или вызываемая сторона.
Выбор тех или иных соглашений зависит в том числе и от компилятора. Их реа лизации могут немного отличаться, поэтому код, собранный разными компиляторами, может оказаться сложным для понимания. Чтобы достичь совместимости, соглашения, касающиеся использования Windows API, реализуются одинаково (см. главу 7).
В листинге 6.16 с помощью псевдокода записаны все форматы вызова функций.
Листинг 6.16. Вызовы функций, выполненные в псевдокоде
int test(int x, int y, int z); int a, b, c, ret;
ret = test(a, b, c);
Самыми распространенными способами вызова функций являются инструкции cdecl, stdcall и fastcall. Ключевые различия между ними будут рассмотрены в следующих разделах.
ПРИМЕЧАНИЕ
Одни и те же соглашения могут по-разному использоваться в разных компиляторах, но мы сосредоточимся на самых популярных реализациях.
cdecl
cdecl — это одно из наиболее распространенных соглашений. Мы сталкивались с ним в главе 4 при знакомстве со стеком и вызовами функций. Операнды cdecl помещаются в стек справа налево, за очистку стека после завершения функции отвечает вызывающая сторона, а возвращаемое значение сохраняется в регистре EAX. Ниже показан пример того, как бы выглядел ассемблерный код из листинга 6.16, если бы он был скомпилирован с использованием cdecl.
Листинг 6.17. Вызов функции cdecl
push c push b push a call test add esp, 12
mov ret, eax
Обратите внимание на выделенный участок, где вызывающая сторона очи щает стек. В этом примере параметры помещаются в стек справа налево, начиная от c.
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
w |
|
|
to |
|
|
146 Часть II • Продвинутый статический анализ |
||||
w Click |
|
|
|
|
|
|
||||
|
|
|
|
|
o |
m |
||||
|
w |
|
|
|
|
|
|
|
|
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-xcha |
|
|
|
|
stdcall
|
|
|
|
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 |
|
|
|
|
Популярное соглашение stdcall похоже на cdecl, но требует, чтобы очисткой стека после завершения функции занималась вызываемая сторона. Следовательно, если бы мы использовали stdcall в листинге 6.17, нам бы не понадобилась инструкция add, поскольку за очистку стека отвечала бы вызванная функция.
Функция test из листинга 6.16 тоже была бы скомпилирована иначе, поскольку ей пришлось бы очищать стек. Эта процедура выполнялась бы в ее конце.
stdcall является стандартным соглашением для Windows API. Код, который вызывает функции этого интерфейса, не должен заниматься очисткой стека, так как это ответственность динамических библиотек, содержащих реализацию этих функций.
fastcall
Соглашение fastcall имеет больше всего различий в разных компиляторах, но в целом оно работает похожим образом в любых ситуациях. Первые несколько аргументов (обычно два) попадают в регистры, среди которых чаще других используются EDX и ECX (такой вариант применяют в Microsoft). Остальные аргументы загружаются справа налево, а вызывающая функция обычно выполняет очистку стека, если это требуется. Это соглашение часто оказывается наиболее эффективным, поскольку оно не так активно использует стек.
Сравнение инструкций push и mov
Помимо разных соглашений о вызове функций, описанных выше, компиляторы могут использовать разные инструкции для выполнения одних и тех же операций. Обычно это касается того, как данные попадают в стек — с помощью mov или push.
В листинге 6.18 показан пример вызова функции на языке C. Функция adder слагает два аргумента и возвращает результат. Функция main вызывает adder и выводит возвращенное значение посредством printf.
Листинг 6.18. Вызов функции на языке C
int adder(int a, int b)
{
return a+b;
}
void main()
{
int x = 1; int y = 2;
|
|
|
|
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 |
|
|
|||
Глава 6. Распознавание конструкций языка C в ассемблере 147 |
to |
|
|
|
|
|
||||
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
printf("the function returned the number %d\n", adder(x,y));
}
Ассемблерный код функции adder не зависит от компилятора и представлен в листинге 6.19. Этот код добавляет arg_0 к arg_4 и сохраняет результат в регистре EAX (как упоминалось в главе 4, EAX хранит возвращаемое значение).
Листинг 6.19. Ассемблерный код функции adder из листинга 6.18
00401730 |
push |
ebp |
00401731 |
mov |
ebp, esp |
00401733 |
mov |
eax, [ebp+arg_0] |
00401736 |
add |
eax, [ebp+arg_4] |
00401739 |
pop |
ebp |
0040173A |
retn |
|
В табл. 6.1 перечислены разные соглашения о вызове функций, которые используются двумя компиляторами: Microsoft Visual Studio и GNU Compiler Collection (GCC). Слева параметры для функций adder и printf помещаются в стек с помощью инструкции push, а справа — с использованием инструкции mov.
Вы должны быть готовы к обоим соглашениям о вызове функций, потому что у вас как у аналитика нет возможности выбрать компилятор. Например, одна из инструкций, представленных слева, не соответствует ни одной инструкции в правой части. Она восстанавливает указатель на стек, что является необязательным в случае с GCC, так как тот никогда не меняет этот указатель.
ПРИМЕЧАНИЕ
Помните, что даже один и тот же компилятор может использовать разные соглашения о вызове в зависимости от настроек и параметров.
Таблица 6.1. Ассемблерный код вызова функции с использованием двух разных соглашений
Visual Studio |
|
GCC |
|
|
|
|
|
|
|
|
|
00401746 |
mov |
[ebp+var_4], 1 |
00401085 |
mov |
[ebp+var_4], 1 |
0040174D |
mov |
[ebp+var_8], 2 |
0040108C |
mov |
[ebp+var_8], 2 |
00401754 |
mov |
eax, [ebp+var_8] |
00401093 |
mov |
eax, [ebp+var_8] |
00401757 |
push |
eax |
00401096 |
mov |
[esp+4], eax |
00401758 |
mov |
ecx, [ebp+var_4] |
0040109A |
mov |
eax, [ebp+var_4] |
0040175B |
push |
ecx |
0040109D |
mov |
[esp], eax |
0040175C |
call |
adder |
004010A0 |
call |
adder |
00401761 |
add |
esp, 8 |
004010A5 |
mov |
[esp+4], eax |
00401764 |
push |
eax |
004010A9 |
mov |
[esp], offset TheFunctionRet |
00401765 |
push |
offset TheFunctionRet |
004010B0 |
call |
printf |
0040176A |
call |
ds:printf |
|
|
|
|
|
|
|
|
|