• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Реверс инжинеринг для начинающих

yashechka

Генератор контента.Фанат Ильфака и Рикардо Нарвахи
Эксперт
Регистрация
24.11.2012
Сообщения
2 344
Реакции
3 563
Что такое реверс-инжиниринг?

Реверс-инжиниринг — это процесс получения скомпилированного двоичного файла и попытки воссоздать (или просто понять) первоначальный способ работы программы. Программист изначально пишет программу, обычно на языке высокого уровня, таком как C++ или Visual Basic (или, не дай Бог, Delphi). Поскольку компьютер по своей сути не говорит на этих языках, код, написанный программистом, собирается в более машинно-специфичный формат, с которым компьютер действительно говорит. Этот код довольно первоначально называется машинным языком. Этот код не очень дружелюбен к человеку, и часто требует больших умственных способностей, чтобы понять, что именно имел в виду программист.

Для чего используется реверс-инжиниринг?

Реверс-инжиниринг можно применять во многих областях информатики, но вот несколько общих категорий:

- Возможность взаимодействия с устаревшим кодом (если у вас нет исходного кода).
- Нарушение защиты от копирования (т. е. произведите впечатление на своих друзей и сэкономьте немного долларов).
- Изучение вирусов и вредоносных программ.
- Оценка качества и надежности программного обеспечения.
- Добавление функциональности к существующему программному обеспечению.

Первая категория — это код реверс инжинеринга для взаимодействия с существующими двоичными файлами, когда исходный код недоступен. Я не буду много обсуждать это, потому что это скучно.

Вторая категория (и самая большая) — это нарушение защиты от копирования. Это означает отключение триалов, отмену регистрации и, в общем, все остальное, чтобы получить коммерческое программное обеспечение бесплатно. Это мы будем обсуждать очень долго.

Третья категория — изучение кода вирусов и вредоносных программ. Реверс инжиниринг требуется, потому что немногие программисты вирусов не присылают инструкции о том, как они написали код, что он должен делать и как он это сделает (если только они не совсем тупые). Это довольно интересная область, но требующая больших знаний. Мы не будем много обсуждать это.

Четвертая категория — оценка безопасности и уязвимостей программного обеспечения. При создании больших (например, операционных систем Windows) реверс-инжиниринг используется, чтобы убедиться, что система не содержит каких-либо серьезных уязвимостей, недостатков безопасности и, честно говоря, чтобы максимально затруднить взлом программного обеспечения.

Последняя категория — добавление функциональности к существующему программному обеспечению. Лично я считаю это одним из самых веселых. Не нравится графика, используемая в вашем программном обеспечении для веб-дизайна? Измените их. Хотите добавить пункт меню для шифрования документов в любимом текстовом процессоре? Добавьте это. Хотите бесконечно раздражать своих коллег, добавляя окна с сообщениями в калькулятор Windows? Давай сделаем это. Об этом мы поговорим позже в этой серии.

Какие знания необходимы?

Как вы, наверное, догадались, чтобы эффективно заниматься реверс-инжинирингом, необходимо много знаний. К счастью, для того, чтобы «начать» реверс-инжиниринг, не требуется много знаний, и именно здесь я надеюсь пригодиться. При этом, чтобы получить удовольствие от реверсирования и получить что-то из этих туториалов, вам необходимо иметь хотя бы базовые знания понимание того, как работает программа (например, вы должны знать, что делает базовый оператор if…then, что такое массив, и хотя бы видеть программу hello world). Во-вторых, настоятельно рекомендуется ознакомиться с языком ассемблера; Вы можете пройти учебные пособия и без него, но в какой-то момент вам захочется стать гуру в ASM, чтобы действительно знать, что вы делаете. Кроме того, много вашего времени будет посвящено обучению использованию инструментов. Эти инструменты бесценны для реверс-инжиниринга, но также требуют изучения недостатков и особенностей каждого инструмента. Наконец, обратный инжиниринг требует значительного количества экспериментов; играть с различными упаковщиками/защитниками/схемами шифрования, изучать программы, изначально написанные на разных языках программирования (даже Delphi), расшифровывать приемы предотвращения обратного проектирования… список можно продолжать и продолжать. В конце этого урока я добавил раздел «Дальнейшее чтение» с некоторыми рекомендуемыми источниками. Если вы действительно хотите научиться реверсить, я настоятельно рекомендую вам прочитать еще немного.

Какие инструменты используются?

При реверсе используется множество различных инструментов. Многие из них относятся к типам защиты, которые необходимо преодолеть, чтобы отреверсить двоичный файл. Есть также несколько, которые просто облегчают жизнь реверсеру. А некоторые из них я считаю «основными» предметами — теми, которые вы используете регулярно. По большей части инструменты делятся на несколько категорий:

