- Главная
- Блог
- Информационная безопасность
- Использование OllyDbg - Часть 1
Использование OllyDbg - Часть 1
Введение
В этом туториале я попытаюсь представить вам основы того, как использовать OllyDbg. Олли содержит множество функций и единственный путь по-настоящему освоить их – это практика и еще раз практика. Тут же я изложу вам самые основы, которых достаточно что бы начать. Какие-то вещи, не затронутые здесь, будут описаны позже, поэтому в итоге вы должны получить солидный багаж знаний и умений по использованию Олли.
Я добавил некоторые файлы в этот туториал. Они включают в себя простой бинарный файл, который мы откроем в Олли, справку по Олли, и мою версию этого отладчика с некоторыми косметическими изменениями и новый .ini файл, которым можно заменить дефолтный .ini файл в папке Олли. Все это можно скачать здесь (ссылка).
Открываем приложение
Первый шаг – это загрузка необходимого нам бинарника в Олли. Вы можете мышкой переместить иконку бинарника в окно Олли, либо кликнуть на иконку загрузки и выбрать нужный файл. В данном случае откройте файл “FirstProgram.exe”, который я прикрепил к этому туториалу. Олли сделает необходимый анализ и остановится на точке входа этой программы.
Первое, что можно заметить это то, что адрес точки входа – 401000, как видно в первом столбце. Это – стандартный адрес для точек входа в исполняемых файлах (по крайней мере если они не подвергались действию пакеров или крипторов). Если вдруг в вашем случае Олли не остановилась на точке входа, попробуйте зайти в меню Appearance и кликните на Debugging Options. Затем откройте вкладку Events и убедитесь, что бы флажок WinMane (if location is kown) был выбран. Затем, перезагрузите приложение.
Давайте посмотрим снимок памяти, занятой нашей программой. Нажмите на икону Me:
Если вы посмотрите на первую колонку, вы увидите, что напротив адреса 401000 указанно: размер 1000, имя “FirstPro” (сокращение от FirstProgram), имя секции .text и она содержит “SFX и Code”. Как мы узнаем позже, исполняемы файлы содержат в себе несколько секций, в которых хранятся различный данные. В секции, которую мы сейчас рассматриваем, указано, что она хранит код. Она иммет размер 1000h (h на конце означает, что это число представлено в шестандцатеричной системе счисления) байтов и начинается по адресу 401000.
Ниже вы увидите другие секции нашей программы. Там находятся секция .rdata, которая содержит данный и импорт (по адресу 402000), секция .data, которая не содержит ничего (по адресу 403000) и, наконец, секция .rsrc, которая содержит ресурсы (такие как диалоговые окна, изображения, текстовые строки и т.д.). Необходимо понимать, что секции могут иметь любые имена – это зависит от программиста.
Вы можете удивится тому факту, что секция .data является пустой. На самом деле она не пустая – в ней хранятся значение некоторых переменных, но анализатор Олли не совсем это понял :)
Выше рассмотренных нами секций находится секция с названием “PE Header”. Это очень важная секция и мы подробнее изучим ее в будущем. На данный момент достаточно знать, что она хранит информацию для операционной системы – сколько место в памяти занимает эта программа, по какому адресу расположены те или иные вещи и т.д. Эта секция находится в начале любого исполняемого файла.
Если вы посмотрите вниз, мы увидим имена не только нашей программы, но и других файлов. Мы увидим comctl32, imm32, gdi32, kernel32 и т.д. Это DLL файлы (или, как еще говорят – DLL библиотеки), в которых нуждается наша программа для своей работы. DLL файл представляет собой набор функций, которые программа может вызвать. Эти файлы предоставляет операционная система. Также они могут быть разработаны программистом самостоятельно. Они могут содержать такие функции, как отображение диалоговых окон, сравнение строк и т.д. Функции WindowsAPI как раз и предоставляются посредством DLL библиотек. Программист их использует, поскольку иначе ему пришлось бы реализовывать все эти функции самостоятельно. Даже такая простая, казалось бы функция, как отображение окна с сообщением, могла бы содержать в себе сотни строк кода. Поэтому, что бы помочь программистам и облегчить процесс разработки Wndows предоставляет готовые функции.
Вам может быть любопытно, каким образом эти Dll библиотеки попадают а адресное пространство нашей программы и откуда Windows знает какие именно библиотеки необходимы данному приложению? Такого рода информация хранится в PE заголовке. Когда Windows загружает наш исполняемый фал в память, он считывает его заголовок и находит там имена необходимых DLL библиотек, а также какие функции из этих библиотек будут использоваться в программе. После этого операционная система подгружает в адресное пространство программы необходимые функции, что бы программа могла их вызывать. Если вы хотите узнать, какие именно функции используются в нашей программе, вы можете кликнуть правой кнопкой мыши в окне дизассемблера и выбрать “Search For” -> “All Intermodular Calls”. После этого вы увидите такое окно:
Это может показаться удивительным, но список довольно маленький. Обычно в реальных программах используются сотни или даже тысячи различных функций, но поскольку мы сейчас имеем дело с очень простым приложением, оно не нуждается в таком количестве функций. На самом деле, с точки зрения того, насколько проста наша программа, список функций, которые она использует довольно внушительный. Это окно отображает сначала имена Dll библиотек, а затем имена самих функций. Например в строчке “User32.LoadIconA”, User32 – это имя библиотеки, а LoadIconA – название функции. Это функция обычно загружает иконку в левый верхний угол окна приложения.
Далее, давайте посмотрим все строки, которые используется в программе. Для этого кликните правой кнопкой мыши по окну дизассемблера и выберите “Search For” -> “All Referenced Text Strings:
Это окно показывает все текстовые строки, которые Олли смогла обнаружить в приложении. В большинстве реальных программ их будет намного больше (иногда – в сотни и тысячи раз), если, конечно, они не были запакованы или зашифрованы. В таком случае вы, вероятно, не увидите ни одной из них. Строки чаще всего шифруют, поскольку реверс-инженеры (по крайней мере – новички) сильно полагаются на текстовые строки при анализе того или иного приложения и если эти строки зашифровать – это может значительно усложнить анализ. Представьте, если вы просматриваете строки какой-либо программы и натыкаетесь на такую: “Поздравляем! Вы ввели верный код!”. Если наша цель – взлом программы, опираясь на эту строку мы легко можем выйти на код, проверяющий корректность введенного серийного номера и затем, и затем, изменить его. Если вы дважды кликните на какую-либо текстовую строчку, вас перекинет на инструкцию, которая использует эту строчку. Это очень удобная функция.
Запуск программы
Если вы посмотрите в левый верхний угол Олли вы увидите желтое окно, на котором написано “Paused”. Это говорит нам о том, что выполнение программы на данный момент приостановлено (на точке входа в данном случае) и вы можете сделать необходимые вам действия. Так давайте что-нибудь сделаем! Нажмите клавишу F9 (либо кликните на “Run”). После этого наша программа начнет выполнение и высветит диалоговое окно. Это окно может отобразится позади окна Олли, так что если вы его вдруг не видите – попробуйте свернуть отладчик.
В том окне, где раньше было написано “Paused” теперь написано “Runned”. Это означает, что приложение на данный момент запущено, но запущено внутри Олли, что дает нам возможность взаимодействовать с этой программой. Давайте посмотрим, как это происходит. Если вы случайно закроете окно нашей программы, что бы загрузить ее снова, – вернитесь к отладчику и нажмите комбинацию клавиш ctrl-F2 (или кликните на кнопку Restart из меню Debug). Так вы снова попадете на точку входа приложения и теперь можете снова его запустить.
Теперь попробуйте следующее: когда программа запущена, нажмите на кнопу паузы в Олли (или клавишу F2, или кликните Debug-Pause). Это остановит выполнение программы в текущем месте исполнения. Если вы в этот момент обратите внимание на окно программы, вы увидите что оно как бы заморожено (либо оно может вообще не отображаться). Это так, потому что когда программа на паузе – ее окно не обновляется. Теперь нажмите F9 снова и приложение снова станет активным. Если что-то пойдет не так– вы всегда можете перезагрузить приложение, нажав комбинацию клавиш ctrl-F2, либо кликнув на Debug-Restart. И теперь, после перезагрузки, можете запускать программу снова.
Пошаговое исполнение программы
Когда приложение просто запущено – это, конечно, уже хорошо – но это не дает нам большого количества информации о том, что именно происходит с программой. Для более детального анализа нам может такая функция как пошаговое исполнение. Перезагрузите приложение (нажав комбинацию клавиш ctrl-F2, либо кликнув на Debug-Restart) и вы, как вы наверно уже знаете, остановитесь на точке входа. Теперь нажмите клавишу F8. Вы заметите, что выделение строчки сдвинется вниз. Олли только что выполнила одну строчку кода и остановилась на следующей. Если вы наблюдательны, то вы заметили, что окно стека прокрутилось вниз и теперь имеет новое значение на своей вершине:
Это случилось, потому что инструкция, которую мы выполняли, “PUSH 0”, поместила новое значение в стек. Это отобразилось в окне стека как “pModule = NULL”. Null – это еще один способ обозначения нуля. Вы могли также заметить, что в окне регистров, регистры ESP и EIP стали отображаться красным цветом:
Когда регистр меняет цвет на красный, это означает что его содержимое, в результате выполнения последней инструкции, было изменено. В данном случае, регистр ESP (который указывает на адрес вершины стека) был увеличен на единицу, поскольку мы положили новое значение на вершину стека. Регистр EIP, который указывает на адрес инструкции, которая сейчас будет исполнятся увеличился на 2. Это случилось, потому инструкция по предыдущему адресу (401000) занимала два байта и уже была исполнена. После ее выполнения в регистр, соответственно, попало значение инструкции, которую надо исполнить сейчас (ее адрес – 401002).
Инструкция, на которой мы сейчас остановились – это Call. Суть этой инструкции в том, что мы временно приостанавливаем выполнение той функции в которой мы сейчас находимся и начинаем исполнять другую функцию. Если проводить аналогию с высокоуровневыми языками программирования – это как вызов функции внутри другой функции, например:
int main()
{
int x = 1;
call doSomething();
x = x + 1;
}
В этом коде мы первым делом присвоили переменной x значение 1. После этого вы вызываем функцию doSomething(). Когда эта функция будет выполнена, мы продолжаем выполнение оригинальной функции main() и увеличиваем значение переменной x на единицу.
Такой же порядок выполнения сохраняется и в ассемблере. Сначала мы поместили ноль на вершину стека, и теперь мы вызываем функцию, в данной случае это функция GetModuleHandleA() из Dll библиотеки Kernel32.dll.
Теперь, нажмите на клавишу F8 еще раз. Выделение линии сместиться на одну строчку вниз, регистр EIP будет красным и увеличится на 5 (инструкция, которая сейчас была выполнена занимала имела размер 5 байтов) и стек вернулся к своему изначальному виду. Случилось следующее: т.к. мы нажали клавишу F8, что означает “Step-Over” (переступить через), Олли за один раз исполнила весь код внутри функции и затем остановилась на следующей за функцией команде. Внутри этой функции могла происходить что угодно, но мы пропустили ее и не стали вдаваться в детали. Если же мы хотим “шагнуть внутрь” функции, необходимо во-первых перезагрузить программу, потом снова исполнить первую строчку, нажав F8, но когда мы остановились на команде CALL нажать уже не F8, а F7, что бы начать исполнение самой функции, не пропуская ее. Если так сделать после выполнения команды CALL мы затем попадаем сюда:
Мы попали сюда, потому что клавиша F7 выполняет команду “Step-IN”, которая означает, что в случае выполнения функции, Олли переходит на первую команду этой функции и там останавливается. В данном случае нас перебросило на другой участок памяти (EIP = 4012d6). В принципе, если мы продолжим пошаговое исполнение кода, мы в итоге вернемся в то место, откуда функция была вызвана, в следующую инструкцию, которая следовала за командой вызова функции (CALL). Но это может занять довольно много времени, так что давайте перезапустим в Олли нашу программу и начнем заново ее пошагово исполнять, без захода во внутренности этой функции (нажимая клавишу F8, а не F7).
Итак, после перезагрузки, когда мы снова остановлены в точке входа, нажмите клавишу F8 (“Step-Over”) 4 раза и мы в итоге дойдем до данного места:
Вы заметите 4 команды PUSH подряд. Если вы пошагово их исполните, то увидите как на вершину стека один за другим помещаются 4 новых значения. Думаю, у вас уже начала появляться интуиция относительно того, как работает стек.
Вам может стать любопытно: почему мы поместили эти четыре значения в стек. Это произошло, потому что эти 4 значения передались функции в качестве аргументов. Сама команда вызова функции расположена по адресу 401021. Что бы лучше это все понять, давайте снова посмотрим на аналогию с кодом на высокоуровневом языке программирования:
int main()
{
int x = 1;
int y = 0;
call doSomething( x, y );
x = x + 1;
}
В этом коде мы сначала определяем две переменных: x и y, а затем передаем их как аргументы функции doSomething(). Функция doSomething, вероятно, будет как-то использовать эти переменные в свой работе, и после того как она выполнится управление передастся на код, который следует за этой функцией. Стек – это один из способов передать функции какие-либо переменные: необходимые переменные кладутся на вершину стека, функция вызывается и она уже через стек получает доступ к этим переменным (обычно с помощью команды POP – это команда, обратная PUSH - она снимает значения с вершины стека).
Стек – не единственный способ передать функции переменные, но часто-встречающийся. Необходимые переменные могут быть также помещены в регистры, и функция через регистры получает доступ к этим переменным. Но в нашем случае, как мы видим, использовался именно стек. Это все станет понятней, когда вы поближе познакомитесь с ассемблером. Я еще немного затрону эту тему в будущем.
Сейчас, если вы нажмете F8 еще один раз, вы увидите, что на панели статуса Олли высветится “Running” и наша программа отобразить диалоговое окно. Это случилось, потому что мы переступили через выполнение функции, а внутри этой функции и был основной код программы. Внутри этой функции запустился цикл, который ждет каких-то наших действий что бы прерваться, поэтому мы до стх пор не перешли к коду, который находится за вызовом функции. Давайте исправим это. Кликните по окну нашей программе и нажмите на крестик в левом верхнем углу, что бы закрыть ее. После этого Олли перейдет на данную строчку кода:
Вы также заметите, что окно программы пропало. Это случилось, потому что где-то внутри функции диалоговое окно было закрыто. Если вы обратите внимание на нижележащий код, вы увидите, что программа сейчас собирается вызвать функцию ExitProcess из библиотеки kernel32.dll. Это функция Windows API, которая закрывает приложение. Получается, мы поймали момент, когда окно программы уже было закрыто, но сама программа еще работала. Если вы сейчас нажмете клавишу F9 (продолжить исполнение), программа завершиться, на панели статуса Олли отобразиться “Terminated” и процесс отладки на этом завершиться.
Брейкпоинты (точки останова)
Давайте попробуем сделать что-нибудь еще. Перезагрузите приложение (ctrl-F2) и дважды кликните левой кнопкой мыши по второй колонке строчки по адресу 401011 (вы должны будете кликнуть по опкоду 6A 0A). Колонка с адресом в этой строчке после этого поменяет цвет на красный:
То что вы сейчас сделали – это установили точку останова (их еще называют брейкпоинтами или просто бряками) по адресу 401011. Брейкпоинты заставляют Олли останавливаться, когда исполнение дойдет до этого места. Существует несколько типов брейкпоинтов:
Программные брейкпоинты
Программный брейкпоинт заменяет байт по адресу, на который необходимо поставить брейкпоинт на CC, что означает команду int 3. Это является прерыванием, которое говорит операционной системе что отладчик хочет остановиться здесь и с этого момента получить контроль над выполнением кода. В коде программы, который отображает Олли вы не увидите, что байт был заменен на CC, т.к. Олли не отображает это, но она делает эту замену в любом случае. Когда в результате этой замены происходит прерывание Олли вступает в игру и ловит это прерывание, давая нам власть над дальнейшим исполнением программы. Если вы решите продолжить дальнейшее исполнение, то Олли за место CC вернет оригинальное значение измененного байта.
Что бы поставить программный брейкпоинт, вы можете дважды кликнуть левой кнопкой мыши по колонке с опкодами на определенном адресе, или вы можете выделить мышкой строчку кода, на которую вы хотите установить брейкпоинт, затем кликнуть по ней правой кнопкой мыши и в выпадающем меню выбрать Breakpoints->Toggle (либо нажать клавишу F2). Что бы удалить уже установленный брейпоинт – дважды кликните левой кнокой мыши по колонке с опкодом на строчке, на которой брейкпоинт установлен, либо выделите эту строчку, кликните по ней правой кнопой мыши и выберите Breakpoint->Remove Software Breakpoint (или опять же нажмите клавишу F2).
Сейчас, когда имеем брейкпоинт, установленный по адресу 401011, а наша программа стоит на точке входа, нажмите F9 (run). Наша программа запуститься и потом остановится на той строчке кода, на которой установлен наш брейкпоинт.
Я хотел бы также дать один полезный прием. Нажмите на иконку “Br” на тулбаре или выберите View->Breakpoints. Должно открыться окно, которое отображает все брейкпоинты, установленные в программе:
Отсюда вы также можете быстро перейти к месту, в котором установлен брейкпоинт, для этого дважды кликните левой кнопкой мыши по нужному брейкпоинту из списка. Если вы выделите какой-либо брейкпоинт и потом нажмете на клавишу пробела, этот брейкпоинт поменяет свой статус с активного на выключенный и наоборот. Можно также удалить выделенный брекйкпоинт с помощью клавиши DEL.
Даватй еще попробуем такую вещь: перезагрузите программу и откройте окно со списком брейкпоинтов, выделите брейкпоинт, который мы поставили по адресу 401011 и кликните на клавише пробела. “Active” смениться на “Disabled”. Теперь запустите программу (F9). На этот раз программа запустится без каких-либо остановок, потому что мы отключили установленный ранее брейкпоинт.
Аппаратные брейкпоинты
Аппаратные брейкпоинты используют отладочные регистры в процессоре. В процессоре есть 8 таких регистров: R0-R7. Несмотря на то, что их 8 мы реально можем использовать 4 из них. Они могут быть использованы для прерывания на чтении, записи или исполнении секции памяти. Разница в аппаратных и программных брейкпоинтов заключается в том, что аппаратные, в отличии от программных, не вносят изменения в память процесса, поэтому они могут быть более предпочтительными в случаях, когда мы имеем дело с защищенными программами. Что бы установить аппаратный брейкпоинт необходимо кликнуть правой кнопкой мыши по выбранной строке кода, в выпадающем меню выбрать “Breakpoint – Hardware, on Execution”:
Брейкпоинты на память
Иногда вы можете найти строку или константу в памяти программы, но вы не знаете в каком месте в программе она используется. Используя брйкпоинты на память вы можете дать указание Олли остановиться в любом месте, где какая-либо команда в программе будет либо записывать либо считывать данные по адресу, с интересующими вас данными. Существует три способа установить брейкпоинт на память:
- Для инструкции: кликните по нужной строчке кода правой кнопкой мыши и выберите Breakpoint->Memory, On Access or Memory, On Write
- Что бы установить брейкпоинт по какому-либо адресу в дампе памяти: выделите один или несколько байтов в окне дампа памяти, кликните по ней правой кнопкой мыши и выберите такие же опции, как указано выше.
- Вы можете также установить брейкпоинт на целую секции памяти. Откройте окно Memory, кликните правой кнопкой мыши по нужной вам секции и выберите “Set Break On Acces” либо “Set Break On Write”.
Использование окна дампа
Вы можете использовать окно дампа что бы следить за значениями в памяти по каким-либо адресам в адресном пространстве процесса, который вы отлаживаете. Если инструкция, которую вы видите в окне дизассемблера, регистр или какой-либо элемент стека содержать адрес какой-либо локации памяти, вы можете кликнуть по ним правой кнопкой мыши и выбрать “Follow in Dump” – после этого окно дампа покажет вам содержимое этого адреса. Вы можете также кликнуть правой кнопкой мыши в пределах окна дампа, и выбрать “Go To”, что бы ввести туда адрес, по которому вы хотите перейти. Давайте попробуем теперь сделать это на практике.
Убедитесь, что FirstProgram.exe загружена в Олли и остановлена на точке входа. Теперь начинаем пошаговое исполнение программы – нажмите F8 восемь раз и мы попадем на инструкцию по адресу 401021, которая выглядит как CALL FirstPro.40102C Если вы посмотрите на эту строчку, вы увидите что этот вызов функции перебросит нас на адрес 40102C, который находится на три строчки ниже нашей текущей позиции. Нажмите F7, что бы войти внутрь функции и мы, как и предполагалось, попадем на адрес 40102C.
Если вы вспомните, как работает инструкция CALL, то поймете что в конце концов, после выполнения функции, мы должны перейти на адрес, который идет сразу после 401021, то есть сразу после инструкции CALL.
А пока продолжайте пошаговое исполнение кода, пока мы не попадем на адрес 401062. Вы можете также установить брейкпоинт на эту строчку, нажать F9, что бы продолжить исполнение, и потом Олли остановиться, когда исполнение дойдет до инструкции по этому адресу. Вы помните как ставить брейкпоинты? Дважды кликните по месту с опкодом на строчке, на которую вы хотите поставить бряк. Вы можете также выделить нужную вам строку и после этого нажать клавишу F2. В итоге мы остановимся на адресе 401062:
Сейчас, давайте посмотрим на строчку кода, на которой мы стоим. Он выглядит как
MOV [LOCAL.3], FirstPro.00403009. Эта инструкция двигает значение, находящееся по адресу 403009 в стек (который Олли обозначает как LOCAL.3). Вы можете заметить в колонке с комментариями, что Олли обнаружила, что по этому адресу расположена строка “MyMenu”. Давайте сами в этом убедимся. Кликните по инструкции правой кнопкой мыши и выберите “Follow in Dump”. Вы заметите, что нам доступно несколько опций:
В данном случае, выберите “Immediate Constant”. Это отобразит в окне дампа то, что находится по адресу, на который ссылается инструкция:
Как вы видите, в окне дампа сейчас отобразилось содержимое памяти, начиная с адреса 403009, который является адресом, по которому расположена строка, на которую ссылалась команда. В правой части вы можете видеть строку “MyMenu”, в левой части – шестнадцатеричный код, который соответствует этой строке. Как можно заметить, после строки “MyMenu” идут другие строки. Эти строки используются в других частях программы.
Наконец, что-то интересное!
Что бы закончить этот туториал, давайте сделаем что-нибудь веселое. Давайте отредактируем бинарник так, что бы программа отображала наш собственный текст! Мы изменим строку “Dialog As Main” на другую и посмотрим, что из этого получится.
Во-первых, нажмите на символ “D” строки “Dialog As Main” в ASCII-секции окна дампа:
Вы заметите, что байт, который соответствует символу “D” также выделится. Давайте теперь выделим строку “Dialog As Main” целиком:
Теперь кликните правой кнопкой мыши по выделенной строке и выберите “Binary” -> “Edit”. Это позволит изменить нам содержание памяти нашей программы:
Должно появится такое вот окно:
Первое поле показывает нам нашу строку в ASCII-кодировке. Второе – в Юникоде (в этой программе данная кодировка не используется поэтому окно – пустое). Последнее поле отображает шестнадцатеричный код. Теперь – давайте изменим строку. Для корректной работы программы необходимо, что бы новая строка не была длиннее оригинальной! Потому что в противном случае мы изменим байты, которые относятся уже к другим данным. В моем случае я изменил строку на “Program R4ndom”.
Теперь нажмите OK и запустите приложение (F9). Напечатайте в нем что-нибудь и потом выберите “Options” -> “Get Text”.
Обратите внимание на заголовок открывшегося окна :)