1. Дизассемблеры
Дизассемблеры пытаются взять коды машинного языка в двоичном виде и отобразить их в более удобном формате. Они также экстраполируют данные, такие как вызовы функций, передаваемые переменные и текстовые строки. Это делает исполняемый файл более похожим на удобочитаемый код, а не на набор связанных вместе чисел. Существует множество дизассемблеров, некоторые из которых специализируются на определенных вещах (например, двоичные файлы, написанные на Delphi). Чаще всего выбор сводится к тому, который вам наиболее удобен. Я всегда работаю с IDA (доступна бесплатная версия http://www.hex-rays.com/), а также с парой менее известных, которые помогают в конкретных случаях.

2. Отладчики
Отладчики — это хлеб с маслом для реверс-инженеров. Сначала они анализируют двоичный код, подобно дизассемблерному отладчику, а затем позволяют реверсору пошагово выполнить код, выполняя по одной строке за раз и исследуя результаты. Это неоценимо для понимания того, как работает программа. Наконец, некоторые отладчики позволяют изменять определенные инструкции в коде, а затем запускать их снова с этими изменениями. Примерами отладчиков являются Windbg и Ollydbg. Я почти исключительно использую Ollydbg (http://www.ollydbg.de/), за исключением случаев отладки двоичных файлов режима ядра, но мы вернемся к этому позже.

3. Шестнадцатеричные редакторы
Шестнадцатеричные редакторы позволяют просматривать фактические байты в двоичном формате и изменять их. Они также обеспечивают поиск определенных байтов, сохранение разделов двоичного файла на диск и многое другое. Существует множество бесплатных шестнадцатеричных редакторов, и большинство из них подходят. Мы не будем часто использовать их в этих уроках, но иногда они бесценны.

4. PE и программы просмотра/редактирования ресурсов

Каждый двоичный файл, предназначенный для запуска на компьютере с Windows (и Linux, если уж на то пошло), имеет в начале очень специфический раздел данных, который сообщает операционной системе, как настроить и инициализировать программу. Он сообщает ОС, сколько памяти ей потребуется, какие библиотеки поддержки DLL программе необходимо заимствовать, информацию о диалоговых окнах и тому подобное. Это называется переносимым исполняемым файлом, и все программы, предназначенные для работы в Windows, должны иметь его.

В мире реверс-инжиниринга такая структура байтов становится очень важной, поскольку она дает реверсеру необходимую информацию о двоичном файле. В конце концов, вы захотите (или вам нужно) изменить эту информацию, либо чтобы заставить программу делать что-то другое, чем то, для чего она была изначально придумана, либо изменить программу НАЗАД на то, чем она была изначально (например, до того, как защитник сделал код действительно сложным чтобы его понять). Существует множество вьюверов и редакторов PE. Я использую CFF Explorer (http://www.ntcore.com/exsuite.php) и LordPE (http://www.woodmann.com/collaborative/tools/index.php/LordPE), но вы можете свободно использовать как вам удобно.

Большинство файлов также имеют разделы ресурсов. К ним относятся графика, элементы диалога, пункты меню, значки и текстовые строки. Иногда можно развлечься, просто просматривая (и изменяя ;) разделы ресурса. В конце этого урока я покажу вам пример.

5. Инструменты мониторинга системы

При реверсе программ иногда важно (а при изучении вирусов и вредоносных программ крайне важно) увидеть, какие изменения приложение вносит в систему; создаются или запрашиваются ключи реестра? созданы ли файлы .ini? создаются ли отдельные процессы, возможно, чтобы помешать обратному проектированию приложения? Примерами инструментов мониторинга системы являются procmon , regshot и Process hacker. Мы обсудим это позже в уроке.

6. Разные инструменты и информация
Есть инструменты, которые мы подберем по пути, такие как скрипты, распаковщики, идентификаторы упаковщиков и т. д. Также в этой категории находится своего рода ссылка на Windows API. Этот API огромен и порой сложен. При реверс-инжиниринге чрезвычайно полезно точно знать, что делают вызываемые функции.

7. Пиво.

Так что давайте уже займемся этим!


Несмотря на то, что мы начинаем с очень небольшими знаниями, я хотел дать вам хотя бы немного попробовать реверс в этом первом уроке. В это руководство я включил средство просмотра/редактирования ресурсов (см. папку «Файлы») под названием XN Resource Editor. Это бесплатное программное обеспечение. По сути, эта программа позволяет вам видеть раздел ресурсов в exe-файле, а также изменять эти ресурсы. Я обнаружил, что с ними можно получить массу удовольствия — они позволяют вам изменять меню, значки, графику, диалоги и многое другое в программах. Давайте попробуем сами…

Сначала запустите XN. Нажмите на значок загрузки вверху, выберите Windows\System32\ и загрузите Calc.exe (расположение Windows по умолчанию может отличаться). Вы должны увидеть несколько доступных папок:

1699699908401.png


Вы можете видеть, что есть папки для растровых изображений (любая графика, отображаемая программой), меню (пункты верхнего меню), диалоговых окон (диалоговых окон вместе с текстом и кнопками), таблицы строк, группы значков и т. д. Вы можете свободно возиться с этими вещами. Просто не забудьте сохранить его как другой файл (вам бы не хотелось переустанавливать Windows только ради тупого калькулятора). В частности, попробуйте следующее:

Нажмите на плюс рядом с пунктом «Меню». После этого вы увидите папку с номером в качестве имени. Это идентификатор, который Windows будет использовать для доступа к этому ресурсу в программе. Откройте и эту папку. Теперь вы должны увидеть значок «Английский (США)» или что-то в этом роде. Если вы нажмете на него, вы увидите диаграмму того, как будет выглядеть меню (вы даже можете щелкнуть по нему — оно работает так же, как настоящее меню).

1699699923709.png


Теперь нажмите на пункт меню «Научный». Поле «Заголовок» должно измениться на «&Научный». Амперсанд сообщает вам, что такое «горячая клавиша», в данном случае «S». Если бы вместо этого мы хотели, чтобы «е» была горячей клавишей, это выглядело бы так: «Научный». Итак, вам уже не нравятся встроенные горячие клавиши для вычислений? Просто поменяйте их!! Но давайте поступим по-другому. В поле «Заголовок» замените &Scientific на «&Nerd». Теперь это изменит параметр меню на «Nerd» и будет использовать горячую клавишу «N» (я просмотрел другие параметры в меню, чтобы пользователь не использовал другой пункт меню «N» в качестве горячей клавиши). Вы должны сделать это для всех пунктов меню. Теперь перейдите в «Файл» (в ресурсе XN) и выберите «Сохранить как…». Сохраните новую версию Calc под другим именем (и желательно в другом месте), а затем запустите ее.

1699699937720.png


Конечно, вам не обязательно останавливаться на достигнутом. Чтобы свести с ума своих коллег, я изменил все цифры в их расчетах.

1699699947906.png


Как видите, нет предела возможностям.

До следующего раза…

-R4ndom

пс. Спасибо Lena151 за то, что показали мне путь, ASL, Snaker, Qwerton и Jibz за то, что показали мне секреты, а также Олли, Yoda-mmi, uCF и Колину Уилсону за то, что дали мне инструменты.


Для дальнейшего чтения
1. Язык ассемблера. Язык ассемблера для компьютеров на базе процессоров Intel — де-факто книга по нему. Вы также можете посетить некоторые веб-сайты, которые предлагают огромное количество загрузок, инструкций , примеров кода и помощи. Еще один хороший ресурс — «Искусство Ассемблера» . Я включу это в одно из следующих руководств, но вы также можете скачать его здесь .

2. Структура PE-файла. Один из лучших ресурсов принадлежит самой Microsoft: « Углубленный анализ формата переносимых исполняемых файлов Win32 ». Еще один хороший документ (с множеством красивых картинок) — « Структура файлов PE ». Это загружаемый PDF-файл.

3. Внутреннее устройство операционной системы Windows. Де-факто книга — « Внутренности Microsoft Windows » Марка Руссиновича. Это так же увлекательно, как женский бейсбол.

4. Учебники по взлому. www.Tuts4You.com — это то место, где стоит побывать.
 
Урок №2: Введение в Olly

Что такое Olly Debugger?


От автора Олега Ющука: «OllyDbg — это 32-битный отладчик анализа ассемблерного уровня для Microsoft® Windows®. Акцент на анализе двоичного кода делает его особенно полезным в случаях, когда исходный код недоступен. Olly также является «динамическим» отладчиком, то есть он позволяет пользователю изменять множество вещей во время работы программы. Это очень важно при экспериментировании с двоичным файлом, когда вы пытаетесь понять, как он работает. У Olly много замечательных функций, и именно поэтому это, вероятно, отладчик номер один, используемый для обратного проектирования (по крайней мере, в кольце 3, но мы вернемся к этому позже).

Обзор

Вот изображение основного дисплея Олли вместе с некоторыми надписями:

1699780310928.png


Олли открывается с открытым окном по умолчанию ЦП. Именно здесь находится большая часть данных. Если вы когда-нибудь закроете это окно, просто щелкните значок «C» на панели инструментов. Он разделен на 4 основных поля; Дизассемблер, регистры, стек и дамп. Вот описание каждого раздела.

1. Дизассемблер


В этом окне находится дизассемблированный код бинарника. Здесь Олли отображает информацию в двоичном формате, включая коды операций и переведенные в язык ассемблера. Первый столбец — это адрес (в памяти) инструкции. Второй столбец — это то, что называется кодами операций: на языке ассемблера с каждой инструкцией связан хотя бы один код (многие из них имеют несколько). Это код, который действительно нужен процессору, и единственный код, который он может прочитать. Эти коды операций составляют «машинный язык», язык компьютера. Если бы вы просмотрели необработанные данные в двоичном формате (с помощью шестнадцатеричного редактора), вы бы увидели строку этих кодов операций и ничего более. Одна из основных задач Олли — «разобрать» этот «машинный язык» на более удобочитаемый язык ассемблера. Третий столбец — это язык ассемблера. Конечно, для человека, не знакомого с ассемблером, это выглядит не намного лучше, чем коды операций, но по мере того, как вы узнаете больше, ассемблер дает НАМНОГО больше понимания того, что делает код.

Последний столбец — это комментарии Олли к этой строке кода. Иногда он содержит имена вызовов API (если Олли может их понять), например CreateWindow и GetDlgItemX. Олли также пытается помочь нам понять код, называя любые вызовы, которые не являются частью API, полезными именами, в случае с этим изображением: «ImageRed.00510C84» и «ImageRed.00510BF4». Конечно, это не так уж и полезно, но Олли также позволяет нам менять их на более значимые имена. Вы также можете оставить в этой колонке свои комментарии; просто дважды щелкните строку в этом столбце, и появится окно, позволяющее ввести свой комментарий. Эти комментарии будут автоматически сохранены для следующего раза.

2. Регистры

Каждый процессор имеет набор регистров. Это временные хранилища значений, очень похожие на переменные в любом языке программирования высокого уровня. Вот более подробный (и помеченный) вид окна регистров:

1699780332477.png


Вверху находятся фактические регистры ЦП. Регистры изменят цвет, если они были изменены с черного на красный (что позволяет легко отслеживать изменения). Вы также можете дважды щелкнуть любой из регистров, чтобы изменить его содержимое. Эти регистры используются для многих вещей, и нам придется многое сказать о них позже.

Средняя часть — это флаги, используемые ЦП для обозначения кода, что что-то произошло (два числа равны, одно число больше другого и т. д.). Двойной щелчок по одному из флагов меняет его. Они также сыграют важную роль в нашем путешествии.

Нижняя часть — это регистры FPU или модуля с плавающей запятой. Они используются всякий раз, когда ЦП выполняет какие-либо арифметические действия. Они редко используются реверсерами, в основном, когда мы занимаемся шифрованием.

3. Стек

1699780350177.png


Стек — это раздел памяти, зарезервированный для двоичного файла в качестве «временного» списка данных. Эти данные включают в себя указатели на адреса в памяти, строки, маркеры и, что наиболее важно, адреса возврата, по которым код возвращается при вызове функции. Когда метод в программе вызывает другой метод, управление необходимо передать этому новому методу, чтобы он мог перенастроиться. ЦП должен отслеживать, откуда был вызван этот новый метод, чтобы, когда этот новый метод будет выполнен, ЦП мог вернуться туда, откуда он был вызван, и продолжить выполнение кода после вызова. Стек — это место, где ЦП будет хранить этот адрес возврата.

О стеке следует знать одну вещь: это структура данных типа «первым пришел — последним вышел». Обычно используемая метафора — это одна из тех стопок тарелок в кафетерии. Когда вы «кладете» тарелку на верх, все тарелки внизу опускаются вниз. Когда вы снимаете («выталкиваете») тарелку сверху, все тарелки, которые были внизу, поднимаются на один уровень вверх. Мы увидим это в действии в следующем уроке, так что не волнуйтесь, если это будет сейчас немного туманно.

На этом рисунке первый столбец — это адрес каждого элемента данных, второй столбец — это шестнадцатеричное 32-битное представление данных, а последний столбец — это комментарии Олли об этом элементе данных, если он сможет их понять. Если вы заметили первую строку, вы увидите комментарий «ВОЗВРАТ к ядру…». Это адрес, который ЦП помещает в стек по завершении текущей функции, чтобы он знал, куда вернуться.

В Olly вы можете щелкнуть правой кнопкой мыши по стеку и выбрать «Изменить», чтобы изменить содержимое.

4. Дамп

1699780363096.png


Ранее в этом уроке, когда мы говорили о необработанных «кодах операций», которые ЦП считывает внутри двоичного файла, я упоминал, что вы можете увидеть эти необработанные данные в шестнадцатеричном средстве просмотра. Окно дампа — это встроенная программа просмотра шестнадцатеричных файлов, которая позволяет вам видеть необработанные двоичные данные только в памяти, а не на диске. Обычно он показывает два представления одних и тех же данных; шестнадцатеричный и ASCII. Они представлены в двух правых столбцах на предыдущем рисунке (первый столбец — это адрес в памяти, по которому находятся данные). Olly позволяет изменять эти представления данных, и мы увидим это позже в руководствах.

Панель инструментов

К сожалению, панель инструментов Olly оставляет желать лучшего (тем более, что английский не является родным языком автора). Я пометил значки левой панели инструментов, чтобы помочь:

1699780379426.png


Это ваши основные элементы управления для запуска кода. Имейте в виду, что, особенно когда вы начинаете использовать Olly, все эти кнопки также доступны из раскрывающегося меню «Отладка», поэтому, если вы не знаете, что это такое, вы можете посмотреть это там.

Сделаю пару замечаний по поводу некоторых иконок. «Перезагрузка» — это, по сути, перезапуск приложения и приостановка его в точке входа. Все патчи (см. ниже) будут удалены, некоторые точки останова отключены, и приложение еще не будет запускать какой-либо код. «Выполнить» и «Пауза» делают именно это. «Step into» означает запуск одной строки кода, а затем снова паузу, делая вызов функции, если таковой был. «Step Over» делает то же самое, но перескакивает через вызов другой функции. «Анимация» похожа на «Шаг вперед и назад», за исключением того, что она выполняется достаточно медленно, чтобы вы могли ее смотреть. Вы не будете использовать так много, но иногда интересно наблюдать за выполнением кода, особенно если это полиморфный двоичный файл, и вы можете наблюдать за изменением кода.

Далее идут (еще более загадочные) значки Windows:

1699780393025.png


Каждый из этих значков открывает окно, некоторые из которых вы будете использовать часто, некоторые — редко. Поскольку это не самые интуитивно понятные буквы, вы также можете поступить так же, как я, и просто начать щелкать их все, пока не найдете то, что хотите. Каждое из них также доступно в меню «Вид», так что вы можете получить некоторую помощь при первом запуске. Прямо сейчас я рассмотрю некоторые из наиболее распространенных окон:

1. (М)память

1699780407881.png


В окне памяти отображаются все блоки памяти, выделенные программой. Оно включает в себя основные разделы работающего приложения (в данном случае элементы «Showstr» в столбце «Владелец»). Вы также можете увидеть множество других разделов в списке; это библиотеки DLL, которые программа загрузила в память и планирует использовать. Если дважды щелкнуть любую из этих строк, откроется окно с дизассемблированием (или шестнадцатеричным дампом) этого раздела. В этом окне также показаны тип блока, права доступа, размер и адрес памяти, где находится раздел загружен.

2. (П)атчи


1699780419280.png


В этом окне отображаются любые сделанные вами «патчи», т.е. любые изменения в исходном коде. Обратите внимание, что состояние установлено как Активное; если вы перезагрузите приложение (щелкнув значок перезагрузки), эти патчи станут отключены. Чтобы снова включить их (или отключить), просто нажмите на нужный патч и нажмите пробел. Это включает/выключает патч. Также обратите внимание, что в столбцах «Старый» и «Новый» показаны исходные инструкции, а также измененные инструкции.

3. (Б)ряки

1699780430936.png


В этом окне показано, где установлены все текущие точки останова. Это окно будет вашим другом:)

3. (K)олл стек
(Ну и дела, интересно, почему новичкам трудно запомнить эти значки…)

1699780444629.png


Это окно отличается от окна «Стек», рассмотренного ранее. Оно показывает гораздо больше информации о вызовах, выполняемых в коде, значениях, отправленных в эти функции, и многом другом. Вскоре мы увидим больше этого.

* В следующий урок я включу свою версию Olly со многими «обновлениями», некоторые из которых представляют собой кнопки, которые вы действительно можете понять. Здесь вы можете увидеть это изображение *

1699780462376.png


Контекстное меню


В последнем пункте этого урока я хотел бы быстро познакомить вас с контекстным меню в Olly. Здесь происходит много событий, поэтому вы должны хотя бы быть с этим знакомы. Щелчок правой кнопкой мыши в любом месте раздела дизассемблирования вызывает это:

1699780475955.png


Сейчас я остановлюсь только на самых популярных пунктах. По мере приобретения опыта вы в конечном итоге будете использовать некоторые из менее используемых вариантов.

«Двоичный» позволяет редактировать двоичные данные на побайтовом уровне. Здесь вы можете изменить строку «Unregistered», скрытую в двоичном файле, на «Registered» :). «Точка останова» позволяет установить точку останова. Существует несколько типов точек останова, и мы рассмотрим их в следующем уроке. «Поиск» — это довольно большое подменю, в котором вы ищете в двоичном файле такие данные, как строки, вызовы функций и т. д. «Анализ» заставляет Олли повторно проанализировать раздел кода, который вы сейчас просматриваете. Иногда Олли не понимает, просматриваете ли вы код или данные (помните, что они оба просто числа), поэтому это заставляет Олли учитывать, где вы находитесь в коде, и пытаться угадать, как должен выглядеть этот раздел.

Также обратите внимание, что мое меню будет отличаться от вашего: у меня установлены некоторые плагины, которые добавляют некоторую функциональность. Не волнуйтесь, мы рассмотрим все это в будущих уроках.

-Ну, до следующего раза.
 
Урок №3: Использование OllyDBG, часть 1

В этом уроке я попытаюсь познакомить вас с использованием OllyDBG. У Olly множество функций, и единственный способ по-настоящему изучить их все — экспериментировать и практиковаться. При этом этот урок даст вам краткий обзор. Дополнительные темы, не затронутые в этом уроке, будут обсуждаться в последующих уроках, так что к концу вы должны иметь достаточно хорошее представление об Олли.

Я включаю несколько файлов в этот урок. Вы можете скачать файлы и PDF-версию этого руководства на странице учебных пособий - https://legend.octopuslabs.io/sample-page.html. Они включают в себя простой двоичный файл, который мы будем использовать в olly, шпаргалку Olly, мою версию Olly с некоторыми косметическими изменениями и новый ini-файл, которым вы можете заменить файл инициализации Olly по умолчанию, который поможет новым пользователям (спасибо, за это спасибо бессмертной Лене151). Их можно скачать прямо здесь - https://legend.octopuslabs.io/files/tuts/R4ndom_tutorial_3.zip или со страницы руководств. Если вы предпочитаете использовать оригинальную версию Olly, вы можете скачать ее здесь - http://www.ollydbg.de/.

Загрузка приложения

Первым шагом является загрузка предполагаемого двоичного файла в Olly. Вы можете либо перетащить файл в окно Олли, либо щелкнуть значок загрузки в крайнем левом верхнем углу и выбрать файл. В этом случае загрузите «FirstProgram.exe», скачанный с этого сайта. Олли выполнит анализ (если вы сможете прочитать его достаточно быстро в нижней строке дисплея Олли) и остановится на точке входа программы (EP):

1699789180209.png


Первое, на что следует обратить внимание, это то, что EP находится по адресу 401000, как мы видим в первом столбце. Это довольно стандартная отправная точка для исполняемого файла (по крайней мере, исполняемого файла, который все равно не был упакован или обфусцирован). Если ваш экран выглядит по-другому и Олли не остановился на 401000, попробуйте зайти в «Внешний вид», выбрать параметры отладки, перейти на вкладку «События» и убедиться, что установлен флажок «WinMain (если местоположение известно)». Затем перезапустите приложение).

Давайте сделаем снимок пространства памяти, занимаемого «FirstProgram.exe». Нажмите на значок «Me» (или «М», если используете другую версию Olly):

1699789205012.png


Если вы посмотрите в столбце адреса, вы увидите, что в позиции 401000 строка содержит размер 1000, имя «FirstPro» (сокращение от FirstProgram), имя раздела «.text» и что оно содержит «SFX, code». Как мы узнаем позже в этой серии, exe-файлы имеют разные разделы, содержащие разные типы данных. В этом разделе находится «код» к программе. Его длина составляет 1000 байт, и он начинается с адреса 401000 в памяти.

Ниже вы увидите другие разделы нашей программы; есть раздел .rdata, который содержит данные и импорт по адресу 402000, раздел под названием «.data», который ничего не содержит по адресу 403000, и, наконец, раздел под названием «.rsrc», который содержит ресурсы (такие как диалоговые окна, изображения, текст и т. д. Имейте в виду, что эти разделы могут называться как угодно – это полностью на усмотрение программиста.

Вы можете задаться вопросом, почему в разделе .data ничего нет. Ну, на самом деле так и есть. В нем есть такие вещи, как глобальные переменные и случайные данные. Олли просто предпочитает не указывать это, поскольку он точно не знает, какие данные там хранятся.

Вверху разделов находится раздел под названием «PE-заголовок». Это очень важный раздел, о котором мы подробно поговорим в следующей статье. А пока просто знайте, что это похоже на инструкцию для Windows с инструкциями по загрузке этого файла в память, сколько места ему нужно для запуска, где находятся определенные вещи и т. д. Он находится в начале практически любого exe (и DLL)

Если вы теперь посмотрите вниз по списку, вы увидите другие файлы, кроме нашего приложения First Program. Мы видим comctl32, imm32, gdi32, kernel32 и т. д. Это файлы DLL, необходимые нашему приложению для работы. Файл Dll представляет собой набор функций, которые может вызывать наша программа и которые были предоставлены Windows (или другим программистом). Это такие вещи, как открытие диалоговых окон, сравнение строк, создание окон и тому подобное. В совокупности это Windows API. Причина, по которой программы используют их, заключается в том, что если бы нам пришлось программировать каждую функцию, то простое отображение окна сообщения могло бы занять тысячи строк кода. Вместо этого в Windows предусмотрена такая функция, как CreateWindow, которая делает это за нас. Это значительно упрощает программирование для программиста.

Вы можете задаться вопросом, как эти DLL попали в адресное пространство нашей программы и как Windows узнала, какие из них нужны. Эта информация хранится в PE-заголовке, указанном выше. Когда Windows загружает наш exe в память, она проверяет этот заголовок и находит имена DLL, а также какие функции в каждой DLL нужны нашей программе, а затем загружает их в пространство памяти нашей программы, чтобы наша программа могла их вызвать. Каждая программа, загружаемая в память, также будет иметь необходимые DLL, которые программа должна загрузить в свое пространство памяти. Это означает, что, возможно, некоторые DLL могут быть загружены в память несколько раз, если в данный момент загружено несколько программ и все они используют эту конкретную DLL. Если вы хотите посмотреть, какие именно функции вызывает наша программа, вы можете щелкнуть правой кнопкой мыши в окне дизассемблирования Олли и выбрать «Искать» -> «Все межмодульные вызовы». Это покажет что-то вроде следующего:

1699789222091.png


Это может показаться удивительным, но этот список ОЧЕНЬ мал. Обычно для коммерческого продукта необходимы сотни или тысячи функций, но поскольку наш пример программы настолько прост, его не нужно очень много. Хотя, если задуматься о том, что делает наша программа, то кажется, что функций для выполнения такой базовой роли достаточно много! Добро пожаловать в Windows :). В этом окне сначала отображается имя DLL, а затем имя функции. Например, User32.LoadIconA находится в DLL User32, а имя функции — LoadIconA . Эта функция обычно загружает значок в верхнем левом углу окна.

Далее давайте выполним поиск по всем строкам в приложении. Щелкните правой кнопкой мыши окно дизассемблирования и выберите «Искать» -> «Все текстовые строки, на которые ссылаются:

1699789237833.png


В этом окне показаны все текстовые строки, которые можно найти в нашем приложении. Поскольку это приложение очень простое, их всего пара. Большинство приложений будут иметь НАМНОГО больше (иногда сотни тысяч), если только они не упакованы. В этом случае вы можете вообще ничего не увидеть! Причина, по которой упаковщики делают это, заключается в том, что реверс-инженеры (по крайней мере, новички) в значительной степени полагаются на текстовые строки для поиска важных функций в двоичном файле, а удаление текстовых строк значительно усложняет задачу. Представьте, что вы выполнили поиск по текстовым строкам и увидели «Поздравляем! Вы ввели правильный серийный номер»? Что ж, это было бы огромным подспорьем для реверсора (и мы увидим это снова и снова). Кстати, двойной щелчок по одной из строк приведет вас к инструкции, использующей ее в окне дизассемблирования. Это хорошая функция, позволяющая сразу перейти к коду, использующему эту строку.

Запуск программы

Если вы посмотрите в верхний левый угол Олли, вы увидите желтое окно с надписью «Приостановлено». Это означает, что приложение приостановлено (в данном случае в начале) и готово к каким-либо действиям. Так давайте что-нибудь сделаем!. Попробуйте нажать F9 (или выберите «Выполнить» в меню «Отладка»). Через секунду наша программа откроет диалоговое окно (оно может открыться позади Олли, поэтому на всякий случай сверните окно Олли).

1699789250694.png


В том же поле, где было написано «Приостановлено», теперь должно быть написано «Выполняется». Это означает, что приложение запущено, но внутри Olly. Вы можете взаимодействовать с нашей программой, а точнее :) увидеть, как она работает и что делает. Если вы случайно закрыли его, вернитесь к Олли и нажмите Ctrl-F2 (или выберите «Отладка-> Перезапустить»), чтобы перезагрузить программу, и вы можете нажать F9, чтобы запустить ее снова.

Теперь попробуйте следующее: во время работы программы щелкните Олли и щелкните значок паузы (или нажмите F12, или выберите «Отладка->Пауза»). Это приостановит нашу программу в любом месте памяти, где она выполнялась. Если вы сейчас попытаетесь просмотреть программу, она будет выглядеть смешно (или вообще не будет отображаться). Это связано с тем, что Windows не обновляет представление, когда оно приостановлено. Теперь снова нажмите F9, и вы снова сможете играть с программой. Если что-то пойдет не так, просто щелкните значок двойной стрелки влево или выберите «Отладка-перезапуск» (или Ctrl-F2), и приложение перезагрузится и приостановится в начале. Теперь вы можете запустить его снова, если хотите.

Проход по программе


Запуск приложения — это хорошо и здорово, но оно не дает вам много информации о том, что происходит. Давайте попробуем одношаговый метод. Перезагрузите приложение (значок перезагрузки, ctrl-F2 или отладка->перезапуск), и мы будем приостановлены при запуске приложения, теперь нажмите F8. Вы заметите, что текущий селектор строки переместился на одну строку вниз. Олли выполнил одну строку инструкций, а затем снова сделал паузу. Если бы вы были действительно наблюдательны, вы бы заметили, что окно стека прокрутилось на одну позицию вниз и вверху появилась новая запись:
1699789264304.png


Это связано с тем, что выполненная нами инструкция PUSH 0 «поместила» ноль в стек. Это отображается в стеке как «pModule = NULL». NULL — другое название нуля. Вы также могли заметить, что в окне регистров регистры ESP и EIP стали красными:

1699789270922.png


Когда регистр становится красным, это означает, что последняя выполненная инструкция изменила этот регистр. В этом случае регистр ESP (который указывает на адрес вершины стека) увеличился на единицу, поскольку мы поместили в стек новое значение. Регистр EIP, указывающий на текущую выполняемую инструкцию, также увеличился на два. Это связано с тем, что мы больше не находимся по адресу 401000, а по адресу 401002 — выполнение последней инструкции имело длину два байта, и теперь мы приостановлены на следующей инструкции. Эта инструкция находится по адресу 401002, что соответствует текущему значению EIP.

Инструкция, на которой сейчас остановился Олли, — это CALL. Инструкция вызова означает, что мы хотим временно приостановить текущую функцию, в которой мы находимся, и запустить другую функцию. Это аналогично вызову метода внутри языка высокого уровня, например:

1699789329265.png


В этом коде мы сначала делаем x равным 1, затем хотим приостановить эту строку и вместо этого вызвать doSomething(). Когда doSomething будет выполнено, мы возобновим работу с исходной строки и увеличим x на 1.

Ну, то же самое верно и для языка ассемблера. Сначала мы отправили ноль в стек, а теперь хотим вызвать функцию, в данном случае функцию GetModuleHandleA() из Kernel32.dll:

1699789279520.png


Теперь нажмите F8 еще раз. Индикатор текущей строки сместится на единицу вниз, регистр EIP останется красным и увеличится на 5 (поскольку длина выполняемой инструкции составляла 5 байт), а стек вернулся к тому состоянию, которое он показывал изначально. Здесь произошло следующее: поскольку мы нажали F8, что означает «шаг», код внутри вызова был выполнен, и Олли сделал паузу на следующей строке после вызова. Внутри этого вызова программа могла бы сделать что угодно, но мы «перешагнули» это.

Теперь, чтобы увидеть другой вариант, перезапустите программу (ctrl-F2), нажмите F8, чтобы пройти первую инструкцию, но на этот раз нажмите F7 в инструкции вызова. Вы заметите, что все окно теперь выглядит по-другому:


1699789288938.png


Это связано с тем, что F7 означает «Вход», что означает, что Олли сделал вызов и остановился на первой строке этой новой функции. В этом случае вызов перешел в новую область памяти (EIP = 4012d6). Теоретически, если бы мы продолжали проходить по строкам кода этой новой функции, мы бы в конечном итоге вернулись к оператору после вызова, который привел нас сюда, в начало. Конечно, для этого есть ярлыки, но сейчас давайте просто перезапустим программу и начнем все сначала, так как мы не хотим слишком заблудиться.

Теперь, когда мы остановились в начале программы, нажмите F8 (шаг) 4 раза, и мы приземлимся на этом операторе:

1699789300574.png


Вы заметите, что подряд идут 4 оператора PUSH. На этот раз наблюдайте за окном стека, когда вы нажимаете F8 4 раза, и наблюдайте, как стек растет (на самом деле он растет вниз – помните пример с тарелкой?). Я думаю, мы начинаем понимать, что такое PUSH в стеке…

Теперь вы можете спросить, ПОЧЕМУ мы поместили эти произвольные числа в стек? В данном случае это потому, что эти 4 числа передаются в качестве параметров функции (функции, которую мы собираемся вызвать по адресу 401021). Если взять нашу предыдущую высокоуровневую программу и немного ее модифицировать, то станет понятнее:

1699789371551.png


Здесь мы объявляем две переменные, x и y, и передаем их в функцию doSomething. Функция doSomething (вероятно) что-то сделает с этими переменными, а затем вернет управление обратно вызывающей программе. Стек — это один из основных способов передачи переменных в функцию: каждая переменная помещается в стек, вызывается функция, а внутри функции осуществляется доступ к этим переменным, обычно с использованием обратной инструкции PUSH, которая - POP.

Стек — не единственный способ сделать это, просто он используется чаще всего. Эти переменные также могли быть помещены в регистры и доступны через эти регистры внутри вызываемой функции, но в данном случае компилятор нашей программы решил поместить их в стек. Все это станет яснее после того, как вы изучите язык ассемблера (вы ведь изучаете ассемблер, не так ли?). Мы также вернемся к этому несколько раз в будущем.

Теперь, если мы нажмем F8 еще раз, вы заметите, что на активной панели Olly будет написано «Running», и появится диалоговое окно нашей программы. Это потому, что мы пропустили вызов, в котором фактически содержится большая часть программы. Внутри этого вызова находится код, который входит в цикл, ожидая, пока мы что-то сделаем, поэтому мы никогда не возвращаем управление линии после вызова. Что ж, давайте это исправим… Нажмите на нашу программу и нажмите кнопку «Закрыть», чтобы закрыть приложение. Олли сразу же сделает паузу на следующей строке после вызова:

1699789383267.png


Вы также заметите, что наша программа исчезла. Это потому, что где-то во время этого вызова диалоговое окно было закрыто. Кроме того, если вы посмотрите на одну строку ниже, вы увидите, что мы собираемся вызвать kernel32.dll -> ExitProcess. Это Windows API, который останавливает приложение, поэтому, по сути, Олли приостановил нашу программу после того, как она закрыла окно, но до того, как она была фактически завершена! Если вы теперь нажмете F9, программа завершится, на активной панели Olly появится надпись «Завершено», и мы больше не будем ничего отлаживать.

Точки останова


Давайте попробуем что-нибудь еще, перезагрузим приложение (ctrl-F2) и дважды щелкните строку по адресу 401011 во втором столбце (вы будете нажимать на коды операций «6A 0A». Адрес 401011 теперь станет красным):

1699789395250.png


Что вы сделали, так это установили точку останова по адресу 401011. Точки останова заставляют Olly приостановить выполнение, когда он достигнет этого адреса. Существуют разные типы точек останова для остановки выполнения разных событий:

Программные точки останова

Программная точка останова заменяет байт по адресу вашей точки останова кодом операции 0xCC, который представляет собой int 3. Это специальное прерывание, которое сообщает операционной системе, что отладчик хочет сделать паузу здесь и передать управление отладчику перед выполнением инструкции. Вы не увидите, как инструкция изменится на 0xCC, Олли делает это незаметно, но когда Олли проходит через неё, возникает исключение, и Олли перехватывает исключение и позволяет пользователю делать то, что он/она желает. Если вы решите разрешить программе продолжить работу (путем ее запуска или пошагового выполнения), код операции 0xCC заменяется обратно на исходный.

Чтобы установить программную точку останова, вы можете либо дважды щелкнуть столбец кода операции, либо выделить строку, для которой хотите установить точку останова, щелкнуть ее правой кнопкой мыши и выбрать «Точки останова» -> «Переключить» (или нажать F2). Чтобы удалить точку останова, вы можете дважды щелкнуть ту же строку или щелкнуть правой кнопкой мыши и выбрать «Точка останова» -> «Удалить программную точку останова» (или еще раз нажать F2).

Теперь, когда у нас есть BP (точка останова) по адресу 401011 и наша программа приостановлена на первой инструкции, нажмите F9 (выполнить). Наша программа запустится, но остановится на строке с нашим BP.

Я также хочу отметить кое-что очень полезное. Нажмите на значок «Br» на панели инструментов или выберите «Просмотр» -> «Точки останова». В окне точки останова вы увидите запись, показывающую наше текущее установленное BP:
1699789414966.png


Это дает вам быстрый обзор всех установленных вами точек останова. Вы можете дважды щелкнуть одну из них, и окно дизассемблирования перейдет к этой точке останова (хотя EIP останется прежним, поскольку вы фактически не меняете поток управления программой. Дважды щелкните регистр EIP, чтобы вернуться к текущей строка настроенной на выполнение следующей).

Если вы выделите точку останова и щелкните пробел, точка останова будет переключаться между включенной и отключенной. Вы также можете выделить строку точки останова и нажать клавишу «DEL», которая удалит точку останова.

Наконец, перезапустите программу, войдите в окно точек останова, выделите точку останова, которую мы установили по адресу 401011, и нажмите пробел. Столбец «Активно» изменится на «Отключено». Теперь запустите программу (F9). Вы заметите, что Олли не остановился на нашем БП, потому что он был отключен.

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


Аппаратная точка останова использует регистры отладки ЦП. В процессор встроено 8 таких регистров, R0-R7. Несмотря на то, что в чип встроено 8, мы можем использовать только четыре из них. Их можно использовать для прерывания чтения, записи или выполнения раздела памяти. Разница между аппаратными и программными точками останова заключается в том, что аппаратные BP не изменяют память процесса, поэтому они могут быть более надежными, особенно в упакованных или защищенных программах. Вы устанавливаете аппаратную точку останова, щелкнув правой кнопкой мыши нужную строку, выбрав Точка останова, а затем выбрав Hardware, on Execution
1699789427043.png


Единственный способ узнать, какие параметры памяти вы установили, — это выбрать «Отладка» и «Аппаратные точки останова». Есть плагин, который сделает это намного проще, но мы обсудим это позже.:)

1699789441642.png


Точки останова в памяти

Иногда вы можете найти строку или константу в памяти программы, но не знаете, где в программе к ней осуществляется доступ. Использование точки останова в памяти сообщает Олли, что вы хотите сделать паузу всякий раз, когда ЛЮБАЯ инструкция в программе читает или записывает этот адрес памяти (или группы адресов). Существует три способа выбора точки останова в памяти:

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

- Чтобы установить БП по адресу в дампе памяти, выделите один или несколько байтов в окне дампа, щелкните их правой кнопкой мыши и выберите тот же вариант, что и выше.

- Также можно задать БП для целого раздела памяти. Откройте окно «Память» (значок «Me» или «Просмотр->Память»), щелкните правой кнопкой мыши нужный раздел памяти, затем щелкните правой кнопкой мыши и выберите «Set Break On Access for either Access or Write"
1699789462735.png


Использование панели дампа


Вы можете использовать панель дампа для проверки содержимого любой области памяти в пространстве памяти отлаживаемого процесса. Если инструкция в окне дизассемблирования, регистре или любом элементе стека содержит ссылку на ячейку памяти, вы можете щелкнуть правой кнопкой мыши по этой ссылке и выбрать «Следовать в дампе», и на панели дампа отобразится раздел адреса. Вы также можете щелкнуть правой кнопкой мыши в любом месте панели дампа и выбрать «Перейти», чтобы ввести адрес для просмотра. Давай попробуем.

Убедитесь, что FirstProgram загружен и приостановлен в начале. Теперь нажмите F8 восемь раз, и мы перейдем к инструкции по адресу 401021, в которой написано CALL FirstPro.40102c. Если вы посмотрите на эту строку, вы заметите, что этот вызов переведет нас на адрес 40102c, который находится на 3 строки ниже того места, где мы сейчас находимся. Нажмите F7, чтобы перейти к прыжку, и мы окажемся по адресу 40102c. Помните, что это инструкция CALL, поэтому в конечном итоге мы вернемся к 401021 (или, по крайней мере, к инструкции после нее).

1699789474812.png


Теперь проходите код (F8), пока не доберемся до адреса 401062. Вы также можете просто установить точку останова на этой строке и нажать F9, чтобы перейти к ней. Помните, как установить точку останова? дважды щелкните столбец кода операции в строке, в которой вы хотите установить BP. Вы также можете просто выделить строку и нажать F2, чтобы включить или выключить BP). Теперь мы остановлены по адресу 401062:
1699789484023.png


Теперь давайте посмотрим на строку, на которой мы остановились. Инструкция MOV [LOCAL.3], FirstPro.00403009. Как я уверен, вы знаете (поскольку вы изучали язык ассемблера :p), эта инструкция перемещает все, что находится по адресу 00403009, в стек (который Олли называет LOCAL.3). В колонке комментариев вы можете видеть, что Олли обнаружил, что по этому адресу находится строка ASCII «MyMenu». Что ж, давайте посмотрим. Щелкните правой кнопкой мыши по инструкции и выберите «Следовать в дампе». Вы заметите, что здесь у нас есть несколько вариантов:

1699789491253.png


В этом случае выберите «Immediate Constan». При этом загружается любой адрес, на который влияет инструкция. Если бы вы выбрали «Selection», в окне дампа был бы показан адрес выделенной строки, в данном случае 401062 (строка, на которой мы остановились). По сути, мы просто смотрели бы на дамп того, что мы видели в окне дизассемблирования. Наконец, если бы мы выбрали «Адрес памяти», на экране дампа отобразилась бы память для LOCAL.3. Фактически это покажет память для локальных переменных, с которыми мы работали (в стеке). Вот как выглядит дамп после выбора «Immediate Constan»:

1699789499392.png


Как вы можете видеть, дамп теперь показывает память, начиная с адреса 403009, который является адресом, с которого инструкция Олли загружала строку ASCII. Справа вы можете увидеть строку «MyMenu». Слева вы можете увидеть фактический хекс представление для каждого символа. Вы можете заметить, что после «MyMenu» идут дополнительные строки. Эти строки будут использоваться в других частях программы.

Наконец-то что-то веселое!

Чтобы закончить эту часть урока, давайте займемся чем-нибудь забавным. Давайте отредактируем двоичный файл, чтобы отобразить наше собственное сообщение! Мы изменим строку «Dialog As Main» на что-то свое и посмотрим, что произойдет.

Сначала нажмите «D» в строке «Dialog As Main» в разделе ASCII окна дампа:

1699789513563.png


Вы заметите, что первая шестнадцатеричная цифра также выделена слева. Эта цифра соответствует букве «D». Если вы посмотрите на диаграмму ASCII, вы увидите, что шестнадцатеричный код буквы «D» равен 0×44. Теперь щелкните и перетащите, чтобы выбрать всю строку «Dialog As Main»:

1699789519696.png


Теперь щелкните правой кнопкой мыши в любом месте выделения и выберите «Двоичный» -> «Редактировать». Это позволяет нам изменять содержимое памяти нашей программы:
1699789526423.png


После этого у нас должен появиться экран, который выглядит следующим образом:

1699789533553.png


Первое поле показывает нам нашу строку в ASCII. Второе поле предназначено для Юникода (который эта программа не использует, поэтому поле пустое), а последнее поле — это необработанные данные, относящиеся к этой строке. Теперь давайте изменим это. Нажмите на первую букву строки («D») и введите все, что хотите, поверх строки «Dialog As Main». Просто убедитесь, что вы не добавляете больше букв, чем в исходной строке! Вы можете перезаписать другие строки, которые нужны программе, или, что еще хуже, код, который нужен программе!!! В моем случае я набрал «Program R4ndom»:

1699789547084.png


Теперь нажмите «ОК» и запустите приложение (щелкните внутри Olly и нажмите F9). Переключитесь на нашу программу, введите что-нибудь и выберите «Параметры» -> «Получить текст». Теперь посмотрите на наше диалоговое окно!!!

1699789556933.png


Обратите внимание на изменения в заголовке диалогового окна.:)
 
Урок №4: Использование Olly, часть 2

Введение

В этом уроке мы продолжим обучение использованию Olly. Мы будем использовать ту же программу, что и в прошлом уроке (я также включу ее в список загрузок этого руководства).

Вы можете скачать файлы и PDF-версию этого руководства на странице учебных пособий.

Библиотеки DLL


Как я уже говорил в предыдущем руководстве, библиотеки DLL загружаются системным загрузчиком при запуске приложения. На этот раз позвольте мне быть более красноречивых. DLL (библиотеки динамической компоновки) — это наборы функций, обычно предоставляемые Windows (хотя они могут быть предоставлены кем угодно), которые часто используются в программах Windows. Это также функции, которые упрощают программистам выполнение утомительных и повторяющихся задач. Эти функции хранятся в библиотеках, которые динамически подключаются при необходимости.

Например, во многих приложениях необходимо выполнить преобразование строки в верхний регистр. У вас есть три варианта, если ваше приложение использует эту функцию несколько раз в своем приложении; 1) вы можете закодировать это самостоятельно и поместить в свое приложение. Проблема в том, что, если вы знаете, что ваше следующее приложение также будет использовать эту функцию много раз вам придется вырезать и вставлять его в каждое созданное вами приложение, которое его использует! 2) Вы можете создать свою собственную библиотеку, которую сможет вызывать любое созданное вами приложение. В этом случае вы создадите DLL, которую вы будете включать в каждое приложение, и эта DLL будет иметь функцию ConvertToUpper, а также другие общие функции, которые ваши приложения могут вызывать, поэтому вам придется написать ее только один раз. Еще одна хорошая вещь: скажем, вы придумали хорошую оптимизацию для преобразования строки в верхний регистр. В первом примере вам нужно будет скопировать этот новый код в каждое приложение, которое его использует, но в случае с общей DLL вы просто измените код в DLL, и каждое приложение, использующее эту DLL, получит более быстрый код. Круто. На самом деле это была причина появления DLL.

Последний вариант — использовать одну из тысяч функций, которые Windows включила в собственный набор DLL. В этом есть много преимуществ. Во-первых, программисты Microsoft потратили годы на оптимизацию своих функций, так что, скорее всего, они лучше ваших. Во-вторых, вам не нужно включать в свое приложение какие-либо собственные библиотеки DLL, поскольку эти библиотеки встроены во все системы Windows. И, наконец, если Windows решит изменить свою операционную систему, ваши собственные библиотеки DLL могут быть несовместимы с новой операционной системой , а если вы используете библиотеки DLL Windows, они гарантированно будут совместимы.

Как используются библиотеки DLL

Теперь, когда вы знаете, что такое DLL, давайте поговорим о том, как они используются. По сути, DLL — это просто библиотека функций, которые может вызывать ваше приложение. Когда вы впервые загружаете приложение, загрузчик Windows проверяет специальный раздел PE-заголовка (помните PE-заголовок?) и проверяет, какие функции вызывает ваше приложение и в какой DLL находятся эти функции. После загрузки приложения в память затем загрузчик перебирает эти библиотеки DLL и загружает каждую из них в пространство памяти вашего приложения. Затем загрузчик просматривает весь код вашего приложения и вводит правильный адрес того места, где он поместил эти функции DLL, в вашу программу везде, где ваша программа вызывает эту функцию. Например, если одним из ваших первых вызовов является преобразование буфера букв в верхний регистр путем вызова StrToUpper в DLL kernel32 (просто пример), загрузчик найдет место, где он загрузил DLL kernel32, найдет адрес функции StrToUpper и вставьте этот адрес в строки кода вашего приложения, вызывающие эту функцию. Затем ваше приложение вызовет пространство DLL ядра kernel32 в памяти, выполнит функцию StrToUpper, а затем вернется обратно в вашу программу.

Давайте посмотрим на это в действии. Загрузите программу FirstProgram.exe, включенную в это руководство, в Olly. Олли прервет первую строку кода (с этого момента она называется точкой входа — это важно, поскольку именно для этого так называется в заголовке PE, когда мы начнем обсуждать это подробно).

1700048351170.png


Если вы посмотрите на вторую строку кода, вы увидите вызов функции kernel32.GetModuleHandleA. Для начала давайте посмотрим, что делает эта функция. Если вы не получили его в последнем уроке, я включил в загрузку этого урока файл WIN32.HLP, а также текстовый документ, объясняющий, как его установить в Olly. Этот файл позволяет вам щелкнуть правой кнопкой мыши незнакомый вам API Windows, и он покажет вам, что делает этот API. Возможно, вам придется перезапустить Olly после копирования. Щелкните правой кнопкой мыши GetModuleHandleA и выберите «Справка по символическому имени». Олли откроет шпаргалку этого API:

1700048363761.png


1700048372441.png


Итак, по сути, эта функция получает дескриптор пространства памяти нашей программы. В Windows, если вы хотите ЧТО-ТО сделать с окном (или практически с любым другим объектом в этом отношении), вы должны получить его дескриптор. По сути, это просто уникальный идентификатор, поэтому Windows знает, на какой объект вы ссылаетесь. GetModuleHandle на самом деле немного сложнее, но давайте вернемся, когда у нас будет больше знаний.

Закройте окно справки и давайте посмотрим, куда именно идет этот вызов. Поскольку Олли попытался помочь и заменил фактический адрес GetModuleHandleA на имя функции, давайте посмотрим, по какому адресу он находится. Щелкните один раз строку вызова GetModuleHandleA и нажмите пробел. Откроется окно дизассемблера:

1700048419912.png


Это окно служит двум целям; во-первых, он показывает вам точные инструкции языка ассемблера, которые вычисляются (в случае, если Олли услужливо заменил адрес), а во-вторых, оно позволяет нам редактировать язык ассемблера. Мы не будем ничего редактировать до следующего урока, поэтому сейчас давайте просто посмотрим на адрес: 4012D6. Есть два способа перейти по этому адресу (без фактического запуска кода), чтобы увидеть, что там. Вы можете выделить строку «CALL GetModuleHandleA» и нажать «Enter». Вы также можете нажать Ctrl-G и ввести адрес вручную. Давайте попробуем первый способ — выберите строку по адресу 401002 (в третьем столбце с фактической инструкцией) и нажмите Enter, и вы перейдете к коду, который вызвал этот вызов:

1700048431004.png


Вот это интересно: это точно не похоже на код, который бы выполнял GetModuleHandleA. Это больше похоже на серию переходов. Для этого есть очень веская причина, но, к сожалению, требующая небольшого пояснения.

Таблица переходов по адресам

Первое, что нужно знать, это то, что библиотеки DLL не всегда загружаются в память в одном и том же месте. Загрузчику Windows, который отвечает за загрузку вашего приложения и всех вспомогательных DLL, необходимых вашей программе, разрешено изменять место в памяти, где могут быть загружены эти DLL (и, честно говоря, он может даже менять место загрузки вашего приложения, но мы займемся этим позже). Причина этого в том, что, скажем, Windows DLL, одна из первых загруженных, отображается по адресу 80000000. Допустим, вы также включили в свое приложение DLL, которая хочет загружаться по тому же адресу. Поскольку обе библиотеки DLL не могут быть загружены по одному и тому же адресу, загрузчик должен переместить одну из этих DLL на другой адрес. Это происходит постоянно и называется релокацией.

Вот в чем проблема: когда вы впервые написали код своего приложения и написали инструкцию под названием GetModuleHandleA, компилятор точно знал, где находится нужная DLL, поэтому поместил в эту инструкцию адрес, что-то вроде «CALL 800000000». Теперь, когда ваша программа загружается в память, у нее все еще есть этот вызов 800000000 (я тут слишком упрощаю :), но что, если загрузчик решил переместить эту DLL в 80000E300? Ваш вызов вызовет не ту функцию!

PE-файл и, следовательно, файл Windows обошли эту проблему путем создания таблицы переходов. Это означает, что при первой компиляции вашего кода каждый вызов GetModuleHandleA указывал на одно место в вашем приложении, и это единственное место немедленно переходит на произвольный адрес (который в конечном итоге станет правильным адресом). Фактически, все вызовы функций в DLL используют тот же метод; каждый из них вызывает определенный адрес, который затем немедленно переходит на произвольный адрес. Когда загрузчик загружает все библиотеки DLL, он проходит через эту «таблицу переходов» и заменяет все произвольные адреса реальными адресами функций в памяти. Вот как выглядит таблица переходов после заполнения всех реальных адресов:

1700048447165.png


Поскольку это довольно сложная идея, позвольте мне привести вам пример. Мы напишем короткую программу, используя совершенно произвольную информацию (просто для доказательства нашей точки зрения), которая вызывает функцию в kernel32.dll под названием ShowMarioBrosPicture. Вот наша программа (без конкретного языка):

1700048458096.png


После компиляции вызовы функций будут заменены реальными адресами и будут выглядеть примерно так (опять же, не на каком-то конкретном языке):

1700048467122.png


а внизу будет наша таблица переходов (в данном случае в ней есть только showMarioBrosPicture)
402000 JMP XXXXXXXX

Теперь, поскольку наша программа понятия не имеет, где будет находиться showMarioBrosPicture (или где будет находиться DLL-файл kernel32), компилятор нашей программы просто заполнит X для фактического вызова адреса.

Теперь, когда загрузчик Windows загружает наше приложение, он сначала загружает в память наш двоичный файл вместе с таблицей переходов, но в таблице переходов еще нет реальных адресов. Затем он начинает загружать библиотеки DLL в наше пространство памяти и, наконец, начинает выяснять, где находятся все функции. Как только он найдет адрес showMarioBrosPicture, он зайдет в нашу таблицу переходов и заменит X на фактический адрес этой функции. Допустим, адрес showMarioBrosPicture был 77CE550A. Наш код таблицы переходов тогда будет заменен на:

402000 JMP 77CE550A

Теперь, поскольку Олли может понять, что это указывает на showMarioBrosPicture, он легко перейдет к нашей таблице переходов и отобразит ее как:

402000 JMP DWORD PTR DS:[<&kernel32.showMarioBrosPicture>]


Теперь давайте вернемся и посмотрим на таблицу переходов в нашем приложении FirstProgram:

1700048485982.png


Когда эта программа была впервые написана, все эти функции вызывались в различных DLL, но компилятор не знал, где эти функции будут размещены в памяти при запуске нашей программы, поэтому он создал бы что-то, что выглядело так:

1700048493522.png


После того, как загрузчик загрузил наше приложение, а затем загрузил все библиотеки DLL и нашел адрес этих функций, он затем прошел бы каждую из них и заменил бы их фактическим адресом, по которому сейчас находятся эти функции, а именно тем, что вы видели в предыдущем картина. Если подумать, это довольно разумный способ справиться с этим. Если этого не сделать, загрузчику придется пройти через все наше приложение и заменить КАЖДЫЙ вызов КАЖДОЙ функции в КАЖДОЙ DLL и заменить этот адрес истинным адресом. Это потребует много работы. Таким образом, загрузчику нужно будет заменить адрес только в одном месте для каждого вызова функции, а именно в этой строке функции в таблице переходов.

Давайте посмотрим сами. Перезагрузите приложение в Olly и нажмите F7. Нажмите на инструкцию в строке 401002 (как мы делали раньше) и нажмите пробел (как мы делали раньше):

1700048501390.png


Я просто хотел, чтобы вы еще раз обратили внимание на адрес: 4012D6. Теперь нажмите F7, чтобы перейти к вызову, и вы заметите, что мы приземлимся по адресу 4012D6. Если вы прокрутите немного вверх, вы также заметите, что мы вызвали середину таблицы переходов:

1700048508718.png


Теперь снова нажмите F7, и мы попадем на РЕАЛЬНЫЙ адрес GetModuleHandleA — 7780B741. Вы можете определить, что мы сейчас находимся в модуле kernel32, двумя способами, оба из которых вы будете использовать в разное время. Первый — это заголовок окна процессора Олли:

1700048517020.png


Вы можете видеть, что там написано «модуль kernel32». И второй способ узнать это — зайти в окно памяти и посмотреть адрес:

1700048524410.png


Вы можете видеть, что адрес, по которому мы находимся (7780B741), попадает в адресное пространство раздела кода ядра32.

Теперь давайте вернемся и посмотрим на пару других вызовов функций. Перезапустите приложение и нажмите F8 до адреса 40100C, строки, в которой есть вызов GetCommandLineA. Нажмите на инструкцию и нажмите пробел, чтобы мы могли увидеть, на какой адрес она указывает, в данном случае 4012D0:

1700048532294.png


(Извините, что мышка навела прямо на адрес. :( Это 4012D0). Теперь давайте попробуем вручную перейти по этому адресу, так как вы будете часто его использовать. Либо нажмите Ctrl-G, либо щелкните значок GOTO и введите адрес, по которому мы хотим перейти:

1700048539828.png


Обратите внимание, что ваше окно «GOTO» может выглядеть немного иначе, но это скоро будет исправлено. Теперь нажмите «ОК», и мы перейдем к таблице переходов для функции GetCommandLineA:

1700048549411.png


Теперь нажмите F7, чтобы совершить прыжок, и мы приземлимся в начале функции GetCommandLineA в kernel32.dll. Эта функция начинается с 7C812FBD:

1700048556844.png


Вход и выход из DLL


По мере прохождения программы вы в разное время попадете в библиотеки DLL. Обычно это не то место, где вам нужно быть, если вы пытаетесь обойти какую-то схему защиты, поскольку библиотеки DLL Windows на самом деле их не содержат. Единственное предостережение: если программа, которую вы пытаетесь реконструировать, поставляется со своими собственными DLL, и вы также хотите их реверсить (или защиты на самом деле находится в DLL). Есть несколько способов вернуться к коду нашей программы из DLL. Один из способов — просто пройтись по всему функциональному коду DLL, пока вы, наконец, не вернетесь к своей программе, хотя это может занять довольно много времени (а в некоторых случаях, как в Visual Basic, навсегда). Второй вариант — перейти в пункт меню «Отладка» в Olly и выбрать «Выполнить до кода пользователя» или нажать Alt-F9. Это означает, что код в этой DLL будет выполняться до тех пор, пока мы не вернемся к коду нашей собственной программы. Имейте в виду, что иногда это не работает, потому что, если DLL обращается к буферу или переменной, которая находится в рабочей области нашей программы, Олли остановится на этом, поэтому вам может потребоваться несколько раз вызвать Alt-F9, прежде чем вы наконец вернетесь.

Давайте попробуем этот вариант сейчас. В настоящее время мы должны быть приостановлены по адресу 7C812FBD, в начале GetCommandLineA. Теперь нажмите Alt-F9. Это вернет нас к инструкции в нашей программе после вызова kernel32 (если вы прокрутите одну строку вверх, вы увидите вызов).

Теперь попробуем другой вариант возвращения к нашему коду. Перезапустите программу, выполните шаг (F8) до вызова GetCommandLineA (40100C), перейдите к этому вызову (F7) и перейдите к переходу в таблице переходов (F7). Теперь мы вернулись к началу GetCommandLineA:

1700048576038.png


Теперь откройте окно памяти и прокручивайте его, пока не увидите разделы с кодом нашей программы (начиная с адреса 400000 с PE-заголовком):

1700048608335.png


Теперь щелкните строку по адресу 401000, строку, в которой есть наш раздел .text. Теперь нажмите F2, чтобы переключить точку останова при доступе к этому разделу памяти (или щелкните правой кнопкой мыши и выберите «Точка останова при доступе»):

1700048600560.png


Теперь запустите приложение. Олли разорвет ту же строку, что и выше, по адресу 401011, строку, следующую после нашего вызова DLL!!! Теперь удалите точку останова в памяти, иначе вы удивитесь, почему каждый раз, когда вы запускаете приложение, оно прерывается на следующей строке.:)

Подробнее о стеке


Стек — очень важная часть реверс-инжиниринга, и без четкого его понимания вы никогда не станете хорошим реверс-инженером. Давайте немного поэкспериментируем…

Сначала взгляните на окно регистров (после перезапуска приложения) и посмотрите на регистр ESP. Этот регистр указывает на адрес «верхушки» стека. В этом случае значение ESP равно 12FFC4. Теперь посмотрите на окно стека и вы заметите, что верхний адрес в списке соответствует этому адресу:

1700048625623.png


Теперь нажмите F8 (или F7) один раз, чтобы поместить нулевое значение в стек, и посмотрите на окно стека:

1700048636712.png


Как мы уже говорили в прошлом уроке, в стек помещается ноль (null). Теперь посмотрим на наш регистр ESP:

1700048642760.png


Оно изменилось на 12FFc0, поскольку после помещения байта в стек это новая вершина стека. Теперь нажмите F8 один раз, минуя вызов GetModuleHandleA, и посмотрите на окно стека:

1700048649268.png


Вы заметите, что наш стек уменьшился на единицу (и наш регистр ESP вернулся к тому, что было). Это связано с тем, что функция GetModuleHandleA использовала этот ноль, который был помещен в стек в качестве аргумента, а затем «вытащил» его из стека, поскольку он больше не нужен. Как было затронуто в прошлом уроке, это один из способов, которым наша программа передает аргументы функциям: они помещают их в стек, вызываемая функция извлекает их из стека, использует их, а затем возвращает, обычно с любой необходимой нам информацией (как мы вскоре увидим).

Давайте посмотрим еще пару примеров… Если вы дважды нажмете F8, чтобы обойти вызов GetCommandLineA, вы заметите, что стек не изменился. Это потому, что мы ничего не помещали в стек для использования этой функцией. Далее мы достигаем инструкции PUSH 0A. Это первый аргумент, который мы собираемся передать следующей вызываемой функции. Пройдя через это, вы заметите, что 0A будет на вершине стека, а регистр ESP уменьшился на 4 (когда вы помещаете значение в стек, регистр ESP уменьшается по мере того, как стек «растет» в памяти). . Теперь снова нажмите F8, и регистр ESP снова уменьшится на 4. Это потому, что мы поместили в стек 4-байтовое значение. Если вы посмотрите на вершину стека, вы заметите, что мы поместили в стек число 00000000. Почему?

Давайте посмотрим на строку, которая на самом деле поместила 401013:

PUSH DWORD PTR DS:[40302c]


Что означает эта строка (я уверен, вы знаете, поскольку изучали язык ассемблера :p), так это взять содержимое четырех байтов по адресу 40302C и поместить их в стек. Что находится в 40302C? Ну 00000000 конечно! (Шучу. Давайте искать сами. Щелкните :) правой кнопкой мыши по инструкции по адресу 401013 и выберите «Следовать в дампе» -> «Адрес памяти». При этом загрузится окно дампа с содержимым памяти, начиная с 40302C:

1700048661910.png


Теперь, по крайней мере, теперь вы знаете, откуда взялся ноль. Если вы хотите узнать больше о том, что здесь происходит, это пространство памяти создается для переменных и в конечном итоге будет заполнено этими переменными, но на данный момент все переменные инициализированы нулями.

Теперь нажмите F8 один раз, и мы перейдем к следующему PUSH, но на этот раз с адреса 403028. Если вы прокрутите окно дампа вверх, вы увидите, что по этому адресу еще больше нулей (сразу после строки, которую мы изменили в последнем уроке) :) . Итак, в этом разделе происходит передача указателей на адреса памяти, которые в настоящее время установлены на ноль, которые наш код будет использовать в качестве переменных. Перейдите через последний PUSH и войдите в вызов по адресу 40101C. Первое, что вы должны заметить, это то, что в стек было добавлено что-то новое: адрес возврата для нашего вызова 401026.

Когда какой-либо код использует инструкцию CALL, адрес следующей инструкции, которая была бы выполнена, если бы мы не сделали вызов, автоматически помещается в стек. Причина этого в том, что после того, как вызванная нами функция выполнила все необходимое, ей необходимо знать, куда вернуться. Этот адрес, который автоматически помещается в стек, является адресом возврата. Посмотрите на верхнюю часть окна стека:

1700048673548.png


Вы увидите, что Олли понял, что это обратный адрес и он указывает на нашу программу (FirstPro) и что адрес, по которому необходимо вернуться, — 40102C (одна инструкция после вызова).

Теперь, в конце этой функции, будет использоваться инструкция RETN (и вы, конечно, знаете, что это означает «возврат», потому что это было в начале вашей книги по ассемблеру). Эта инструкция возврата на самом деле означает «POP адрес вершины стека и укажите наш работающий код на этот адрес» (по сути, она заменяет регистр EIP — регистр текущей строки кода, который мы выполняем — на это значение). Итак, теперь вызываемая функция точно знает, куда ей нужно вернуться после завершения! Фактически, если вы прокрутите немного вниз, вы увидите оператор RETN по адресу 4011A3, который извлекает этот адрес из стека и начинает выполнять код по этому адресу:

1700048687849.png


(Цифра 10 после оператора RETN означает просто дать мне обратный адрес, а также удалить 10h байтов из стека, поскольку они мне больше не нужны. Просто посмотрите следующую страницу вашей книги по ассемблеру :) )

Позвольте мне воспользоваться моментом и начать то, что, я уверен, станет знаменитой мантрой в сообществе реверс-инжиниринга. Мне нравится называть это «Основные истины об реверск данных». Я официально начинаю этот список, который скоро станет легендой, со следующей заповеди:

№1. Вы ДОЛЖНЫ выучить язык ассемблера.

Если вы этого не сделаете, вы не добьетесь успеха в реверс-инжиниринге. Это так просто.

Последнее, о чем я собираюсь поговорить в этом уроке, — это то, как Olly обрабатывает отображение аргументов и локальных переменных. Если вы дважды щелкните регистр EIP, чтобы мы могли вернуться к текущей строке кода (по адресу 40101C), и посмотрите на пару строк вниз, вы увидите несколько синих меток с надписью Local (и одну с надписью Arg):

1700048697788.png


Если у вас нет опыта программирования, вы можете не знать, в чем разница между локальной переменной и аргументом. Аргумент, как мы обсуждали ранее, — это переменные, передаваемые функции, которая нужна этой функции, обычно передаваемые в стек. Локальная переменная — это переменная, которую «создает» вызываемая функция для хранения чего-то временного. Вот пример небольшой программы, в которой используются две разные концепции:

1700048704895.png


В этой программе строка «R4ndom» является аргументом, передаваемым в функцию SayHello. В обычном языке ассемблера эта строка (или, по крайней мере, адрес этой строки) будет помещена в стек, чтобы функция SayHello могла ссылаться на нее. Как только управление передается функции SayHello, SayHello необходимо настроить пару ЛОКАЛЬНЫХ ПЕРЕМЕННЫХ, то есть переменных, которые эта функция будет использовать, но которые не понадобятся после завершения этой функции. Примерами локальных переменных являются целое число numTimes, строка привет, и целое число x. К сожалению, на тот случай, если стек не был достаточно сложным, в нем хранятся и аргументы, и локальные переменные. Стек выполняет это с помощью регистра ESP, хотя этот регистр не обладает экстрасенсорными способностями. Обычно он указывает на вершину стека, но его можно изменить. Допустим, мы входим в функцию SayHello, и в стеке есть следующие данные:

1. Адрес строки «R4ndom».
2. Адрес возврата, который привел нас сюда.


Что ж, если мы хотим, чтобы t0 создал локальную переменную, все, что нам нужно сделать, это вычесть сумму из регистра ESP, и это освободит место в стеке! Допустим, мы вычитаем 4 из ESP (это будет 4 байта или одно 32-битное число). Тогда стек будет выглядеть так:

1. Пустое 32-битное число
2. Адрес строки «R4ndom»
3. Адрес возврата, который нас сюда привел.


Теперь мы могли бы поместить в этот адрес все, что захотим, например, мы могли бы использовать его для переменной numTimes в нашей функции SayHello. Поскольку наша функция использует три переменные (все длиной 32 бита), она действительно вычтет 12 байтов (или 0xC в шестнадцатеричном формате) из ESP, и тогда у нас будут три локальные переменные, которые мы можем использовать. Вот как тогда может выглядеть стек:

1. Пустой 32-битный адрес, указывающий на строку «hello»
2. Пустое 32-битное число для переменной «x»
3. Пустое 32-битное число для переменной «numTimes»
4. Адрес строки «R4ndom»
5. Адрес возврата, который привел нас сюда.


Теперь SayHello может заполнять, изменять и повторно использовать эти адреса для игры с нашими переменными, и в первую очередь в эту функцию по-прежнему передается аргумент (строка «R4ndom»). Когда функция SayHello завершена, у нее есть два способа удалить эти локальные переменные и аргументы (поскольку они больше не понадобятся после завершения этой функции) и вернуть стек в прежнее состояние: 1) изменить ESP вернуть то, что было до того, как мы его изменили, или 2) использовать специальную инструкцию RETN с номером после нее. В первом случае, чтобы программа могла запомнить исходное значение ESP, она использует другой регистр, EBP, цель которого — отслеживать исходное местоположение, на которое указывал стек, когда мы впервые вошли в функцию SayHello. Когда он готов вернуться, он просто копирует исходное значение ESP (сохраненное в EBP вначале) из EBP обратно в ESP и БУM, переменные исчезают. Адрес возврата теперь находится на вершине стека, и при выполнении инструкции RETN она будет использовать этот tpo для возврата в нашу основную программу.

Во втором случае вы можете сообщить процессору, сколько байтов вам больше не нужно в стеке, и он удалит их из вершины стека. В нашем случае мы бы использовали RETN 16 (0xF в шестнадцатеричном формате), и это позволило бы избавиться от первых 16 байтов (или 4 32-битных чисел) с вершины стека, оставив новую вершину стека с адресом возврата, чтобы вернуться к нашей основной программе. Тип используемого механизма возврата обычно зависит от компилятора, но вы увидите оба варианта.

Теперь давайте вернемся к нашему файлу FirstProgram.exe:

1700048724509.png


Вы можете видеть, что Олли расшифровал один аргумент и 12 локальных переменных. Эти локальные переменные используются в нашей программе для отслеживания таких вещей, как адрес нашего значка, адрес буфера для ввода текста, длина ввода текста и т. д. И когда это будет сделано, он либо вытолкнет эти значения, изменит регистр ESP обратно на EBP или RETN с номером (в данном случае используются все три!!!),
локальные переменные

Я знаю, что стек — это очень сложная конструкция, но я гарантирую, что вы начнете в нем разбираться, поработав с ним некоторое время. Эта книга по ассемблеру тоже очень поможет.:)
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх