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

Мануал/Книга THE IDA PRO BOOK 2 ИЗДАНИЕ - Неофициальное руководство по самому популярному дизассемблеру в мире

Просто приведу примеры книг с озона который нет на русском
 
За лайки и подписки спасибо большое, по донатам пока по нулям. Пруфы есть если что.
 
7. МАНИПУЛЯЦИИ С ДИЗАССЕМБЛЕРНЫМ КОДОМ

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

ПРИМЕЧАНИЕ. Помните: в IDA нет отмены. Помните об этом, когда начнете манипулировать базой данных.

Имена и наименования

На этом этапе мы столкнулись с двумя категориями имен в IDA: имена, связанные с виртуальными адресами (именованные местоположения), и имена, связанные с переменными кадра стека. В большинстве случаев IDA автоматически генерирует все эти имена в соответствии с ранее обсужденными рекомендациями. IDA называет такие автоматически сгенерированные имена как dummy.

К сожалению, эти имена редко намекают на предполагаемое назначение местоположения или переменной и поэтому обычно не помогают нам понять поведение программы. Когда вы начинаете анализировать любую программу, один из первых и наиболее распространенных способов манипулирования листингом - это изменить имена по умолчанию на более значимые имена. К счастью, IDA позволяет легко изменить любое имя и обрабатывает все детали распространения всех изменений имени на протяжении всего кода. В большинстве случаев изменить имя так же просто, как щелкнуть имя, которое вы хотите изменить (это выделит имя), и использовать горячую клавишу N, чтобы открыть диалоговое окно изменения имени. В качестве альтернативы, щелчок правой кнопкой мыши по имени, которое нужно изменить, обычно вызывает контекстное меню, которое содержит параметр "Переименовать", как показано на рисунке 6-5. Процесс изменения имени несколько отличается для переменных стека и именованных переменных, и эти различия подробно описаны в следующих разделах.

Параметры и локальные переменные

Имена, связанные с переменными стека, являются простейшей формой имени в листинге дизассемблирования, прежде всего потому, что они не связаны с конкретным виртуальным адресом и, следовательно, никогда не могут отображаться в окне имен. Как и в большинстве языков программирования, такие имена считаются ограниченными в области видимости в зависимости от функции, которой принадлежит данный кадр стека. Таким образом, каждая функция в программе может иметь свою собственную переменную стека с именем arg_0, но ни одна функция не может иметь более одной переменной с именем arg_0. Диалоговое окно, показанное на рисунке 7-1, используется для переименования переменной стека.

1.png


После того, как новое имя подставлено, IDA заботится об изменении каждого вхождения старого имени в контексте текущей функции. Изменение имени var_5C на y для demo_stackframe приведет к появлению нового списка, показанного здесь, с изменениями в [1]

2.png
3.png


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

Именованные местоположения

Переименование именованного местоположения или добавление имени к безымянному местоположению немного отличается от изменения имени переменной стека. Процесс доступа к диалоговому окну изменения имени идентичен (горячая клавиша N), но все быстро меняется. На рис. 7-2 показан диалог переименования, связанный с именованными местоположениями.

Это диалоговое окно информирует вас, какой именно адрес вы назначаете, а также список атрибутов, которые могут быть связаны с именем. Максимальная длина имени просто отражает значение из одного из файлов конфигурации IDA (<IDADIR> /cfg/ida.cfg). Вы можете использовать имена длиннее, чем это значение, что вызовет жалобы IDA, сообщив вам, что вы превысили максимальную длину имени, и предложив увеличить максимальную длину имени для вас. Если вы решите это сделать, новое значение максимальной длины имени будет применяться только в текущей базе данных. Любые новые базы данных, которые вы создаете, будут по-прежнему регулироваться максимальной длиной имени, содержащейся в файле конфигурации.

4.png


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

Локальное имя

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

Включить в список имен

Выбор этого параметра приводит к добавлению имени в окно "Имена", что может упростить поиск имени, когда вы захотите вернуться к нему. Автоматически сгенерированные имена никогда не включаются в окно имен по умолчанию.

Публичное имя

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

Автоматически созданное имя

Этот атрибут не оказывает заметного влияния на дизассемблер. При его выборе IDA не генерирует имя автоматически.

Слабое имя

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

Все равно создать имя

Как обсуждалось ранее, никаким двум местоположениям в функции нельзя давать одно и то же имя. Точно так же никаким двум местоположениям вне какой-либо функции (в глобальной области видимости) не может быть присвоено одно и тоже имя. Этот параметр несколько сбивает с толку, поскольку он ведет себя по-разному в зависимости от типа имени, которое вы пытаетесь создать. Если вы редактируете имя в глобальной области (например, имя функции или глобальная переменная) и пытаетесь назначить имя, которое уже используется в базе данных, IDA отобразит диалоговое окно конфликтующего имени, показанное на рисунке 7-3. , предлагая автоматически сгенерировать уникальный числовой суффикс для разрешения конфликта. Это диалоговое окно отображается независимо от того, выбрали ли вы параметр "Все равно создать имя" или нет. Однако, если вы редактируете локальное имя в функции и пытаетесь назначить имя, которое уже используется, поведение по умолчанию - просто отклонить попытку. Если вы настроены использовать данное имя, вы должны выбрать “Создать имя в любом случае”, чтобы заставить IDA сгенерировать уникальный числовой суффикс для локального имени. Конечно, самый простой способ разрешить любой конфликт имен - выбрать имя, которое еще не используется.

5.png


Регистровые имена

Третий тип имени, который часто упускается из виду, - это имя регистра. В рамках функции IDA позволяет переименовывать регистры. Может быть полезно переименовать регистр, когда компилятор решил разместить переменную в регистре, а не в программном стеке, и вы хотите, например, ссылаться на переменную, используя имя, более подходящее для ее цели, чем EDX. Переименование работает так же, как переименование в любом другом месте. Используйте горячую клавишу N или щелкните правой кнопкой мыши имя регистра и выберите "Переименовать", чтобы открыть диалоговое окно переименования регистра. Когда вы переименовываете регистр, вы, по сути, предоставляете псевдоним для обращения к регистру на время выполнения текущей функции (IDA даже обозначает этот псевдоним синтаксисом alias = register в начале функции). IDA позаботится о замене всех экземпляров имени регистра на предоставленный вами псевдоним. Невозможно переименовать регистр, используемый в коде, который не принадлежит функции.

Комментирование в IDA

Еще одна полезная функция IDA - это возможность встраивать комментарии в ваши базы данных. Комментарии - это особенно полезный способ оставить для себя заметки о своем прогрессе при анализе программы. В частности, комментарии полезны для описания последовательностей инструкций на языке ассемблера на более высоком уровне. Например, вы можете написать комментарии, используя операторы языка C, чтобы суммировать поведение конкретной функции. При последующем анализе функции комментарии помогут освежить вашу память быстрее, чем повторно анализируют операторы языка ассемблера.

IDA предлагает несколько стилей комментариев, каждый из которых подходит для разных целей. Комментарии могут быть связаны с любой строкой листинга дизассемблера с помощью параметров, доступных в меню Правка->Комментарии. Горячие клавиши или контекстные меню предлагают альтернативный доступ к функциям комментирования IDA. Чтобы помочь вам разобраться в возможностях комментирования в IDA, мы рассмотрим следующий дизассемблерный код:

6.png


Большинство комментариев IDA начинаются с точки с запятой, чтобы указать, что остаток строки следует рассматривать как комментарий. Это похоже на стили комментариев, используемые многими ассемблерами, и приравнивается к комментариям в стиле # во многих языках сценариев или комментариям в стиле // в C++.

Регулярные комментарии

Самый простой комментарий - это обычный комментарий. Обычные комментарии помещаются в конце существующих линий, как в предыдущем листинге [1]. Щелкните правой кнопкой мыши в правом поле дизассемблерного кода или используйте горячую клавишу с двоеточием :)), чтобы активировать диалоговое окно ввода комментария. Обычные комментарии будут занимать несколько строк, если вы введете несколько строк в диалоговом окне ввода комментария. Каждая строка будет иметь отступ, чтобы соответствовать правой стороне дизассемблерного кода. Чтобы отредактировать или удалить комментарий, вы должны повторно открыть диалоговое окно ввода комментария и отредактировать или удалить весь текст комментария соответствующим образом. По умолчанию обычные комментарии отображаются синим цветом.

Сама IDA широко использует регулярные комментарии. На этапе анализа IDA вставляет обычные комментарии для описания параметров, которые передаются для вызовов функций. Это происходит только тогда, когда IDA имеет имя параметра или информацию о типе для вызываемой функции. Эта информация обычно содержится в библиотеках типов, которые обсуждаются в главах 8 и 13, но также может быть введена вручную.

Повторяющиеся комментарии

Повторяемый комментарий - это комментарий, который вводится один раз, но может автоматически появляться во многих местах на протяжении дизассемблерного кода. Место [2] в предыдущем листинге показывает повторяющийся комментарий. В листинге дизассемблера по умолчанию для повторяющихся комментариев используется синий цвет, что делает их неотличимыми от обычных комментариев. В данном случае важно поведение, а не внешний вид. Поведение повторяющихся комментариев связано с концепцией перекрестных ссылок. Когда одна ячейка программы ссылается на вторую ячейку, содержащую повторяющийся комментарий, комментарий, связанный со второй ячейкой, отражается в первой ячейке. По умолчанию отображаемый комментарий отображается в виде серого текста, что позволяет отличить повторяющийся комментарий от других комментариев. Горячая клавиша для повторяющихся комментариев - точка с запятой (;), что позволяет легко спутать повторяющиеся комментарии с обычными комментариями.

Обратите внимание, что в предыдущем листинге комментарий по адресу [3] идентичен комментарию по адресу [2]. Комментарий [2] был повторен, потому что инструкция [3] (jge short loc_40106C) ссылается на адрес [2] (0040106C).

Обычный комментарий, добавленный в месте, где отображается повторяющийся комментарий, переопределяет повторяющийся комментарий, поэтому будет отображаться только обычный комментарий. Если вы ввели обычный комментарий в [3], повторяющийся комментарий, унаследованный от [2], больше не будет отображаться в [3]. Если вы затем удалили обычный комментарий в [3], повторяющийся комментарий снова отобразится.

Вариант формы повторяющегося комментария связан со строками. Каждый раз, когда IDA автоматически создает строковую переменную, виртуальный повторяющийся комментарий добавляется во все места, ссылающиеся на строковую переменную. Мы говорим виртуальный, потому что комментарий не может редактироваться пользователем. Содержимое виртуального комментария устанавливается равным содержимому строковой переменной и отображается в базе данных так же, как и повторяемый комментарий. В результате в любых местах программы, которые ссылаются на строковую переменную, будет отображаться содержимое строковой переменной как повторяющийся комментарий. Три комментария с аннотациями [4] демонстрируют, что такие комментарии отображаются как результат ссылок на строковые переменные.

Первые и последнии линии

Первая и последняя линии - это полнострочные комментарии, которые появляются либо непосредственно перед (передняя), либо после (задняя) данной линией дизассемблерного кода. Эти комментарии - единственные комментарии IDA, которые не имеют префикса с точкой с запятой. Пример комментария передней линии появляется в предыдущем листинге [5]. Вы можете отличить переднюю строку от задней, сравнив адрес, связанный с этой строкой, с адресом, связанным с инструкцией, непосредственно предшествующей или следующей за строкой.

Комментарии к функциям

Комментарии к функциям позволяют группировать комментарии для отображения в верхней части списка дизассемблирования функции. Пример комментария к функции показан на месте [6], где был введен прототип функции. Вы вводите комментарии к функциям, сначала выделяя имя функции в верхней части функции, а затем добавляя обычный или повторяемый комментарий. Комментарии к повторяющимся функциям отражаются в любом месте, где вызывается комментируемая функция. IDA будет автоматически генерировать комментарии в стиле прототипа функции, когда вы используете команду "Установить тип функции", описанную в главе 8.

Основы преобразования кода

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

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

- Преобразование данных в код
- Преобразование кода в данные
- Назначение последовательности инструкций как функции
- Изменение начального или конечного адреса существующей функции
-Изменение формата отображения операндов инструкций

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

Параметры отображения кода

Простейшие преобразования, которые вы можете внести в листинг дизассемблирования, включают настройку количества информации, которую IDA генерирует для каждой строки дизассемблирования. Каждую дизассемблированную линию можно рассматривать как набор частей, которые IDA, что неудивительно, называет частями линии дизассемблированнию. Ярлыки, мнемоники и операнды всегда присутствуют в строке дизассемблированния. Вы можете выбрать дополнительные детали для каждой линии дизассемблированнию через Параметры->Общие как показано на рисунке 7-4.

7.png


Раздел "Отображение деталей линии дизассемблинга" в правом верхнем углу предлагает несколько вариантов настройки линий дизассемблерного кода. Для представления дизассемблерного кода в IDA по умолчанию выбраны префиксы строк, комментарии и повторяющиеся комментарии. Каждый элемент описан здесь и показан в следующем списке.

Префиксы строк

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

Указатель стека

IDA выполняет обширный анализ каждой функции, чтобы отслеживать изменения в указателе стека программы. Этот анализ важен для понимания структуры кадра стека каждой функции. Выбор опции указателя стека заставляет IDA отображать относительное изменение указателя стека в каждой функции. Это может быть полезно для распознавания несоответствий в соглашениях о вызовах (например, IDA может не понимать, что конкретная функция использует stdcall) или необычных манипуляций с указателем стека. Отслеживание указателя стека показано в столбце под [1]. В этом примере указатель стека изменился на четыре байта после первой инструкции и всего на 0x7C байтов после третьей инструкции. К моменту завершения функции указатель стека восстанавливается до исходного значения (относительное изменение нулевых байтов). Каждый раз, когда IDA встречает оператор возврата функции и обнаруживает, что значение указателя стека не равно нулю, состояние ошибки помечается, а строка команд выделяется красным цветом. В некоторых случаях это может быть преднамеренная попытка помешать автоматическому анализу. В других случаях компилятор может использовать прологи и эпилоги, которые IDA не может точно проанализировать.

Комментарии и повторяющиеся комментарии

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

Авто комментарии

IDA может автоматически комментировать некоторые типы инструкций. Это может служить напоминанием о том, как ведут себя определенные инструкции. Никаких комментариев не добавляется к тривиальным инструкциям, таким как x86 mov. Комментарии по адресу [2] являются примерами автоматических комментариев. Комментарии пользователей имеют приоритет над автоматическими комментариями; в этом случае, если вы хотите видеть автоматический комментарий IDA для строки, вам придется удалить все добавленные вами комментарии (обычные или повторяющиеся).

Плохая инструкция <BAD>

IDA может помечать инструкции, которые допустимы для процессора, но могут не распознаваться некоторыми ассемблерами. Недокументированные (в отличие от незаконных) инструкции ЦП могут попадать в эту категорию. В таких случаях IDA дизассемблирует инструкцию как последовательность байтов данных и отображает недокументированную инструкцию в виде комментария с предисловием <BAD>. Цель состоит в том, чтобы произвести код, с которой может справиться большинство ассемблеров. Обратитесь к файлу справки IDA для получения дополнительной информации об использовании знаков <BAD>.

Количество байтов опкода

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

Это довольно просто, когда вы дизассемблируете код для процессоров, которые имеют фиксированный размер инструкций, но это несколько сложнее для процессоров инструкций переменной длины, таких как x86, для которых размер инструкций может составлять от одного до более чем дюжины байтов. Независимо от длины инструкции, IDA резервирует отображаемое пространство в листинге дизассемблирования для указанного здесь количества байтов, сдвигая оставшиеся части строки дизассемблирования вправо для размещения указанного количества байтов кода операции. Количество байтов кода операции было установлено равным 5 в следующей дизассемлерном коде, и их можно увидеть в столбцы под [3]. Символ + в [4] указывает на то, что указанная инструкция слишком длинная для полного отображения при текущих настройках.

8.png


Вы можете дополнительно настроить отображение дизассемлерного кода, отрегулировав значения отступов и полей, показанные в правом нижнем углу рисунка 7-4. Любые изменения этих параметров влияют только на текущую базу данных. Глобальные настройки для каждой из этих опций хранятся в основном файле конфигурации <IDADIR> /cfg/ida.cfg.

Операнды инструкции форматирования

В процессе дизассемблирования IDA принимает множество решений относительно форматирования операндов, связанных с каждой инструкцией. Наиболее важные решения обычно связаны с форматированием различных целочисленных констант, используемых в самых разных типах инструкций. Среди прочего, эти константы могут представлять относительные смещения в командах перехода или вызова, абсолютные адреса глобальных переменных, значения, которые будут использоваться в арифметических операциях, или константы, определенные программистом. Чтобы сделать листинг более читабельным, IDA пытается по возможности использовать символические имена, а не числа. В некоторых случаях решения о форматировании принимаются на основе контекста дизассемблируемой инструкции (например, инструкции вызова); в других случаях решение основывается на используемых данных (таких как доступ к глобальной переменной или смещение в кадре стека). Во многих других случаях точный контекст, в котором используется константа, может быть неясным. Когда это происходит, связанная константа обычно форматируется как шестнадцатеричная константа.

Если вы не один из немногих людей в мире, которые едят, спят и дышат шестнадцатеричным образом, вам понравятся возможности форматирования операндов в IDA. Щелчок правой кнопкой мыши по любой константе в листинге открывает контекстное меню, подобное показанному на рис. 7-5.

9.png


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

Во многих случаях программисты используют именованные константы в своем исходном коде. Такие константы могут быть результатом операторов #define (или их эквивалентов) или они могут принадлежать набору перечисляемых констант. К сожалению, к тому времени, когда компилятор завершит работу с исходным кодом, уже невозможно определить, использовал ли источник символьную константу или буквальную числовую константу. IDA поддерживает большой каталог именованных констант, связанных со многими распространенными библиотеками, такими как стандартная библиотека C или Windows API.

Этот каталог доступен через опцию "Использовать стандартную символическую константу" в контекстном меню, связанном с любым постоянным значением. Выбор этой опции для константы 0Ah на рисунке 7-5 открывает диалоговое окно выбора символа, показанное на рисунке 7-6.

10.png


Диалоговое окно заполняется из внутреннего списка констант IDA после фильтрации в соответствии со значением константы, которую мы пытаемся отформатировать. В этом случае мы видим, что все константы, которые, как известно, IDA, приравниваются к значению 0Ah. Если мы определили, что значение использовалось вместе с созданием сетевого соединения в стиле X.25, то мы могли бы выбрать AF_CCITT и получить следующую строку

11.png


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

Управление функциями

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

Создание новых функций

При определенных обстоятельствах новые функции могут быть созданы там, где функция не существует. Новые функции могут быть созданы из существующих инструкций, которые еще не принадлежат функции, или они могут быть созданы из байтов необработанных данных, которые не были определены IDA каким-либо другим образом (например, двойные слова или строки). Вы создаете функции, помещая курсор на первый байт или инструкцию, которая должна быть включена в новую функцию, и выбирая Edit-> Functions-> Create Function. При необходимости IDA пытается преобразовать данные в код. Затем она просматривает, чтобы проанализировать структуру функции и найти оператор возврата. Если IDA может найти подходящий конец функции, она генерирует новое имя функции, анализирует кадр стека и реструктурирует код в форме функции. Если она не может найти конец функции или встречает недопустимые инструкции, операция завершается ошибкой.

Удаление функций

Вы можете удалить существующие функции, используя Правка-> Функции-> Удалить функцию. Вы можете удалить функцию, если считаете, что IDA допустила ошибку при автоматическом анализе.

Функциональные блоки

Функциональные блоки обычно встречаются в коде, созданном компилятором Microsoft Visual C++. Чанки - это результат того, что компилятор перемещает блоки кода, которые реже выполняются, чтобы втиснуть часто выполняемые блоки в страницы памяти, которые с меньшей вероятностью будут выгружены.

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

12.png


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

13.png


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

Вы создаете новые функциональные блоки, выбирая диапазон адресов, которые принадлежат блоку, который не должен быть частью какой-либо существующей функции, и выбирая Edit-> Functions-> Append Function Tail. На этом этапе вам будет предложено выбрать родительскую функцию из списка всех определенных функций.

ПРИМЕЧАНИЕ
В листингах дизассемблирования функциональные блоки называются просто функциональными блоками. В системе меню IDA функциональные блоки вместо этого называются функциональными тейлами.

Вы можете удалить существующие функциональные блоки, поместив курсор на любую строку внутри удаляемого блока и выбрав Правка -> Функции-> Удалить тейл функции. На этом этапе вам будет предложено подтвердить свое действие перед удалением выбранного фрагмента.

Если функциональные блоки создают больше проблем, чем они того стоят, вы можете попросить IDA не создавать функциональные блоки, сняв флажок "Create function tails loader" при первой загрузке файла в IDA. Эта опция - одна из опций загрузчика, доступных через Опции ядра ( см. Главу 4) в диалоге начальной загрузки файла. Если вы отключите хвосты функций, основное отличие, которое вы можете заметить, состоит в том, что функции, которые в противном случае содержали бы тейлы, содержат переходы в области за пределами границ функций. IDA выделяет такие прыжки красными линиями и стрелками в окнах стрелок в левой части разобранного кода. В виде графика для соответствующей функции цели таких переходов не отображаются.

Атрибуты функции

IDA связывает ряд атрибутов с каждой распознаваемой функцией. Диалог свойств функции, показанный на рис. 7-7, можно использовать для редактирования многих из этих атрибутов. Здесь объясняется каждый атрибут, который можно изменить.

Название функции

Альтернативный способ изменения имени функции.

Начальный адрес

Адрес первой инструкции в функции. IDA чаще всего определяет это автоматически, либо во время анализа, либо по адресу, используемому во время операции создания функции.

14.png



Конечный адрес

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

Область локальных переменных

Это количество байтов стека, выделенных для локальных переменных (см. Рисунок 6-4) для функции. В большинстве случаев это значение вычисляется автоматически на основе анализа поведения указателя стека внутри функции.

Сохраненные регистры

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

Очищенные байты

Очищенные байты показывают количество байтов параметров, которые функция удаляет из стека при возврате вызывающей функции. Для функций cdecl это значение всегда равно нулю. Для функций stdcall это значение представляет объем пространства, занимаемого любыми параметрами, которые передаются в стек (см. Рисунок 6-4). В программах x86 IDA может автоматически определять это значение, когда наблюдает за использованием варианта RETN инструкции возврата.

Дельта указателя кадра

В некоторых случаях компиляторы могут настроить указатель кадра функции так, чтобы он указывал куда-нибудь в середину области локальной переменной, а не на сохраненный указатель кадра в нижней части области локальной переменной. Это расстояние от настроенного указателя кадра до указателя сохраненного кадра называется дельтой указателя кадра. В большинстве случаев любая дельта указателя кадра будет вычисляться автоматически при анализе функции. Компиляторы используют дельту кадра стека для оптимизации скорости. Назначение дельты - сохранить как можно больше переменных кадра стека в пределах досягаемости 1-байтового смещения со знаком (–128 .. + 127) от указателя кадра.

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

Не возвращаться

Функция не возвращается к вызывающему кода. Когда такая функция вызывается, IDA не предполагает, что выполнение продолжается после связанной инструкции вызова.

Дальняя функция

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

Библиотечная функция

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

Статическая функция

Не делает ничего, кроме отображения модификатора static в списке атрибутов функции.

Фрейм основанный на BP

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

BP равен SP

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

Настройки указателя стека

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

Самый простой пример такого случая возникает, когда одна функция вызывает другую функцию, которая использует соглашение о вызовах stdcall. Если вызываемая функция находится в разделяемой библиотеке, о которой IDA не знает (IDA поставляется с информацией о сигнатурах и соглашениях о вызовах многих общих библиотечных функций), то IDA не будет знать, что функция использует stdcall и не сможет выполнить учет, потому что указатель стека будет изменен вызываемой функцией перед возвратом. Таким образом, IDA будет отражать неточное значение указателя стека для оставшейся части функции. Следующая последовательность вызова функции, в которой some_imported_func находится в разделяемой библиотеке, демонстрирует эту проблему (обратите внимание, что была включена опция части строки указателя стека):

15.png


Поскольку some_imported_func использует stdcall, она очищает три параметра из стека при возврате, и правильное значение указателя стека в {1} должно быть 01C. Один из способов решить эту проблему - сделать ручную настройку стека с инструкцией в {2}. Настройки стека можно добавить, выделив адрес, к которому применяется настройка, выбрав Edit-> Functions-> Change Stack Pointer (горячая клавиша ALT-K) и указав количество байтов, на которое изменяется указатель стека, в данном случае 12.

В то время как предыдущий пример служит для иллюстрации, есть лучшее решение этой конкретной проблемы. Рассмотрим случай, когда some_imported_func вызывается много раз. В этом случае нам нужно будет произвести настройку стека, которую мы только что сделали, в каждом месте, из которого вызывается some_imported_func. Очевидно, это может быть очень утомительно, и мы можем что-то упустить. Лучшее решение - обучить IDA поведению some_imported_func. Поскольку мы имеем дело с импортированной функцией, когда мы пытаемся перейти к ней, мы в конечном итоге попадаем в запись таблицы импорта для этой функции, которая выглядит примерно так:

16.png


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

Чтобы улучшить свой автоматический анализ, IDA включает передовые методы, которые пытаются разрешить несоответствия указателя стека путем решения системы линейных уравнений, связанных с поведением указателя стека. В результате вы можете даже не осознавать, что IDA не знает деталей таких функций, как some_imported_func. Для получения дополнительной информации об этих методах обратитесь к сообщению блога Ильфака под названием "Симплексный метод в IDA Pro" по адресу http://hexblog.com/2006/06/.

Преобразование данных в код (и наоборот)

На этапе автоматического анализа байты иногда неправильно классифицируются. Байты данных могут быть неправильно классифицированы как байты кода и разобраны на инструкции, или байты кода могут быть неправильно классифицированы как байты данных и отформатированы как значения данных. Это происходит по многим причинам, включая тот факт, что некоторые компиляторы встраивают данные в раздел кода программ, или тот факт, что некоторые байты кода никогда не упоминаются напрямую как код, и IDA предпочитает не дизассемблировать их. В частности, запутанные программы имеют тенденцию стирать различие между разделами кода и разделами данных.

Независимо от причины, по которой вы хотите переформатировать свой разобранный код, сделать это довольно просто. Первый вариант переформатирования чего-либо - удалить его текущее форматирование (код или данные). Можно отменить определение функций, кода или данных, щелкнув правой кнопкой мыши элемент, который вы хотите отменить, и выбрав "Отменить определение" (также Edit-> Undefine или горячая клавиша U) в появившемся контекстно-зависимом меню. Отмена определения элемента приводит к тому, что базовые байты переформатируются в список необработанных значений байтов. Большие области можно не определять, используя операцию "щелкнуть и перетащить" для выбора диапазона адресов перед выполнением операции undefine. В качестве примера рассмотрим следующий простой список функций:

17.png


Если отменить определение этой функции, мы получим серию неклассифицированных байтов, показанных здесь, которые мы могли бы переформатировать практически любым способом:

18.png

Чтобы дизассемблировать последовательность неопределенных байтов, щелкните правой кнопкой мыши первый байт, который нужно дизассемблировать, и выберите Код (также Правка-> Код или горячую клавишу C). Это заставляет IDA дизассемблировать все байты, пока не обнаружит определенный элемент или недопустимую инструкцию. Большие области можно преобразовать в код, используя операцию “щелкнуть и перетащить” для выбора диапазона адресов перед выполнением операции преобразования кода.

Дополнительная операция преобразования кода в данные немного сложнее. Во-первых, невозможно преобразовать код в данные с помощью контекстного меню. Доступные альтернативы включают Edit-> Data и горячую клавишу D. Массовое преобразование инструкций в данные проще всего выполнить, если сначала отменить определение всех инструкций, которые вы хотите преобразовать в данные, а затем соответствующим образом отформатировать данные. Основное форматирование данных обсуждается в следующем разделе.

Основные преобразования данных

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

1. Типы данных и/или размеры могут быть выведены из способа использования регистров. Наблюдаемая инструкция загрузки 32-битного регистра из памяти подразумевает, что соответствующая ячейка памяти содержит 4-байтовый тип данных (хотя мы, возможно, не сможем различить 4-байтовое целое число и 4-байтовый указатель).

2. Прототипы функций могут использоваться для присвоения типов данных параметрам функции. IDA поддерживает большую библиотеку прототипов функций именно для этой цели. Анализ выполняется для параметров, передаваемых функциям, в попытке привязать параметр к области памяти. Если такая связь может быть обнаружена, то тип данных может быть применен к соответствующей ячейке памяти. Рассмотрим функцию, единственный параметр которой является указателем на CRITICAL_SECTION (тип данных Windows API). Если IDA может определить адрес, переданный при вызове этой функции, то IDA может пометить этот адрес как объект CRITICAL_SECTION.

3. Анализ последовательности байтов может выявить вероятные типы данных. Именно это происходит, когда двоичный файл сканируется на предмет строкового содержимого. Когда встречаются длинные последовательности символов ASCII, есть основания предполагать, что они представляют собой массивы символов.

В следующих нескольких разделах мы обсудим некоторые базовые преобразования, которые вы можете выполнять с данными в своих дизассемблерах.

Указание размеров данных

Самый простой способ изменить фрагмент данных - изменить его размер. IDA предлагает ряд спецификаторов размера/типа данных. Наиболее часто встречающиеся спецификаторы - это db, dw и dd, представляющие 1-, 2- и 4-байтовые данные соответственно. Первый способ изменить размер элемента данных - через диалоговое окно Параметры-> Настройка типов данных, показанное на рисунке 7-8.

19.png


Этот диалог состоит из двух частей. Левая часть диалогового окна содержит столбец кнопок, используемых для немедленного изменения размера данных текущего выбранного элемента. Правая часть диалогового окна содержит столбец флажков, используемых для настройки того, что IDA называет каруселью данных. Обратите внимание, что для каждой кнопки слева есть соответствующий флажок справа. Карусель данных - это постоянно обновляемый список типов данных, который содержит только те типы, для которых установлены флажки. Изменение содержимого карусели данных не оказывает немедленного влияния на отображение IDA. Вместо этого каждый тип карусели данных указан в контекстно-зависимом меню, которое появляется, когда вы щелкаете правой кнопкой мыши элемент данных. Таким образом, легче переформатировать данные в тип, указанный в карусели данных, чем в тип, не указанный в карусели данных. Учитывая типы данных, выбранные на рис. 7-8, щелчок правой кнопкой мыши по элементу данных предоставит вам возможность переформатировать этот элемент в данные байта, слова или двойного слова.

Название карусели данных происходит от поведения соответствующей горячей клавиши форматирования данных: D. Когда вы нажимаете D, элемент по текущему выбранному адресу переформатируется в следующий тип в списке карусели данных. При указанном ранее списке из трех элементов элемент, отформатированный как db, переключается на dw, элемент, отформатированный как dw, переключается на dd, а элемент, отформатированный как dd, переключается обратно на db, чтобы завершить кругооборот. Использование горячей клавиши данных для элемента, не связанного с данными, такого как код, приводит к тому, что элемент форматируется как первый тип данных в списке карусели (в данном случае db).

Переключение типов данных приводит к тому, что элементы данных увеличиваются, сжимаются или остаются того же размера. Если размер элемента остается прежним, то единственное заметное изменение - это способ форматирования данных. Если вы уменьшите размер элемента, например, с dd (4 байта) до db (1 байт), любые дополнительные байты (в данном случае 3) станут неопределенными. Если вы увеличиваете размер элемента, IDA жалуется, если байты, следующие за элементом, уже определены, и спрашивает вас окольным путем, хотите ли вы, чтобы IDA отменила определение следующего элемента, чтобы расширить текущий элемент. В таких случаях вы можете увидеть сообщение "Непосредственно преобразовать в данные?" Это сообщение обычно означает, что IDA отменит определение достаточного количества следующих элементов, чтобы удовлетворить ваш запрос. Например, при преобразовании байтовых данных (db) в данные с двойным словом (dd) для формирования нового элемента данных должны быть использованы 3 дополнительных байта.

Типы данных и размеры могут быть указаны для любого места, которое описывает данные, включая переменные стека. Чтобы изменить размер переменных, выделенных стеком, откройте подробное представление кадра стека, дважды щелкнув переменную, которую вы хотите изменить; затем измените размер переменной, как и любой другой переменной.

Работа со строками

IDA распознает большое количество строковых форматов. По умолчанию IDA ищет и форматирует строки с завершающим нулем в стиле C. Чтобы принудительно преобразовать данные в строку, используйте параметры в меню Правка-> Строки, чтобы выбрать определенный стиль строки. Если байты, начинающиеся с текущего выбранного адреса, образуют строку выбранного стиля, IDA группирует эти байты вместе в однострочную переменную. В любое время вы можете использовать горячую клавишу A для форматирования текущего выбранного местоположения в стиль строки по умолчанию.

Два диалога отвечают за настройку строковых данных. К первому, показанному на рис. 7-9, можно получить доступ через Options-> ASCII String Style, хотя ASCII в этом случае немного неверно, поскольку понимается гораздо более широкий спектр стилей строк.

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

20.png


Второй диалог, используемый для настройки строковых операций, - это диалог Параметры -> Общие, показанный на Рисунке 7-10, где вкладка Строки позволяет настроить дополнительные параметры, связанные со строками. Хотя здесь вы также можете указать тип строки по умолчанию, используя доступный раскрывающийся список, большинство доступных параметров имеют дело с именованием и отображением строковых данных, независимо от их типа. Область создания имени в правой части диалогового окна видна только в том случае, если выбран параметр "Создать имена". Когда генерация имен отключена, строковым переменным даются фиктивные имена, начинающиеся с префикса asc_.

21.png


Когда генерация имени включена, параметры генерации имени управляют тем, как IDA генерирует имена для строковых переменных. Если параметр "Генерировать серийные имена"не выбран (по умолчанию), указанный префикс комбинируется с символами, взятыми из строки, для создания имени, которое не превышает текущую максимальную длину имени. Пример такой строки появляется здесь:

22.png


В имени используется регистр заголовка, и любые символы, которые нельзя использовать в именах (например, пробелы), опускаются при формировании имени. Параметр "Пометить как автоматически созданный" заставляет сгенерированные имена отображаться другим цветом (темно-синим по умолчанию), чем имена, указанные пользователем (по умолчанию синий). Сохранять регистр заставляет имя использовать символы в том виде, в котором они появляются в строке, а не преобразовывать их в регистр заголовка. Наконец, функция "Генерация серийных имен" заставляет IDA сериализовать имена путем добавления числовых суффиксов (начиная с Number). Количество цифр в сгенерированных суффиксах контролируется полем ширина. Как показано на рисунке 7-10, первые три имени, которые будут сгенерированы, будут a000, a001 и a002.

Указание массивов

Одним из недостатков списков дизассемблирования, полученных из языков более высокого уровня, является то, что они дают очень мало подсказок относительно размера массивов. В листинге дизассемблирования указание массива может потребовать огромного количества места, если каждый элемент в массиве указан в отдельной строке дизассемблирования. В следующем листинге показаны объявления данных, следующие за именованной переменной unk_402060. Тот факт, что какие-либо инструкции ссылаются только на первый элемент в списке, предполагает, что это может быть первый элемент в массиве. Вместо прямого обращения к дополнительным элементам в массивах часто используются более сложные вычисления индекса для смещения от начала массива.

23.png


IDA предоставляет средства для группировки последовательных определений данных в одно определение массива. Чтобы создать массив, выберите первый элемент массива (мы выбрали unk_402060) и используйте Edit-> Array, чтобы запустить диалог создания массива, показанный на рисунке 7-11. Если элемент данных был определен в данном месте, то при щелчке правой кнопкой мыши по элементу будет доступен параметр “Массив”. Тип создаваемого массива определяется типом данных, связанным с элементом, выбранным в качестве первого элемента в массиве. В этом случае мы создаем массив байтов.

24.png


ПРИМЕЧАНИЕ Перед созданием массива убедитесь, что вы выбрали правильный размер для элементов массива, изменив размер первого элемента в массиве на соответствующее значение.

Ниже приведены описания полезных полей для создания массива:

Ширина элемента массива

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

Максимально возможный размер

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

Количество элементов

Здесь вы указываете точный размер массива. Общее количество байтов, занимаемых массивом, можно вычислить как количество элементов * ширина элемента массива.

Элементы в строке

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

Ширина элемента

Это значение предназначено только для целей форматирования и контролирует ширину столбца, когда несколько элементов отображаются в одной строке.

Использовать конструкцию "dup"

Эта опция заставляет идентичные значения данных группироваться в один элемент со спецификатором повторения.

Подписанные элементы

Указывает, будут ли данные отображаться в виде значений со знаком или без знака.

Индексы отображения

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

Создать как массив

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

Принятие параметров, указанных на рис. 7-11, приводит к следующему объявлению компактного массива, который может быть прочитан как массив байтов (db) с именем byte_402060, состоящий из значения 0, повторяемого 416 (1A0h) раз.

25.png


В результате 416 строк разобранного кода были сжаты в одну строку (в основном из-за использования dup). В следующей главе мы обсудим создание массивов внутри фреймов стека.

Резюме

Вместе с предыдущей главой эта глава охватывает наиболее распространенные операции, которые пользователям IDA когда-либо придется выполнять. Используя модификации базы данных, вы объедините свои собственные знания со знаниями, полученными от IDA на этапе анализа, чтобы создать гораздо более полезные базы данных. Как и в случае с исходным кодом, эффективное использование имен, присвоение типов данных и подробные комментарии не только помогут вам запомнить то, что вы проанализировали, но также значительно помогут другим, кому может потребоваться использовать вашу работу. В следующей главе мы продолжим углубляться в возможности IDA, рассмотрев, как работать с более сложными структурами данных, например, представленными структурой C, и перейдем к изучению некоторых низкоуровневых деталей скомпилированного кода на C++.
 
Плиз, донэйт.
 
8. ТИПЫ ДАННЫХ И СТРУКТУРЫ ДАННЫХ

Плохое понимания поведения двоичных программ заключается в каталогизации библиотечных функций, которые вызывает программа. Программа на C, вызывающая функцию connect, создает сетевое соединение. Программа Windows, вызывающая RegOpenKey, обращается к реестру Windows. Однако требуется дополнительный анализ, чтобы понять, как и почему вызываются эти функции.

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

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

Screenshot_36.png


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

Способность IDA распространять информацию о типах из прототипов функций не ограничивается библиотечными функциями, содержащимися в библиотеках типов IDA. IDA может распространять имена формальных параметров и типы данных из любой функции в вашей базе данных, если вы явно указали информацию о типе функции. После первоначального анализа IDA назначает фиктивные имена и универсальный тип int всем аргументам функции, если только через распространение типа у нее нет причин поступить иначе. В любом случае, вы должны установить тип функции, используя команду Edit-> Functions-> Set Function Type, щелкнув правой кнопкой мыши имя функции и выбрав Set Function Type в контекстном меню или используя горячую клавишу Y. Для функции, показанной ниже, это приводит к диалоговому окну, показанному на рисунке 8-1, в котором вы можете ввести правильный прототип функции.

Screenshot_37.png


Как показано ниже, IDA предполагает тип возвращаемого значения int, правильно делает вывод о том, что соглашение о вызовах cdecl используется на основе типа используемой инструкции ret, включает имя функции, которую мы изменили, и предполагает, что все параметры имеют тип int. Поскольку мы еще не изменили имена аргументов, IDA отображает только их типы.

Screenshot_38.png


Если мы изменим прототип так, чтобы он читался как int __cdecl foo (float f, char * ptr), IDA автоматически вставит комментарий прототипа (1) для функции и изменит имена аргументов (2) при дизассемблировании, как показано ниже.

Screenshot_39.png


Наконец, IDA распространяет эту информацию на все вызывающие функции вновь измененную функцию, в результате чего улучшаются аннотации всех связанных вызовов функций, как показано здесь. Обратите внимание, что имена аргументов f и ptr были распространены как комментарии (3) в вызывающей функции и использовались для переименования переменных (4), которые раньше использовали фиктивные имена.

Screenshot_40.png


Возвращаясь к импортированным библиотечным функциям, часто бывает, что IDA уже знает прототип функции. В таких случаях вы можете легко просмотреть прототип, удерживая указатель мыши над именем функции. Когда IDA не знает последовательности параметров функции, она должна, как минимум, знать имя библиотеки, из которой была импортирована функция (см. окно “Импорт”). Когда это происходит, вашими лучшими ресурсами для изучения поведения функции являются любые связанные справочные страницы или другая доступная документация по API (например, MSDN в Интернете). Когда ничего не помогает, помните пословицу: Google - ваш друг.

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

Распознавание использования структуры данных

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

Доступ к членам массива

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

int array_demo[100];

вычисляется как

int bytes = 100 * sizeof(int);


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

Screenshot_41.png


Предположим, для примера, что sizeof (int) составляет 4 байта, тогда первый доступ к массиву в (1) обращается к целочисленному значению, которое находится в 80 байтах в массиве, а второй доступ к массиву в (2) обращается к последовательным целым числам при смещениях 0, 4, 8, .. 96 байтов в массив. Смещение для первого доступа к массиву может быть вычислено во время компиляции как 20 * 4. В большинстве случаев смещение для второго доступа к массиву должно вычисляться во время выполнения, потому что значение счетчика цикла i, не фиксируется во время компиляции. Таким образом, для каждого прохождения цикла необходимо вычислить произведение i * 4, чтобы определить точное смещение в массиве. В конечном счете, способ доступа к элементу массива зависит не только от типа используемого индекса, но и от того, где массив размещается в пространстве памяти программы.

Глобально размещенные массивы

Когда массив выделяется в области глобальных данных программы (например, в разделе .data или .bss), базовый адрес массива известен компилятору во время компиляции. Фиксированный базовый адрес позволяет компилятору вычислять фиксированные адреса для любого элемента массива, к которому осуществляется доступ с использованием фиксированного индекса. Рассмотрим следующую тривиальную программу, которая обращается к глобальному массиву, используя как фиксированные, так и переменные смещения:

Screenshot_42.png


Эта программа дизассемблируется в следующее:

Screenshot_43.png


Хотя эта программа имеет только одну глобальную переменную, строки дизассемблирования в (1), (2) и (3), кажется, указывают на наличие трех глобальных переменных. Вычисление смещения (eax * 4) в (4) - единственное, что, кажется, намекает на наличие глобального массива с именем dword_40B720, но это то же имя, что и глобальная переменная, найденная в (1).

Основываясь на фиктивных именах, присвоенных IDA, мы знаем, что глобальный массив состоит из 12 байтов, начинающихся с адреса 0040B720. В процессе компиляции компилятор использовал фиксированные индексы (0, 1, 2) для вычисления фактических адресов соответствующих элементов в массиве (0040B720, 0040B724 и 0040B728), на которые ссылаются с помощью глобальных переменных в (1), (2) и (3). Используя операции форматирования массива IDA, обсуждавшиеся в предыдущей главе (Edit-> Array), dword_40B720 можно отформатировать как трехэлементный массив, в результате чего будут получены альтернативные строки дизассемблирования, показанные в следующем листинге. Обратите внимание, что это конкретное форматирование подчеркивает использование смещений в массиве:

Screenshot_44.png


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

Выделенные стеком массивы

Чем отличается доступ к массиву, если вместо этого массив выделяется как переменная стека? Инстинктивно мы могли бы подумать, что он должен быть другим, поскольку компилятор не может знать абсолютный адрес во время компиляции, поэтому, конечно, даже доступы, использующие постоянные индексы, должны требовать некоторых вычислений во время выполнения. Однако на практике компиляторы обрабатывают массивы, выделенные стеком, почти так же, как массивы, размещенные глобально.

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

Screenshot_45.png


Адрес, по которому будет размещен stack_array, неизвестен во время компиляции, поэтому компилятор не может предварительно вычислить адрес stack_array [1] во время компиляции, как это было в примере с глобальным массивом. Изучая листинг дизассемблера для этой функции, мы понимаем, как осуществляется доступ к массивам, выделенным стеком:

Screenshot_46.png


Как и в примере с глобальным массивом, эта функция имеет три переменных (var_10, var_C и var_8), а не массив из трех целых чисел. Основываясь на константных операндах, используемых в (1), (2) и (3), мы знаем, что-то, что кажется ссылками на локальные переменные, на самом деле является ссылками на три элемента stack_array, первый элемент которых должен находиться в var_10, локальной переменной с наименьшим адресом памяти.

Чтобы понять, как компилятор разрешил ссылки на другие элементы массива, подумайте, что компилятор проходит при работе со ссылкой на stack_array[1], который находится на 4 байта в массиве или на 4 байта за пределами местоположения var_10. В кадре стека компилятор решил выделить stack_array по адресу ebp - 0x10. Компилятор понимает, что stack_array[1] находится по адресу ebp - 0x10 + 4, что упрощается до ebp - 0x0C. В результате IDA отображает это как ссылку на локальную переменную. В результате, как и в случае с глобально распределенными массивами, использование постоянных значений индекса имеет тенденцию скрывать наличие массива, распределенного в стеке. Только доступ к массиву в (4) намекает на тот факт, что var_10 является первым элементом в массиве, а не простой целочисленной переменной. Кроме того, строка дизассемблирования в (4) также помогает нам сделать вывод, что размер отдельных элементов в массиве составляет 4 байта.

Таким образом, массивы, выделенные стеком, и массивы, размещенные глобально, обрабатываются компиляторами очень одинаково. Однако есть дополнительная информация, которую мы можем попытаться извлечь из дизассемблирования примера стека. Основываясь на расположении idx в стеке, можно сделать вывод, что массив, который начинается с var_10, содержит не более трех элементов (в противном случае idx будет перезаписан). Если вы разработчик эксплойтов, это может быть очень полезно для точного определения того, сколько данных вы можете уместить в массиве, прежде чем вы его переполните и начнете повреждать последующие данные.

Выделенные в куче массивы

Выделенные в куче массивы выделяются с помощью функции распределения динамической памяти, такой как malloc(C) или new (C++). С точки зрения компилятора, основное отличие в работе с массивом, выделенным в куче, состоит в том, что компилятор должен генерировать все ссылки на массив на основе значения адреса, возвращаемого функцией выделения памяти. Теперь для сравнения рассмотрим следующую функцию, которая выделяет небольшой массив в куче программы:

Screenshot_47.png


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

Screenshot_48.png


Screenshot_49.png


Начальный адрес массива (возвращенный из malloc в регистре EAX) хранится в локальной переменной heap_array. В этом примере, в отличие от предыдущих примеров, каждый доступ к массиву начинается с чтения содержимого heap_array для получения базового адреса массива, прежде чем можно будет добавить значение смещения для вычисления адреса правильного элемента в массиве. Ссылки на heap_array [0], heap_array[1] и heap_array[2] требуют смещения 0, 4 и 8 байтов соответственно, как показано в (1), (2) и (3). Операция, которая больше всего напоминает предыдущие примеры, - это ссылка на heap_array[idx] в (4), в которой смещение в массиве продолжает вычисляться путем умножения индекса массива на размер элемента массива.

У массивов, размещенных в куче, есть одна особенно приятная особенность. Когда можно определить как общий размер массива, так и размер каждого элемента, легко вычислить количество элементов, выделенных для массива. Для массивов с распределением в куче параметр, переданный в функцию распределения памяти (0x0C передан в malloc в(5)), представляет общее количество байтов, выделенных для массива. Разделив это на размер элемента (4 байта в этом примере, как видно из смещений в (1), (2) и (3)), мы узнаем количество элементов в массиве. В предыдущем примере был выделен трехэлементный массив.

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

Доступ к членам структуры

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

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

Screenshot_50.png


Минимально необходимое пространство для выделения структуры определяется суммой пространства, необходимого для выделения каждого поля в структуре. Однако никогда не следует предполагать, что компилятор использует минимально необходимое пространство для выделения структуры. По умолчанию компиляторы стремятся выровнять поля структуры по адресам памяти, что обеспечивает наиболее эффективное чтение и запись этих полей. Например, 4-байтовые целочисленные поля будут выровнены по смещениям, которые делятся на 4, а 8-байтовые поля двойной точности будут выровнены по смещениям, которые делятся на 8. В зависимости от состава структуры для выполнения требований к выравниванию может потребоваться вставка байтов заполнения, в результате чего фактический размер структуры будет больше, чем сумма полей ее компонентов. Смещения по умолчанию и результирующий размер структуры для показанного ранее примера структуры можно увидеть в столбце “Default offset”.

Структуры можно упаковать в минимально необходимое пространство, используя параметры компилятора для запроса согласования определенных элементов. Microsoft Visual C/ C++ и GNU gcc/g++ распознают прагму pack как средство управления выравниванием полей структуры. Компиляторы GNU дополнительно распознают упакованный атрибут как средство управления выравниванием структуры для каждой структуры. Запрос 1-байтового выравнивания для полей структуры заставляет компиляторы сжимать структуру до минимально необходимого пространства. Для структуры нашего примера это дает смещения и размер структуры, указанные в столбце ”Minimum offset”. Обратите внимание, что некоторые процессоры работают лучше, когда данные выровнены в соответствии с их типом, в то время как другие процессоры могут создавать исключения, если данные не выровнены по определенным границам.

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

Глобально размещенные структуры

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

Screenshot_51.png


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

Screenshot_52.png


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

Структуры с размещением в стеке

Как и массивы с выделенным стеком структуры с размещением в стеке одинаково трудно распознать, основываясь только на макете стека. Модификация предыдущей программы для использования структуры с выделением стека, объявленной в main, приводит к следующему дизассемблированию:

Screenshot_53.png


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

Структуры с распределением в куче

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

Изменение нашего примера структуры еще раз, чтобы использовать структуру, выделенную в куче, приводит к следующему дизассемблированию. Подобно примеру массива с выделенной кучей на странице 134, мы объявляем указатель в main и назначаем ему адрес блока памяти, достаточно большого для хранения нашей структуры:

Screenshot_54.png


В этом примере, в отличие от примеров глобальной структуры и структуры с размещением в стеке, мы можем различить точный размер и расположение структуры. Размер структуры может быть определен как 24 байта на основе объема памяти, запрошенной от malloc (6). Структура содержит следующие поля на указанных смещениях:

- 4-байтовое (двойное слово) поле со смещением 0 (1)
- 2-байтовое (слово) поле со смещением 4 (2)
- 1-байтовое поле со смещением 6 (3)
- 4-байтовое (двойное слово) поле со смещением 8 (4)
- 8-байтовое (qword) поле со смещением 16 (10h) (5)

Основываясь на использовании инструкций с плавающей запятой, мы можем сделать вывод, что поле qword на самом деле является двойным. Та же самая программа, скомпилированная для упаковки структур с 1-байтовым выравниванием, приводит к следующему дизассемблированию:

Screenshot_55.png


Единственные изменения в программе - это меньший размер структуры (теперь 19 байтов) и скорректированные смещения для учета перестройки каждого поля структуры.

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

Массивы структур

Некоторые программисты сказали бы, что прелесть составных структур данных в том, что они позволяют создавать произвольно сложные структуры, встраивая меньшие структуры в большие. Среди других возможностей эта возможность позволяет создавать массивы структур, структуры внутри структур и структуры, которые содержат массивы в качестве членов. Предыдущие обсуждения массивов и структур также применимы к таким вложенным типам, как эти. В качестве примера рассмотрим массив структур, подобный следующей простой программе, в которой heap_struct указывает на массив из пяти элементов ch8_struct:

Screenshot_56.png


Операции, необходимые для доступа к полю field1 в (1), включают умножение значения индекса на размер элемента массива, в данном случае размер структуры, а затем добавление смещения к желаемому полю. Соответствующий дизассемблированный код показан здесь:

Screenshot_57.png


Дизассемблированный код показывает, что из кучи запрошено 120 байтов (2). Индекс массива умножается на 24 в (3) перед добавлением к начальному адресу массива в (4). Никакого дополнительного смещения не требуется для генерации окончательного адреса для ссылки на (4). Из этих фактов мы можем вывести размер элемента массива (24), количество элементов в массиве (120/24 = 5) и тот факт, что есть 4-байтовое (двойное слово) поле со смещением 0 внутри каждого элемент массива. Этот короткий список не предлагает достаточно информации, чтобы сделать какие-либо выводы о том, как оставшиеся 20 байтов в каждой структуре распределяются между дополнительными полями.

Создание структур IDA

В предыдущей главе мы увидели, как возможности IDA по агрегации массивов позволяют упростить листинги дизассемблирования за счет сворачивания длинных списков объявлений данных в одну строку дизассемблирования. В следующих нескольких разделах мы рассмотрим возможности IDA для улучшения читаемости кода, который управляет структурами. Наша цель - отойти от ссылок на структуры, таких как [edx + 10h], и перейти к чему-то более читабельному, например [edx + ch8_struct.field5].

Всякий раз, когда вы обнаруживаете, что программа манипулирует структурой данных, вам нужно решить, хотите ли вы включить имена полей структуры в свою дизассемблированную версию или можете ли вы понять все числовые смещения, разбросанные по всему листингу. В некоторых случаях IDA может распознать использование структуры, определенной как часть стандартной библиотеки C или Windows API. В таких случаях IDA может знать точную компоновку структуры и иметь возможность преобразовывать числовые смещения в более символические имена полей. Это идеальный случай, поскольку у вас остается намного меньше работы. Мы вернемся к этому сценарию, когда немного больше поймем, как IDA работает с определениями структур в целом.

Создание новой структуры (или объединения)

Когда кажется, что программа использует структуру, для которой IDA не знает макета, IDA предлагает средства для определения состава структуры и включения вновь определенной структуры в дизассемблированный код. Создание структуры в IDA происходит в окне Структуры (см. Рис. 8-2). Никакая структура не может быть включена в дизассемблированный код, пока она не будет впервые указана в окне “Структуры”. Любая структура, известная IDA и признанная используемой программой, будет автоматически указана в окне Структуры.

Screenshot_58.png


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

Первые четыре строки текста в окне "Структуры" служат постоянным напоминанием об операциях, которые возможны в этом окне. Основные операции, которые нас интересуют, включают добавление, удаление и редактирование структур. Добавление структуры инициируется с помощью клавиши INSERT, которая открывает диалоговое окно Create Structure / Union, показанное на рисунке 8-3.

Screenshot_59.png



Чтобы создать новую структуру, вы должны сначала указать имя в поле “Имя структуры”. Первые два флажка определяют, где и будет ли новая структура отображаться в окне “Структуры”. Третий флажок, “Создать объединение” ,указывает, определяете ли вы структуру или объединение в стиле C. Для структур размер вычисляется как сумма размеров каждого поля компонента, а для объединений размер вычисляется как размер самого большого поля компонента. Кнопка “Добавить стандартную структуру” используется для доступа к списку всех типов данных структуры, о которых в настоящее время известно IDA. Поведение этой кнопки обсуждается в разделе “Использование стандартных структур” на странице 151. После того как вы укажете имя структуры и нажмете OK, в окне Структуры будет создано пустое определение структуры, как показано на рисунке 8-4.

Screenshot_60.png



Это определение структуры необходимо отредактировать, чтобы завершить определение макета структуры.

Редактирование элементов структуры

Чтобы добавить поля в вашу новую структуру, вы должны использовать команды создания полей D, A и клавишу звездочки (*) на цифровой клавиатуре. Изначально полезна только команда D, и, к сожалению, ее поведение сильно зависит от положения курсора. По этой причине для добавления полей в структуру рекомендуются следующие шаги.

1. Чтобы добавить новое поле в структуру, поместите курсор на последнюю строку определения структуры (содержащую концы) и нажмите D. Это приводит к добавлению нового поля в конец структуры. Размер нового поля будет установлен в соответствии с первым размером, выбранным на карусели данных (Глава 7). Первоначально имя поля будет field_N, где N - числовое смещение от начала структуры до начала нового поля (например, field_0).

2. Если вам нужно изменить размер поля, вы можете сделать это, сначала убедившись, что курсор находится на новом имени поля, а затем выбрав правильный размер данных для поля, повторно нажимая D, чтобы циклически перемещаться по типы данных в карусели данных. В качестве альтернативы вы можете использовать Параметры-> Настройка типов данных, чтобы указать размер, который недоступен в карусели данных. Если поле является массивом, щелкните имя правой кнопкой мыши и выберите “Массив”, чтобы открыть диалоговое окно спецификации массива (глава 7).

3. Чтобы изменить имя поля структуры, щелкните имя поля и используйте горячую клавишу N или щелкните имя правой кнопкой мыши и выберите “Переименовать”; затем укажите новое имя для поля.

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

- Смещение байта к полю отображается в виде восьмизначного шестнадцатеричного значения в левой части окна Структуры.
- Каждый раз, когда вы добавляете или удаляете поле структуры или изменяете размер существующего поля, новый размер структуры будет отражаться в первой строке определения структуры.
- Вы можете добавлять комментарии в поле структуры так же, как вы можете добавлять комментарии к любой строке дизассемблированного кода. Щелкните правой кнопкой мыши (или используйте горячую клавишу) поле, к которому вы хотите добавить комментарий, и выберите один из доступных вариантов комментария.
- В отличие от инструкций в верхней части окна Структуры, клавиша U удаляет поле структуры, только если это последнее поле в структуре. Для всех других полей нажатие U просто отменяет определение поля, которое удаляет имя, но не удаляет байты, выделенные полю.
- Вы несете ответственность за правильное выравнивание всех полей в определении структуры. IDA не делает различий между упакованными и неупакованными структурами. Если вам требуются байты заполнения для правильного выравнивания полей, вы несете ответственность за их добавление. Байты заполнения лучше всего добавлять как фиктивные поля надлежащего размера, которые вы можете или не можете удалить после добавления дополнительных полей.
- Байты, выделенные в середине структуры, могут быть удалены, только если сначала отменить определение связанного поля, а затем выбрать Edit-> Shrink Struct Type, чтобы удалить неопределенные байты.
- Байты можно вставить в середину структуры, выбрав поле, которое будет следовать за новыми байтами, а затем с помощью Edit->Expand Struct Type вставить указанное количество байтов перед выбранным полем.
- Если вы знаете размер конструкции, но не знаете ее макет, вам нужно создать два поля. Первое поле должно быть массивом размером 1 байт. Второе поле должно быть 1-байтовым. После того как вы создали второе поле, отмените определение первого поля (массива). Размер структуры будет сохранен, и вы можете легко вернуться позже, чтобы определить поля и их размеры, когда узнаете больше о макете структуры.

Путем повторения этих шагов (добавление поля, установка размера поля, добавление заполнения и т. Д.) Вы можете создать IDA-представление ch8_struct (распакованная версия), как показано на рисунке 8-5.

Screenshot_61.png


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

Если вы когда-нибудь почувствуете, что определение структуры занимает слишком много места в вашем окне Structures, вы можете свернуть определение в однострочную сводку, выбрав любое поле в структуре и нажав клавишу “минус” (-) на цифровой клавиатуре. Это полезно, когда структура полностью определена и не требует дальнейшего редактирования. Свернутая версия ch8_struct показана на рисунке 8-6.

Большинство структур, о которых уже известно IDA, будут отображаться однострочным способом, поскольку не ожидается, что их нужно будет редактировать. Свернутый дисплей служит напоминанием о том, что вы можете использовать клавишу “плюс” (+) на цифровой клавиатуре, чтобы развернуть определение. В качестве альтернативы, двойной щелчок по имени структуры также расширит определение.

Screenshot_62.png


Фреймы стека как специализированные конструкции

Вы можете заметить, что определения структуры несколько похожи на подробные представления фреймов стека, связанные с функциями. Это не случайно, поскольку внутри IDA идентично обоим. Оба представляют собой непрерывные блоки байтов, которые можно разделить на именованные поля компонентов, каждое из которых связано с числовым смещением в структуре. Незначительное различие состоит в том, что кадры стека используют как положительные, так и отрицательные смещения поля с центром на указателе кадра или адресе возврата, в то время как структуры используют положительные смещения от начала структуры.

Использование шаблонов структуры

Есть два способа использовать определения структуры в ваших дизассемблерах. Во-первых, вы можете переформатировать ссылки в памяти, чтобы сделать их более читаемыми, преобразовав смещения числовой структуры, такие как [ebx + 8], в символьные ссылки, такие как [ebx + ch8_struct.field4]. Последняя форма предоставляет гораздо больше информации о том, на что делается ссылка. Поскольку в IDA используется иерархическая нотация, ясно, к какому типу структуры и к какому полю внутри этой структуры осуществляется доступ. Этот метод применения шаблонов структур чаще всего используется, когда на структуру ссылаются через указатель. Второй способ использования шаблонов структур - предоставить дополнительные типы данных, которые можно применить к стеку и глобальным переменным.

Чтобы понять, как определения структуры могут применяться к операндам инструкций, полезно рассматривать каждое определение как нечто похожее на набор перечисляемых констант. Например, определение ch8_struct на рисунке 8-5 может быть выражено на псевдо-C следующим образом:

Screenshot_63.png


Учитывая такое определение, IDA позволяет вам преобразовать любое постоянное значение, используемое в операнде, в эквивалентное символьное представление. На рис. 8-7 показана именно такая операция. Ссылка на память [ecx + 10h] может представлять доступ к полю 5 в ch8_struct.

Screenshot_64.png


Параметр “Смещение структуры”, доступный в этом случае при щелчке правой кнопкой мыши на 10h, предлагает три альтернативы форматирования операнда инструкции. Альтернативы извлекаются из набора структур, содержащих поле со смещением 16.

В качестве альтернативы форматированию отдельных ссылок на память стек и глобальные переменные могут быть отформатированы как целые структуры. Чтобы отформатировать переменную стека как структуру, откройте подробное представление кадра стека, дважды щелкнув переменную, которую нужно отформатировать как структуру, и затем используйте Edit-> Struct Var (ALT-Q), чтобы отобразить список известных структур, подобных этой показано на рисунке 8-8.

Screenshot_65.png


Выбор одной из доступных структур объединяет соответствующее количество байтов в стеке в соответствующий тип структуры и переформатирует все связанные ссылки на память как ссылки на структуры. Следующий код представляет собой отрывок из примера структуры с выделением стека, который мы рассмотрели ранее:

Screenshot_66.png


Напомним, мы пришли к выводу, что var_18 на самом деле является первым полем в 24-байтовой структуре. Подробный кадр стека для этой конкретной интерпретации показан на рисунке 8-9.

Screenshot_67.png


Выбор var_18 и форматирование ее как ch8_struct (Edit-> Struct Var) сворачивает 24 байта (размер ch8_struct), начиная с var_18, в единую переменную, в результате чего отображается переформатированный стек, показанный на рисунке 8-10. В этом случае применение шаблона структуры к var_18 сгенерирует предупреждающее сообщение, указывающее, что некоторые переменные будут уничтожены в процессе преобразования var_18 в структуру. Исходя из нашего более раннего анализа, этого следовало ожидать, поэтому мы просто подтверждаем предупреждение, чтобы завершить операцию.

Screenshot_68.png


После переформатирования IDA понимает, что любая ссылка на память в 24-байтовом блоке, выделенном для var_18, должна ссылаться на поле в структуре. Когда IDA встречает такую ссылку, она прилагает все усилия, чтобы разрешить ссылку на память в одно из определенных полей в переменной структуры. В этом случае дизассемблированный код автоматически переформатируется для включения компоновки структуры, как показано здесь:

Screenshot_69.png


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

Процедура форматирования глобальных переменных как структур почти идентична той, что используется для переменных стека. Для этого выберите переменную или адрес, который отмечает начало структуры, и используйте Edit-> Struct Var (ALT-Q), чтобы выбрать соответствующий тип структуры. В качестве альтернативы только для неопределенных глобальных данных (не для данных стека) вы можете использовать контекстное меню IDA и выбрать опцию структуры для просмотра и выбрать доступный шаблон структуры для применения по выбранному адресу.

Импорт новых структур

Поработав какое-то время с функциями создания и редактирования структуры IDA, вы, возможно, захотите найти более простой способ делать что-либо. К счастью, IDA предлагает несколько сокращений в отношении новых структур. IDA способна анализировать отдельные объявления данных C (не C++), а также целые файлы заголовков C и автоматически строить представления структур IDA для любых структур, определенных в этих объявлениях или файлах заголовков. Если у вас есть исходный код или, по крайней мере, файлы заголовков для двоичного файла, который вы реверсите, вы можете сэкономить много времени, если IDA извлечет связанные структуры непосредственно из исходного кода.

Парсинг объявлений структуры C

Окно подвида локальных типов доступно с помощью команды View-> OpenSubviews-> Local Types. В окне “Локальные типы” отображается список всех типов, которые были проанализированы в текущей базе данных. Для новых баз данных окно “Локальные типы” изначально пусто, но оно предлагает возможность синтаксического анализа новых типов с помощью клавиши вставки или параметра “Вставить” в контекстном меню. Получающееся диалоговое окно ввода типа показано на рисунке 8-11.

Screenshot_70.png


Ошибки, обнаруженные при синтаксическом анализе нового типа, отображаются в окне вывода IDA. Если объявление типа успешно проанализировано, тип и связанное с ним объявление отображаются в окне “Локальные типы”, как показано на рисунке 8-12.

Screenshot_71.png


Обратите внимание, что парсер IDA использует выравнивание элементов структуры по умолчанию в 4 байта. Если ваша структура требует альтернативного выравнивания, вы можете включить его, и IDA распознает директиву pragma pack, чтобы указать желаемое выравнивание членов.

Типы данных, добавленные в окна локальных типов, не сразу доступны через окно структур. Есть два метода добавления объявлений локального типа в окно Structures. Самый простой способ - щелкнуть правой кнопкой мыши нужный локальный тип и выбрать “Синхронизировать с idb”. Либо по мере добавления каждого нового типа в список стандартных структур; новый тип можно импортировать в окно “Структуры”, как описано в разделе “Использование стандартных структур” на стр. 151.

Разбор файлов заголовков C

Чтобы проанализировать файл заголовка, используйте File-> Load File-> Parse C Header File, чтобы выбрать заголовок, который вы хотите проанализировать. Если все идет хорошо, IDA возвращает сообщение: Компиляция выполнена успешно. Если парсер обнаружит какие-либо проблемы, вы получите уведомление об ошибках. Все связанные сообщения об ошибках отображаются в окне вывода IDA.

IDA добавляет все структуры, которые были успешно проанализированы, как в список локальных типов, так и в список стандартных структур (а точнее, в конец списка), доступных в текущей базе данных. Когда новая структура имеет то же имя, что и существующая структура, определение существующей структуры перезаписывается новым макетом структуры. Ни одна из новых структур не появится в окне “Структуры”, пока вы не выберете их явное добавление, как описано выше для локальных типов или “Использование стандартных структур” на стр. 151. При синтаксическом анализе файлов заголовков C полезно иметь в виду следующие моменты:

- Встроенный синтаксический анализатор не обязательно использует то же выравнивание элементов структуры по умолчанию, что и ваш компилятор, хотя он соблюдает прагму pack. По умолчанию синтаксический анализатор создает структуры, выровненные по 4 байта.

- Парсер понимает директиву включения препроцессора C. Чтобы разрешить директивы include, синтаксический анализатор выполняет поиск в каталоге, содержащем анализируемый файл, а также во всех каталогах, перечисленных в качестве подключаемых каталогов в диалоговом окне Options->Compiler

- Парсер понимает только стандартные типы данных C. Однако синтаксический анализатор также понимает директиву определения препроцессора, а также оператор C typedef. Таким образом, такие типы, как uint32_t, будут правильно проанализированы, если синтаксический анализатор обнаружил соответствующий typedef до их использования.

- Если у вас нет исходного кода, вам может быть проще быстро определить макет структуры в нотации C с помощью текстового редактора и проанализировать полученный файл заголовка или вставить объявление как новый локальный тип вместо использования громоздких IDA ручные инструменты определения структуры.

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

В общем, чтобы максимизировать ваши шансы на успешный синтаксический анализ файла заголовка, вы захотите максимально упростить определения вашей структуры за счет использования стандартных типов данных C и минимизации использования включаемых файлов. Помните, что самое важное при создании структур в IDA - убедиться, что макет правильный. Правильный макет гораздо больше зависит от правильного размера каждого поля и правильного выравнивания структуры, чем от получения точного типа каждого поля в самый раз. Другими словами, если вам нужно заменить все вхождения uint32_t на int, чтобы получить файл для правильного анализа, вы должны пойти и сделать это.

Использование стандартных структур

Как упоминалось ранее, IDA распознает огромное количество структур данных, связанных с различными библиотеками и функциями API. При первоначальном создании базы данных IDA пытается определить компилятор и платформу, связанные с двоичным файлом, и загружает шаблоны структуры, полученные из связанных файлов заголовков библиотеки. Когда IDA сталкивается с фактическими манипуляциями со структурой при разборке, она добавляет соответствующие определения структуры в окно Structures. Таким образом, окно Структуры представляет собой подмножество известных структур, которые применяются к текущему двоичному файлу. Помимо создания собственных пользовательских структур, вы можете добавить дополнительные стандартные структуры в окно “Структуры”, используя список известных типов структур в IDA.

Процесс добавления новой структуры начинается с нажатия клавиши INSERT внутри окна Structures. На рис. 8-3 показано диалоговое окно “Создать структуру/объединение”, одним из компонентов которого является кнопка “Добавить стандартную структуру”. Нажатие на эту кнопку предоставляет доступ к главному списку структур, относящихся к текущему компилятору (как обнаружено на этапе анализа) и формату файла. Этот главный список структур также содержит любые структуры, которые были добавлены в базу данных в результате анализа файлов заголовков C. Диалоговое окно выбора структуры, показанное на рисунке 8-13, используется для выбора структуры для добавления в окно Структуры.

Screenshot_72.png


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

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

Проведя некоторое исследование формата двоичного файла PE, вы узнаете, что файл PE начинается со структуры заголовка MS-DOS с именем IMAGE_DOS_HEADER. Кроме того, данные, содержащиеся в IMAGE_DOS_HEADER, указывают на расположение структуры IMAGE_NT_HEADERS, которая детализирует структуру памяти двоичного файла PE. Выбрав загрузку заголовков PE, вы можете увидеть что-то похожее на следующую разборку неформатированных данных. Читатели, знакомые с файловой структурой PE, могут узнать знакомое магическое значение MS-DOS MZ как первые два байта в файле.

Screenshot_73.png


Поскольку этот файл отформатирован здесь, вам понадобится справочная документация по PE-файлу, которая поможет вам разобраться в каждом из байтов данных. Используя шаблоны структуры, IDA может форматировать эти байты как IMAGE_DOS_HEADER, делая данные гораздо более полезными. Первый шаг - добавить стандартный IMAGE_DOS_HEADER, как описано выше (вы можете добавить структуру IMAGE_NT_HEADERS, пока вы в ней). Второй шаг - преобразовать байты, начинающиеся с __ImageBase, в структуру IMAGE_DOS_HEADER, используя Edit->Struct Var (ALT-Q). В результате отображается переформатированное окно, показано здесь:

Screenshot_74.png

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

Screenshot_75.png


К сожалению, поля IMAGE_DOS_HEADER не имеют особо значимых имен, поэтому нам может потребоваться обратиться к ссылке на PE-файл, чтобы напомнить себе, что поле e_lfanew (1) указывает смещение файла, по которому может быть найдена структура IMAGE_NT_HEADERS. Применение всех предыдущих шагов для создания IMAGE_NT_HEADER по адресу 00400080 (0x80 байт в базе данных) дает хорошо отформатированную структуру, частично показанную здесь:

Screenshot_76.png


К счастью для нас, имена полей в этом случае несколько более значимы. Мы быстро видим, что файл состоит из пяти секций (1) и должен быть загружен в память по виртуальному адресу 00400000 (2). Расширенные структуры можно вернуть в их свернутое состояние с помощью клавиши «минус» (-) на клавиатуре.

Файлы IDA TIL

Вся информация о типах данных и прототипах функций в IDA хранится в файлах TIL. IDA поставляется с информацией о библиотеках типов для многих основных компиляторов и API, хранящейся в каталоге <IDADIR> /til. В окне View->Open subview->Type Libraries перечислены загруженные в данный момент файлы .til, и оно используется для загрузки дополнительных файлов .til, которые вы, возможно, захотите использовать. Библиотеки типов загружаются автоматически на основе атрибутов двоичного файла, обнаруженных на этапе анализа. В идеальных условиях большинству пользователей никогда не придется иметь дело с файлами .til напрямую.

Загрузка новых файлов TIL

В некоторых случаях IDA может не обнаружить, что для создания двоичного файла использовался конкретный компилятор, возможно, потому, что двоичный файл подвергся некоторой форме обфускации. В этом случае вы можете загрузить дополнительные файлы .til, нажав клавишу INSERT в окне “Типы” и выбрав нужные файлы .til. Когда загружается новый файл .til, все определения структур, содержащиеся в файле, добавляются в список стандартных структур, и информация о типе применяется к любым функциям в двоичном файле, которые имеют соответствующие прототипы во вновь загруженном файле .til. Другими словами, когда IDA получает новые знания о природе функции, она автоматически применяет эти новые знания.

Совместное использование файлов TIL

IDA также использует файлы .til для хранения любых пользовательских определений структур, которые вы создаете вручную в окне «Структуры» или путем анализа файлов заголовков C. Такие структуры хранятся в специальном файле .til, связанном с базой данных, в которой они были созданы. Этот файл имеет базовое имя базы данных и расширение .til. Для базы данных с именем some_file.idb связанный файл библиотеки типов будет some_file.til. В обычных условиях вы никогда не увидите этот файл, если только база данных не будет открыта в IDA. Напомним, что файл .idb на самом деле является архивным файлом (похожим на файл .tar), который используется для хранения компонентов базы данных, когда они не используются. При открытии базы данных файлы компонентов (одним из которых является файл .til) извлекаются как рабочие файлы для IDA.

Обсуждение того, как разделять файлы .til между базами данных, можно найти на http://www.hex-rays.com/forum/viewtopic.php?f=6&t=986. Там упоминаются две техники. Первый метод является несколько неофициальным и включает в себя копирование файла .til из открытой базы данных в каталог IDA til, из которого его можно открыть в любой другой базе данных через окно “Типы”. Более официальный способ извлечения информации о настраиваемом типе из базы данных - создание сценария IDC, который можно использовать для воссоздания настраиваемых структур в любой другой базе данных. Такой сценарий может быть создан с помощью команды File->Produce File->Dump Typeinfo to IDC File. Однако, в отличие от первого метода, этот метод выгружает только структуры, перечисленные в окне Структуры, которые могут не включать все структуры, проанализированные из файлов заголовков C (тогда как метод копирования файлов .til будет).

Hex-Rays также предоставляет автономный инструмент tilib для создания файлов .til вне IDA. Утилита доступна в виде файла .zip для зарегистрированных пользователей на странице загрузки Hex-Rays IDA. Установка так же проста, как извлечение содержимого файла .zip в <IDADIR>. Утилиту tilib можно использовать для вывода списка содержимого существующих файлов .til или создания новых файлов .til путем синтаксического анализа файлов заголовков C (не C++). Следующая команда выведет список содержимого библиотеки типов Visual Studio 6:

C:\Program Files\IdaPro>tilib -l til\pc\vc6win.til

Создание нового файла .til включает в себя имя файла заголовка для анализа и создаваемого файла .til. Параметры командной строки позволяют указать дополнительные каталоги включаемых файлов или, в качестве альтернативы, предварительно проанализированные файлы .til, чтобы разрешить любые зависимости, содержащиеся в файле заголовка. Следующая команда создает новый файл .til, содержащий объявление ch8_struct. Полученный файл .til необходимо переместить в <IDADIR>/til, прежде чем IDA сможет его использовать.

C:\Program Files\IdaPro>tilib -c -hch8_struct.h ch8.til

Утилита tilib содержит значительное количество дополнительных возможностей, некоторые из которых подробно описаны в файле README, включенном в дистрибутив tilib, а другие кратко описаны при запуске tilib без аргументов. До версии 6.1 tilib распространяется только как исполняемый файл Windows; однако генерируемые им файлы .til совместимы со всеми версиями IDA.

Пример по реверсингу C++

Классы C ++ являются объектно-ориентированными расширениями структур C, поэтому логично завершить обсуждение структур данных обзором возможностей скомпилированного кода C++. C++ достаточно сложен, поэтому подробное рассмотрение этой темы выходит за рамки этой книги. Здесь мы попытаемся осветить основные моменты и некоторые различия между Microsoft Visual C++ и GNU g++.

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

Указатель this

Этот указатель является указателем, доступным во всех нестатических функциях-членах C++. Всякий раз, когда вызывается такая функция, она инициализируется, чтобы указать на объект, используемый для вызова функции. Рассмотрим вызовы следующих функций:

//object1, object2, and *p_obj are all the same type.
object1.member_func();
object2.member_func();
p_obj->member_func();


В трех вызовах member_func он принимает значения &object1, &object2 и p_obj соответственно. Проще всего рассматривать это как скрытый первый параметр, передаваемый всем нестатическим функциям-членам. Как обсуждалось в главе 6, Microsoft Visual C++ использует соглашение о вызовах thiscall и передает его в регистр ECX. Компилятор GNU g++ обрабатывает это точно так, как если бы это был первый (крайний левый) параметр для нестатических функций-членов, и перед вызовом функции помещает адрес объекта, используемого для вызова функции, как самый верхний элемент в стеке.

С точки зрения реверсинижиниринга, перемещение адреса в регистр ECX непосредственно перед вызовом функции является вероятным индикатором двух вещей. Сначала файл был скомпилирован с использованием Visual C. Во-вторых, функция является функцией-членом. Когда один и тот же адрес передается двум или более функциям, мы можем сделать вывод, что все эти функции принадлежат одной и той же иерархии классов.

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

Для кода, скомпилированного с использованием g++, вызовы функций-членов выделяются несколько меньше. Однако любая функция, которая не принимает указатель в качестве первого аргумента, безусловно, может быть исключена как функция-член.

Виртуальные функции и таблицы

Виртуальные функции предоставляют средства для полиморфного поведения в программах на C++. Для каждого класса (или подкласса через наследование), содержащего виртуальные функции, компилятор создает таблицу, содержащую указатели на каждую виртуальную функцию в классе. Такие таблицы называются vtables. Кроме того, каждому классу, содержащему виртуальные функции, предоставляется дополнительный член данных, цель которого - указать на соответствующую vtable во время выполнения. Этот член обычно называется указателем vtable и выделяется как первый член данных в классе. Когда объект создается во время выполнения, его указатель vtable устанавливается так, чтобы указывать на соответствующую vtable. Когда этот объект вызывает виртуальную функцию, правильная функция выбирается путем поиска в vtable объекта. Таким образом, vtables - это базовый механизм, который облегчает разрешение вызовов виртуальных функций во время выполнения.

Несколько примеров могут помочь прояснить использование vtables. Рассмотрим следующие определения классов C++:

class BaseClass {
public:
BaseClass();
virtual void vfunc1() = 0;
virtual void vfunc2();
virtual void vfunc3();
virtual void vfunc4();
private:
int x;
int y;
};
158 Chapter 8
class SubClass : public BaseClass {
public:
SubClass();
virtual void vfunc1();
virtual void vfunc3();
virtual void vfunc5();
private:
int z;
};


В этом случае SubClass наследуется от BaseClass. BaseClass содержит четыре виртуальных функции, а SubClass - пять (четыре из BaseClass плюс новый vfunc5). Внутри BaseClass vfunc1 - это чистая виртуальная функция в силу использования = 0 в ее объявлении. Чистые виртуальные функции не имеют реализации в своем объявляющем классе и должны быть переопределены в подклассе, прежде чем класс будет считаться конкретным. Другими словами, не существует функции с именем BaseClass::vfunc1, и до тех пор, пока подкласс не предоставит реализацию, объекты не могут быть созданы. SubClass предоставляет такую реализацию, поэтому можно создавать объекты SubClass.

На первый взгляд кажется, что BaseClass содержит два члена данных, а подкласс - три члена данных. Напомним, однако, что любой класс, содержащий виртуальные функции, явно или потому, что они унаследованы, также содержит указатель на vtable. В результате созданные экземпляры объектов BaseClass фактически имеют три члена данных, а созданные экземпляры объектов SubClass имеют четыре члена данных. В каждом случае первым членом данных является указатель vtable. В SubClass указатель vtable фактически наследуется от BaseClass, а не вводится специально для SubClass. На рис. 8-14 показана упрощенная схема памяти, в которой динамически выделяется один объект SubClass. Во время создания объекта компилятор гарантирует, что указатель vtable нового объекта указывает на правильную vtable (в данном случае SubClass).

Screenshot_77.png


Обратите внимание, что vtable для SubClass содержит два указателя на функции, принадлежащие BaseClass (BaseClass::vfunc2 и BaseClass::vfunc4). Это связано с тем, что SubClass не отменяет ни одну из этих функций, а вместо этого наследует их от BaseClass. Также показана типичная обработка записей чисто виртуальных функций. Поскольку нет реализации для чистой виртуальной функции BaseClass:: vfunc1, нет адреса для хранения в слоте BaseClass vtable для vfunc1. В таких случаях компиляторы вставляют адрес функции обработки ошибок, часто называемой purecall, которая в теории никогда не должна вызываться, но которая обычно прерывает программу, если она каким-то образом вызывается.

Одним из следствий наличия указателя vtable является то, что вы должны учитывать его при манипулировании классом в IDA. Напомним, что классы C ++ являются расширением структур C. Поэтому вы можете использовать функции определения структуры IDA для определения макета классов C++. В случае классов, содержащих виртуальные функции, вы должны не забыть включить указатель vtable в качестве первого поля внутри класса. Указатели Vtable также необходимо учитывать в общем размере объекта. Это наиболее очевидно при наблюдении за динамическим распределением объекта с помощью оператора new, где значение размера, переданное в new, включает пространство, используемое всеми явно объявленными полями в классе (и любых суперклассах), а также любое пространство, необходимое для vtable указатель.

В следующем примере объект SubClass создается динамически, а его адрес сохраняется в указателе BaseClass. Затем указатель передается функции (call_vfunc), которая использует указатель для вызова vfunc3.

void call_vfunc(BaseClass *b) {
b->vfunc3();
}
int main() {
BaseClass *bc = new SubClass();
call_vfunc(bc);
}


Поскольку vfunc3 - виртуальная функция, компилятор должен гарантировать, что в этом случае вызывается SubClass::vfunc3, поскольку указатель указывает на объект SubClass. Следующая дизассемблированная версия call_vfunc демонстрирует, как разрешается вызов виртуальной функции:

Screenshot_78.png


Указатель vtable считывается из структуры в (1) и сохраняется в регистре EDX. Поскольку параметр b указывает на объект подкласса, это будет адрес vtable подкласса. В (2) vtable индексируется для чтения третьего указателя (в данном случае адреса SubClass::vfunc3) в регистр EAX. Наконец, в (3) вызывается виртуальная функция.

Обратите внимание, что операция индексации vtable в (2) очень похожа на операцию ссылки на структуру. Фактически, это ничем не отличается, и можно определить структуру для представления макета vtable класса, а затем использовать определенную структуру, чтобы сделать дизассемблирование более читаемым, как показано здесь:

Screenshot_79.png


Эта структура позволяет переформатировать операцию ссылки vtable следующим образом:

.text:004010AB mov eax, [edx+SubClass_vtable.vfunc3]

Жизненный цикл объекта


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

Для глобальных и статически выделенных объектов конструкторы вызываются во время запуска программы и до входа в основную функцию. Конструкторы для объектов, выделенных стеком, вызываются в момент, когда объект попадает в область видимости в функции, в которой он объявлен. Во многих случаях это происходит сразу после входа в функцию, в которой он объявлен. Однако, когда объект объявляется в операторе блока, его конструктор не вызывается до тех пор, пока этот блок не будет введен, если он вообще введен. Когда объект динамически размещается в куче программы, его создание представляет собой двухэтапный процесс. На первом этапе вызывается оператор new для выделения памяти объекта. На втором этапе вызывается конструктор для инициализации объекта. Основное различие между Visual C++ от Microsoft и g++ от GNU заключается в том, что Visual C++ гарантирует, что результат new не будет нулевым до вызова конструктора.

Когда конструктор выполняется, происходит следующая последовательность действий:

1. Если у класса есть суперкласс, вызывается конструктор суперкласса.

2. Если в классе есть какие-либо виртуальные функции, указатель vtable инициализируется, чтобы указывать на vtable класса. Обратите внимание, что это может перезаписать указатель vtable, который был инициализирован в суперклассе, что и является желаемым поведением.

3. Если в классе есть какие-либо члены данных, которые сами являются объектами, то для каждого такого члена данных вызывается конструктор.

4. Наконец, выполняется специфичный для кода конструктор. Это код, представляющий поведение C++ конструктора, указанного программистом.

Конструкторы не указывают возвращаемый тип; однако конструкторы, созданные Microsoft Visual C++, фактически возвращают это в регистре EAX. Тем не менее, это деталь реализации Visual C++, которая не позволяет программистам на C++ получить доступ к возвращаемому значению.

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

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

1. Если в классе есть какие-либо виртуальные функции, указатель vtable для объекта восстанавливается, чтобы указывать на vtable для связанного класса. Это необходимо в случае, если подкласс перезаписал указатель vtable как часть процесса своего создания.

2. Выполняется указанный программистом код деструктора.

3. Если в классе есть элементы данных, которые сами по себе являются объектами, выполняется деструктор для каждого такого члена.

4. Наконец, если у объекта есть суперкласс, вызывается деструктор суперкласса.

Понимая, когда вызываются конструкторы и деструкторы суперкласса, можно проследить иерархию наследования объекта через цепочку вызовов связанных с ним функций суперкласса. Последний момент, касающийся vtables, касается того, как на них ссылаются в программах. Есть только два обстоятельства, при которых на vtable класса имеется прямая ссылка, внутри конструктора(ов) класса и деструктора. Когда вы обнаруживаете vtable, вы можете использовать возможности IDA по перекрестным ссылкам на данные (см. Главу 9), чтобы быстро найти все конструкторы и деструкторы для связанного класса.

Mangling

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

Изменение имен - это деталь реализации компилятора программ C++ и, как таковая, не является частью спецификации языка C++. Неудивительно, что поставщики компиляторов разработали свои собственные, часто несовместимые соглашения по изменению имен. К счастью, IDA понимает соглашения об изменении имен, используемые Microsoft Visual C++ и GNU g++, а также некоторыми другими компиляторами. По умолчанию, когда в программе встречается искаженное имя, IDA отображает удаленный эквивалент в виде комментария в любом месте, где это имя появляется при дизассемблировании. Параметры разметки имен IDA выбираются с помощью диалогового окна, показанного на рисунке 8-15, доступ к которому можно получить, выбрав Options → Demangled Names.

Screenshot_80.png


Три основных параметра определяют, будут ли размеченные имена отображаться в виде комментариев, будут ли размечены сами имена. Отображение разобранных имен в виде комментариев приводит к отображению, подобному следующему:

Screenshot_81.png

Аналогичным образом, отображение разобранных имен в виде имен приводит к следующему:

Screenshot_82.png


где (1) представляет первую строку дизассемблированной функции, а (2) представляет вызов этой функции.

Флажок “Предположить имена GCC v3.x” используется для различения схемы искажения, используемой в g++ версии 2.9.x, и той, которая используется в g ++ версии 3.x и более поздних. В нормальных условиях IDA должна автоматически определять соглашения об именах, используемые в коде, скомпилированном с помощью g++. Кнопки “Настроить короткие имена” и “Настроить длинные имена” предлагают детальный контроль над форматированием размеченных имен с большим количеством опций, которые задокументированы в справочной системе IDA.

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

Идентификация типа среды выполнения

C++ предоставляет операторы, которые позволяют определять во время выполнения (typeid) и проверять (dynamic_cast) тип данных объекта. Чтобы облегчить эти операции, компиляторы C++ должны встраивать информацию о типе в двоичный файл программы и реализовывать процедуры, посредством которых тип полиморфного объекта может быть определен с уверенностью независимо от типа указателя, который может быть разыменован для доступа к объекту. К сожалению, как и в случае с изменением имени, идентификация типа среды выполнения (RTTI) - это деталь реализации компилятора, а не проблема языка, и не существует стандартных средств, с помощью которых компиляторы реализуют возможности RTTI.

Мы кратко рассмотрим сходства и различия между реализациями RTTI Microsoft Visual C++ и GNU g++. В частности, единственные детали, представленные здесь, касаются того, как найти информацию RTTI и, оттуда, как узнать имя класса, к которому эта информация относится. Читатели, желающие более подробно обсудить реализацию RTTI от Microsoft, должны ознакомиться со ссылками, перечисленными в конце этой главы. В частности, в справочниках подробно описано, как пройти по иерархии наследования класса, включая то, как отслеживать эту иерархию, когда используется множественное наследование.

Рассмотрим следующую простую программу, в которой используется полиморфизм:

class abstract_class {
public:
virtual int vfunc() = 0;
};
class concrete_class : public abstract_class {
public:
concrete_class();
int vfunc();
};
164 Chapter 8
void print_type(abstract_class *p) {
cout << typeid(*p).name() << endl;
}
int main() {
abstract_class *sc = new concrete_class();
print_type(sc);
}


Функция print_type должна правильно печатать тип объекта, на который указывает указатель p. В этом случае легко понять, что ”concrete_class” должен быть напечатан на основании того факта, что объект concrete_class создается в основной функции. Здесь мы отвечаем на следующий вопрос: как print_type, а точнее typeid, знает, на какой тип объекта указывает p?

Ответ на удивление прост. Поскольку каждый полиморфный объект содержит указатель на vtable, компиляторы используют этот факт, совмещая информацию о типе класса с классом vtable. В частности, компилятор помещает указатель непосредственно перед классом vtable. Этот указатель указывает на структуру, содержащую информацию, используемую для определения имени класса, владеющего vtable. В коде g++ этот указатель указывает на структуру type_info, которая содержит указатель на имя класса. В Visual C ++ указатель указывает на структуру Microsoft RTTICompleteObjectLocator, которая, в свою очередь, содержит указатель на структуру TypeDescriptor. Структура TypeDescriptor содержит массив символов, определяющий имя полиморфного класса.

Важно понимать, что информация RTTI требуется только в программах на C++, которые используют оператор typeid или dynamic_cast. Большинство компиляторов предоставляют опции для отключения генерации RTTI в двоичных файлах, которые этого не требуют; поэтому не стоит удивляться, если информация RTTI когда-либо будет отсутствовать.

Отношения наследования

Если вы углубитесь в некоторые реализации RTTI, вы обнаружите, что можно распутать отношения наследования, хотя для этого вы должны понимать конкретную реализацию RTTI в компиляторе. Кроме того, RTTI может отсутствовать, если программа не использует операторы typeid или dynamic_cast. Какие методы можно использовать при отсутствии информации RTTI для определения отношений наследования между классами C++?

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

Альтернативный способ определения отношений наследования включает анализ и сравнение vtables. Например, сравнивая vtables, показанные на рисунке 8-14, мы отмечаем, что vtable для SubClass содержит два таких же указателя, которые появляются в vtable для BaseClass. Мы можем легко сделать вывод, что BaseClass и SubClass должны быть каким-то образом связаны, но какой из них является базовым классом, а какой - подклассом? В таких случаях мы можем применять следующие рекомендации, по отдельности или в комбинации, чтобы попытаться понять характер их отношений.

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

- Когда vtable для класса X содержит больше записей, чем vtable для класса Y, класс X может быть подклассом класса Y.

- Когда vtable для класса X содержит записи, которые также находятся в vtable для класса Y, тогда должно существовать одно из следующих отношений: X - подкласс Y, Y - подкласс X, или X и Y - подклассы общего суперкласса Z.

- Когда vtable для класса X содержит записи, которые также находятся в vtable для класса Y, а vtable для класса X содержит по крайней мере одну запись purecall, которая также отсутствует в соответствующей записи vtable для класса Y, тогда класс Y является подкласс класса X.

Хотя приведенный выше список ни в коем случае не является исчерпывающим, мы можем использовать эти рекомендации, чтобы вывести связь между BaseClass и SubClass на рис. 8-14. В этом случае применяются все три последних правила, но последнее правило специально приводит нас к выводу, основываясь только на анализе vtable, что SubClass наследуется от BaseClass.

Ссылки на ресурсы по реверс инжинирингу C++

Для дальнейшего чтения по теме реверс инжинирингу , скомпилированного на C++, ознакомьтесь с этими отличными ссылками:

- Статья Игоря Скочинского “Реверсинг Microsoft Visual C ++, часть II: классы, методы и RTTI”, доступная по адресу http://www.openrce.org/articles/full_view/23.

- Статья Пола Винсента Сабаналя и Марка Винсента Ясона “Реверсинг C++” доступна по адресу http://www.blackhat.com/presentations/bh-dc-07/Sabanal_Yason/Paper/bh-dc-07-Sabanal_Yason-WP.pdf.

Хотя многие детали в каждой из этих статей относятся конкретно к программам, скомпилированным с использованием Microsoft Visual C++, многие из концепций в равной степени применимы к программам, скомпилированным с использованием других компиляторов C++.

Резюме

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

В следующей главе мы завершим обсуждение основных возможностей IDA обсуждением перекрестных ссылок и построения графиков, прежде чем перейти к более продвинутым аспектам использования IDA, которые отличают его от других инструментов реверс инижниринга.
 
Плиз, донэйт. Буду рад любой копеечки!!!
 
9. ПЕРЕКРЕСТНЫЕ ССЫЛКИ И ГРАФЫ

Некоторые из наиболее распространенных вопросов, которые задают при реверс инжиниринге двоичного файла, обычно такие "Откуда эта функция вызывается?" и "Какие функции имеют доступ к этим данным?". Эти и другие подобные вопросы направлены на каталогизацию ссылок на различные ресурсы в программе. Два примера показывают полезность таких вопросов.

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

В другом случае рассмотрите двоичный файл, который содержит большое количество строк ASCII, по крайней мере, одну из которых вы считаете подозрительной, например "Выполнение атаки отказа в обслуживании!" Указывает ли наличие этой строки на то, что двоичный файл действительно выполняет атаку отказа в обслуживании? Нет, это просто указывает на то, что двоичный файл содержит именно эту последовательность ASCII. Вы можете сделать вывод, что сообщение каким-то образом отображается непосредственно перед началом атаки; однако вам необходимо найти соответствующий код, чтобы подтвердить свои подозрения. Вот ответ на вопрос "Где указана эта строка?" поможет вам быстро отследить расположение программ, в которых используется строка. Оттуда, возможно, IDA может помочь вам найти любой фактический код атаки отказа в обслуживании.

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

Перекрестные ссылки

Мы начинаем обсуждение с того, что отметим, что перекрестные ссылки в IDA часто называют просто xrefs. В этом тексте мы будем использовать xrefs только там, где они используются для ссылки на содержимое пункта меню или диалогового окна IDA. Во всех остальных случаях мы будем придерживаться термина “перекрестная ссылка”.

В IDA есть две основные категории перекрестных ссылок: перекрестные ссылки кода и перекрестные ссылки данных. В каждой категории мы подробно рассмотрим несколько различных типов перекрестных ссылок. С каждой перекрестной ссылкой связано понятие направления. Все перекрестные ссылки делаются с одного адреса на другой. Адреса "от и до" могут быть либо кодом, либо адресом данных. Если вы знакомы с теорией графов, вы можете думать об адресах как об узлах ориентированного графа, а перекрестные ссылки - как о ребрах этого графа. На рис. 9-1 можно быстро освежить в памяти терминологию графов. В этом простом графе три узла {1} соединены двумя направленными ребрами {2}.

1.png


Обратите внимание, что узлы также могут называться вершинами. Направленные углы отображаются с помощью стрелок, указывающих допустимое направление движения через угол. На рисунке 9-1 можно перемещаться от верхнего узла к любому из нижних узлов, но невозможно перейти от любого из нижних узлов к верхнему узлу.

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

Прежде чем мы углубимся в детали перекрестных ссылок, полезно понять, как IDA отображает информацию о перекрестных ссылках в листинге дизассемблера. На рис. 9-2 показана строка заголовка дизассемблированной функции (sub_401000), содержащая перекрестную ссылку в виде обычного комментария (правая часть рисунка).

2.png


Текст CODE XREF указывает, что это перекрестная ссылка кода, а не перекрестная ссылка данных (DATA XREF). Далее следует адрес, в данном случае _main + 2A, указывая адрес, с которого происходит перекрестная ссылка. Обратите внимание, что это более описательная форма адреса, чем, например, .text:0040154A. Хотя обе формы представляют собой одно и то же место в программе, формат, используемый в перекрестной ссылке, предлагает дополнительную информацию о том, что перекрестная ссылка делается из функции с именем _main, в частности 0x2A (42) байтов в функцию _main. Стрелка вверх или вниз всегда будет следовать за адресом, указывая относительное направление к месту привязки. На рисунке 9-2 стрелка вниз указывает, что _main + 2A находится по более высокому адресу, чем sub_401000, и поэтому вам нужно будет прокрутить вниз, чтобы добраться до него. Точно так же стрелка вверх указывает на то, что ссылка находится на более низком адресе памяти, и для ее достижения требуется прокрутка вверх. Наконец, каждый комментарий перекрестной ссылки содержит односимвольный суффикс для идентификации типа перекрестной ссылки, которая делается. Каждый суффикс описан позже, когда мы подробно рассмотрим все типы перекрестных ссылок IDA.

Перекрестные ссылки кода

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

C++:
int read_it;            //integer variable read in main
int write_it;           //integer variable written 3 times in main
int ref_it;             //integer variable whose address is taken in main
void callflow() {}      //function called twice from main
int main() {
   int *p = &ref_it;    //results in an "offset" style data reference
   *p = read_it;        //results in a "read" style data reference
   write_it = *p;       //results in a "write" style data reference
   callflow();          //results in a "call" style code reference
   if (read_it == 3) {  //results in "jump" style code reference
      write_it = 2;     //results in a "write" style data reference
   }
   else {               //results in an "jump" style code reference
      write_it = 1;     //results in a "write" style data reference
   }
   callflow();          //results in an "call" style code reference
}

Программа содержит операции, которые будут использовать все функции перекрестных ссылок IDA, как указано в тексте комментария.

Обычный поток - это простейший тип потока, который представляет собой последовательный поток от одной инструкции к другой. Это поток выполнения по умолчанию для всех неразветвленных инструкций, таких как ADD. Для обычных потоков нет специальных индикаторов, кроме порядка, в котором указаны инструкции при дизассемблировании. Если инструкция A переходит к инструкции B обычным образом, то инструкция B немедленно следует за инструкцией A в листинге дизассемблирования. В следующем листинге каждая инструкция, кроме {1} и {2}, имеет связанный обычный поток для ее непосредственного преемника:

3.png


Инструкциям, используемым для вызова функций, таких как инструкции вызова x86 в {3}, назначается поток вызовов, указывающий на передачу управления целевой функции. В большинстве случаев для инструкций вызова также назначается обычный поток, поскольку большинство функций возвращаются в то место, которое следует за вызовом. Если IDA считает, что функция не возвращает (как было определено на этапе анализа), то вызовам этой функции не будет назначен обычный поток. Потоки вызовов отмечаются отображением перекрестных ссылок на целевую функцию (адрес назначения потока). Результирующий дизассембилнг функции callflow показана здесь:

4.png


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

Каждой инструкции безусловного и условного перехода назначается поток перехода. Условным переходам также назначаются обычные потоки для учета потока управления, когда переход не выполняется. Безусловные переходы не имеют связанного обычного потока, потому что в таких случаях всегда выполняется переход. Разрыв пунктирной линии в {5} - это устройство отображения, используемое для обозначения того, что между двумя соседними командами не существует обычного потока. Потоки перехода связаны с перекрестными ссылками в стиле перехода, отображаемыми в цели перехода, как показано на {6}. Как и в случае перекрестных ссылок в стиле вызова, перекрестные ссылки перехода отображают адрес места ссылки (источника перехода). Перекрестные ссылки перехода различаются использованием суффикса j.

Перекрестные ссылки на данные

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

5.png


Перекрестная ссылка для чтения используется для обозначения того, что осуществляется доступ к содержимому ячейки памяти. Перекрестные ссылки чтения могут исходить только из адреса инструкции, но могут относиться к любому месту в программе. Глобальная переменная read_it читается в местах, отмеченных {7} в листинге 9-1. Связанные комментарии перекрестных ссылок, показанные в этом листинге, указывают, какие именно места в main ссылаются на read_it и распознаются как перекрестные ссылки чтения на основе использования суффикса r. Первое чтение, выполняемое для read_it, - это 32-битное чтение в регистр ECX, что заставляет IDA форматировать read_it как dword (dd). В общем, IDA принимает как можно больше сигналов, чтобы определить размер и/или тип переменных в зависимости от того, как к ним обращаются и как они используются в качестве параметров функций.

На глобальную переменную write_it есть ссылки в местах, отмеченных {8} в листинге 9-1. Связанные перекрестные ссылки записи генерируются и отображаются в виде комментариев для переменной write_it, указывающих места в программе, которые изменяют содержимое переменной. В перекрестных ссылках используется суффикс w. И здесь IDA определила размер переменной на основе того факта, что 32-битный регистр EAX скопирован в write_it. Обратите внимание, что список перекрестных ссылок, отображаемый в write_it, заканчивается многоточием ({10} выше), указывая, что количество перекрестных ссылок на write_it превышает текущий предел отображения перекрестных ссылок. Это ограничение можно изменить с помощью параметра Число отображаемых внешних ссылок на вкладке Перекрестные ссылки в диалоговом окне Параметры-> Общие. Как и в случае с перекрестными ссылками для чтения, перекрестные ссылки для записи могут происходить только из программной инструкции, но могут ссылаться на любое место в программе. Вообще говоря, перекрестная ссылка записи, нацеленная на байт инструкции программы, указывает на самомодифицирующийся код, который обычно считается плохой формой и часто встречается в процедурах деобфускации, используемых во вредоносных программах.

Третий тип перекрестной ссылки данных, перекрестная ссылка смещения, указывает, что используется адрес местоположения (а не его содержимое). Адрес глобальной переменной ref_it берется в позиции {9} в листинге 9-1, в результате чего появляется комментарий перекрестной ссылки смещения в ref_it в предыдущем листинге (суффикс o). Смещенные перекрестные ссылки обычно являются результатом операций с указателями либо в коде, либо в данных. Например, операции доступа к массиву обычно реализуются путем добавления смещения к начальному адресу массива. В результате первый адрес в большинстве глобальных массивов часто можно распознать по наличию перекрестной ссылки смещения. По этой причине большинство строковых данных (строки являются массивами символов в C / C ++) являются целью перекрестных ссылок смещения.

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

6.png


Здесь вы видите, что адрес vtable используется в функции SubClass::SubClass (void), которая является конструктором класса. Строки заголовка для функции SubClass::vfunc3 (void), показанные здесь, показывают перекрестную ссылку смещения, которая связывает функцию с vtable.

7.png


Этот пример демонстрирует одну из характеристик виртуальных функций C++, которая становится совершенно очевидной в сочетании со смещенными перекрестными ссылками, а именно, что виртуальные функции C ++ никогда не вызываются напрямую и никогда не должны быть целью перекрестной ссылки вызова. Вместо этого на все виртуальные функции C++ должна ссылаться хотя бы одна запись vtable и всегда должна быть цель хотя бы одной перекрестной ссылки смещения. Помните, что переопределение виртуальной функции не обязательно. Следовательно, виртуальная функция может появляться более чем в одной vtable, как описано в главе 8. Перекрестные ссылки смещения со смещением с возвратом - это одна из техник, позволяющая легко находить vtables C++ в разделе данных программы.

Списки перекрестных ссылок

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

Есть два метода просмотра полного списка перекрестных ссылок на местоположение. Первый метод - открыть подвид перекрестных ссылок, связанный с конкретным адресом. Поместив курсор на адрес, который является целью одной или нескольких перекрестных ссылок, и выбрав View-> Open Subviews->Cross-References, вы можете открыть полный список перекрестных ссылок на заданное место, как показано на рисунке 9-3, где показан полный список перекрестных ссылок на переменную write_it.

8.png


Второй способ получить доступ к списку перекрестных ссылок - выделить имя, о котором вы хотите узнать, и выбрать “Перейти” -> “Перейти к внешней ссылке” (горячая клавиша CTRL-X), чтобы открыть диалоговое окно, в котором перечислены все места, которые ссылаются на выбранный символ. Получившееся диалоговое окно, показанное на рисунке 9-4, почти идентично по внешнему виду подвиду перекрестных ссылок, показанному на рисунке 9-3. В этом случае диалоговое окно было активировано с помощью горячей клавиши CTRL-X с выбранным первым экземпляром write_it (.text: 0040102B).

9.png


Основное различие между двумя окнами - поведенческое. Будучи модальным диалогом, окно на Рис, 9-4 имеет кнопки, с которыми можно взаимодействовать и закрывать диалог. Основная цель этого диалогового окна - выбрать точку привязки и перейти к ней. Двойной щелчок по одному из перечисленных мест закрывает диалоговое окно и перемещает окно дизассемблера в выбранное место. Второе различие между диалоговым окном и подвидом перекрестных ссылок заключается в том, что первое можно открыть с помощью горячей клавиши или контекстно-зависимого меню из любого экземпляра символа, в то время как второе можно открыть, только поместив курсор на адрес, который является целью перекрестной ссылки и выберите “Просмотр” -> “Открыть представления” -> “Перекрестные ссылки”. Другой способ заключается в том, что диалоговое окно можно открыть в источнике любой перекрестной ссылки, в то время как подвид можно открыть только в месте назначения перекрестной ссылки.

Примером полезности списков перекрестных ссылок может быть быстрое определение местоположения каждого места, из которого вызывается конкретная функция. Многие считают использование функции C strcpy * опасным. Используя перекрестные ссылки, найти каждый вызов strcpy так же просто, как найти любой один вызов strcpy, использовать горячую клавишу CTRL-X для вызова диалогового окна перекрестной ссылки и прокладывать себе путь через перекрестную ссылку каждого вызова. Если вы не хотите тратить время на то, чтобы найти strcpy, используемую где-то в двоичном файле, вы можете даже добавить комментарий с текстом strcpy в нем и активировать диалог перекрестной ссылки с помощью комментария.

Вызов функций

Список специализированных перекрестных ссылок, относящихся исключительно к вызовам функций, доступен при выборе View-> Open Subviews-> Function Calls. На рис. 9-5 показан результирующий диалог, в котором перечислены все места, вызывающие текущую функцию (как определено положением курсора в момент открытия окна) в верхней половине окна, а также все вызовы, сделанные текущей функцией в нижняя половина окна.

10.png


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

Графы IDA

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

Создание графов на основе сторонеего ПО


Возможности внешнего построения графов IDA используют сторонние графические приложения для отображения файлов графов, созданных IDA. Для версий Windows до 6.1 IDA поставляется с приложением для построения графов под названием wingraph32. Для IDA 6.0 версии IDA, отличные от Windows, по умолчанию настроены на использование программы просмотра графов dotty5. Начиная с IDA 6.1, все версии IDA поставляются с программой просмотра графов qwingraph, которая является кроссплатформенным портом Wingraph32 для Qt, и настроены на ее использование. Хотя точечные параметры конфигурации остаются видимыми для пользователей Linux, по умолчанию они закомментированы. Средство просмотра графов, используемое IDA, можно настроить, отредактировав переменную GRAPH_VISUALIZER в <IDADIR> /cfg/ida.cfg.

Каждый раз, когда запрашивается график внешнего стиля, источник графа создается и сохраняется во временном файле; затем запускается назначенная сторонняя программа просмотра графов для отображения графов. IDA поддерживает два языка спецификации графов: Graph Description Language7 (GDL) и язык DOT, используемый проектом graphviz. Язык спецификации графов, используемый IDA, можно настроить, отредактировав переменную GRAPH_FORMAT в <IDADIR> /cfg/ida.cfg. Допустимые значения для этой переменной - DOT и GDL. Вы должны убедиться, что указанный здесь язык совместим со средством просмотра, указанным в GRAPH_VISUALIZER.

Из подменю View-> Graphs можно создать пять типов графов.

Доступные графов внешнего режима включают следующее:

- Функциональная блок-схема
- Граф вызовов для всего двоичного файла
- Граф перекрестных ссылок на символ
- Граф перекрестных ссылок от символа
- Настраиваемый граф перекрестных ссылок

Для двух из них, блок-схемы и графа вызовов, IDA может создавать и сохранять файлы GDL (не DOT) для использования независимо от IDA. Эти параметры можно найти в подменю File-> Produce file. Сохранение файла спецификации для других типов графов может быть возможным, если настроенная вами программа просмотра графов позволяет вам сохранять текущий отображаемый граф. При работе с любым внешним графом существует ряд ограничений. Прежде всего, это то, что внешние графы не интерактивны. Манипулирование отображаемыми внешними графами ограничено возможностями выбранного вами внешнего средства просмотра графов (часто только масштабирование и панорамирование).

ОСНОВНЫЕ БЛОКИ

В компьютерной программе базовый блок - это группа из одной или нескольких инструкций с одним входом в начало блока и одним выходом в конце блока. В общем, кроме последней инструкции, каждая инструкция в базовом блоке передает управление ровно одной последующей инструкции в блоке. Точно так же, кроме первой инструкции, каждая инструкция в базовом блоке получает управление ровно от одной инструкции-предшественника в блоке. В целях определения базового блока тот факт, что инструкции вызова функции передают управление за пределы текущей функции, обычно игнорируется, если только не известно, что вызываемая функция не может нормально вернуться. Важная поведенческая характеристика базовых блоков состоит в том, что после выполнения первой инструкции в базовом блоке гарантируется выполнение оставшейся части блока до завершения. Это может существенно повлиять на инструментирование программы во время выполнения, поскольку больше нет необходимости устанавливать точку останова для каждой инструкции в программе или даже пошагово выполнять программу, чтобы записывать, какие инструкции были выполнены. Вместо этого точки останова могут быть установлены для первой инструкции каждого базового блока, и при достижении каждой точки останова каждая инструкция в соответствующем блоке может быть помечена как выполненная. Компонент Process Stalker фреймворка PaiMei Педрама Амини работает именно так.

Внешние блок-схемы

Когда курсор находится внутри функции, View->Graphs->Flow Chart (горячая клавиша F12) генерируется и отображается внешняя блок-схему. Отображение блок-схемы - это внешний граф, который больше всего напоминает интегрированное представление дизассемблерного кода на основе графов в IDA. Это не блок-схемы, которым вас, возможно, научили во время вводного курса программирования. Вместо этого эти графы лучше было бы назвать "графами потока управления", поскольку они группируют инструкции функции в базовые блоки и используют ребра для обозначения потока от одного блока к другому.

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

11.png


Графы блок-схемы получаются путем отслеживания обычных и переходных потоков для каждой инструкции в функции, начиная с точки входа в функцию.

Графы внешних вызовов

Граф вызовов функций полезен для быстрого понимания иерархии вызовов функций, выполняемых в программе. Графы вызовов генерируются путем создания узла графа для каждой функции и последующего соединения узлов функций на основе существования перекрестной ссылки вызова от одной функции к другой. Процесс создания графа вызовов для одной функции можно рассматривать как рекурсивный спуск по всем функциям, которые вызываются из исходной функции. Во многих случаях достаточно прекратить спуск по дереву вызовов после достижения библиотечной функции, поскольку легче узнать, как работает библиотечная функция, прочитав документацию, связанную с библиотекой, а не пытаясь реконструировать скомпилированную версию функция. Фактически, в случае динамически связанного двоичного файла невозможно перейти к библиотечным функциям, поскольку код для таких функций не присутствует в динамически связанном двоичном файле. Статически связанные двоичные файлы представляют собой другую проблему при создании графиков. Поскольку статически связанные двоичные файлы содержат весь код для библиотек, связанных с программой, графы вызовов связанных функций могут стать чрезвычайно большими.

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

C:
#include <stdio.h>
void depth_2_1() {
   printf("inside depth_2_1\n");
}
void depth_2_2() {
   fprintf(stderr, "inside depth_2_2\n");
}
void depth_1() {
   depth_2_1();
   depth_2_2();
   printf("inside depth_1\n");
}
int main() {
   depth_1();
}

После компиляции динамически связанного двоичного файла с помощью GNU gcc мы можем попросить IDA сгенерировать граф вызовов функций с помощью View->Graphs->Function Calls, который должен дать граф, подобный показанному на рисунке 9-7. В этом случае мы немного усекли левую часть графа, чтобы предложить немного больше деталей. Граф вызовов, связанный с основной функцией, можно увидеть в области, обведенной кружком на рисунке.

12.png


Читатели могут заметить, что компилятор заменил вызовы put и fwrite на printf и fprintf, соответственно, поскольку они более эффективны при печати статических строк. Обратите внимание, что IDA использует разные цвета для представления разных типов узлов на графах, хотя цвета никак не настраиваются.

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

Попытка построить граф статически связанной версии той же программы приводит к неприятному беспорядку, показанному на рис. 9-8.

Граф на рис. 9-8 демонстрирует общее поведение внешних графов, а именно то, что они всегда изначально масштабируются для отображения всего графа, что может привести к загромождения окнами. Для этого конкретного графа строка состояния в нижней части окна WinGraph32 указывает, что есть 946 узлов и 10 125 ребер, которые пересекаются друг с другом в 100 182 местах. Помимо демонстрации сложности статически связанных двоичных файлов, этот граф практически непригоден. Никакое масштабирование и панорамирование не упростят граф, и, кроме того, нет другого способа легко найти конкретную функцию, например основную, кроме как путем чтения метки на каждом узле. К тому времени, когда вы увеличите масштаб, достаточный для чтения меток, связанных с каждым узлом, только несколько десятков узлов поместятся на экране.

13.png


Графы внешних перекрестных ссылок

Для глобальных символов (функций или глобальных переменных) могут быть созданы два типа графов перекрестных ссылок: перекрестные ссылки на символ (Вид->Графики->Внешние ссылки на) и перекрестные ссылки из символа (Вид->Графики->Внешние ссылки из ). Для создания графа "Внешние ссылки" выполняется рекурсивное восхождение путем отслеживания всех перекрестных ссылок на выбранный символ до тех пор, пока не будет достигнут символ, на который не ссылаются никакие другие символы. При анализе двоичного файла вы можете использовать граф "Внешние ссылки для", чтобы ответить на вопрос: "Какая последовательность вызовов должна быть сделана для достижения этой функции?" На рис. 9-9 показано использование графа "Внешние ссылки" для отображения путей, по которым можно перейти к функции puts.

14.png


Точно так же графы Xrefs To могут помочь вам визуализировать все местоположения, которые ссылаются на глобальную переменную, и цепочку вызовов функций, необходимых для достижения этих местоположений. Графы перекрестных ссылок - единственные графы, способные включать информацию перекрестных ссылок данных.

Для создания графа “Xrefs From” выполняется рекурсивный спуск, следуя перекрестным ссылкам из выбранного символа. Если символ является именем функции, следуют только ссылки на вызовы из функции, поэтому ссылки на данные глобальных переменных не отображаются на графе. Если символ является инициализированной глобальной переменной-указателем (что означает, что он на что-то указывает), то выполняется соответствующая перекрестная ссылка смещения данных. Когда вы строите граф перекрестных ссылок из функции, эффективное поведение - это граф вызовов функции, основанный на выбранной функции, как показано на рисунке 9-10.

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

15.png


Настраиваемые графы перекрестных ссылок

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

View->Graphs->User Xrefs Chart открывает диалоговое окно настройки графа, показанное на рисунке 9-11. Каждый глобальный символ, встречающийся в указанном диапазоне адресов, отображается как узел в результирующем графе, который строится в соответствии с параметрами, указанными в диалоговом окне. В наиболее распространенном случае при создании перекрестных ссылок из одного символа начальный и конечный адреса идентичны. Если начальный и конечный адреса различаются, то результирующий граф создается для всех нелокальных символов, встречающихся в указанном диапазоне. В крайнем случае, когда начальный адрес - это самый низкий адрес в базе данных, а конечный адрес - это самый высокий адрес в базе данных, результирующий граф вырождается в граф вызовов функций для всего двоичного файла.

16.png


Параметры, выбранные на рисунке 9-11, представляют собой параметры по умолчанию для всех настраиваемых графов перекрестных ссылок. Ниже приводится описание назначения каждого набора опций:

Начальное направление

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

Параметры

Параметр “Рекурсивный” включает рекурсивный спуск (Xrefs From) или подъем (Xrefs To) от выбранных символов. Следовать только текущему направлению заставляет любую рекурсию происходить только в одном направлении. Другими словами, если эта опция выбрана, и обнаруживается, что узел B достижим из узла A, рекурсивный спуск в B добавляет дополнительные узлы, которые могут быть достигнуты только из узла B. Недавно обнаруженные узлы, которые ссылаются на узел B, не будут добавлены в граф. Если вы решите снять флажок “Следовать только текущему направлению”, тогда, когда выбраны оба начальных направления, каждый новый узел, добавленный к графику, будет повторяться как в направлении, так и в направлении от.

Глубина рекурсии

Эта опция устанавливает максимальную глубину рекурсии и полезна для ограничения размера генерируемых графиков. Значение -1 заставляет рекурсию проходить как можно глубже и генерировать максимально возможные графы.

Игнорировать

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

Параметры печати

Эти параметры управляют двумя аспектами форматирования графа. Печать комментариев приводит к включению любых комментариев к функциям в узел графа функции. Если выбран параметр “Печатать точки рекурсии” и рекурсия будет продолжаться за пределами указанного предела рекурсии, отображается узел, содержащий многоточие, чтобы указать, что дальнейшая рекурсия возможна.

На рис. 9-12 показан настраиваемый граф перекрестных ссылок, созданный для функции depth_1 в нашем примере программы с параметрами по умолчанию и глубиной рекурсии 1.

17.png


Графы перекрестных ссылок, создаваемые пользователем, - это самая мощная возможность построения графов внешнего режима, доступная в IDA. Внешние блок-схемы в значительной степени вытеснены интегрированным представлением IDA на основе графов, а остальные типы внешних графов представляют собой просто стандартные версии графов перекрестных ссылок, созданных пользователем.

Интегрированное графическое представление IDA

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

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

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

Основное изменение пользовательского интерфейса, связанное с режимом графа, касается работы с отдельными узлами графа. На рис. 9-13 показан простой узел графа и связанные с ним элементы управления в виде кнопок в строке заголовка.

18.png


Слева направо три кнопки в строке заголовка узла позволяют вам изменить цвет фона узла, назначить или изменить имя узла и получить доступ к списку перекрестных ссылок на узел. Раскрашивание узлов - это полезный способ напомнить себе, что вы уже проанализировали узел, или просто выделить его среди других, возможно, потому, что он содержит код, представляющий особый интерес. После того, как вы назначаете цвет узлу, этот цвет также используется в качестве цвета фона для соответствующих инструкций в текстовом режиме. Чтобы легко удалить любую окраску, щелкните правой кнопкой мыши строку заголовка узла и выберите "Установить цвет узла по умолчанию".

Средняя кнопка в строке заголовка на рис. 9-13 используется для присвоения имени адресу первой инструкции базового блока узла. Поскольку базовые блоки часто являются целью инструкций перехода, многим узлам уже может быть присвоено фиктивное имя в результате нацеливания на перекрестную ссылку перехода. Однако возможно, что базовый блок начнется без присвоения имени. Рассмотрим следующие строки кода:

19.png


У инструкции в (1) есть два потенциальных преемника: loc_401053 и инструкция в (2). Поскольку у него есть два преемника, (1) должен завершать базовый блок, что приводит к тому, что (2) становится первой инструкцией в новом базовом блоке, даже если он не нацелен явно на переход и, следовательно, не имеет присвоенного фиктивного имени.

Крайняя правая кнопка на рис. 9-13 используется для доступа к списку перекрестных ссылок, нацеленных на узел. Поскольку комментарии перекрестных ссылок не отображаются по умолчанию в режиме графа, это самый простой способ получить доступ и перейти к любому месту, которое ссылается на узел. В отличие от списков перекрестных ссылок, которые мы обсуждали ранее, сгенерированный список перекрестных ссылок узла также содержит запись для обычного потока в узел (обозначенного типом ^). Это необходимо, потому что в графическом представлении не всегда очевидно, какой узел является линейным предшественником данного узла. Если вы хотите просмотреть обычные комментарии перекрестных ссылок в графическом режиме, перейдите на вкладку "Перекрестные ссылки" в разделе "Параметры"->"Общие" и установите для параметра "Число отображаемых внешних ссылок"значение, отличное от нуля.

Узлы в графе могут быть сгруппированы сами по себе или с другими узлами, чтобы уменьшить беспорядок в графе. Чтобы сгруппировать несколько узлов, нажмите, удерживая CTRL, строку заголовка каждого узла, который нужно сгруппировать, а затем щелкните правой кнопкой мыши строку заголовка любого выбранного узла и выберите “Группировать узлы”. Вам будет предложено ввести текст (по умолчанию первая инструкция в группе), который будет отображаться в свернутом узле. На рис. 9-14 показан результат группировки узла на рис. 9-13 и изменения текста узла на свернутую демонстрацию узла.

20.png


Обратите внимание, что в строке заголовка теперь присутствуют две дополнительные кнопки. В порядке слева направо эти кнопки позволяют развернуть сгруппированный узел и отредактировать текст узла. Развертывание узла просто расширяет узлы в группе до их исходной формы; это не меняет того факта, что узел или узлы теперь принадлежат группе. Когда группа разворачивается, две только что упомянутые новые кнопки удаляются и заменяются одной кнопкой "Свернуть группу". Развернутую группу можно легко снова свернуть с помощью кнопки "Свернуть группу" или щелкнув правой кнопкой мыши строку заголовка любого узла в группе и выбрав "Скрыть группу". Чтобы полностью удалить группировку, примененную к одному или нескольким узлам, необходимо щелкнуть правой кнопкой мыши строку заголовка свернутого узла или одного из участвующих несвернутых узлов и выбрать "Разгруппировать узлы". Это действие имеет побочный эффект расширения группы, если она была свернута в то время.

Резюме

Графы - это мощный инструмент, который поможет вам в анализе любого двоичного файла. Если вы привыкли просматривать дизассемблерный код в чисто текстовом формате, может потребоваться некоторое время, чтобы приспособиться к использованию графического отображения. В IDA, как правило, важно понимать, что вся информация, которая была доступна в текстовом отображении, остается доступной в графическом отображении; однако он может быть отформатирован несколько иначе. Перекрестные ссылки, например, становятся ребрами, которые соединяют основные блоки в графическом отображении.

Выбор правильного графа для просмотра играет важную роль в оптимизации использования графов для анализа. Если вы хотите знать, как достигается конкретная функция, вас, вероятно, интересует вызов функции или граф перекрестных ссылок. Если вы хотите знать, как достигается конкретная инструкция, то вас, вероятно, больше интересует граф потока управления.

Некоторое разочарование, которое пользователи испытывали в прошлом по поводу графических возможностей IDA, напрямую связано с негибкостью приложения wingraph32 и связанных с ним графов. Частично эти разочарования были устранены за счет введения интегрированного режима дизассемблинга на основе графов. Однако IDA в первую очередь является дизассемблером, и генерация графов не является его основной целью. Читатели, интересующиеся специальными инструментами анализа на основе графов, могут захотеть изучить приложения, разработанные специально для этой цели, такие как BinNavi11, разработанные компанией Zynamics Халвара Флейка.
 
10. МНОГОЛИКАЯ IDA

В течение многих лет версия Windows GUI была суперзвездой в стабильной версии IDA. После выпуска версии IDA 6.0 это уже не так, поскольку пользователи Linux и OS X теперь могут пользоваться версиями IDA с графическим интерфейсом для своих платформ. Однако эта новая версия никоим образом не меняет того факта, что существует несколько альтернативных способов использования IDA. Первоначальная версия IDA была фактически консольным приложением MS-DOS, и консольная версия остается доступной на всех платформах по сей день. Благодаря встроенным возможностям удаленной отладки, IDA представляет собой мощный мультиплатформенный инструмент для анализа и отладки.

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

Консольный режим IDA

Сердцем всех консольных версий IDA является разработанная Borland библиотека консольного ввода-вывода под названием TVision, которая была перенесена на несколько платформ, включая Windows, Linux и Mac OS X, среди прочих. Hex-Rays делает исходный код своего текущего порта TVision доступным для платных клиентов IDA на своей странице загрузки IDA.

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

Общие особенности режима консоли

Как подразумевает термин консольный режим, все текстовые версии IDA выполняются в терминале или в какой-то оболочке. Эти консоли могут иметь разную степень поддержки изменения размера и использования мыши, что приводит к ограничениям, с которыми вам нужно будет научиться жить. Типы ограничений зависят от того, какую платформу и терминальную программу вы используете.

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

Окна IDA занимают пространство между верхней панелью меню и нижней панелью команд. Однако общее ограничение, независимо от того, какую программу терминала вы используете, заключается в том, что при ограничении экрана размером примерно 80 на 25 символов и без графики остается мало места для отображения. Поэтому консольные версии IDA по умолчанию обычно открывают только два окна отображения: окно дизассемблера и окно сообщений. Чтобы приблизиться к отображаемым окнам с вкладками, присутствующим в версии с графическим интерфейсом пользователя, IDA использует возможность перекрытия окон библиотеки TVision для текстовых окон и назначает клавишу F6 (вместо вкладок заголовков окон) для циклического просмотра доступных открытых окон. Каждое окно пронумеровано последовательно, а идентификатор окна указан в верхнем левом углу.

Если в консоли доступна поддержка мыши, можно изменить размер окна IDA, щелкнув и перетащив правый нижний угол окна до желаемого размера. Чтобы изменить положение окна, щелкните и перетащите верхнюю границу дисплея. При отсутствии поддержки мыши вы можете перемещать и изменять размер отдельных окон с помощью Window->Resize/Move (CTRL-F5), а затем использовать клавиши со стрелками для перемещения и клавиши со стрелками SHIFT для изменения размера активного окна. Если размер вашей терминальной программы можно изменить с помощью мыши, IDA распознает новый размер терминала и расширяет (или сжимает), чтобы заполнить его соответствующим образом.

Без графических возможностей интегрированный режим дизассемблирования на основе графов недоступен, а стрелки потока управления не отображаются в левом поле окна списка дизассемблирования. Однако все представления, доступные в версии с графическим интерфейсом пользователя, доступны в консольных версиях. Как и в версии с графическим интерфейсом пользователя, большинство вложенных представлений доступно через меню "Просмотр"->"Открыть вложенные представления". Одним из основных отличий доступных окон является то, что шестнадцатеричные дампы недоступны в качестве уникального подвида. Вместо этого вы можете переключить окно дизассемблера в шестнадцатеричный дамп и обратно, используя Параметры -> Дамп/Нормальный вид (CTRL-F4). Чтобы одновременно открывать и дизассемблированный, и шестнадцатеричный вид, вы должны открыть второе окно дизассемблирования (Вид->Открыть подвиды->Дизассемблирование) и переключить новый вид на шестнадцатеричный дамп. К сожалению, нет возможности синхронизировать новый шестнадцатеричный дамп с существующим представлением дизассемблирования.

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

Особенности консоли Windows

Терминал Windows cmd.exe (command.exe в семействе Windows 9x) не очень гибкий, но он довольно хорошо поддерживается консольной версией IDA. Консольная версия IDA для Windows называется idaw.exe, а версия с графическим интерфейсом пользователя - idag.exe. Соответствующие версии для 64-битных двоичных файлов (доступные в расширенной версии IDA) называются idaw64.exe и idag64.exe соответственно.

Чтобы поддержка мыши IDA работала в Windows, необходимо убедиться, что режим QuickEdit отключен для терминала, в котором вы запускаете IDA. Чтобы настроить режим QuickEdit как одно из свойств терминала, щелкните правой кнопкой мыши строку заголовка терминала и выберите "Свойства"; затем отмените выбор режима QuickEdit на вкладке "Параметры". Это необходимо сделать до запуска IDA, так как изменение не будет распознано во время работы IDA.

В отличие от терминалов Linux, работающих под X Windows, cmd.exe не может быть расширен с помощью мыши для увеличения окна. Только для Windows консольная версия IDA предлагает пункт меню Window->Set Video Mode для изменения размера cmd.exe до одного из шести фиксированных размеров терминала, максимум 255 на 100.

Хотя в окне дизассемблирования нет режима графа, доступны внешние параметры построения графов IDA. Выбор из меню View->Graphs заставит IDA запустить настроенную программу просмотра графов (например, qwingraph) для отображения результирующего графа. В версиях IDA для Windows можно открыть сразу несколько графов и продолжать использовать IDA, пока графы открыты.

Особенности консоли Linux

Консольная версия IDA для Linux называется idal (или idal64 для анализа 64-битных двоичных файлов). До версии IDA 6.0 консольные версии Linux и OS X были включены как стандартные компоненты вашего дистрибутива IDA. Таким образом, когда вы копируете эти версии консоли на свою платформу Linux или OS X, вы также должны скопировать файл ключа IDA (ida.key), чтобы ваша версия консоли работала правильно. Обратите внимание, что для этого требуется, чтобы вы установили IDA на компьютере с Windows хотя бы один раз, даже если вы никогда не собираетесь запускать версию для Windows. В системах в стиле Unix вы также можете скопировать файл ключа в $HOME/.idapro/ ida.key. Если вы не создадите его, IDA автоматически создаст каталог личных настроек IDA ($ HOME/.idapro) при первом запуске IDA.

Установка IDA 6.x намного проще. Поскольку IDA 6.x приобретается для конкретной платформы, процедура установки на вашей платформе включает установку версии с графическим интерфейсом пользователя, версии консоли и ключевого файла IDA в подходящие места.

Базовая навигация в версии для Linux аналогична навигации в консольной версии Windows; В этом разделе рассматриваются некоторые особенности Linux. Пристрастия пользователей к терминальным программам Linux столь же разнообразны, как и их вкусы к дистрибутивам Linux в целом. IDA включает файл с именем tvtuning.txt, который предлагает некоторые сведения о том, как настроить различные типы терминалов, включая удаленные клиенты терминалов Windows, такие как SecureCRT и PuTTY.

Одна из самых больших проблем, с которой вы столкнетесь при использовании терминальных программ Linux, это убедиться, что ваши последовательности горячих клавиш полностью передаются в IDA, а не захватываются самой терминальной программой. Например, будет ли ALT+F открывать меню "Файл" IDA или меню "Файл" консоли? Есть два варианта решения этой проблемы: найти программу терминала, последовательности горячих клавиш которой не перекрывают IDA (или которую можно настроить так, чтобы она не перекрывалась), или отредактировать файл конфигурации IDA для переназначения команд горячим клавишам, которые не используются вашим терминалом. Если вы решите переназначить горячие клавиши, вы можете обновить сопоставления горячих клавиш на каждом компьютере, на котором вы используете IDA, чтобы вам не приходилось запоминать, какое сопоставление действует в каждом месте. Вы также можете столкнуться с трудностями при взаимодействии с другими пользователями IDA, которые используют сопоставления по умолчанию.

Если вы решите использовать стандартный текстовый дисплей Linux, размеры вашей консоли IDA будут фиксированными, а поддержка вашей мыши будет зависеть от использования вами GPM (сервер мыши консоли Linux). Если вы не используете GPM для поддержки мыши, вам следует указать опцию noGPM для TVision при запуске IDA, как показано здесь:

# TVOPT=noGPM ./idal [file to disassemble]

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

Если вы используете X, возможно, вы используете консоль KDE, gnome-terminal Gnome, xterm или какой-либо другой вариант на терминале. Помимо xterm, большинство терминалов предлагают свои собственные меню и соответствующие горячие клавиши, которые могут или не могут перекрывать назначения горячих клавиш IDA. Следовательно, xterm - неплохой выбор для запуска IDA, хотя и не обязательно самый привлекательный с точки зрения внешнего вида. Консоль KDE - наша предпочтительная консоль Linux, поскольку она предлагает лучший внешний вид, наименьшее количество конфликтов горячих клавиш и максимальную плавность работы мыши.

Чтобы решить некоторые проблемы, связанные с использованием клавиатуры и мыши в различных консолях X Windows, Джереми Купер разработал собственный порт X11 для библиотек TVision. Использование этой модифицированной версии TVision позволяет запускать IDA в собственном X-окне, а не использовать всю консоль. Компиляция порта TVision позволяет отказаться от libtvision.so, разделяемой библиотеки TVision, используемой idal. После установки новой библиотеки вы можете получить сообщение об ошибке о том, что шрифт VGA не может быть загружен при попытке запустить IDA. Если это произойдет, вам нужно будет установить шрифт VGA и сообщить вашему X-серверу, где его найти. Подходящий шрифт VGA доступен по адресу http://gilesorr.com/bashprompt/xfonts/ (загрузите как vga, так и sabvga). Еще одна интересная особенность использования собственного порта X11 заключается в том, что вы можете перенаправить окно X11 на другую машину. Таким образом, вы можете запустить IDA в Linux, но перенаправить окно X11 (конечно, через ssh) на Mac.

Для удаленного доступа к вашей установке IDA на базе Linux с использованием поставляемых Hex-Rays библиотек TVision мы рекомендуем вам настроить программное обеспечение терминала на эмуляцию xterm (дополнительную информацию см. В tvtuning.txt и документации эмулятора терминала), а затем запустить IDA. согласно инструкции, содержащейся в tvtuning.txt. Например, вы должны указать TVOPT = xtrack, чтобы мышь могла работать с IDA при использовании SecureCRT в качестве эмулятора терминала.

Вы, конечно, можете экспортировать настройки TVOPT, избавившись от необходимости указывать их каждый раз при запуске IDA. Для полного обзора доступных опций TVision обратитесь к linux.cpp в исходном дистрибутиве TVision.

Внешние графические представления в Linux доступны из консольной версии только в том случае, если вы запускаете IDA в оконной среде и вы настроили переменную GRAPH_VISUALIZER в ida.cfg так, чтобы она указывала на подходящую программу отрисовки графиков. Версии IDA до 6.0 могут создавать графики только с помощью GDL. Вы можете установить программу просмотра GDL, такую как aiSee, и настроить IDA для запуска нового приложения, отредактировав основной файл конфигурации IDA, <IDADIR> /cfg/ida.cfg. Параметр конфигурации GRAPH_VISUALIZER определяет команду, которая будет использоваться для просмотра графиков GDL IDA (всех графиков устаревшего режима). Настройка по умолчанию выглядит примерно так:

GRAPH_VISUALIZER = "qwingraph.exe -remove -timelimit 10"

Опция remove просит qwingraph удалить входной файл, что полезно при отображении временных файлов. Параметр timelimit указывает количество секунд, которое нужно потратить на попытку создания красивого графа. Если граф не может быть аккуратно выложен за это время, qwingraph переключается на "быстрый и уродливый" алгоритм. Начиная с IDA 6.0, параметр GRAPH_VISUALIZER заключен в условный блок для предоставления отдельных настроек для платформ Windows и других платформ. Если вы редактируете ida.cfg на платформе, отличной от Windows, убедитесь, что вы редактируете правильную часть файла. Если вы установили программу просмотра GDL, такую как aiSee, вам необходимо отредактировать GRAPH_VISUALIZER, чтобы он указывал на выбранную вами программу просмотра. При обычной установке aiSee это может привести к следующему:

GRAPH_VISUALIZER = "/usr/local/bin/aisee"


Обратите внимание, что всегда лучше указывать полный путь к программе просмотра GDL, чтобы гарантировать, что она будет найдена, когда IDA попытается ее запустить. Наконец, поскольку qwingraph является программным обеспечением с открытым исходным кодом, пользователи старых версий IDA могут бесплатно загрузить исходный код qwingraph из Hex-Rays (см. Главу 9), собрать его и интегрировать qwingraph в свои установки IDA.

Особенности консоли OS X

Консольные версии IDA для OS X называются так же, как версии для Linux (idal и idal64). Как и в случае с консольными версиями Linux и Windows, версии OS X полагаются на библиотеку TVision для поддержки консольного ввода-вывода.

Тот факт, что клавиатура Mac имеет раскладку, отличную от клавиатуры ПК, создает несколько проблем при использовании версии IDA для Mac, в первую очередь потому, что клавиша OPTION/ALT на Mac не ведет себя как клавиша ALT на ПК в том, что касается меню приложений.

Очевидный выбор для попытки запустить IDA - это приложение Mac Terminal. При запуске IDA с использованием Терминала обязательно настройте ключ OPTION как ключ ALT для использования в IDA. Это позволяет получить доступ с клавиатуры к сочетаниям клавиш ALT, таким как все основные меню IDA (например, ALT+F для меню "Файл"). Если вы не выберете эту опцию, вам придется использовать клавишу ESC вместо ALT; таким образом, ESC+F вызывает меню File. Поскольку в IDA ESC имеет функцию возврата или закрытия окна, этот подход не рекомендуется. На рисунке 10-1 показано диалоговое окно Terminal Inspector, доступ к которому осуществляется через Terminal->Preferences, когда Терминал активен. Установите флажок Use option key as meta key, чтобы клавиша OPTION работала как клавиша ALT.

Одной из возможных альтернатив Терминалу является iTERM 6, который позволяет использовать ALT для клавиши OPTION, а также поддерживает мышь. Еще один терминал, который, похоже, нравится многим разработчикам, - это терминал gnome, который был перенесен на X11 в OS X. Поскольку для этого требуется установка XCODE и X11, мы не будем делать ничего, кроме упоминания о существовании порта. Для большинства пользователей достаточно использовать Терминал по умолчанию или iTERM.

Альтернативный способ запустить IDA в OS X - установить X11 (доступный на установочных дисках OS X в качестве дополнительного пакета) и модифицированную библиотеку TVision Джереми Купера (libtvision.dylib для OS X) для запуска IDA как собственного приложения X11. Вы можете добавить /usr/X11R6/bin в системный PATH (отредактировать PATH в /etc/ profile) для более легкого доступа к двоичным файлам, связанным с X11.

1.png


В этой конфигурации IDA может быть запущена из xterm, и она будет выполняться в собственном окне с полной функциональностью мыши. Однако проблема с клавишей OPTION/ALT останется, поскольку X11 рассматривает этот ключ как Mode_switch и не может передать ключ в IDA. К счастью, X11 позволяет переназначать ключи с помощью утилиты xmodmap. Одно из решений - создать (или отредактировать) файл с именем .Xmodmap в вашем домашнем каталоге (что-то вроде /Users/idabook/.Xmodmap), содержащий следующие команды:

clear Mod1
keycode 66 = Alt_L
keycode 69 = Alt_R
add Mod1 = Alt_L
add Mod1 = Alt_R


Сценарий запуска X11 по умолчанию (/ etc/X11/xinit/xinitrc) содержит команды для чтения .Xmodmap при каждом запуске X11. Если вы создали свой собственный файл .xinitrc, который переопределяет xinitrc по умолчанию, вы должны убедиться, что он содержит команду, подобную следующей; в противном случае ваш файл .Xmodmap не будет обработан.

xmodmap $HOME/.Xmodmap


Наконец, вам нужно изменить настройки по умолчанию для X11, чтобы система не переопределяла вашу измененную карту ключей. На рисунке 10-2 показано диалоговое окно настроек X11.

2.png


Чтобы система не переопределяла ваши раскладки клавиатуры, вы должны отменить выбор среднего параметра: Следовать системной раскладке клавиатуры. После внесения этого изменения перезапустите X11, и ваши измененные настройки клавиатуры должны вступить в силу, и клавиша ALT станет доступной для доступа к меню IDA. Вы можете убедиться, что X11 распознает клавишу ALT, используя xmodmap для печати текущего списка модификаторов клавиатуры, как показано ниже:

idabook:~ idabook$ xmodmap
xmodmap: up to 2 keys per modifier, (keycodes in parentheses):
shift Shift_L (0x40), Shift_R (0x44)
lock Caps_Lock (0x41)
control Control_L (0x43), Control_R (0x46)
⵼ mod1 Alt_L (0x42), Alt_R (0x45)
mod2 Meta_L (0x3f)
mod3
mod4
mod5


Если mod1 не перечисляет Alt_L и Alt_R, как показано в (2), то ваша карта ключей не была обновлена, и в этом случае вам следует повторно запустить команду xmodmap, указанную в (1) в предыдущем коде.

Использование пакетного режима IDA

Все версии IDA могут выполняться в пакетном режиме для облегчения задач автоматической обработки. Основная цель использования пакетного режима - запустить IDA, запустить определенный сценарий IDC и завершить его после завершения сценария. Доступны несколько параметров командной строки для управления обработкой, выполняемой во время выполнения в пакетном режиме.

Версии IDA с графическим интерфейсом пользователя не требуют консоли для выполнения, что упрощает их включение практически в любой сценарий автоматизации или программу-оболочку. При запуске в пакетном режиме версии IDA с графическим интерфейсом не отображают никаких графических компонентов. При запуске консольных версий Windows (idaw.exe и idaw64.exe) создается полное отображение консоли, которое автоматически закрывается по завершении пакетной обработки. Отображение консоли можно подавить, перенаправив вывод на нулевое устройство (NUL для cmd.exe, /dev/null в cygwin), как показано здесь:

C:\Program Files\Ida>idaw -B some_program.exe > NUL

Пакетный режим IDA управляется параметрами командной строки, перечисленными здесь:

- Параметр -A заставляет IDA работать в автономном режиме, что означает, что никакие диалоги, требующие взаимодействия с пользователем, отображаться не будут. (Если вы никогда не просматривали лицензионное соглашение IDA, диалоговое окно с лицензионным соглашением будет отображаться, несмотря на наличие этого переключателя.)

- Параметр -c заставляет IDA удалить любую существующую базу данных, связанную с файлом, указанным в командной строке, и создать совершенно новую базу данных.

-Опция -S используется для указания того, какой сценарий IDC IDA должен выполнять при запуске. Чтобы выполнить myscript.idc, используйте синтаксис -Smyscript.idc (без пробела между S и именем скрипта). IDA ищет указанный сценарий в каталоге <IDADIR>/idc. Если у вас правильно установлен IDAPython, вы также можете указать здесь скрипт python.

-Опция -B вызывает пакетный режим и эквивалентна предоставлению IDA с -A -c -Sanalysis.idc при выполнении. Сценарий analysis.idc, поставляемый с IDA, просто ожидает, пока IDA проанализирует файл, указанный в командной строке, перед сбросом списка (файл .asm) и закрытия IDA, чтобы сохранить и закрыть вновь созданную базу данных.

Параметр -S является ключом к пакетному режиму, так как IDA завершит работу только в том случае, если указанный сценарий заставит IDA завершить работу. Если сценарий не завершает работу IDA, то все параметры просто объединяются для автоматизации процесса запуска IDA. Создание сценариев с IDC обсуждается в главе 15.

Из-за ограничений библиотеки TVision, используемой версиями IDA для Linux и OS X, пакетное выполнение должно выполняться в консоли TTY. Это делает невозможными такие простые вещи, как перенаправление вывода и фоновая обработка. К счастью, последняя версия TVision распознает переменную среды TVHEADLESS, которая позволяет перенаправлять вывод консоли (stdout), как показано здесь:

# TVHEADLESS=1 ./idal –B input_file.exe > /dev/null

Полное отключение от консоли для выполнения в фоновом режиме требует дополнительного перенаправления как stdin, так и stderr.

Ильфак обсуждает пакетный режим в одном из своих сообщений в блоге: http: //hexblog.com/2007/03/on_batch_analysis.html. Среди прочего, он подробно описывает, как выйти за рамки вызова одного сценария, и обсуждает, как выполнить подключаемый модуль IDA из пакетного режима.

Резюме

Хотя версии IDA с графическим интерфейсом пользователя остаются наиболее полнофункциональными доступными версиями, альтернативы консольному режиму и возможности пакетной обработки предлагают пользователям IDA огромную гибкость в создании сложных аналитических решений, основанных на возможностях автоматического анализа IDA.

На этом этапе мы рассмотрели все основные возможности IDA, и пора перейти к более продвинутым функциям. В течение следующих нескольких глав мы рассмотрим некоторые из наиболее полезных параметров конфигурации IDA и представим некоторые дополнительные утилиты, предназначенные для улучшения возможностей анализа двоичных файлов в IDA.
 
ЧАСТЬ III.

РАСШИРЕННОЕ ИСПОЛЬЗОВАНИЕ IDA

11. КАСТОМИЗАЦИЯ IDA


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

Файлы конфигурации

Большая часть поведения IDA по умолчанию определяется настройками, содержащимися в различных файлах конфигурации. По большей части файлы конфигурации хранятся в каталоге <IDADIR>/cfg, за одним заметным исключением является файл конфигурации подключаемых модулей, который находится в <IDADIR>/plugins/plugins.cfg (plugins.cfg будет рассмотрен в Глава 17). Хотя вы можете заметить довольно много файлов в основном каталоге конфигурации, большинство файлов используются модулями процессора и применимы только при анализе определенных типов процессоров. Три основных файла конфигурации - ida.cfg, idagui.cfg и idatui.cfg. Параметры, которые применяются ко всем версиям IDA, обычно находятся в ida.cfg, а idagui.cfg и idatui.cfg содержат параметры, относящиеся к версиям GUI и версиям IDA для текстового режима, соответственно.

Главный файл конфигурации: ida.cfg

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

Общие интересующие параметры в ida.cfg включают параметры настройки памяти (VPAGESIZE), создание файлов резервных копий (CREATE_BACKUPS) и имя внешнего средства просмотра графов (GRAPH_VISUALIZER).

Иногда при работе с очень большими полями ввода IDA может сообщать, что недостаточно памяти для создания новой базы данных. В таких случаях для решения проблемы обычно достаточно увеличения VPAGESIZE и последующего открытия входного файла.

Большое количество параметров, управляющих форматом строк дизассемблирования, также содержится в ida.cfg, включая значения по умолчанию для многих параметров, доступных через Options->General. К ним относятся значения по умолчанию для количества отображаемых байтов кода операции (OPCODE_BYTES), насколько инструкции должны быть смещены (INDENTATION), должно ли смещение указателя стека будет отображаться с каждой инструкцией (SHOW_SP), и максимальное количество перекрестных ссылок, отображаемых в строке дизассемблирования (SHOW_XREFS). Дополнительные параметры управляют форматом линий дизассемблера в графическом режиме.

Глобальный параметр, определяющий максимальную длину имени для именованных местоположений программы (в отличие от переменных стека), содержится в ida.cfg и называется MAX_NAMES_LENGTH. Этот параметр по умолчанию составляет 15 символов и заставляет IDA генерировать предупреждающее сообщение каждый раз, когда вы вводите имя, длина которого превышает текущий предел. Длина по умолчанию остается небольшой, потому что некоторые ассемблеры не могут обрабатывать имена длиннее 15 символов. Если вы не планируете запускать сгенерированный IDA файл обратно через ассемблер, вы можете смело увеличивать лимит.

Список символов, разрешенных в назначаемых пользователем именах, регулируется параметрами NameChars. По умолчанию в этом списке разрешены буквенно-цифровые символы и четыре специальных символа _ $? @. Если IDA жалуется на символы, которые вы хотите использовать при присвоении новых имен местоположениям или переменным стека, вы можете добавить дополнительные символы в набор NameChars. Например, NameChars можно изменить, если вы хотите сделать символ точки (.) Допустимым для использования в именах IDA. Следует избегать использования в именах символов точки с запятой, двоеточия, запятой и пробела, поскольку они могут привести к путанице, поскольку эти символы обычно считаются разделителями для различных частей строки кода.

Последние два параметра, о которых стоит упомянуть, влияют на поведение IDA при синтаксическом анализе файлов заголовков C (см. Главу 8). Параметр C_HEADER_PATH указывает список каталогов, в которых IDA будет искать для разрешения зависимостей #include. По умолчанию отображается общий каталог, используемый Microsoft Visual Studio. Если вы используете другой компилятор или ваши файлы заголовков C находятся в нестандартном формате location, вам следует подумать о редактировании этого параметра. Параметр C_PREDEFINED_MACROS может использоваться для указания списка макросов препроцессора по умолчанию, который IDA будет включать независимо от того, обнаружила ли IDA их при анализе файла заголовка C. Эта опция предлагает ограниченное средство обхода для работы с макросами, которые могут быть определены в файлах заголовков, к которым у вас нет доступа.

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

Последним шагом в процессе обработке ida.cfg является поиск файла с именем <IDADIR>/cfg/idauser.cfg. Если он присутствует, этот файл рассматривается как расширение ida.cfg, и любые параметры в файле заменяют соответствующие параметры в ida.cfg. Если вам неудобно редактировать ida.cfg, вам следует создать idauser.cfg и добавить к нему все параметры, которые вы хотите переопределить. Кроме того, idauser.cfg предлагает самые простые средства для переноса ваших индивидуальных параметров из одной версии IDA в другую. Например, с idauser.cfg вам не нужно повторно редактировать ida.cfg каждый раз, когда вы обновляете свою копию IDA. Вместо этого просто копируйте существующий idauser.cfg в новую установку IDA при каждом обновлении.

Файл конфигурации графического интерфейса пользователя: idagui.cfg

Элементы конфигурации, характерные для версии IDA с графическим интерфейсом пользователя, находятся в отдельном файле: <IDADIR>/cfg/idagui.cfg. Этот файл состоит примерно из трех разделов: поведение графического интерфейса по умолчанию, сопоставление горячих клавиш клавиатуры и конфигурация расширения файла для диалогового окна File-> Open. В этом разделе мы обсудим несколько наиболее интересных вариантов. Посмотрите сами idagui.cfg для получения полного списка доступных опций, который в большинстве случаев сопровождается комментариями, описывающими их назначение.

Версия IDA для графического интерфейса пользователя Windows позволяет указать дополнительный файл справки с помощью параметра HELPFILE. Любой указанный здесь файл не заменяет основной файл справки IDA. Предполагаемая цель этой опции - предоставить доступ к дополнительной информации, которая может применяться в определенных ситуациях реверс инжиниринга. Если указан дополнительный файл справки, CTRL-F1 заставляет IDA открывать указанный файл и искать тему, которая соответствует слову под курсором. Если совпадений не найдено, вы попадете в индекс файла справки. Например, если вы не учитываете автоматические комментарии, IDA не предлагает никакой справочной информации относительно мнемоники инструкций при дизассемблировании. Если вы анализируете двоичный файл x86, вы можете захотеть иметь ссылку на инструкцию x86, доступную в команде. Если вы можете найти файл справки, который содержит разделы для каждой инструкции x86, то справка для любой инструкции находится всего на расстоянии одной горячей клавиши. Единственное предостережение относительно дополнительных файлов справки - это то, что IDA поддерживает только старые файлы справки в стиле WinHelp (.hlp). IDA не поддерживает использование скомпилированных файлов справки HTML (.chm) в качестве дополнительных файлов справки.

ПРИМЕЧАНИЕ. Microsoft Windows Vista и более поздние версии не обеспечивают встроенную поддержку 32-разрядных файлов WinHelp, поскольку файл WinHlp32.exe не входит в комплект этих операционных систем. Дополнительные сведения см. В статье 9176073 базы знаний Майкрософт.

Часто задаваемый вопрос об использовании IDA: "Как я могу пропатчить двоичные файлы с помощью IDA? " Вкратце, ответ – "никак", но мы отложим обсуждение деталей этого вопроса до главы 14. Что вы можете сделать с IDA, так это исправить базу данных, чтобы изменить инструкции или данные практически любым способом, который вы сочтете нужным. Как только мы обсудим сценарии (глава 15), вы поймете, что изменить базу данных не так уж и сложно. Но что, если вы не заинтересованы или не готовы изучать язык сценариев IDA? IDA содержит меню исправлений базы данных, которое не отображается по умолчанию. Параметр DISPLAY_PATCH_SUBMENU используется для отображения или скрытия меню патчей IDA, которое отображается через Edit->Patch Program. Параметры, доступные в этом меню, обсуждаются в главе 14.

Однострочное поле ввода в нижней части рабочего пространства IDA известно как строка команд IDA. Вы можете использовать параметр DISPLAY_COMMAND_LINE, чтобы контролировать, отображается ли это поле или нет. По умолчанию поле будет показана. Если у вас мало места на экране и вы не ожидаете, что вам понадобится вводить однострочные скрипты, то отключение этой функции может помочь вам освободить немного места в вашем окне IDA. Обратите внимание, что эта командная строка не позволяет вам выполнять команды операционной системы, как если бы вы вводили их в командной строке.

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

Практически все опции, которые IDA делает доступными через пункты меню или кнопки панели инструментов, перечислены в этом разделе. К сожалению, названия команд, как правило, не совпадают с текстом, используемым в меню IDA, поэтому могут потребоваться некоторые усилия, чтобы точно определить, какая опция файла конфигурации соответствует определенному пункту меню. Например, команда Jump-> Jump to Problem приравнивается к опции JumpQ (которая действительно соответствует ее горячей клавише: CTRL-Q) в idagui.cfg. Кроме того, хотя многие команды имеют соответствующие комментарии для описания их назначения, многие команды вообще не имеют описания, поэтому вам остается определять поведение команды на основе ее имени в файле конфигурации. Трюк, который может помочь вам выяснить, с каким пунктом меню связано действие файла конфигурации, - это поиск действия в справочной системе IDA. Результаты таких поисков обычно приводят к описанию соответствующего пункта меню действия.

Следующие строки представляют собой пример назначения горячих клавиш в idagui.cfg:

"Abort" = 0 // Abort IDA, don't save changes
"Quit" = "Alt-X" // Quit to DOS, save changes


Первая строка - это назначение горячих клавиш для команды Abort IDA, которая в данном случае не имеет назначения горячих клавиш. Значение 0 без кавычек указывает на то, что команде не назначена горячая клавиша. Во второй строке показано назначение горячих клавиш для действия выхода IDA. Последовательности горячих клавиш указываются в виде строки в кавычках, обозначающей последовательность клавиш. В idagui.cfg существует множество примеров назначения горячих клавиш.

Последняя часть idagui.cfg связывает описания типов файлов с соответствующими расширениями файлов и указывает, какие типы файлов будут перечислены в раскрывающемся списке Тип файлов в диалоговом окне File->Open. Большое количество типов файлов уже описано в файле конфигурации; однако, если вы часто работаете с файлом недоступного типа, вам может потребоваться для редактирования списка типов файлов, чтобы добавить свой тип файла в список. Параметр FILE_EXTENSIONS описывает все ассоциации файлов, известные IDA. Следующая строка представляет собой пример типичной ассоциации типов файлов.

CLASS_JAVA, "Java Class Files", "*.cla*;*.cls"

Строка содержит три компонента, разделенных запятыми: имя ассоциации (CLASS_JAVA), описание и шаблон имени файла. В шаблоне имени файла можно использовать подстановочные знаки, и можно указать несколько шаблонов, разделяя их точкой с запятой. Второй тип ассоциации файлов позволяет сгруппировать несколько существующих ассоциаций в одну категорию. Например, следующая строка группирует все ассоциации, имена которых начинаются с EXE_, в единую ассоциацию с именем EXE.

EXE, "Executable Files", EXE_*

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

IDA_BOOK, "Ida Book Files", "*.book"


Мы можем выбрать любое имя для ассоциации, если оно еще не используется; однако простого добавления новой ассоциации в список FILE_EXTENSIONS недостаточно, чтобы эта ассоциация появилась в диалоговом окне File->Open. Параметр DEFAULT_FILE_FILTER перечисляет имена всех ассоциаций, которые появятся в диалоговом окне File->Open. Чтобы завершить процесс и сделать нашу новую ассоциацию доступной, нам нужно добавить IDA_BOOK в список DEFAULT_FILE_FILTER.

Подобно файлу idauser.cfg, последняя строка в idagui.cfg содержит директиву для включения файла с именем <IDADIR>/cfg/idauserg.cfg. Если вам неудобно редактировать idagui.cfg, вам следует создать idauserg.cfg и добавить к нему все параметры, которые вы хотите переопределить.

Файл конфигурации консоли: idatui.cfg

Аналог idagui.cfg для пользователей консольной версии IDA - <IDADIR>/cfg/idatui.cfg. Этот файл очень похож по структуре и функциональности на idagui.cfg. Среди прочего, спецификации горячих клавиш сделаны точно так же, как и в idagui.cfg. Поскольку эти два файла очень похожи, здесь мы подробно остановимся только на различиях.

Во-первых, параметры DISPLAY_PATCH_SUBMENU и DISPLAY_COMMAND_LINE недоступны в консольной версии и не включены в idatui.cfg. Диалог File-> Open, используемый в консольной версии, намного проще, чем диалог, используемый в версии с графическим интерфейсом пользователя, поэтому все команды ассоциации файлов, доступные в idagui.cfg, отсутствуют в idatui.cfg.

С другой стороны, некоторые опции доступны только для консольных версий IDA. Например, вы можете использовать параметр NOVICE, чтобы IDA запускалась в режиме для начинающих, в котором она отключает некоторые из своих более сложных функций в попытке облегчить изучение. Заметная разница в режиме для новичков - почти полное отсутствие подменю.

Пользователи консоли гораздо чаще полагаются на использование последовательностей горячих клавиш. Чтобы упростить автоматизацию общих последовательностей горячих клавиш, IDA консольного режима предоставляет синтаксис определения макроса клавиатуры. Несколько примеров макросов можно найти в idatui.cfg; однако идеальным местом для размещения любых разрабатываемых вами макросов является <IDADIR>/cfg/idausert.cfg (консольный эквивалент idauserg.cfg). Пример макроса, содержащийся в файле idatui.cfg по умолчанию, может выглядеть следующим образом (в фактическом файле idatui.cfg этот макрос закомментирован):

MACRO "Alt-H" // this sample macro jumps to "start" label
{
"G"
's' 't' 'a' 'r', 't'
"Enter"
}


Определения макросов вводятся с помощью ключевого слова MACRO {1}, за которым следует горячая клавиша {2}, которая должна быть связана с макросом. Сама последовательность макросов указывается в фигурных скобках как последовательность строк или символов имени клавиш, которые, в свою очередь, могут представлять собой сами последовательности горячих клавиш. Предыдущий пример макроса, активированный с помощью ALT+H, открывает диалоговое окно "Перейти к адресу" с помощью горячей клавиши G, вводит начало метки в диалоговом окне по одному символу за раз, а затем закрывает диалоговое окно с помощью клавиши ENTER. Обратите внимание, что мы не можем использовать синтаксис "start"для ввода имени символа, так как это будет воспринято как имя горячей клавиши и приведет к ошибке.

ПРИМЕЧАНИЕ. Макросы и режим для новичков недоступны в версии IDA с графическим интерфейсом пользователя.

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

Дополнительные параметры конфигурации IDA

IDA имеет огромное количество дополнительных опций, которые необходимо настраивать через пользовательский интерфейс IDA. Варианты форматирования отдельных строк дизассемблирования обсуждались в главе 7. Доступ к дополнительным параметрам IDA осуществляется через меню параметров, и в большинстве случаев любые параметры, которые вы изменяете, применяются только к текущей открытой базе данных. Значения этих параметров сохраняются в соответствующем файле базы данных при закрытии базы данных. Параметры IDA Color (Options->Colors) и Шрифт (Options->Font) являются двумя исключениями из этого правила, поскольку они являются глобальными параметрами, которые после установки остаются в силе во всех будущих сеансах IDA. Для версий IDA для Windows значения параметров хранятся в реестре Windows в разделе реестра HKEY_CURRENT_USER\Software\Hex-Rays\IDA. Для версий IDA, отличных от Windows, эти значения хранятся в вашем домашнем каталоге в файле проприетарного формата с именем $ HOME / .idapro / ida.reg.

Другая часть информации, которая сохраняется в реестре, касается диалоговых окон, для которых вы можете выбрать параметр - Больше не отображать это диалоговое окно. Это сообщение иногда появляется в виде флажка в нижней правой части некоторых диалоговых окон с информационными сообщениями, которые вы, возможно, не захотите видеть в будущем. Если вы выберете этот параметр, значение реестра будет создано в разделе реестра HKEY_CURRENT_USER\Software\Hex-Rays\IDA\ Hidden Messages. Если позже вы захотите, чтобы скрытый диалог отображался еще раз, вам нужно будет удалить соответствующее значение в этом разделе реестра.

Цвета IDA

Цвет практически каждого элемента на дисплее IDA можно настроить в диалоговом окне Options→Colors, показанном на рис. 11-1.

1.png


Вкладка Disassembly управляет цветами, используемыми для различных частей каждой строки в окне disassembly. Примеры каждого типа текста, который может появиться при дизассемблировании, приведены в окне примеров {1}. Когда вы выбираете элемент в окне примера, его тип отображается в списке {2}. Используя кнопку Change Color, вы можете назначить любой желаемый цвет любому желаемому предмету.

Диалоговое окно выбора цвета содержит вкладки для назначения цветов, используемых в полосе навигации, отладчике, стрелках перехода в левом поле представления дизассемблерного кода и различных компонентах в представлении графика. В частности, вкладка Graph управляет раскраской узлов графа, их заголовков и ребер, соединяющих каждый узел, а вкладка Disassembly управляет раскраской дизассемблерного текста в графическом представлении. Вкладка Misc позволяет настроить цвета, используемые в окне сообщений IDA.

Настройка панелей инструментов IDA

В дополнение к меню и горячим клавишам версия IDA с графическим интерфейсом пользователя предлагает большое количество кнопок панели инструментов, распределенных по более чем двум дюжинам панелей инструментов. Панели инструментов обычно закрепляются в области основной панели инструментов под строкой меню IDA. Два предопределенных расположения панелей инструментов, доступных с помощью меню View->Toolbars - это базовый режим, который включает семь панелей инструментов IDA, и расширенный режим, который включает все панели инструментов IDA. Отдельные панели инструментов можно отсоединять, перетаскивать и перемещать в любое место на экране по своему вкусу. Если вы обнаружите, что вам не нужна определенная панель инструментов, вы можете полностью удалить ее с кона через меню View-> Toolbars, которое показано на рисунке 11-2.

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

2.png


Если вы выбрали расположение панелей инструментов, которое вам нравится, и хотите сделать его по умолчанию, то вам следует сохранить текущее расположение рабочего стола в качестве рабочего стола по умолчанию, используя Windows->Save Desktop, при этом открывается диалоговое окно, показанное на рисунке 11-3.

3.png


Каждый раз, когда вы сохраняете конфигурацию рабочего стола, вас просят указать имя для конфигурации. Когда установлен флажок Default, текущий макет рабочего стола становится по умолчанию для всех новых баз данных и рабочего стола, к которому вы вернетесь, если выберете Windows->Reset desktop. Чтобы восстановить отображение на одном из ваших настраиваемых рабочих столов, выберите Windows->Load Desktop и выберите именованный макет, который вы хотите загрузить. Сохранение и восстановление рабочих столов особенно полезно в ситуациях, когда используется несколько мониторов с разными размерами и/ или разрешением (что может быть обычным для ноутбуков, использующих разные док-станции, или при подключении к проекторам для презентаций).

Резюме

Начиная работу с IDA, вы можете быть полностью удовлетворены как поведением по умолчанию, так и макетом графического интерфейса по умолчанию. Когда вы освоитесь с основными функциями IDA, вы обязательно найдете способы настроить IDA в соответствии со своими предпочтениями. Хотя нет возможности полностью охватить все возможные возможности, предлагаемые IDA, в одной главе, мы попытались предоставить указатели на основные местоположения, в которых эти возможности могут быть найдены. Мы также попытались выделить те возможности, которыми вы, скорее всего, захотите манипулировать в какой-то момент вашей работы с IDA. Поиск дополнительных полезных опций остается делом исследования для любознательных читателей.
 
РАСПОЗНАВАНИЕ БИБЛИОТЕК С ПОМОЩЬЮ ФЛИРТ СИГНАТУР

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

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

Технология быстрой идентификации и распознавания библиотек

Технология быстрой идентификации и распознавания библиотек, более известная как FLIRT, включает в себя набор методов, используемых IDA для идентификации последовательностей кода как библиотечного кода. В основе FLIRT лежат алгоритмы сопоставления с образцом, которые позволяют IDA быстро определять, соответствует ли дизассемблированная функция одной из многих сигнатур, известных IDA. Каталог <IDADIR>/sig содержит файлы смгнатур, поставляемые с IDA. По большей части это библиотеки, которые поставляются с обычными компиляторами Windows, хотя также включены некоторые сигнатуры, отличные от Windows.

Файлы сигнатур используют настраиваемый формат, в котором основная часть данных сигнатур сжимается и помещается в специальный заголовок IDA. В большинстве случаев имена файлов сигнатур не указывают четко, из какой библиотеки были сгенерированы соответствующие сигнатуры. В зависимости от того, как они были созданы, файлы сигнатур могут содержать комментарий имени библиотеки, который описывает их содержимое. Если мы просмотрим первые несколько строк извлеченного содержимого ASCII из файла сигнатур, этот комментарий часто обнаруживается. Следующая команда в стиле Unix обычно показывает комментарий во второй или третьей строке вывода:

# strings sigfile | head -n 3


В IDA есть два способа просмотра комментариев, связанных с файлами сигнатур. Во-первых, вы можете получить доступ к списку сигнатур, примененных к двоичному файлу, через View->Open Subviews->Signatures. Во-вторых, список всех файлов сигнатур отображается как часть процесса приложения вручную, который запускается через File->Load File->FLIRT Signature.

Применение сигнатур FLIRT

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

MAIN VS. _START

Напомним, что точка входа в программу - это адрес первой инструкции, которая будет выполнена. Многие древние программисты на C ошибочно полагают, что это адрес функции с именем main, хотя на самом деле это не так. Тип файла программы, а не язык, на котором она была создана, определяет способ, которым аргументы командной строки предоставляются программе. Чтобы согласовать любые различия между тем, как загрузчик представляет аргументы командной строки, и тем, как программа ожидает их получить (например, через параметры в main), некоторый код инициализации должен выполняться до передачи управления в main. Именно эту инициализацию IDA определяет как точку входа в программу и маркирует _start.

Этот код инициализации также отвечает за любые задачи инициализации, которые должны быть выполнены до того, как будет разрешено выполнение main. В программе на C++ этот код отвечает за то, чтобы конструкторы для глобально объявленных объектов вызывались до выполнения main. Точно так же вставляется код очистки, который выполняется после завершения функции main, чтобы вызвать деструкторы для всех глобальных объектов до фактического завершения программы.


Если IDA идентифицирует компилятор, используемый для создания определенного двоичного файла, то файл сигнатуры для соответствующих библиотек компилятора загружается и применяется к оставшейся части двоичного файла. Сигнатуры, поставляемые с IDA, обычно связаны с проприетарными компиляторами, такими как Microsoft Visual C++ или Borland Delphi. Причина этого в том, что с этими компиляторами поставляется конечное количество двоичных библиотек. Для компиляторов с открытым исходным кодом, таких как GNU gcc, двоичные варианты связанных библиотек столь же многочисленны, как и операционные системы, с которыми поставляются компиляторы. Например, каждая версия FreeBSD поставляется с уникальной версией стандартной библиотеки C. Для оптимального сопоставления с образцом файлы сигнатуры должны быть созданы для каждой версии библиотеки. Обратите внимание на сложность сбора всех вариантов libc.a, поставляемых с каждой версией каждого дистрибутива Linux. Это просто непрактично. Частично эти различия связаны с изменениями в исходном коде библиотеки, которые приводят к другому скомпилированному коду, но огромные различия также возникают из-за использования различных параметров компиляции, таких как параметры оптимизации и использование разных версий компилятора для сборки библиотеки. В итоге IDA поставляется с очень небольшим количеством файлов сигнатур для библиотек компилятора с открытым исходным кодом. Хорошая новость, как вы скоро увидите, заключается в том, что Hex-Rays предоставляет инструменты, позволяющие создавать собственные файлы сигнатур из статических библиотек.

Итак, при каких обстоятельствах вам может потребоваться вручную применить сигнатуры к одной из ваших баз данных? Иногда IDA правильно определяет компилятор, используемый для создания двоичного файла, но не имеет сигнатур для связанных библиотек компилятора. В таких случаях вам либо придется жить без сигнатур, либо вам нужно будет получить копии статических библиотек, используемых в двоичном файле, и сгенерировать свои собственные сигнатуры. В других случаях IDA может просто не идентифицировать компилятор, из-за чего невозможно определить, какие сигнатуры следует применить к базе данных. Это обычное явление при анализе запутанного кода, в котором процедуры запуска в достаточной степени искажены, чтобы исключить идентификацию компилятора. Итак, первое, что нужно сделать, - это деобфусцировать двоичный файл в достаточной степени, прежде чем у вас появится хоть какая-то надежда на сопоставление библиотечных сигнатур. Мы обсудим методы работы с запутанным кодом в главе 21.

Независимо от причины, если вы хотите вручную применить сигнатуры к базе данных, вы делаете это через File → Load File → FLIRT Signature File, который открывает диалоговое окно выбора сигнатуры, показанное на рисунке 12-1.

1634809484889.png


Столбец File отражает имя каждого файла .sig в каталоге <IDADIR>/sig IDA. Обратите внимание, что нет возможности указать альтернативное расположение для файлов .sig. Если вы когда-нибудь создадите свои собственные сигнатуры, их необходимо поместить в <IDADIR>/ sig вместе с каждым другим файлом .sig. В столбце "Имя библиотеки" отображается комментарий имени библиотеки, встроенный в каждый файл. Имейте в виду, что эти комментарии носят информативный характер только в той мере, в какой автор сигнатур (а это можете быть вы!) решает их сделать.

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

ПРЕДУПРЕЖДЕНИЕ . Только функции с фиктивным именем IDA могут быть переименованы автоматически. Другими словами, если вы переименовали функцию, и эта функция позже будет сопоставлена сигнатуре, то функция не будет переименована в результате сопоставления. Поэтому в ваших интересах применять сигнатур как можно раньше в процессе анализа.

Напомним, что статически связанные двоичные файлы стирают различие между кодом приложения и кодом библиотеки. Если вам посчастливилось иметь статически связанный двоичный файл, в котором не были удалены символы, у вас, по крайней мере, будут полезные имена функций (столь же полезные, как и надежный программист), которые помогут вам разобраться в коде. Однако, если двоичный файл был удален, у вас, возможно, будут сотни функций, все с именами, сгенерированными IDA, которые не могут указать, что функция делает. В обоих случаях IDA сможет идентифицировать библиотечные функции только при наличии сигнатур (имена функций в незарезанном двоичном файле не предоставляют IDA достаточно информации, чтобы окончательно идентифицировать функцию как библиотечную функцию). На рис. 12-2 показан обзорный навигатор для статически связанного двоичного файла.

1634809509206.png


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

1634809520702.png


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

При нанесении сигнатур следует помнить о двух моментах. Во-первых, сигнатуры полезны даже при работе с двоичным файлом, который не был удален, и в этом случае вы используете сигнатуры больше, чтобы помочь IDA идентифицировать функции библиотеки, чем переименовывать эти функции. Во-вторых, статически связанные двоичные файлы могут состоять из нескольких отдельных библиотек, что требует применения нескольких наборов сигнатур для полной идентификации всех функций библиотеки. С каждым дополнительным приложением для сигнатуры дополнительные части навигатора будут преобразованы, чтобы отразить обнаружение библиотечного кода. На рис. 12-4 показан один из таких примеров. На этом рисунке вы видите двоичный файл, который был статически связан как со стандартной библиотекой C, так и с криптографической библиотекой OpenSSL.

1634809534380.png


В частности, вы видите, что после применения соответствующих сигнатур для версии OpenSSL, используемой в этом приложении, IDA пометила небольшую полосу (более светлая полоса к левому краю диапазона адресов) как код библиотеки. Статически связанные двоичные файлы часто создаются, сначала беря код приложения, а затем добавляя необходимые библиотеки для создания результирующего исполняемого файла. Учитывая это изображение, мы можем сделать вывод, что пространство памяти справа от библиотеки OpenSSL, вероятно, занято дополнительным кодом библиотеки, в то время как код приложения, скорее всего, находится в очень узкой полосе слева от библиотеки OpenSSL. Если мы продолжим применять сигнатуры к двоичному файлу, показанному на рисунке 12-4, мы в конечном итоге придем к отображению рисунка 12-5.

1634809545494.png


В этом примере мы применили сигнатуры для libc, libcrypto, libkrb5, libresolv и других. В некоторых случаях мы выбирали сигнатуры на основе строк, находящихся в двоичном файле; в других случаях мы выбирали сигнатуры на основе их тесной связи с другими библиотеками, уже расположенными в двоичном файле. В результате окно продолжает показывать темную полосу в середине полосы навигации и меньшую темную полосу на крайнем левом крае полосы навигации. Требуется дальнейший анализ, чтобы определить природу этих оставшихся небиблиотечных частей двоичного файла. В этом случае мы бы узнали, что более широкая темная полоса посередине является частью неопознанной библиотеки, а темная полоса слева - это код приложения.

Создание файлов сигнатур FLIRT

Как мы обсуждали ранее, для IDA просто непрактично поставлять файлы сигнатур для каждой существующей статической библиотеки. Чтобы предоставить пользователям IDA инструменты и информацию, необходимые для создания их собственных подписей, Hex-Rays распространяет набор инструментов Fast Library Acquisition for Identification and Recognition (FLAIR). Инструменты FLAIR доступны на вашем компакт-диске с дистрибутивом IDA или через загрузку с веб-сайта Hex-Rays6 для авторизованных клиентов. Как и несколько других надстроек IDA, инструменты FLAIR распространяются в виде Zip-файла. Hex-Rays не обязательно выпускает новую версию инструментов FLAIR с каждой версией IDA, поэтому вам следует использовать самую последнюю версию FLAIR, которая не превышает вашу версию IDA.

Установка утилит FLAIR - это простой вопрос извлечения содержимого связанного Zip-файла, хотя мы настоятельно рекомендуем вам создать специальный каталог flair в качестве места назначения, поскольку Zip-файл не организован с каталогом верхнего уровня. Внутри дистрибутива FLAIR вы найдете несколько текстовых файлов, которые составляют документацию для инструментов FLAIR. Особый интерес представляют следующие файлы:

readme.txt
Это общий обзор процесса создания сигнатуры.

plb.txt
В этом файле описывается использование анализатора статической библиотеки plb.exe. Более подробно библиотечные синтаксические анализаторы обсуждаются в разделе "Создание файлов шаблонов" на странице 219.

pat.txt
В этом файле подробно описан формат файлов шаблонов, которые представляют собой первый шаг в процессе создания подписи. Файлы шаблонов также описаны в разделе "Создание файлов шаблонов" на странице 219.

sigmake.txt
В этом файле описывается использование sigmake.exe для создания файлов .sig из файлов шаблонов. Пожалуйста, обратитесь к разделу "Создание файлов сигнратур" на странице 221 для получения более подробной информации.

Дополнительный интересующий контент верхнего уровня включает каталог bin, который содержит все исполняемые файлы инструментов FLAIR, и каталог запуска, который содержит файлы шаблонов для общих последовательностей запуска, связанных с различными компиляторами и связанными с ними типами выходных файлов (PE, ELF, и так далее). До версии 6.1 область инструментов FLAIR была доступна только для Windows; однако полученные файлы сигнатур можно использовать со всеми вариантами IDA (Windows, Linux и OS X).

Обзор создания сигнатуры

Базовый процесс создания файлов сигнатур не кажется сложным, поскольку сводится к четырем простым шагам.

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

2. Воспользуйтесь одним из анализаторов FLAIR, чтобы создать файл шаблона для библиотеки.

3. Запустите sigmake.exe, чтобы обработать получившийся файл шаблона и сгенерировать файл сигнатуры.

4. Установите новый файл сигнатуры в IDA, скопировав его в <IDADIR>/sig.


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

Идентификация и получение статических библиотек

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

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

OpenSSL 1.0.0b-fips 16 Nov 2010

Уведомления об авторских правах и строки ошибок часто достаточно уникальны, поэтому вы снова можете использовать поиск в Интернете, чтобы сузить круг кандидатов. Если вы решили запускать строки из командной строки, не забудьте использовать параметр -a, чтобы строки сканировали весь двоичный файл; в противном случае вы можете пропустить некоторые потенциально полезные строковые данные.

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

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

$ file sample_file_1
sample_file_1: ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD),
statically linked, for FreeBSD 8.0 (800107), stripped


В этом случае мы могли бы сразу перейти к системе FreeBSD 8.0 и для начала отследить libc.a. Однако следующий пример несколько более неоднозначен:

$ file sample_file_2
sample_file_2: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux),
statically linked, for GNU/Linux 2.6.32, stripped


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

GCC: (GNU) 4.5.1 20100924 (Red Hat 4.5.1-4)

Здесь поиск был сужен до дистрибутивов Red Hat (или производных), которые поставлялись с gcc версии 4.5.1. Подобные теги GCC нередки в двоичных файлах, скомпилированных с помощью gcc, и, к счастью для нас, они переживают процесс удаления и остаются видимыми для строк.

Имейте в виду, что файловая утилита - это еще не все, и все дело в идентификации файлов. Следующий вывод демонстрирует простой случай, когда файл, кажется, знает тип проверяемого файла, но для которого вывод довольно неспецифичен.

$ file sample_file_3
sample_file_3: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV),
dynamically linked (uses shared libs), stripped


Этот пример был взят из системы Solaris 10 x86. И здесь утилита strings может оказаться полезной для точного определения этого факта.

Создание файлов шаблонов

На этом этапе у вас должна быть одна или несколько библиотек, для которых вы хотите создать сигнатуры. Следующим шагом является создание файла шаблона для каждой библиотеки. Файлы шаблонов создаются с помощью соответствующей утилиты парсера FLAIR. Как и исполняемые файлы, файлы библиотеки создаются в соответствии с различными спецификациями форматов файлов. FLAIR предоставляет парсеры для нескольких популярных форматов файлов библиотек. Как подробно описано в файле readme.txt FLAIR, в каталоге bin FLAIR можно найти следующие парсеры:

plb.exe/ plb
Парсер для библиотек OMF (обычно используется компиляторами Borland)

pcf.exe/ pcf
Парсер для библиотек COFF (обычно используется компиляторами Microsoft)

pelf.exe/ pelf
Парсер для библиотек ELF (есть во многих системах Unix)

ppsx.exe/ ppsx
Парсер для библиотек Sony PlayStation PSX

ptmobj.exe/ ptmobj
Парсер для библиотек TriMedia

pomf166.exe/ pomf166
Парсер для объектных файлов Kiel OMF 166

Чтобы создать файл шаблона для данной библиотеки, укажите синтаксический анализатор, соответствующий формату библиотеки, имя библиотеки, которую вы хотите проанализировать, и имя результирующего файла шаблона, который должен быть сгенерирован. Для копии libc.a из системы FreeBSD 8.0 вы можете использовать следующее:

$ ./pelf libc.a libc_FreeBSD80.pat
libc.a: skipped 1, total 1089


Здесь синтаксический анализатор сообщает файл, который был проанализирован (libc.a), количество пропущенных функций (1), 7 и количество сгенерированных шаблонов подписи (1089). Каждый синтаксический анализатор принимает несколько отличающийся набор параметров командной строки, задокументированных только через оператор использования синтаксического анализатора. При выполнении синтаксического анализатора без аргументов отображается список параметров командной строки, принимаемых этим анализатором. Файл plb.txt содержит более подробную информацию о параметрах, принимаемых парсером plb. Этот файл является хорошим основным источником информации, поскольку другие парсеры также принимают многие из описываемых им параметров. Во многих случаях достаточно просто назвать библиотеку для анализа и файл шаблона, который нужно сгенерировать.

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

57568B7C240C8B742410FC8B4C2414C1E902F3A775108B4C241483E103F3A675 1E A55D 003E :0000 _memcmp
0FBC442404740340C39031C0C3...................................... 00 0000 000D :0000 _ffs
57538B7C240C8B4C2410FC31C083F90F7E1B89FAF7DA83E20389CB29D389D1F3 12 9E31 0032 :0000 _bzero


Формат отдельного шаблона описан в файле pat.txt FLAIR. Вкратце, первая часть шаблона перечисляет начальную последовательность байтов функции до максимум 32 байтов. Делается поправка на байты, которые могут изменяться в результате перемещаемых записей. Такие байты отображаются двумя точками. Точки также используются для заполнения шаблона до 64 символов, когда функция короче 32 байтов (как _ffs в предыдущем коде). Помимо начальных 32 байтов, записывается дополнительная информация, чтобы обеспечить большую точность в процессе сопоставления сигнатуры. Дополнительная информация, закодированная в каждой строке шаблона, включает значение CRC169, вычисленное для части функции, длину функции в байтах и список имен символов, на которые ссылается функция. В общем, более длинные функции, которые ссылаются на многие другие символы, дают более сложные линии рисунка. В ранее созданном файле libc_FreeBSD80.pat некоторые строки шаблонов превышают длину 20 000 символов.

Несколько сторонних программистов создали служебные программы, предназначенные для генерации шаблонов из существующих баз данных IDA. Одной из таких утилит является IDB_2_PAT, плагин IDA, написанный Дж. К. Робертсом, который способен генерировать шаблоны для одной или нескольких функций в существующей базе данных. Такие утилиты полезны, если вы ожидаете встретить аналогичный код в дополнительных базах данных и не имеете доступа к исходным файлам библиотеки, используемым для создания анализируемого двоичного файла.

Создание файлов сигнатур

После того, как вы создали файл шаблона для данной библиотеки, следующим шагом в процессе создания сигнатур будет создание файла .sig, подходящего для использования с IDA. Формат файла сигнатуры IDA существенно отличается от формата файла шаблона. Файлы сигнатур используют собственный двоичный формат, предназначенный как для минимизации объема пространства, необходимого для представления всей информации, присутствующей в файле шаблона, так и для обеспечения эффективного сопоставления сигнатур с фактическим содержимым базы данных. Подробное описание структуры файла сигнатур доступно на веб-сайте Hex-Rays.

Утилита FLAIR sigmake используется для создания файлов сигнатур из файлов шаблонов. Разделив генерацию шаблона и генерацию сигнатуры на две отдельные фазы, процесс генерации сигнатуры полностью независим от процесса генерации шаблона, что позволяет использовать сторонние генераторы шаблонов. В простейшей форме генерация сигнатуры происходит с помощью sigmake для анализа файла .pat и создания файла .sig, как показано здесь:

$ ./sigmake libssl.pat libssl.sig


Если все пойдет хорошо, создается файл .sig, готовый к установке в <IDADIR> / sig. Однако этот процесс редко проходит так гладко.

ПРИМЕЧАНИЕ

В файле документации sigmake, sigmake.txt, рекомендуется, чтобы имена файлов сигнатур соответствовали соглашению о длине имен MS-DOS 8.3. Однако это не жесткое требование. При использовании более длинных имен файлов в диалоговом окне выбора сигнатур отображаются только первые восемь символов основного имени файла.


Генерация сигнатуры часто является итеративным процессом, поскольку именно на этом этапе необходимо обрабатывать конфликты. Коллизия возникает каждый раз, когда две функции имеют одинаковые шаблоны. Если коллизии не разрешены каким-либо образом, невозможно определить, какая функция фактически сопоставляется во время процесса приложения сигнатуры. Следовательно, sigmake должен иметь возможность разрешать каждую сгенерированную сигнатуру точно в одно имя функции. Когда это невозможно из-за наличия идентичных шаблонов для одной или нескольких функций, sigmake отказывается генерировать файл .sig и вместо этого генерирует файл исключений (.exc). Более типичный первый проход с использованием sigmake и нового файла .pat (или набора файлов .pat) может дать следующее.

$ ./sigmake libc_FreeBSD80.pat libc_FreeBSD80.sig
libc_FreeBSD80.sig: modules/leaves: 1088/1024, COLLISIONS: 10
See the documentation to learn how to resolve collisions.


Упомянутая документация - sigmake.txt, в которой описывается использование sigmake и процесс разрешения конфликтов. На самом деле, каждый раз, когда выполняется sigmake, он ищет соответствующий файл исключений, который может содержать информацию о том, как разрешить любые коллизии, с которыми sigmake может столкнуться при обработке именованного файла шаблона. В отсутствие такого файла исключений и при возникновении коллизий sigmake генерирует такой файл исключений, а не файл сигнатуры. В предыдущем примере мы нашли только что созданный файл с именем libc_FreeBSD80.exc. При первом создании файлы исключений представляют собой текстовые файлы, в которых подробно описаны конфликты, с которыми sigmake столкнулся при обработке файла шаблона. Файл исключений должен быть отредактирован, чтобы дать sigmake указания относительно того, как он должен разрешать конфликтующие шаблоны. Ниже приводится общий процесс редактирования файла исключений.

При создании sigmake все файлы исключений начинаются со следующих строк:

;--------- (delete these lines to allow sigmake to read this file)
; add '+' at the start of a line to select a module
; add '-' if you are not sure about the selection
; do nothing if you want to exclude all modules


Назначение этих строк - напомнить вам, что делать для разрешения коллизий, прежде чем вы сможете успешно сгенерировать сигнатуры. Самое важное, что нужно сделать, - это удалить четыре строки, начинающиеся с точки с запятой, иначе sigmake не сможет проанализировать файл исключений во время последующего выполнения. Следующий шаг - сообщить sigmake о своем желании разрешить коллизии. Здесь появляются несколько строк, извлеченных из libc_FreeBSD80.exc:

_index 00 0000 538B4424088A4C240C908A1838D974074084DB75F531C05BC3..............
_strchr 00 0000 538B4424088A4C240C908A1838D974074084DB75F531C05BC3..............
_rindex 00 0000 538B5424088A4C240C31C0908A1A38D9750289D04284DB75F35BC3..........
_strrchr 00 0000 538B5424088A4C240C31C0908A1A38D9750289D04284DB75F35BC3..........
_flsl 01 EF04 5531D289E58B450885C0741183F801B201740AD1E883C20183F80175F65D89D0
_fls 01 EF04 5531D289E58B450885C0741183F801B201740AD1E883C20183F80175F65D89D0


Эти строки детализируют три отдельных коллизии. В этом случае нам говорят, что индекс функции неотличим от strchr, rindex имеет ту же сигнатуру, что и strrchr, а flsl конфликтует с fls. Если вы знакомы с какой-либо из этих функций, этот результат может вас не удивить, поскольку функции столкновения практически идентичны (например, index и strchr выполняют одно и то же действие). Чтобы предоставить вам возможность управлять своей судьбой, sigmake ожидает, что вы назначите не более одной функции в каждой группе в качестве надлежащей функции для соответствующей сигнатуры. Вы выбираете функцию, добавляя к имени префикс символа плюса (+), если вы хотите, чтобы имя применялось каждый раз, когда соответствующая сигнатура совпадает в базе данных, или знак минуса (-), если вы просто хотите, чтобы комментарий добавлялся в базу данных всякий раз, когда соответствующая сигнатуры совпадает. Если вы не хотите, чтобы какие-либо имена применялись при сопоставлении соответствующей сигнатуры в базе данных, вы не добавляете никаких символов. Следующий список представляет собой один из возможных способов обеспечить допустимое разрешение трех упомянутых ранее столкновений:

+_index 00 0000 538B4424088A4C240C908A1838D974074084DB75F531C05BC3..............
_strchr 00 0000 538B4424088A4C240C908A1838D974074084DB75F531C05BC3..............
_rindex 00 0000 538B5424088A4C240C31C0908A1A38D9750289D04284DB75F35BC3..........
_strrchr 00 0000 538B5424088A4C240C31C0908A1A38D9750289D04284DB75F35BC3..........
_flsl 01 EF04 5531D289E58B450885C0741183F801B201740AD1E883C20183F80175F65D89D0
-_fls 01 EF04 5531D289E58B450885C0741183F801B201740AD1E883C20183F80175F65D89D0


В этом случае мы выбираем использование индекса имени всякий раз, когда совпадает первая сигнатура, вообще ничего не делаем, когда сопоставляется вторая сигнатура, и добавляем комментарий о fls, когда сопоставляется третья сигнатура. Следующие моменты полезны при попытке разрешить коллизии:

1. Чтобы выполнить минимальное разрешение конфликтов, просто удалите четыре закомментированных строки в начале файла исключений.

2. Никогда не добавляйте +/- более чем к одной функции в группе коллищзий.

3. Если группа столкновений содержит только одну функцию, не добавляйте +/- перед этой функцией; просто оставьте это в покое.

4. Последующие сбои sigmake вызывают добавление данных, включая строки комментариев, к любому существующему файлу исключений. Эти дополнительные данные следует удалить, а исходные данные исправить (если бы данные были правильными, sigmake не завершился бы ошибкой во второй раз) перед повторным запуском sigmake.

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

$ ./sigmake libc_FreeBSD80.pat libc_FreeBSD80.sig

После успешного создания файла сигнатуры вы делаете его доступным для IDA, копируя его в каталог <IDADIR>/ sig. Затем ваши новые сигнатуры доступны с помощью меню "File → Load File → FLIRT Signature File ".

Обратите внимание, что мы намеренно уполчали про все параметры, которые могут быть предоставлены как для генераторов шаблонов, так и для sigmake. Краткое изложение доступных опций представлено в plb.txt и sigmake.txt. Единственная опция, которую мы отметим, - это опция -n, используемая с sigmake. Этот параметр позволяет встроить описательное имя в сгенерированный файл сигнатуры. Это имя отображается в процессе выбора сигнатуры (см. Рис. 12-1) и может быть очень полезно при сортировке списка доступных сигнатур. Следующая командная строка включает строку имени "FreeBSD 8.0C standard library " в сгенерированный файл сигнатуры:

$ ./sigmake -n"FreeBSD 8.0 C standard library" libc_FreeBSD80.pat libc_FreeBSD80.sig

В качестве альтернативы имена библиотек можно указать с помощью директив в файлах исключений. Однако, поскольку файлы исключения могут потребоваться не во всех случаях генерации сигнатур, опция командной строки обычно более полезна. Дополнительные сведения см. в sigmake.txt.

Сигнатуры при запуске

IDA также распознает специальную форму сигнатур, называемую сигнатурами запуска. Сигнатуры запуска применяются, когда двоичный файл впервые загружается в базу данных в попытке идентифицировать компилятор, который использовался для создания двоичного файла. Если IDA может идентифицировать компилятор, используемый для построения двоичного файла, тогда дополнительные файлы сигнатур, связанные с идентифицированным компилятором, автоматически загружаются во время первоначального анализа двоичного файла.

Учитывая, что тип компилятора изначально неизвестен при первой загрузке файла, сигнатуры запуска группируются и выбираются в соответствии с типом файла загружаемого двоичного файла. Например, если загружается двоичный файл Windows PE, то загружаются сигнатуры запуска, специфичные для двоичных файлов PE, чтобы определить компилятор, используемый для создания рассматриваемого двоичного файла PE.

Чтобы сгенерировать сигнатуры запуска, sigmake обрабатывает шаблоны, которые описывают процедуру запуска, сгенерированную различными компиляторами, и группирует полученные сигнатуры в один файл сигнатуры для конкретного типа. Каталог запуска в дистрибутиве FLAIR содержит шаблоны запуска, используемые IDA, вместе со сценарием startup.bat, используемым для создания соответствующих сигнатур запуска из этих шаблонов. Обратитесь к startup.bat за примерами использования sigmake для создания сигнатур запуска для определенного формата файла.

В случае с PE-файлами вы могли бы заметить несколько файлов pe_*.oat в каталоге запуска, которые описывают шаблоны запуска, используемые несколькими популярными компиляторами Windows, включая pe_vc.pat для шаблонов Visual Studio и pe_gcc.pat для шаблонов Cygwin/gcc. Если вы хотите добавить дополнительные шаблоны запуска для файлов PE, вам нужно будет добавить их в один из существующих файлов шаблонов PE или создать новый файл шаблона с префиксом pe_, чтобы сценарий генерации сигнатуры запуска мог правильно найти ваши шаблоны и включить их во вновь сгенерированные сигнатуры PE.

Последнее замечание о шаблонах запуска касается их формата, который, к сожалению, немного отличается от шаблонов, созданных для библиотечных функций. Разница заключается в том, что строка шаблона запуска может связывать шаблон с дополнительными наборами сигнатур, которые также должны применяться, если выполняется сопоставление с шаблоном. За исключением примеров шаблонов запуска, включенных в каталог запуска, формат шаблона запуска не задокументирован ни в одном из текстовых файлов, включенных в FLAIR.

Резюме

Автоматическая идентификация кода библиотеки - важная возможность, которая значительно сокращает время, необходимое для анализа статически связанных двоичных файлов. Благодаря своим возможностям FLIRT и FLAIR IDA делает такое автоматическое распознавание кода не только возможным, но и расширяемым, позволяя пользователям создавать свои собственные библиотечные сигнатуры из существующих статических библиотек. Знакомство с процессом генерации сигнатур является важным навыком для любого, кто ожидает столкнуться со статически связанными двоичными файлами.
 
13. РАСШИРЕНИЕ ЗНАНИЙ О IDA

К настоящему времени должно быть ясно, что высококачественное дизассемблирование - это гораздо больше, чем список мнемоник и операндов, полученных из последовательности байтов. Чтобы сделать дизассемблирование полезным, важно дополнить разобранный код информацией, полученной в результате обработки различных данных, связанных с API, таких как прототипы функций и стандартные типы данных. В главе 8 мы обсудили, как IDA обрабатывает структуры данных, в том числе как получить доступ к стандартным структурам данных API и как определять свои собственные пользовательские структуры данных. В этой главе мы продолжим обсуждение расширения знаний IDA путем изучения использования утилит idsutils и loadint в IDA. Эти утилиты доступны на вашем компакт-диске с дистрибутивом IDA или их можно загрузить с сайта загрузки Hex-Rays.

Информация о дополнительных функциях

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

В главе 8 мы обсудили файлы библиотеки типов как механизм, с помощью которого IDA сохраняет структуру сложных структур данных. Файлы библиотеки типов также являются средством, с помощью которого IDA записывает информацию о соглашениях о вызовах функций и последовательности параметров. IDA использует информацию о сигнатуре функций несколькими способами. Во-первых, когда двоичный файл использует разделяемые библиотеки, IDA не имеет возможности узнать, какие соглашения о вызовах могут использоваться функциями в этих библиотеках. В таких случаях IDA пытается сопоставить библиотечные функции с соответствующими сигнатурами в файле библиотеки типов. Если соответствующая сигнатура найдена, IDA может понять соглашение о вызовах, используемое функцией, и внести необходимые изменения в указатель стека (напомним, что функции stdcall выполняют собственную очистку стека). Второе использование сигнатур функций - аннотировать параметры, передаваемые в функцию, комментариями, которые точно указывают, какой параметр помещается в стек перед вызовом функции. Объем информации, представленной в комментарии, зависит от того, сколько информации содержится в сигнатуре функции, которую IDA смогла проанализировать. Две нижеследующие сигнатуры являются законными объявлениями C, хотя вторая дает более полное представление о функции, поскольку она предоставляет формальные имена параметров в дополнение к типам данных.

LSTATUS _stdcall RegOpenKey(HKEY, LPCTSTR, PHKEY);
LSTATUS _stdcall RegOpenKey(HKEY hKey, LPCTSTR lpSubKey, PHKEY phkResult);


Библиотеки типов IDA содержат информацию о сигнатуре для большого количества общих функций API, включая значительную часть Windows API. Дизассемблирование по умолчанию вызова функции RegOpenKey показано здесь:

1634978737768.png


Обратите внимание, что IDA добавила комментарии в правое поле [1], указывающие, какой параметр вставляется в каждую инструкцию, ведущую к вызову RegOpenKey. Когда в сигнатуре функции доступны формальные имена параметров, IDA пытается сделать еще один шаг и автоматически присваивает имена переменным, которые соответствуют определенным параметрам. В двух случаях в предыдущем примере [2] мы видим, что IDA назвала локальную переменную (hKey) и глобальную переменную (SubKey) на основе их соответствия формальным параметрам в прототипе RegOpenKey. Если проанализированный прототип функции содержал только информацию о типе и не содержал формальных имен параметров, то в комментариях в предыдущем примере были бы указаны типы данных соответствующих аргументов, а не имена параметров. В случае параметра lpSubKey имя параметра не отображается как комментарий, потому что параметр указывает на глобальную строковую переменную, а содержимое строки отображается с использованием средства повторения комментариев IDA.

Наконец, обратите внимание, что IDA распознала RegOpenKey как функцию stdcall и автоматически скорректировала указатель стека [3], как RegOpenKey после возврата. Вся эта информация извлекается из сигнатуры функции, которую IDA также отображает в виде комментария в процессе дизассемблирования в соответствующем месте таблицы импорта, как показано в следующем листинге:

1634978753018.png


Комментарий, отображающий прототип функции, взят из файла IDA .til, содержащего информацию о функциях Windows API.

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

Точно так же, как мы смогли добавить сложную информацию о типах данных в локальный файл .til базы данных в главе 8, мы можем добавить информацию о прототипе функции в тот же файл .til, заставив IDA проанализировать один или несколько прототипов функций через File → Load File → Parse C Header. Точно так же вы можете использовать tilib.exe (см. Главу 8) для анализа файлов заголовков и создания автономных файлов .til, которые можно сделать глобально доступными, скопировав их в <IDADIR>/ til.

Все это хорошо, когда у вас есть доступ к исходному коду, который вы затем разрешаете IDA (или tilib.exe) анализировать от вашего имени. К сожалению, чаще, чем хотелось бы, у вас не будет доступа к исходному коду, но вам понадобится такой же качественный дизассемблер. Как вы можете обучать IDA, если у вас нет исходного кода для использования? В этом и состоит цель утилит IDS, или idsutils. Утилиты IDS - это набор из трех утилит, используемых для создания файлов .ids. Сначала мы обсудим, что такое файл .ids, а затем перейдем к созданию наших собственных файлов .ids.

РУЧНОЕ ПЕРЕОПРЕДЕЛЕНИЕ БАЙТОВ

Библиотечные функции, использующие соглашение о вызовах stdcall, могут нанести ущерб анализу указателя стека в IDA. Не имея какой-либо библиотеки типов или информации о файле .ids, IDA не может узнать, использует ли импортированная функция соглашение stdcall. Это важно, поскольку IDA может быть не в состоянии должным образом отслеживать поведение указателя стека при вызовах функций, для которых у него нет информации о соглашении о вызовах. Помимо информации о том, что функция использует stdcall, IDA также должна точно знать, сколько байтов функция удаляет из стека после завершения функции. Не имея информации о соглашениях о вызовах, IDA пытается автоматически определить, использует ли функция stdcall, используя метод математического анализа, известный как симплексный метод. В качестве альтернативы пользователи могут вмешаться вручную, чтобы сами указать количество очищенных байтов. На рис. 13-1 показана специализированная форма диалогового окна редактирования функции, используемого для импортированных функций.

1634978768153.png


Вы можете получить доступ к этому диалоговому окну, перейдя к записи в таблице импорта для данной функции, а затем отредактировав функцию (Edit → Functions → Edit Function, или ALT-P). Обратите внимание на ограниченную функциональность этого конкретного диалогового окна (в отличие от диалогового окна «Редактировать функцию» на рис. 7-7). Поскольку это импортированная запись функции, IDA не имеет доступа к скомпилированному телу функции и, следовательно, не имеет связанной информации о структуре фрейма стека функции и не имеет прямых доказательств того, что функция использует соглашение stdcall. При отсутствии такой информации IDA устанавливает в поле Purged bytes значение -1, указывая на то, что она не знает, очищает ли функция какие-либо байты из стека при возврате. Чтобы переопределить IDA в таких случаях, введите правильное значение для количества очищенных байтов, и IDA будет включать предоставленную информацию в свой анализ указателя стека, где бы ни вызывалась связанная функция. В случаях, когда IDA известно о поведении функции (как на рис. 13-1), поле Purged bytes может быть уже заполнено. Обратите внимание, что это поле никогда не заполняется в результате анализа симплексным методом.

Файлы IDS

IDA использует файлы .ids, чтобы пополнить свои знания о функциях библиотеки. Файл .ids описывает содержимое общей библиотеки, перечисляя все экспортируемые функции, содержащиеся в библиотеке. Подробная информация для каждой функции включает имя функции, связанный с ней порядковый номер, использует ли функция stdcall, и если да, сколько байтов функция очищает из стека при возврате, а также дополнительные комментарии, которые будут отображаться, когда функция упоминается в разобранном коде. На практике файлы .ids представляют собой сжатые файлы .idt, причем файлы .idt содержат текстовые описания каждой библиотечной функции.

Когда исполняемый файл впервые загружается в базу данных, IDA определяет, от каких файлов общей библиотеки зависит исполняемый файл. Для каждой разделяемой библиотеки IDA ищет соответствующий файл .ids в иерархии <IDADIR>/ ids, чтобы получить описания любых библиотечных функций, на которые может ссылаться исполняемый файл. Важно понимать, что файлы .ids не обязательно содержат информацию о сигнатуре функций. Поэтому IDA может не предоставлять анализ параметров функции на основе информации, содержащейся только в файлах .ids. Однако IDA может выполнять точный учет указателя стека, если файл .ids содержит правильную информацию, касающуюся соглашений о вызовах, используемых функциями, и количества байтов, которые функции удаляют из стека. В ситуациях, когда DLL экспортирует искаженные имена, IDA может вывести сигнатуру параметра функции из искаженного имени, и в этом случае эта информация становится доступной при загрузке файла .ids. Мы опишем синтаксис файлов .idt в следующем разделе. В этом отношении файлы .til содержат более полезную информацию о дизассемблировании вызовов функций, хотя для создания файлов .til требуется исходный код.

Создание файлов IDS

Утилиты IDA idsutils используются для создания файлов .ids. Утилиты включают два библиотечных парсера: dll2idt для извлечения информации из библиотек Windows DLL и ar2idt для извлечения информации из библиотек в стиле ar. В обоих случаях вывод представляет собой текстовый файл .idt, содержащий одну строку для каждой экспортируемой функции, в которой порядковый номер экспортируемой функции сопоставляется с именем функции. Синтаксис для файлов .idt, который очень прост, описан в файле readme.txt, включенном в idsutils. Большинство строк в файле .idt используются для описания экспортируемых функций по следующей схеме:

- Экспортная запись начинается с положительного числа. Этот номер представляет собой порядковый номер экспортируемой функции.

- За порядковым номером следует пробел, а затем директива Name в форме Name = function, например Name = RegOpenKeyA. Если используется специальное порядковое значение ноль, то директива Name используется для указания имени библиотеки, описанной в текущем файле .idt, например, в этом примере:

0 Name=advapi32.dll

- Необязательная директива Pascal может использоваться, чтобы указать, что функция использует соглашение о вызовах stdcall, и указать, сколько байтов функция удаляет из стека при возврате. Вот пример:

483 Name=RegOpenKeyA Pascal=12

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

483 Name=RegOpenKeyA Pascal=12 Comment=Open a registry key

Дополнительные необязательные директивы описаны в файле idsutils readme.txt. Утилиты синтаксического анализа idsutils предназначены для максимальной автоматизации создания файлов .idt. Первым шагом в создании файла .idt является получение копии библиотеки, которую вы хотите проанализировать; следующий шаг - проанализировать его с помощью соответствующей утилиты синтаксического анализа. Если бы мы хотели создать файл .idt для связанной с OpenSSL библиотеки ssleay32.dll, мы бы использовали следующую команду:

$ ./dll2idt.exe ssleay32.dll
Convert DLL to IDT file. Copyright 1997 by Yury Haron. Version 1.5
File: ssleay32.dll ... ok


В этом случае успешный синтаксический анализ приводит к созданию файла с именем SSLEAY32.idt. Разница в использовании заглавных букв между именем входного файла и именем выходного файла связана с тем, что dll2idt получает имя выходного файла на основе информации, содержащейся в самой DLL. Здесь показаны первые несколько строк итогового файла .idt:

ALIGNMENT 4
;DECLARATION
;
0 Name=SSLEAY32.dll
;
121 Name=BIO_f_ssl
173 Name=BIO_new_buffer_ssl_connect
122 Name=BIO_new_ssl
174 Name=BIO_new_ssl_connect
124 Name=BIO_ssl_copy_session_id


Обратите внимание, что синтаксические анализаторы не могут определить, использует ли функция stdcall и, если да, то сколько байтов удаляется из стека. Добавление любых директив Pascal или Comment должно выполняться вручную с помощью текстового редактора до создания окончательного файла .ids. Последними шагами для создания идентификаторов являются использование утилиты zipids для сжатия файла .idt и последующее копирование полученного файла .ids в <IDADIR>/ ids.

$ ./zipids.exe SSLEAY32.idt
File: SSLEAY32.idt ... {219 entries [0/0/0]} packed
$ cp SSLEAY32.ids ../Ida/ids


На этом этапе IDA загружает SSLEAY32.ids каждый раз, когда загружается двоичный файл, связанный с ssleay32.dll. Если вы решите не копировать вновь созданные файлы .ids в <IDADIR>/ ids, вы можете загрузить их в любое время через File → Load File → IDS File.

Дополнительный шаг в использовании файлов .ids позволяет связать файлы .ids с конкретными файлами .sig или .til. Когда вы выбираете файлы .ids, IDA использует файл конфигурации IDS с именем <IDADIR>/ida/ idsnames. Этот текстовый файл содержит строки, позволяющие:

- Сопоставьте имя общей библиотеки с соответствующим именем файла .ids. Это позволяет IDA находить правильный файл .ids, когда имя разделяемой библиотеки не транслируется точно в имя файла 8.3 в стиле MS-DOS, как показано ниже:

libc.so.6 libc.ids +

- Сопоставьте файл .ids с файлом .til. В таких случаях IDA автоматически загружает указанный файл .til всякий раз, когда загружает указанный файл .ids. В следующем примере openssl.til будет загружаться каждый раз, когда загружается SSLEAY32.ids (подробности синтаксиса см. В idsnames):

SSLEAY32.ids SSLEAY32.ids + openssl.til

- Сопоставьте файл .sig с соответствующим файлом .ids. В этом случае IDA загружает указанный файл .ids каждый раз, когда именованный файл .sig применяется к дизассемблированию. Следующая строка указывает IDA загружать SSLEAY32.ids каждый раз, когда пользователь применяет подпись FLIRT libssl.sig:

libssl.sig SSLEAY32.ids +

В главе 15 мы рассмотрим ориентированную на сценарии альтернативу библиотечным синтаксическим анализаторам, предоставляемым idsutils, и воспользуемся возможностями анализа функций IDA для создания более описательных файлов .idt.

Добавление предопределенных комментариев с помощью loadint

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

.text:08048654 lea ecx, [esp+arg_0] ; Load Effective Address
.text:08048658 and esp, 0FFFFFFF0h ; Logical AND


Источником этих предопределенных комментариев является файл <IDADIR> /ida.int, который содержит комментарии, отсортированные сначала по типу ЦП, а затем по типу инструкции. Когда автоматические комментарии включены, IDA ищет комментарии, связанные с каждой инструкцией в дизассемблировании, и отображает их в правом поле, если они присутствуют в ida.int.

Утилиты loadint предоставляют вам возможность изменять существующие комментарии или добавлять новые комментарии к ida.int. Как и другие дополнительные утилиты, которые мы обсуждали, loadint задокументирован в файле readme.txt, включенном в дистрибутив loadint. Дистрибутив loadint также содержит предопределенные комментарии для всех процессорных модулей IDA в виде многочисленных файлов .cmt. Изменение существующих комментариев - это простой вопрос поиска файла комментариев, связанного с интересующим вас процессором (например, pc.cmt для x86), внесения изменений в любые комментарии, текст которых вы хотите изменить, и запуска loadint для воссоздания ida. int файл комментариев и, наконец, копирование полученного файла ida.int в ваш основной каталог IDA, где он будет загружен при следующем запуске IDA. Простой запуск для восстановления базы данных комментариев выглядит следующим образом:

$ ./loadint comment.cmt ida.int
Comment base loader. Version 2.04. Copyright (c) 1991-2011 Hex-Rays
17566 cases, 17033 strings, total length: 580575


Примеры изменений, которые вы, возможно, захотите внести, включают изменение существующих комментариев или включение комментариев для инструкций, которым не назначен комментарий. В файле pc.cmt, например, некоторые из наиболее распространенных инструкций закомментированы, чтобы не создавать слишком много комментариев, когда включены автоматические комментарии. Следующие строки, извлеченные из pc.cmt, демонстрируют, что инструкции mov x86 по умолчанию не генерируют комментарии:

NN_ltr: "Load Task Register"
//NN_mov: "Move Data"
NN_movsp: "Move to/from Special Registers"


Если вы хотите включить комментарии для инструкций mov, вы должны раскомментировать среднюю строку и перестроить базу данных комментариев, как описано ранее.

Примечание, скрытое в документации для loadint, указывает на то, что loadint должен иметь возможность найти файл ida.hlp, который включен в ваш дистрибутив IDA. Если вы получили следующее сообщение об ошибке, вам следует скопировать ida.hlp в каталог loadint, а затем повторно запустить loadint.

$ ./loadint comment.cmt ida.int
Comment base loader. Version 2.04. Copyright (c) 1991-2011 Hex-Rays
Can't initialize help system.
File name: 'ida.hlp', Reason: can't find file (take it from IDA distribution).


В качестве альтернативы вы можете использовать переключатель -n с loadint, чтобы указать местоположение <IDADIR>, как показано в следующей командной строке:

$ ./loadint -n <IDADIR> comment.cmt ida.int

Файл comment.cmt служит главным входным файлом для процесса loadint. Синтаксис этого файла описан в документации loadint. Вкратце, comment.cmt создает сопоставления типов процессоров с соответствующими файлами комментариев. Отдельные файлы комментариев для конкретного процессора, в свою очередь, определяют сопоставления конкретных инструкций с соответствующим текстом комментария для каждой инструкции.

Весь процесс управляется несколькими наборами пронумерованных (перечисления в стиле C) констант, которые определяют все типы процессоров (находятся в comment.cmt) и все возможные инструкции для каждого процессора (находятся в allins.hpp).

Если вы хотите добавить предопределенные комментарии для совершенно нового типа процессора, этот процесс несколько сложнее простого изменения существующих комментариев и довольно тесно связан с процессом создания новых процессорных модулей (см. Главу 19). Не углубляясь слишком глубоко в процессорные модули, предоставление комментариев для совершенно нового типа процессора требует, чтобы вы сначала создали новый перечисляемый набор констант (общий с вашим процессорным модулем) в allins.hpp, который определяет одну константу для каждой инструкции в интересующем наборе инструкций. Во-вторых, вы должны создать файл комментария, который отображает каждую перечисляемую константу инструкции на связанный с ней текст комментария. В-третьих, вы должны определить новую константу для вашего типа процессора (опять же, совместно с вашим процессорным модулем) и создать запись в comment.cmt, которая сопоставляет ваш тип процессора с соответствующим файлом комментариев. После того, как вы выполнили эти шаги, вы должны запустить loadint, чтобы создать новую базу данных комментариев, которая включает ваш новый тип процессора и связанные с ним комментарии.

Резюме

Хотя idsutils и loadint могут показаться вам не сразу полезными, вы научитесь ценить их возможности, как только начнете выходить за рамки наиболее распространенных вариантов использования IDA. При относительно небольших затратах времени создание одного файла .ids или .til может сэкономить вам бесчисленное количество часов всякий раз, когда вы сталкиваетесь с библиотеками, описанными этими файлами, в будущих проектах. Имейте в виду, что IDA не может поставлять описания для каждой существующей библиотеки. Назначение инструментов, описанных в этой главе, - предоставить вам гибкость для устранения пробелов в охвате библиотек IDA всякий раз, когда вы отклоняетесь от протоптанного пути IDA.
 
14. ПАТЧИНГ БИНАРНЫЙ ФАЙЛОВ И ДРУГИЕ ОГРАНИЧЕНИЯ IDA

Один из наиболее часто задаваемых вопросов новыми или потенциальными пользователями IDA: «Как я могу использовать IDA для патчинга двоичных файлов?» Ответ прост: «Ты не можешь». Предполагаемая цель IDA - помочь вам понять поведение двоичного файла. IDA не предназначена для упрощения модификации исследуемых двоичных файлов. Не желая принимать ответ «нет», упорные патчеры часто задают такие вопросы, как «А как насчет меню Edit → Patch Program?» и «Какова цель File -> Produce File -> Create EXE File?» В этой главе мы обсудим эти очевидные аномалии и посмотрим, сможем ли мы уговорить IDA помочь нам, по крайней мере, немного, с разработкой патчей для двоичных программных файлов.

Меню Infamous Patch

Меню Edit → Patch Program, впервые упомянутое в главе 11, является скрытой функцией в версии IDA с графическим интерфейсом пользователя, которую необходимо включить путем редактирования файла конфигурации idagui.cfg (меню Patch доступно по умолчанию в консольных версиях IDA). На рисунке 14-1 показаны параметры, доступные в подменю Edit -> Patch Program.

1635013666351.png


Каждый из пунктов подменю дразнит вас мыслью, что вы сможете модифицировать двоичный файл потенциально интересными способами. На самом деле эти варианты предлагают три разных способа изменения базы данных. Фактически, эти пункты меню, возможно, больше, чем любые другие, четко проводят различие между базой данных IDA и двоичным файлом, из которого была создана база данных. После создания базы данных IDA никогда не ссылается на исходный двоичный файл. Учитывая его истинное поведение, этот пункт меню было бы более уместно назвать Patch Database.

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

Изменение отдельных байтов базы данных

Пункт меню Edit -> Patch Program -> Change Byte используется для редактирования одного или нескольких байтовых значений в базе данных IDA. На рис. 14-2 показано соответствующее диалоговое окно с редактором.

1635013675042.png


В диалоговом окне отображаются 16-байтовые значения, начиная с текущего положения курсора. Вы можете изменить некоторые или все отображаемые байты, но вы не можете вносить изменения за пределами 16-го байта, не закрыв диалоговое окно, не переместив курсор в новое место дальше в базе данных и повторно открыв диалоговое окно.

Обратите внимание, что в диалоговом окне отображается виртуальный адрес и значение смещения файла для изменяемых байтов. Это значение смещения файла отражает шестнадцатеричное смещение, по которому байты находятся в исходном двоичном файле. Тот факт, что IDA сохраняет исходную информацию о смещении файла для каждого байта в базе данных, будет полезен, если вы действительно хотите разработать патч для исходного двоичного файла. Наконец, независимо от количества изменений, внесенных в байты в базе данных, в поле Original value диалогового окна всегда отображаются исходные байтовые значения, загруженные в базу данных. Автоматическая возможность возврата изменений к исходным байтовым значениям отсутствует, хотя можно создать сценарий IDA для выполнения такой задачи.

В IDA 5.5 появился лучший метод редактирования байтов базы данных в виде более функционального окна Hex View (см. Главу 5). Благодаря встроенной возможности редактирования шестнадцатеричного кода нет необходимости использовать возможность изменения байтов в IDA.

Изменение слова в базе данных

Несколько менее полезна, чем возможность байтового исправления, возможность патчинга слов в IDA. На рисунке 14-3 показано диалоговое окно IDA Patch Word, которое позволяет исправлять только одно 2-байтовое слово за раз.

1635013685191.png


Как и в диалоговом окне изменения байтов, отображаются виртуальный адрес и смещение файла. Важно помнить, что значение слова отображается с использованием естественного порядка байтов соответствующего процессора. Например, при дизассемблировании x86 слова обрабатываются как значения с прямым порядком байтов, в то время как при дизассемблировании MIPS слова обрабатываются как значения с прямым порядком байтов. Помните об этом при вводе новых значений слов. Как и в случае с диалоговым окном изменения байта, в поле Original value всегда отображается начальное значение, загруженное из исходного двоичного файла, независимо от того, сколько раз значение слова могло быть изменено с помощью диалогового окна исправления слова. Как и в случае с байтовым редактированием, может быть проще выполнять редактирование в окне Hex View IDA.

Использование диалогового окна Asseble

Возможно, наиболее интересной возможностью, доступной из меню Patch Program, является опция Assemble (Edit -> Patch Program -> Assemble). К сожалению, эта возможность доступна не для всех типов процессоров, так как она зависит от наличия возможности внутреннего ассемблера в текущем модуле процессора. Например, известно, что модуль процессора x86 поддерживает это, а модуль процессора MIPS не поддерживает. Если ассемблер недоступен, вы получите сообщение об ошибке: «К сожалению, этот процессорный модуль не поддерживает ассемблер».

Параметр Assemble позволяет вводить операторы языка ассемблера, собранные с помощью внутреннего ассемблера. Затем полученные байты инструкции записываются в текущее место на экране. На рис. 14-4 показано диалоговое окно Assemble Instruction, используемое для ввода инструкций.

1635013694419.png


В поле Instruction можно вводить по одной инструкции. Компонент ассемблера для модуля процессора IDA x86 принимает тот же синтаксис, что и в листингах дизассемблирования x86. Когда вы нажимаете OK (или нажимаете ENTER), ваша инструкция собирается, и соответствующие байты инструкции вводятся в базу данных, начиная с виртуального адреса, отображаемого в поле Address. Внутренний ассемблер IDA позволяет вам использовать символические имена в ваших инструкциях, пока эти имена существуют в программе. Такой синтаксис, как mov [ebp + var_4], eax и call sub_401896, совершенно допустим, и ассемблер правильно разрешит символьные ссылки.

После ввода инструкции диалоговое окно остается открытым и готово принять новую инструкцию по виртуальному адресу сразу после ранее введенной инструкции. При вводе дополнительных инструкций в диалоговом окне отображается предыдущая инструкция, введенная в поле Previous line.

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

Важно помнить, что возможности IDA по внесению исправлений в базу данных ограничиваются небольшими простыми исправлениями, которые легко помещаются в существующее пространство в базе данных. Если у вас есть патч, который требует значительного дополнительного места, вам нужно будет найти место, которое выделено в исходном двоичном файле, но не используется двоичным файлом. Такое пространство часто присутствует в виде заполнения, вставляемого компиляторами для выравнивания разделов двоичного файла с конкретными границами файла. Например, во многих файлах Windows PE отдельные разделы программы должны начинаться со смещения файла, кратного 512 байтам. Когда раздел не занимает места, кратное 512 байтам, этот раздел должен быть дополнен внутри файла, чтобы сохранить 512-байтовую границу для следующего раздела. Следующие строки из дизассемблированного PE-файла демонстрируют эту ситуацию:

1635013708207.png


В этом случае IDA использует директиву align [1], чтобы указать, что раздел дополняется до границы размером 512 байт (200h), начиная с адреса .text: 00409644. Верхний конец заполнения - это следующий блок кратный 512 байтам или .text: 00409800. Область с заполнением обычно заполняется компилятором нулями и довольно заметно выделяется в шестнадцатеричном представлении. В этом конкретном двоичном файле есть место для вставки до 444 (0x1BC = 409800h - 409644h) байтов исправленных данных программы, которые могут перезаписать часть или все нулевое заполнение в конце раздела .text. Вы можете исправить функцию, чтобы перейти к этой области двоичного файла, выполнить вновь вставленные программные инструкции, а затем вернуться к исходной функции.

Обратите внимание, что следующий раздел в двоичном файле, раздел .idata, фактически не начинается до адреса .idata: 0040A000. Это результат ограничения выравнивания памяти (не файла), которое требует, чтобы разделы PE начинались в границах 4 Кбайт (одна страница памяти). Теоретически должно быть возможно ввести дополнительные 2048 байтов исправленных данных в диапазон памяти 00409800-0040A000. Сложность этого заключается в том, что в образе диска исполняемого файла нет байтов, соответствующих этому диапазону памяти. Чтобы использовать это пространство, нам нужно будет выполнить нечто большее, чем простую перезапись частей исходного двоичного файла. Сначала нам нужно вставить 2048-байтовый блок данных между концом существующего раздела .text и началом раздела .idata. Во-вторых, нам нужно будет настроить размер раздела .text в заголовках PE-файла. Наконец, нам нужно изменить расположение .idata и всех последующих разделов в заголовках PE, чтобы отразить тот факт, что все последующие разделы теперь расположены на 2048 байт глубже в файле. Эти изменения могут показаться не слишком сложными, но они требуют некоторого внимания к деталям и хорошего практического знания формата PE-файла.

Выходные файлы IDA и создание патчей

Одним из наиболее интересных пунктов меню в IDA является меню File → Produce File. В соответствии с параметрами этого меню IDA может создавать файлы MAP, ASM, INC, LST, EXE, DIF и HTML. Многие из них звучат интригующе, поэтому каждый из них описан в следующих разделах.

Файлы MAP, созданные IDA

Файл .map описывает общую структуру двоичного файла, включая информацию о разделах, составляющих двоичный файл, и расположение символов в каждом разделе. При создании файла .map вас попросят указать имя файла, который вы хотите создать, и типы символов, которые вы хотите сохранить в файле .map. На рис. 14-5 показано диалоговое окно опций файла MAP, в котором вы выбираете информацию, которую хотите включить в файл .map.

1635013719865.png


generation options

Информация об адресах в файле .map представлена с использованием logical addresses. Логический адрес описывает местоположение символа с помощью номера сегмента и смещения сегмента. Первые несколько строк простого файла .map показаны в следующем листинге. В этом листинге мы показываем три сегмента и первые два из множества символов. Логический адрес _fprintf указывает, что он находится по смещению байта 69h в первом сегменте (.text).

1635013731169.png


Файлы MAP, созданные IDA, совместимы с Turbo Debugger Borland. Основная цель файлов .map - помочь в восстановлении имен символов при отладке двоичных файлов, которые могли быть удалены.

Файлы ASM, созданные IDA

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

Синтаксис целевого ассемблера определяется настройкой Целевого ассемблера на вкладке Analysis в меню Options → General . По умолчанию IDA создает файл с ассемблерным кодом, представляющий всю базу данных. Однако вы можете ограничить объем списка, щелкнув и перетащив или используя стрелку SHIFT-вверх или SHIFT-стрелку вниз для прокрутки и выбора региона, который вы хотите сбросить. В консольных версиях IDA вы должны использовать команду Anchor (ALT-L), чтобы установить точку привязки в начале выбранной области, а затем использовать клавиши со стрелками для увеличения размера области.

Файлы INC, созданные IDA

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

Файлы LST, созданные IDA

Файл LST - это не что иное, как дамп текстового файла содержимого окна дизассемблирования IDA. Вы можете сузить область создания списка, выбрав диапазон адресов для дампа, как описано ранее для файлов ASM.

EXE-файлы, созданные IDA

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

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

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

Одна из попыток предоставить возможность генерации EXE для IDA - это pe_scripts Атли Мар Гудмундссон. Это набор сценариев IDA для работы с PE-файлами. Один из сценариев называется pe_write.idc, и его цель - выгрузить рабочий PE-образ из существующей базы данных.

Если вы собираетесь исправить PE-файл, правильная последовательность событий для использования сценариев следующая:

1. Загрузите желаемый PE-файл в IDA. Убедитесь, что вы сняли флажок Make

imports section в диалоговом окне загрузчика.

2. Запустите включенный сценарий pe_sections.idc, чтобы отобразить все разделы исходного двоичного файла в новую базу данных.

3. Внесите необходимые изменения в базу данных.

4. Выполните сценарий pe_write.idc, чтобы выгрузить содержимое базы данных в новый PE-файл.

Создание сценариев с помощью IDC является предметом главы 15.

Файлы DIF, созданные IDA

Файл IDA DIF - это простой текстовый файл, в котором перечислены все байты, которые были изменены в базе данных IDA. Это наиболее полезный формат файла, если ваша цель - исправить исходный двоичный файл на основе изменений, внесенных в базу данных IDA. Формат файла довольно прост, как показано в примере файла .dif здесь:

1635013748671.png


Файл включает однострочный комментарий заголовка, за которым следует имя исходного двоичного файла, а затем список байтов в файле, которые были изменены. Каждая строка изменения указывает смещение файла (не виртуальный адрес) измененного байта, исходное значение байта и текущее значение байта в базе данных. В этом примере база данных для dif_example.exe была изменена в четырех местах, соответствующих смещениям байтов 0x2F8–0x2FB в исходном файле. Написать программу для анализа файлов .dif IDA и применения изменений к исходному двоичному файлу для создания исправленной версии двоичного файла - тривиальная задача. Одна такая утилита доступна на сопутствующем веб-сайте этой книги.

HTML-файлы, созданные IDA

IDA использует возможности разметки, доступные в HTML, для создания раскрашенных списков. HTML-файл, созданный IDA, по сути, представляет собой LST-файл с добавленными HTML-тегами для создания списка, который окрашен так же, как и фактическое окно дизассемблирования IDA. К сожалению, сгенерированные файлы HTML не содержат гиперссылок, которые упростили бы навигацию по файлу, чем если бы использовался стандартный текстовый листинг. Например, одной из полезных функций могло бы быть добавление гиперссылок ко всем ссылкам на имена, что сделало бы следующие ссылки на имена такими же простыми, как переход по ссылке.

Резюме

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

В следующих главах мы рассмотрим множество способов расширения возможностей IDA. Для всех, кто заинтересован в максимальном использовании возможностей IDA, необходимы базовые навыки написания сценариев и понимание архитектуры плагинов IDA, поскольку они предлагают вам возможность добавлять варианты поведения там, где, по вашему мнению, IDA этого не хватает.
 
15. СЦЕНАРИИ В IDA

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

Возможности использования скриптов безграничны и могут варьироваться от простых одинарных инструкций до полноценных программ, которые автоматизируют общие задачи или выполняют сложные функции анализа. С точки зрения автоматизации сценарии IDA можно рассматривать как макросы, тогда как с точки зрения анализа языки сценариев IDA служат языками запросов, которые обеспечивают программный доступ к содержимому базы данных IDA. IDA поддерживает создание сценариев на двух разных языках. Оригинальный встроенный скриптовый язык IDA называется IDC, возможно, потому что его синтаксис очень похож на C. С момента выпуска IDA 5.4 интегрированное создание сценариев с Python также поддерживалось за счет интеграции подключаемого модуля IDAPython, разработанного Гергели Эрдели. В оставшейся части этой главы мы рассмотрим основы написания и выполнения сценариев IDC и Python, а также некоторые из наиболее полезных функций, доступных авторам сценариев.

Выполнение базового сценария

Прежде чем углубляться в детали любого языка сценариев, полезно понять наиболее распространенные способы выполнения сценариев. Для доступа к механизму сценариев IDA доступны три пункта меню: File → Script File, File-> IDC Command и File → Python Command. Выбор File-> Script File указывает на то, что вы хотите запустить автономный скрипт, после чего вам будет представлен диалог выбора файла, который позволяет вам выбрать скрипт для запуска. Каждый раз, когда вы запускаете новый сценарий, программа добавляется в список последних сценариев, чтобы обеспечить легкий доступ для редактирования или повторного запуска сценария. На рис. 15-1 показано окно "Последние сценарии", доступное через пункт меню View-> Recent Scripts.

1647941390417.png


Двойной щелчок по сценарию в списке вызывает его выполнение. Всплывающее контекстное меню предлагает варианты удаления сценария из списка или открытия сценария для редактирования с помощью редактора, указанного в разделе Options → General на вкладке Разное.

В качестве альтернативы выполнению отдельного файла сценария вы можете открыть диалоговое окно ввода сценария с помощью File → IDC Command или File → Python Command. На рисунке 15-2 показан результирующий диалог ввода сценария (в данном случае для сценария IDC), который полезен в ситуациях, когда вы хотите выполнить только несколько операторов, но не хотите создавать отдельный файл сценария.

1647941405570.png


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

Последний способ легко выполняеткоманды сценария - использовать командную строку IDA. Командная строка доступна только в версиях IDA с графическим интерфейсом пользователя, и ее присутствие контролируется значением параметра DISPLAY_COMMAND_LINE в <IDADIR> /cfg/idagui.cfg. Командная строка включена по умолчанию, начиная с версии IDA 5.4. На рис. 15-3 показана командная строка, которая отображается в нижнем левом углу рабочего пространства IDA под окном вывода.

1647941416623.png


Интерпретатор, который будет использоваться для выполнения командной строки, помечен слева от поля ввода командной строки. На рисунке 15-3 командная строка настроена для выполнения операторов IDC. При нажатии на эту метку открывается всплывающее меню, показанное на рисунке 15-3, позволяющее связать любой интерпретатор (IDC или Python) с командной строкой.

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

Имея за плечами базовую способность выполнять сценарии, пришло время сосредоточиться на специфике двух доступных языков сценариев IDA, IDC и Python. Мы начнем с описания собственного языка сценариев IDA, IDC, и закончим обсуждением интеграции IDA с Python, которая будет во многом опираться на фундамент, заложенный в следующих разделах IDC.

Язык IDC

В отличие от некоторых других аспектов IDA, для языка IDC имеется достаточная помощь в справочной системе IDA. Темы, доступные на верхнем уровне справочной системы, включают язык IDC, который охватывает основы синтаксиса IDC, и Указатель функций IDC, который предоставляет исчерпывающий список встроенных функций, доступных программистам IDC.

IDC - это язык сценариев, который заимствует большую часть своих синтаксических элементов из C. Начиная с IDA 5.6, IDC фактически берет на себя большую часть C++ с введением объектно-ориентированных функций и обработки исключений. Из-за его сходства с C и C ++ мы будем описывать IDC в терминах этих языков и сосредоточимся в первую очередь на отличиях IDC.

Переменные IDC

IDC - это язык со слабой типизацией, что означает, что переменные не имеют явного типа. В IDC используются три основных типа данных: целые числа (в документации IDA используется длинное имя типа), строки и значения с плавающей запятой, при этом подавляющее большинство операций выполняется с целыми числами и строками. Строки рассматриваются как собственный тип данных в IDC, и нет необходимости отслеживать пространство, необходимое для хранения строки, а также то, имеет ли строка завершение нуля или нет. Начиная с IDA 5.6, IDC включает ряд дополнительных типов переменных, включая объекты, ссылки и указатели на функции.

Все переменные должны быть объявлены до их использования. IDC поддерживает локальные переменные, а также, начиная с IDA 5.4, глобальные переменные. Ключевое слово IDC auto используется для введения объявления локальной переменной, а объявления локальной переменной могут включать начальные значения. В следующих примерах показаны допустимые объявления локальных переменных IDC:

auto addr, reg, val; // legal, multiple variables declared with no initializers
auto count = 0; // declaration with initialization


IDC распознает многострочные комментарии в стиле C с использованием / * * / и комментарии в конце строки в стиле C++ с использованием //. Также обратите внимание, что несколько переменных могут быть объявлены в одном операторе и что все операторы в IDC заканчиваются точкой с запятой (как в C). IDC не поддерживает массивы в стиле C (срезы представлены в IDA 5.6), указатели (хотя ссылки поддерживаются, начиная с IDA 5.6) или сложные типы данных, такие как структуры и объединения. Классы представлены в IDA 5.6.

Объявления глобальных переменных вводятся с использованием ключевого слова extern, и их объявления допустимы как внутри, так и вне любого определения функции. Нельзя указывать начальное значение при объявлении глобальной переменной. В следующем листинге показано объявление двух глобальных переменных.

extern outsideGlobal;
static main() {
extern insideGlobal;
outsideGlobal = "Global";
insideGlobal = 1;
}


Глобальные переменные выделяются при первом обнаружении во время сеанса IDA и сохраняются, пока этот сеанс остается активным, независимо от количества баз данных, которые вы можете открывать и закрывать.

Выражения IDC

За некоторыми исключениями, IDC поддерживает практически все арифметические и логические операторы, доступные в C, включая тернарный оператор (? :). Составные операторы присваивания в форме op = (+ =, * =, >> = и т.п.) не поддерживаются. Оператор запятой поддерживается, начиная с IDA 5.6. Все целочисленные операнды рассматриваются как значения со знаком. Это влияет на сравнения целых чисел (которые всегда подписаны) и на оператор сдвига вправо (>>), который всегда выполняет арифметический сдвиг с репликацией знаковых битов. Если вам требуется логический сдвиг вправо, вы должны реализовать его самостоятельно, замаскировав верхний бит результата, как показано здесь:

result = (x >> 1) & 0x7fffffff; //set most significant bit to zero

Поскольку строки являются собственным типом в IDC, некоторые операции со строками принимают другое значение, чем в C. Присвоение строкового операнда строковой переменной приводит к операции копирования строки; таким образом, отпадает необходимость в функциях копирования или дублирования строк, таких как strcpy и strdup в языке C. Кроме того, добавление двух строковых операндов приводит к объединению двух операндов; таким образом "Hello" + "World" дает "HelloWorld"; нет необходимости в функции конкатенации, такой как strcat в языке C. Начиная с IDA 5.6, IDC предлагает оператор среза для использования со строками. Программисты Python знакомы со срезами, которые в основном позволяют указывать подпоследовательности переменных, похожих на массивы. Срезы указываются с помощью квадратных скобок и начального (включительно) и конечного (исключительного) индекса. Требуется хотя бы один индекс. Следующий листинг демонстрирует использование срезов IDC.

auto str = "String to slice";
auto s1, s2, s3, s4;
s1 = str[7:9]; // "to"
s2 = str[:6]; // "String", omitting start index starts at 0
s3 = str[10:]; // "slice", omitting end index goes to end of string
s4 = str[5]; // "g", single element slice, similar to array element access


Обратите внимание, что, хотя в IDC нет доступных типов данных массивов, оператор среза эффективно позволяет обрабатывать строки IDC, как если бы они были массивами.

Статементы IDC

Как и в C, все простые операторы заканчиваются точкой с запятой. Единственный составной оператор в стиле C, который не поддерживает IDC, - это оператор switch. При использовании циклов for имейте в виду, что IDC не поддерживает составные операторы присваивания, которые могут повлиять на вас, если вы хотите считать по чему-либо, кроме одного, как показано здесь:
auto i;
for (i = 0; i < 10; i += 2) {} // illegal, += is not supported
for (i = 0; i < 10; i = i + 2) {} // legal


В IDA 5.6 IDC вводит блоки try/catch и связанный с ними оператор throw, синтаксически похожие на исключения C++. Встроенная справка IDA содержит подробные сведения о реализации обработки исключений IDC.

Для составных операторов IDC использует тот же синтаксис и семантику привязки ({}), что и C. Внутри блока в фигурных скобках допустимо объявлять новые переменные, если объявления переменных являются первыми операторами внутри блока. Однако IDC не требует строгого соблюдения объема вновь введенных переменных, поскольку на такие переменные можно ссылаться за пределами блока, в котором они были объявлены. Рассмотрим следующий пример:

if (1) { //always true
auto x;
x = 10;
}
else { //never executes
auto y;
y = 3;
}
Message("x = %d\n", x); // x remains accessible after its block terminates
Message("y = %d\n", y); // IDC allows this even though the else did not execute


Операторы вывода (функция Message аналогична printf в языке C) сообщают нам, что x = 10 и y = 0. Учитывая, что IDC не ограничивает строго область действия x, неудивительно, что нам разрешено печатать значение x. Что несколько удивительно, так это то, что y вообще доступен, учитывая, что блок, в котором объявлен y, никогда не выполняется. Это просто причуда IDC. Обратите внимание на то, что хотя IDC может свободно ограничивать область видимости переменных внутри функции, переменные, объявленные в одной функции, по-прежнему остаются недоступными для любой другой функции.

Функции IDC

IDC поддерживает пользовательские функции только в автономных программах (файлы .idc).Пользовательские функции не поддерживаются при использовании диалогового окна команд IDC (см. "Использование диалогового окна команд IDC" на стр. 255). В синтаксисе IDC для объявления пользовательских функций он больше всего отличается от C. Ключевое слово static используется для представления пользовательской функции, а список параметров функции состоит исключительно из списка имен параметров, разделенных запятыми. В следующем листинге подробно описана базовая структура пользовательской функции:

static my_func(x, y, z) {
//declare any local variables first
auto a, b, c;
//add statements to define the function's behavior
// ...
}

До версии IDA 5.6 все параметры функции вызывались строго по значению. Передача параметров с вызовом по ссылке была введена в IDA 5.6. Интересно, что то, передается ли параметр с использованием вызова по значению или вызова по ссылке, определяется способом вызова функции, а не способом объявления функции. Унарный оператор & используется в вызове функции (а не в объявлении функции) для обозначения того, что аргумент передается по ссылке. В следующих примерах показаны вызовы функции my_func из предыдущего листинга с использованием передачи параметров как по значению, так и по ссылке.

auto q = 0, r = 1, s = 2;
my_func(q, r, s); //all three arguments passed using call-by-value
//upon return, q, r, and s hold 0, 1, and 2 respectively
my_func(q, &r, s); //q and s passed call-by-value, r is passed call-by-reference
//upon return, q, and s hold 0 and 2 respectively, but r may have
//changed. In this second case, any changes that my_func makes to its
//formal parameter y will be reflected in the caller as changes to r


Объявления функций никогда не указывают, возвращает ли функция значение явно или какой тип значения возвращается, когда функция действительно дает результат.

ИСПОЛЬЗОВАНИЕ КОМАНДНОГО ДИАЛОГА IDC

Командный диалог IDC предлагает простой интерфейс для ввода коротких последовательностей кода IDC. Командный диалог - отличный инструмент для быстрого ввода и тестирования новых скриптов без хлопот по созданию отдельного файла скрипта. Самая важная вещь, о которой следует помнить при использовании диалогового окна команд, - это то, что вы не должны определять какие-либо функции внутри диалогового окна. По сути, IDA оборачивает ваши операторы в функцию, а затем вызывает эту функцию для выполнения ваших операторов. Если бы вы определяли функцию в диалоговом окне, результирующим эффектом была бы функция, определенная внутри функции, и, поскольку объявления вложенных функций не разрешены в IDC (или в C, если на то пошло), возникнет синтаксическая ошибка.


Если вы хотите вернуть значение из функции, используйте оператор return, чтобы вернуть желаемое значение. Допускается возвращать совершенно разные типы данных из разных путей выполнения внутри функции. Другими словами, в некоторых случаях функция может возвращать строку, а в других случаях та же функция может возвращать целое число. Как и в C, использование оператора return внутри функции необязательно. Однако, в отличие от C, любая функция, которая явно не возвращает значение, неявно возвращает нулевое значение.

В заключение, начиная с IDA 5.6, функции делают еще один шаг к тому, чтобы стать первоклассными объектами в IDC. Теперь можно передавать ссылки на функции в качестве аргументов другим функциям и возвращать ссылки на функции в результате выполнения функции. Следующий листинг демонстрирует использование параметров функции и функций в качестве возвращаемых значений.

static getFunc() {
return Message; //return the built-in Message function as a result
}
static useFunc(func, arg) { //func here is expected to be a function reference
func(arg);
}
static main() {
auto f = getFunc();
f("Hello World\n"); //invoke the returned function f
useFunc(f, "Print me\n"); //no need for & operator, functions always call-by-reference
}

Объекты IDC


Еще одна функция, представленная в IDA 5.6, - это возможность определять классы и, как следствие, иметь переменные, представляющие объекты. В дальнейшем мы предполагаем, что вы знакомы с объектно-ориентированным языком программирования, таким как C++ или Java.

РАЗВИТИЕ СЦЕНАРИЙ IDA

Если вы не догадывались, что большое количество изменений в IDC было внесено в IDA 5.6, значит, вы не обращали внимания. После интеграции IDAPython в IDA 5.4 компания Hex-Rays попыталась обновить IDC, в результате чего многие из функций, упомянутых в этой главе, были введены в IDA 5.6. Попутно JavaScript даже рассматривался как потенциальное дополнение к линейке сценариев IDA.


IDC определяет корневой класс с именем object, от которого в конечном итоге происходят все классы, и при создании новых классов поддерживается единичное наследование. IDC не использует такие спецификаторы доступа, как публичный и частный; все члены класса фактически публичны. Объявления классов содержат только определения функций-членов класса. Чтобы создать элементы данных в классе, вы просто создайте оператор присваивания, который присваивает значение члену данных. Следующий список поможет прояснить ситуацию.

class ExampleClass {
ExampleClass(x, y) { //constructor
this.a = x; //all ExampleClass objects have data member a
this.b = y; //all ExampleClass objects have data member b
}
~ExampleClass() { //destructor
}
foo(x) {
this.a = this.a + x;
}
//... other member functions as desired
};
static main() {
ExampleClass ex; //DON’T DO THIS!! This is not a valid variable declaration
auto ex = ExampleClass(1, 2); //reference variables are initialized by assigning
//the result of calling the class constructor
ex.foo(10); //dot notation is used to access members
ex.z = "string"; //object ex now has a member z, BUT the class does not
}


Для получения дополнительной информации о классах IDC и их синтаксисе обратитесь к соответствующему разделу встроенного файла справки IDA.

Программы IDC

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

Программные файлы IDC требуют, чтобы вы использовали определяемые пользователем функции. Как минимум, вы должны определить функцию с именем main, которая не принимает аргументов. В большинстве случаев вы также захотите включить файл idc.idc, чтобы подобрать полезные определения макросов, которые он содержит. В следующем листинге подробно описаны компоненты минимального файла программы IDC:

#include <idc.idc> // useful include directive
//declare additional functions as required
static main() {
//do something fun here
}


IDC распознает следующие директивы препроцессора в стиле C:

#include <file>

Включает указанный файл в текущий файл.

#define <name> [optional value]

Создает макрос с именем name и, при необходимости, присваивает ему указанное значение. IDC заранее определяет ряд макросов, которые можно использовать для тестирования различных аспектов среды выполнения вашего скрипта. К ним относятся, среди прочего, _NT_, _LINUX_, _MAC_, _GUI_ и _TXT_. Смотри Раздел "Предопределенные символы" в файле справки IDA для получения дополнительной информации об этих и других символах.

#ifdef <name>

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

#else

Необязательно используется вместе с #ifdef, чтобы предоставить альтернативный набор операторов для обработки в случае, если указанный макрос не существует.

#endif

Это обязательный терминатор для блока #ifdef или # ifdef/#else.

#undef <name>

Удаляет названный макрос.

Обработка ошибок в IDC

Никто и никогда не станет хвалить IDC за ее возможности по сообщению об ошибках. Есть два типа ошибок, с которыми вы можете столкнуться при запуске сценариев IDC: ошибки синтаксического анализа и ошибки времени выполнения. Ошибки синтаксического анализа - это те ошибки, которые препятствуют выполнению вашей программы и включают такие вещи, как синтаксические ошибки, ссылки на неопределенные переменные и предоставление неправильного количества аргументов функции. На этапе синтаксического анализа IDC сообщает только о первой обнаруженной ошибке синтаксического анализа. В некоторых случаях сообщения об ошибках правильно определяют как местоположение, так и тип ошибки (hello_world.idc, 20: отсутствует точка с запятой), в то время как в других случаях сообщения об ошибках не предлагают реальной помощи (синтаксическая ошибка рядом с: <END>). Сообщается только первая ошибка, обнаруженная во время синтаксического анализа. В результате в сценарии с 15 синтаксическими ошибками может потребоваться 15 попыток запуска сценария, прежде чем вы будете проинформированы о каждой ошибке.

Ошибки времени выполнения обычно встречаются реже, чем ошибки синтаксического анализа. При обнаружении ошибок времени выполнения сценарий немедленно завершает работу. Один из примеров ошибки времени выполнения является результатом попытки вызвать неопределенную функцию, которая по какой-то причине не обнаруживается при первоначальном анализе сценария. Другая проблема возникает со сценариями, выполнение которых требует слишком много времени. После запуска сценария не существует простого способа завершить его, если он непреднамеренно заходит в бесконечный цикл или просто выполняется больше времени, чем вы готовы ждать. После того, как сценарий выполняется более двух-трех секунд, IDA отображает диалоговое окно, показанное на рис. 15-4.

1647941660678.png


Этот диалог - единственное средство, с помощью которого вы можете завершить сценарий, который не завершился должным образом.

Отладка - еще одно слабое место IDC. Кроме либерального использования операторов вывода, нет способа отлаживать сценарии IDC. С введением обработки исключений (try/catch) в IDA 5.6 действительно стало возможным создавать более надежные сценарии, которые могут завершаться или продолжаться так же аккуратно, как вы выберете.

Постоянное хранилище данных в IDC

Возможно, вы относитесь к любопытному типу людей, которые, не веря в то, что мы предоставим достаточное освещение возможностей написания сценариев IDA, поспешили посмотреть, что по этому поводу сообщает справочная система IDA. Если да, добро пожаловать обратно, а если нет, мы ценим, что вы так долго с нами. В любом случае, где-то по пути вы, возможно, приобрели знания, утверждающие, что IDC на самом деле поддерживает массивы, и в этом случае вы наверняка сомневаетесь в качестве этой книги. Мы призываем вас, дать нам шанс разобраться в этой потенциальной путанице.

Как упоминалось ранее, IDC не поддерживает массивы в традиционном смысле объявления большого блока хранения и последующего использования нотации нижнего индекса для доступа к отдельным элементам в этом блоке. Однако в документации IDA по сценариям упоминается нечто, называемое глобальными постоянными массивами. Глобальные массивы IDC лучше рассматривать как постоянные именованные объекты. Объекты просто являются разреженными массивами. Глобальные массивы хранятся в базе данных IDA и являются постоянными при вызовах скриптов и сеансах IDA. Данные хранятся в глобальных массивах путем указания индекса и значения данных, которые должны быть сохранены по указанному индексу в массиве. Каждый элемент в массиве может одновременно содержать одно целочисленное значение и одно строковое значение. Глобальные массивы IDC не предоставляют средств для хранения значений с плавающей запятой.

ПРИМЕЧАНИЕ

Для чрезмерно любопытных внутренний механизм IDA для хранения постоянных массивов называется netnode. В то время как функции манипулирования массивами, описанные далее, предоставляют абстрактный интерфейс для сетевых узлов, доступ к данным сетевых узлов более низкого уровня доступен с помощью IDA SDK, который обсуждается вместе с сетевыми узлами в главе 16.


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

long CreateArray(string name)

Эта функция создает постоянный объект с указанным именем. Возвращаемое значение - это целочисленный дескриптор, необходимый для любого будущего доступа к массиву. Если именованный объект уже существует, возвращается значение –1.

long GetArrayId(string name)

После создания массива последующий доступ к нему должен осуществляться через целочисленный дескриптор, который можно получить, просмотрев имя массива. Возвращаемое значение этой функции - целочисленный дескриптор, который будет использоваться для всех будущих взаимодействий с массивом. Если именованный массив не существует, возвращается значение –1.

long SetArrayLong(long id, long idx, long value)

Сохраняет целочисленное значение в массиве, на который ссылается id, в позиции, указанной idx. Возвращаемое значение - 1 в случае успеха или 0 в случае неудачи. Операция завершится неудачно, если идентификатор массива недействителен.

long SetArrayString(long id, long idx, string str)

Сохраняет строковое значение в массиве, на который ссылается id, в позиции, указанной idx. Возвращаемое значение - 1 в случае успеха или 0 в случае неудачи. Операция завершится неудачно, если идентификатор массива недействителен.

string or long GetArrayElement(long tag, long id, long idx)

Хотя существуют различные функции для хранения данных в массиве в зависимости от типа данных, которые должны быть сохранены, существует только одна функция для извлечения данных из массива. Эта функция извлекает целое или строковое значение из указанного индекса (idx) в указанном массиве (id). Будет ли получено целое число или строка, определяется значением параметра тега, который должен быть одной из констант AR_LONG (для получения целого числа) или AR_STR (для получения строки).

long DelArrayElement(long tag, long id, long idx)

Удаляет содержимое указанного массива из указанного массива. Значение тега определяет, будет ли удалено целочисленное или строковое значение, связанное с указанным индексом.

void DeleteArray(long id)

Удаляет массив, на который ссылается id, и все связанное с ним содержимое. После создания массива он продолжает существовать даже после завершения сценария, пока не будет сделан вызов DeleteArray для удаления массива из базы данных, в которой он был создан.

long RenameArray(long id, string newname)

Переименовывает массив, на который ссылается id, на newname. Возвращает 1 в случае успеха или 0 в случае сбоя операции.

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

Значения, хранящиеся в глобальном массиве IDC, сохраняются в течение всего времени существования базы данных, в которой был выполнен сценарий. Вы можете проверить наличие массива, изучив возвращаемое значение функции CreateArray. Если значения, хранящиеся в массиве, применимы только к определенному вызову сценария, то массив следует удалить до завершения сценария. Удаление массива гарантирует, что никакие глобальные значения не переносятся от одного выполнения сценария к последующему выполнению того же сценария.

Связывание скриптов IDC с горячими клавишами

Иногда вы можете разработать сценарий, настолько удивительный по своей полезности, что вам потребуется получить к нему доступ одним или двумя нажатиями клавиш. Когда это произойдет, вам нужно будет назначить последовательность горячих клавиш, которую вы можете использовать для быстрой активации вашего скрипта. К счастью, IDA предоставляет простые средства для этого. Каждый раз при запуске IDA выполняется сценарий, содержащийся в <IDADIR> /idc/ida.idc. Версия этого скрипта по умолчанию содержит пустую основную функцию и поэтому ничего не делает. Чтобы связать горячую клавишу с одним из ваших скриптов, вам нужно добавить две строки в ida.idc. Первая строка, которую вы должны добавить, - это директива include для включения файла сценария в ida.idc. Вторая строка, которую вы должны добавить, - это вызов в main функции AddHotkey, чтобы связать определенную горячую клавишу с вашей замечательной функцией IDC. Это может привести к тому, что ida.idc будет выглядеть так:

#include <idc.idc>
#include <my_amazing_script.idc>
static main() {
AddHotkey("z", "MyAmazingFunc"); //Now 'z' invokes MyAmazingFunc
}


Если горячая клавиша, которую вы пытаетесь связать со своим скриптом, уже была назначена другому действию IDA (горячая клавиша меню или последовательность активации плагина), AddHotkey автоматически завершается сбоем, не имея возможности обнаружить сбой, кроме того факта, что ваша функция не выполняется когда ваша последовательность горячих клавиш активирована.

Здесь есть два важных момента: стандартным каталогом включения для сценариев IDC является <IDADIR> / idc и что вы не должны называть свою функцию сценария main. Если вы хотите, чтобы IDA легко находила ваш скрипт, вы можете скопировать его в <IDADIR> / idc. Если вы собираетесь оставить файл сценария в другом месте, вам нужно будет указать полный путь к вашему сценарию в инструкции include. Во время тестирования вашего скрипта будет полезно запустить ваш скрипт как отдельную программу с функцией main. Однако, когда вы будете готовы связать свой скрипт с горячей клавишей, вы не сможете использовать имя main, потому что оно будет конфликтовать с функцией main в ida.idc. Вы должны переименовать свою основную функцию и использовать новое имя при вызове AddHotkey.

Полезные функции IDC

На этом этапе у вас есть вся информация, необходимая для написания правильно сформированных сценариев IDC. Чего вам не хватает, так это способности выполнять любое полезное взаимодействие с самой IDA. IDC предоставляет длинный список встроенных функций, который предлагает множество различных способов доступа к базе данных. Все функции в некоторой степени задокументированы в справочной системе IDA в разделе Указатель функций IDC. В большинстве случаев документация представляет собой не что иное, как соответствующие строки, скопированные из основного включаемого файла IDC, idc.idc. Знакомство с довольно краткой документацией - один из самых неприятных аспектов изучения IDC. В общем, нет простого способа ответить на вопрос "Как мне сделать x в IDC? " Самый распространенный способ выяснить, как что-то сделать, - просмотреть список функций IDC в поисках той, которая, судя по названию, выполняет то, что вам нужно. Это, конечно, предполагает, что функции названы в соответствии с их назначением, но их назначение не всегда может быть очевидным. Например, во многих случаях функции, извлекающие информацию из базы данных, называются GetXXX; тем не менее; во многих других случаях префикс Get не используется. Функции, изменяющие базу данных, могут называться SetXXX, MakeXXX или как-то иначе. Таким образом, если вы хотите использовать IDC, привыкните просматривать список функций и читать их описания. Если вы оказались в полной растерянности, не бойтесь использовать форумы поддержки на Hex-Rays.

Цель оставшейся части этого раздела - указать на некоторые , из наиболее полезных (по нашему опыту) функции IDC, и сгруппировать их по функциональным областям. Даже если вы собираетесь писать скрипт только на Python, знакомство с перечисленными функциями будет полезно для вас, потому что IDAPython предоставляет эквиваленты Python для каждой функции, перечисленной здесь. Однако мы не пытаемся охватить все функции IDC, поскольку они уже описаны в справочной системе IDA.

Функции для чтения и изменения данных

Следующие функции обеспечивают доступ к отдельным байтам, словам и двойным словам в базе данных:

long Byte(long addr)

Считывает байтовое значение из виртуального адреса addr.

long Word(long addr)

Считывает слово (2-байтовое) значение из виртуального адреса addr.

long Dword(long addr)

Считывает двойное слово (4-байтовое) из виртуального адреса addr.

void PatchByte(long addr, long val)

Устанавливает байтовое значение для виртуального адреса addr.

void PatchWord(long addr, long val)

Устанавливает значение слова для виртуального адреса addr.

void PatchDword(long addr, long val)

Устанавливает значение двойного слова для виртуального адреса addr.

bool isLoaded(long addr)

Возвращает 1, если адрес содержит допустимые данные, в противном случае - 0.

Каждая из этих функций учитывает порядок байтов (прямой или обратный порядок байтов) текущего модуля процессора при чтении и записи базы данных. Функции PatchXXX также обрезают предоставленное значение до подходящего размера, используя только правильное количество младших байтов в соответствии с вызываемой функцией. Например, вызов PatchByte (0x401010, 0x1234) исправит местоположение 0x401010 байтовым значением 0x34 (младший байт 0x1234). Если при чтении базы данных с помощью Byte, Word и Dword указан недопустимый адрес, будут возвращены значения 0xFF, 0xFFFF и 0xFFFFFFFF соответственно.

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

Из-за причуды при обновлении представления дизассемблирования в IDA вы можете обнаружить, что результаты операции исправления не сразу видны. В таких случаях прокрутка от исправленного местоположения и затем прокрутка назад к исправленному местоположению обычно приводит к правильному обновлению дисплея.

Функции взаимодействия с пользователем

Чтобы вообще выполнять какое-либо взаимодействие с пользователем, вам необходимо ознакомиться с функциями ввода/вывода IDC. В следующем списке перечислены некоторые из наиболее полезных интерфейсных функций IDC:

void Message(string format, ...)

Печатает отформатированное сообщение в окне вывода. Эта функция аналогична функции printf в языке C и принимает строку формата printf.

void print(...)

Печатает строковое представление каждого аргумента в окно вывода.

void Warning(string format, ...)

Отображает отформатированное сообщение в диалоговом окне.

string AskStr(string default, string prompt)

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

string AskFile(long doSave, string mask, string prompt)

Отображает диалоговое окно выбора файла, упрощающее задачу выбора файла. Новые файлы могут быть созданы для сохранения данных (doSave = 1) или существующие файлы могут быть выбраны для чтения данных (doSave = 0). Отображаемый список файлов может быть отфильтрован по маске (например, *. * Или * .idc). Возвращает имя выбранного файла или 0, если диалог был отменен.

long AskYN(long default, string prompt)

Предлагает пользователю ответить на вопрос "да" или "нет", выделяя ответ по умолчанию (1 = да, 0 = нет, –1 = отменить). Возвращает целое число, представляющее выбранный ответ.

long ScreenEA()

Возвращает виртуальный адрес текущего местоположения курсора.

bool Jump(long addr)

Перемещает окно дизассембилрования по указанному адресу.

Поскольку в IDC отсутствуют какие-либо средства отладки, вы можете использовать функцию сообщений в качестве основного средства отладки. Существует несколько других функций AskXXX для обработки более специализированных случаев ввода, таких как целочисленный ввод. Пожалуйста, обратитесь к документации по справочной системе для получения полного списка доступных функций AskXXX. Функция ScreenEA очень полезна для определения текущего местоположения курсора, когда вы хотите создать сценарий, который адаптирует свое поведение в зависимости от местоположения курсора. Точно так же функция Jump полезна, когда у вас есть сценарий, который должен привлечь внимание пользователя к определенному месту в процессе разборки.

Функции обработки строк

Хотя простое присваивание строк и конкатенация выполняются с помощью базовых операторов в IDC, более сложные операции должны выполняться с использованием доступных функций обработки строк, некоторые из которых подробно описаны здесь:

string form(string format, ...) // pre IDA 5.6

Возвращает новую строку, отформатированную в соответствии с предоставленными строками и значениями формата. Это приблизительный эквивалент функции sprintf в языке C.

string sprintf(string format, ...) // IDA 5.6+

В IDA 5.6 sprintf заменяет форму (см. Выше).

long atol(string val)

Преобразует десятичное значение val в соответствующее целочисленное представление.

long xtol(string val)

Преобразует шестнадцатеричное значение val (которое может начинаться с 0x) в соответствующее целочисленное представление.

string ltoa(long val, long radix)

Возвращает строковое представление val по указанной системе счисления (2, 8, 10 или 16).

long ord(string ch)

Возвращает значение ASCII односимвольной строки ch.

long strlen(string str)

Возвращает длину предоставленной строки.

long strstr(string str, string substr)

Возвращает индекс подстроки в строке или –1, если подстрока не найдена.

string substr(string str, long start, long end)

Возвращает подстроку, содержащую символы от начала до конца-1 строки str. При использовании срезов (IDA 5.6+) эта функция эквивалентна tr [start: end].

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

Функции ввода/вывода файлов

Окно вывода не всегда может быть идеальным местом для отправки вывода ваших скриптов. Для сценариев, которые генерируют большой объем текста или сценариев, которые генерируют двоичные данные, вы можете вместо этого вывести файлы на диск. Мы уже обсуждали использование функции AskFile для запроса имени файла у пользователя. Однако AskFile возвращает только строку, содержащую имя файла. Функции IDC по работе с файлами подробно описаны здесь:

long fopen(string filename, string mode)

Возвращает целочисленный дескриптор файла (или 0 в случае ошибки) для использования со всеми функциями ввода-вывода файлов IDC. Параметр режима подобен режимам, используемым в fopen C (r для чтения, w для записи и т. д.).

void fclose(long handle)

Закрывает файл, указанный дескриптором файла из fopen.

long filelength(long handle)

Возвращает длину указанного файла или –1 в случае ошибки.

long fgetc(long handle)

Считывает один байт из заданного файла. Возвращает -1 при ошибке.

long fputc(long val, long handle)

Записывает один байт в указанный файл. Возвращает 0 в случае успеха или –1 в случае ошибки.

long fprintf(long handle, string format, ...)

Записывает форматированную строку в указанный файл.

long writestr(long handle, string str)

Записывает указанную строку в указанный файл.

string/long readstr(long handle)

Читает строку из заданного файла. Эта функция считывает все символы (включая не-ASCII) вплоть до символа перевода следующей строки (ASCII 0xA) включительно. Возвращает строку в случае успеха или -1 в конце файла.

long writelong(long handle, long val, long bigendian)

Записывает 4-байтовое целое число в заданный файл, используя порядок байтов с обратным порядком байтов (bigendian = 1) или прямым порядком байтов (bigendian = 0).

long readlong(long handle, long bigendian)

Считывает 4-байтовое целое число из заданного файла с обратным порядком байтов (bigendian = 1) или прямым порядом следования байтов (bigendian = 0).

long writeshort(long handle, long val, long bigendian)

Записывает 2-байтовое целое число в указанный файл с обратным порядком байтов (bigendian = 1) или прямым порядком следования байтов (bigendian = 0).

long readshort(long handle, long bigendian)

Считывает 2-байтовое целое число из заданного файла, используя порядок байтов с обратным порядком байтов (bigendian = 1) или прямым порядком байтов (bigendian = 0).

bool loadfile(long handle, long pos, long addr, long length)

Считывает количество байтов из позиции pos в заданном файле и записывает эти байты в базу данных, начиная с адреса addr.

bool savefile(long handle, long pos, long addr, long length)

Записывает длину в байтах, начиная с адреса базы данных addr, до позиции pos в данном файле.
 
Управление именами баз данных

Необходимость манипулировать именованными локациями довольно часто возникает в сценариях. Следующие функции IDC доступны для работы с именованными местоположениями в базе данных IDA:

string Name(long addr)

Возвращает имя, связанное с данным адресом, или возвращает пустую строку, если местоположение не имеет имени. Эта функция не возвращает назначенные пользователем имена, если имена помечены как локальные.

string NameEx(long from, long addr)

Возвращает имя, связанное с адресом. Возвращает пустую строку, если местоположение не имеет имени. Эта функция возвращает определяемые пользователем локальные имена, если from является любым адресом внутри функции, который также содержит адрес.

bool MakeNameEx(long addr, string name, long flags)

Присваивает данное имя данному адресу. Имя создается с атрибутами, указанными в битовой маске флагов. Эти флаги описаны в документации файла справки для MakeNameEx и используются для указания атрибутов, например, является ли имя локальным или общедоступным или должно ли оно быть указано в окне имен.

long LocByName(string name)

Возвращает адрес местоположения с заданным именем. Возвращает BADADDR (-1), если в базе данных нет такого имени.

long LocByNameEx(long funcaddr, string localname)

Ищет заданное локальное имя в функции, содержащей funcaddr. Возвращает BADADDR (–1), если в данной функции нет такого имени.

Работа с функциями

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

long GetFunctionAttr(long addr, long attrib)

Возвращает запрошенный атрибут для функции, содержащей заданный адрес. Список констант атрибутов см. в справочной документации IDC. Например, чтобы найти конечный адрес функции, используйте GetFunctionAttr(addr, FUNCATTR_END);.

string GetFunctionName(long addr)

Возвращает имя функции, содержащей заданный адрес, или пустую строку, если заданный адрес не принадлежит функции.

long NextFunction(long addr)

Возвращает начальный адрес следующей функции после заданного адреса. Возвращает -1, если в базе данных больше нет функций.

long PrevFunction(long addr)

Возвращает начальный адрес ближайшей функции, предшествующей заданному адресу. Возвращает -1, если перед заданным адресом нет функции. Используйте функцию LocByName, чтобы найти начальный адрес функции по имени функции.

Функции перекрестных ссылок кода

Перекрестные ссылки были рассмотрены в главе 9. IDC предлагает функции для доступа к информации о перекрестных ссылках, связанной с любой инструкцией. Решение о том, какие функции соответствуют потребностям ваших сценариев, может быть немного запутанным. Это требует, чтобы вы понимали, заинтересованы ли вы в отслеживании потоков, покидающих данный адрес, или вы заинтересованы в повторении всех местоположений, которые ссылаются на данный адрес. Здесь описаны функции для выполнения обеих предыдущих операций. Некоторые из этих функций предназначены для поддержки итерации по набору перекрестных ссылок. Такие функции поддерживают понятие последовательности перекрестных ссылок и требуют наличия текущей перекрестной ссылки, чтобы вернуть следующую перекрестную ссылку. Примеры использования итераторов перекрестных ссылок приведены в разделе "Перечисление перекрестных ссылок" .

long Rfirst(long from)

Возвращает первое местоположение, которому данный адрес передает управление. Возвращает BADADDR (-1), если данный адрес не ссылается ни на какой другой адрес.

long Rnext(long from, long current)

Возвращает следующее местоположение, на которое данный адрес (от) передает управление, учитывая, что текущее уже было возвращено предыдущим вызовом Rfirst или Rnext. Возвращает BADADDR, если больше нет перекрестных ссылок.

long XrefType()

Возвращает константу, указывающую тип последней перекрестной ссылки, возвращаемой функцией поиска перекрестных ссылок, такой как Rfirst. Для перекрестных ссылок кода этими константами являются fl_CN (ближний вызов), fl_CF (дальний вызов), fl_JN (ближний переход), fl_JF (дальний переход) и fl_F (обычный последовательный поток).

long RfirstB(long to)

Возвращает первое местоположение, передающее управление на указанный адрес. Возвращает BADADDR (-1), если нет ссылок на данный адрес.

long RnextB(long to, long current)

Возвращает следующее местоположение, которое передает управление на заданный адрес (to), учитывая, что текущее уже было возвращено предыдущим вызовом RfirstB или RnextB. Возвращает BADADDR, если больше нет перекрестных ссылок на данное местоположение.

Каждый раз, когда вызывается функция перекрестной ссылки, устанавливается внутренняя переменная состояния IDC, которая указывает тип последней возвращенной перекрестной ссылки. Если вам нужно знать, какой тип перекрестной ссылки вы получили, то вы должны вызвать XrefType перед вызовом другой функции поиска перекрестной ссылки.

Функции перекрестных ссылок на данные

Функции для доступа к информации о перекрестных ссылках данных очень похожи на функции, используемые для доступа к информации о перекрестных ссылках кода. Эти функции описаны здесь:

long Dfirst(long from)

Возвращает первое местоположение, к которому данный адрес относится к значению данных. Возвращает BADADDR (-1), если данный адрес не ссылается ни накакие другие адреса.

long Dnext(long from, long current)

Возвращает следующее местоположение, к которому данный адрес (from) относится как значение данных, учитывая, что текущее уже было возвращено предыдущим вызовом Dfirst или Dnext. Возвращает BADADDR, если больше нет перекрестных ссылок.

long XrefType()

Возвращает константу, указывающую тип последней перекрестной ссылки, возвращаемой функцией поиска перекрестных ссылок, такой как Dfirst. Для перекрестных ссылок данных эти константы включают dr_O ( смещение), dr_W (запись данных),и dr_R (чтение данных).

long DfirstB(long to)

Возвращает первое местоположение, которое ссылается на данный адрес как на данные. Возвращает BADADDR (-1), если нет ссылок на данный адрес.

long DnextB(long to, long current)

Возвращает следующее местоположение, которое ссылается на заданный адрес (to) как на данные, учитывая, что текущее уже было возвращено предыдущим вызовом DfirstB или DnextB. Возвращает BADADDR, если больше нет перекрестных ссылок на данное местоположение.

Как и в случае с перекрестными ссылками кода, если вам нужно знать, какой тип перекрестной ссылки вы получили, вы должны вызвать XrefType перед вызовом другой функции поиска перекрестных ссылок.

Функции управления базой данных

Существует ряд функций для форматирования содержимого базы данных.

Вот описания некоторых из этих функций:

void MakeUnkn(long addr, long flags)

Отменяет определение элемента по указанному адресу. Флаги (см. документацию IDC для MakeUnkn) определяют, будут ли последующие элементы также неопределенными и будут ли удалены любые имена, связанные с неопределенными элементами. Связанная функция MakeUnknown позволяет отменить определение больших блоков данных.

long MakeCode(long addr)

Преобразует байты по указанному адресу в инструкцию. Возвращает длину инструкции или 0 в случае сбоя операции.

bool MakeByte(long addr)

Преобразует элемент по указанному адресу в байт данных. MakeWord и MakeDword также доступны.

bool MakeComm(long addr, string comment)

Добавляет обычный комментарий по указанному адресу.

bool MakeFunction(long begin, long end)

Преобразует диапазон инструкций от начала до конца в функцию. Если конец указан как BADADDR (-1), IDA пытается автоматически идентифицировать конец функции, находя инструкцию возврата функции.

bool MakeStr(long begin, long end)

Создает строку текущего типа строки (как возвращается GetStringType), охватывающую байты от начала до конца - 1. Если конец указан как BADADDR, IDA пытается автоматически определить конец строки.

Существует множество других функций MakeXXX, которые предлагают поведение, аналогичное только что описанным функциям. Полный список этих функций см. в документации IDC.

Функции поиска в базе данных

Большинство поисковых возможностей IDA доступны в IDC в виде различных функций FindXXX, некоторые из которых описаны здесь. Параметр flags, используемый в функциях FindXXX, представляет собой битовую маску, определяющую поведение операции поиска. Наиболее полезными являются три флага: SEARCH_DOWN, который заставляет поиск сканировать более высокие адреса; SEARCH_NEXT, который пропускает текущее вхождение для поиска следующего вхождения; и SEARCH_CASE, который заставляет двоичный и текстовый поиск выполняться с учетом регистра.

long FindCode(long addr, long flags)

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

long FindData(long addr, long flags)

Ищет элемент данных по заданному адресу.-

long FindBinary(long addr, long flags, string binary)

Ищет последовательность байтов с заданного адреса. Двоичная строка задает последовательность шестнадцатеричных значений байтов. Если параметр SEARCH_CASE не указан, а значение байта указывает прописную или строчную букву ASCII, то поиск также будет соответствовать соответствующим дополнительным значениям регистра. Например, "41 42" будет соответствовать "61 62" (и "61 42"), если не установлен флаг SEARCH_CASE.

long FindText(long addr, long flags, long row, long column, string text)

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

Также обратите внимание, что SEARCH_NEXT не определяет направление поиска, которое может быть вверх или вниз в соответствии с флагом SEARCH_DOWN. Кроме того, когда SEARCH_NEXT не указан, для функции FindXXX вполне разумно возвращать тот же адрес, который был передан в качестве аргумента адреса, когда элемент по адресу удовлетворяет условиям поиска.

Компоненты линии дизассемблера

Время от времени полезно извлекать текст или части текста из отдельных строк дизассемблированного листинга. Следующие функции обеспечивают доступ к различным компонентам линии дизассемблера:

string GetDisasm(long addr)

Возвращает дизассемблированный текст для заданного адреса. Возвращаемый текст включает любые комментарии, но не включает информацию об адресе.

string GetMnem(long addr)

Возвращает мнемоническую часть инструкции по заданному адресу.

string GetOpnd(long addr, long opnum)

Возвращает текстовое представление указанного операнда по указанному адресу. Операнды нумеруются с нуля, начиная с крайнего левого операнда.

long GetOpType(long addr, long opnum)

Возвращает целое число, представляющее тип для данного операнда по данному адресу. Полный список кодов типов операндов см. в документации IDC для GetOpType.

long GetOperandValue(long addr, long opnum)

Возвращает целочисленное значение, связанное с данным операндом по заданному адресу. Характер возвращаемого значения зависит от типа данного операнда, как указано GetOpType.

string CommentEx(long addr, long type)

Возвращает текст любого комментария, присутствующего по данному адресу. Если type равен 0, возвращается текст обычного комментария. Если type равен 1, возвращается текст повторяемого комментария. Если по данному адресу комментарий отсутствует, возвращается пустая строка.

Примеры сценариев IDC

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

Перечисление функций

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

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

#include <idc.idc>
static main() {
auto addr, end, args, locals, frame, firstArg, name, ret;
addr = 0;
for (addr = NextFunction(addr); addr != BADADDR; addr = NextFunction(addr)) {
name = Name(addr);
end = GetFunctionAttr(addr, FUNCATTR_END);
locals = GetFunctionAttr(addr, FUNCATTR_FRSIZE);
frame = GetFrame(addr); // retrieve a handle to the function’s stack frame
ret = GetMemberOffset(frame, " r"); // " r" is the name of the return address
if (ret == -1) continue;
firstArg = ret + 4;
args = GetStrucSize(frame) - firstArg;
Message("Function: %s, starts at %x, ends at %x\n", name, addr, end);
Message(" Local variable area is %d bytes\n", locals);
Message(" Arguments occupy %d bytes (%d args)\n", args, args / 4);
}
}


Этот сценарий использует некоторые функции управления структурой IDC для получения дескриптора кадра стека каждой функции (GetFrame), определения размера кадра стека (GetStrucSize) и определения смещения сохраненного адреса возврата внутри кадра (GetMemberOffset). Первый аргумент функции лежит на 4 байта дальше сохраненного адреса возврата. Размер области аргумента функции вычисляется как пространство между первым аргументом и концом кадра стека. Поскольку IDA не может генерировать кадры стека для импортированных функций, этот скрипт проверяет, содержит ли кадр стека функции сохраненный адрес возврата, в качестве простого средства идентификации вызовов импортированной функции.

Перечисление инструкций

Внутри данной функции вы можете захотеть перечислить каждую инструкцию. В листинге 15-2 подсчитывается количество инструкций, содержащихся в функции, определяемой текущей позицией курсора:

#include <idc.idc>
static main() {
auto func, end, count, inst;
1 func = GetFunctionAttr(ScreenEA(), FUNCATTR_START);
if (func != -1) {
2 end = GetFunctionAttr(func, FUNCATTR_END);
count = 0;
inst = func;
while (inst < end) {
count++;
3 inst = FindCode(inst, SEARCH_DOWN | SEARCH_NEXT);
}
Warning("%s contains %d instructions\n", Name(func), count);
}
else {
Warning("No function found at location %x", ScreenEA());
}
}


Функция начинается {1} с использования GetFunctionAttr для определения начального адреса функции, содержащей адрес курсора (ScreenEA()). Если начало функции найдено, следующим шагом {2} является определение конечного адреса функции, снова используя функцию GetFunctionAttr. После ограничения функции выполняется цикл для последовательного выполнения инструкций в функции с использованием функции поиска функции FindCode {3}. В этом примере функция "Предупреждение" используется для отображения результатов, поскольку функция генерирует только одну строку вывода, а вывод, отображаемый в диалоговом окне "Предупреждение", гораздо более очевиден, чем вывод, генерируемый в окне сообщения. Обратите внимание, что в этом примере предполагается, что все инструкции в данной функции являются смежными. Альтернативный подход может заменить использование FindCode логикой для перебора всех перекрестных ссылок кода для каждой инструкции внутри функции. В правильном написании этот второй подход будет обрабатывать несмежные, также известные как "фрагментированные" функции.

Перечисление перекрестных ссылок

Повторение перекрестных ссылок может сбивать с толку из-за большого количества функций, доступных для доступа к данным перекрестных ссылок, и того факта, что перекрестные ссылки кода являются двунаправленными. Чтобы получить нужные данные, вам нужно убедиться, что вы получаете доступ к правильному типу перекрестной ссылки для вашей ситуации. В нашем первом примере с перекрестными ссылками, показанном в листинге 15-3, мы получаем список всех вызовов функций, сделанных внутри функции, путем итерации по каждой инструкции в функции, чтобы определить, вызывает ли инструкция другую функцию. Одним из способов сделать это может быть анализ результатов GetMnem для поиска инструкций вызова. Это было бы не очень портативное решение, потому что инструкция, используемая для вызова функции, зависит от типа процессора. Во-вторых, потребуется дополнительный синтаксический анализ, чтобы точно определить, какая функция вызывается. Перекрестные ссылки позволяют избежать каждой из этих трудностей, потому что они не зависят от процессора и напрямую сообщают нам о цели перекрестной ссылки.

#include <idc.idc>
static main() {
auto func, end, target, inst, name, flags, xref;
flags = SEARCH_DOWN | SEARCH_NEXT;
func = GetFunctionAttr(ScreenEA(), FUNCATTR_START);

if (func != -1) {
name = Name(func);
end = GetFunctionAttr(func, FUNCATTR_END);
for (inst = func; inst < end; inst = FindCode(inst, flags)) {
for (target = Rfirst(inst); target != BADADDR; target = Rnext(inst, target)) {
xref = XrefType();
if (xref == fl_CN || xref == fl_CF) {
Message("%s calls %s from 0x%x\n", name, Name(target), inst);
}
}
}
}
else {
Warning("No function found at location %x", ScreenEA());
}
}


В этом примере мы должны выполнить итерацию по каждой инструкции в функции. Затем для каждой инструкции мы должны выполнить итерацию по каждой перекрестной ссылке из инструкции. Нас интересуют только перекрестные ссылки, которые вызывают другие функции, поэтому мы должны проверить возвращаемое значение XrefType в поисках перекрестных ссылок типа fl_CN или fl_CF. Опять же, это конкретное решение обрабатывает только те функции, чьи инструкции являются непрерывными. Учитывая, что сценарий уже перебирает перекрестные ссылки из каждой инструкции, не потребуется много изменений, чтобы произвести анализ, управляемый потоком, вместо анализа, управляемого адресом, показанного здесь.

Еще одно применение перекрестных ссылок — определение каждого местоположения, которое ссылается на определенное местоположение. Например, если мы хотим создать малобюджетный анализатор безопасности, нам может быть интересно выделить все вызовы таких функций, как strcpy и sprintf.

ОПАСНЫЕ ФУНКЦИИ

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


char *strcpy(char *dest, const char *source);


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

В примере, показанном в листинге 15-4, мы работаем в обратном порядке, перебирая все перекрестные ссылки на конкретный символ (в отличие от from в предыдущем примере):

#include <idc.idc>
static list_callers(bad_func) {
auto func, addr, xref, source;
1 func = LocByName(bad_func);
if (func == BADADDR) {
Warning("Sorry, %s not found in database", bad_func);
}
else {
2 for (addr = RfirstB(func); addr != BADADDR; addr = RnextB(func, addr)) {
3 xref = XrefType();
4 if (xref == fl_CN || xref == fl_CF) {
5 source = GetFunctionName(addr);
6 Message("%s is called from 0x%x in %s\n", bad_func, addr, source);
}
}
}
}
static main() {
list_callers("_strcpy");
list_callers("_sprintf");
}


В этом примере функция LocByName {1} используется для поиска адреса данной (по имени) плохой функции. Если адрес функции найден, выполняется цикл {2} для обработки всех перекрестных ссылок на неверную функцию. Для каждой перекрестной ссылки, если тип перекрестной ссылки {3} определяется как перекрестная ссылка типа вызова{4}, имя вызывающей функции определяется {5} и отображается пользователю {6}.

Важно отметить, что для правильного поиска имени импортированной функции могут потребоваться некоторые модификации. В частности, в исполняемых файлах ELF, которые объединяют таблицу связывания процедур (PLT) с таблицей глобальных смещений (GOT) для обработки деталей связывания с разделяемыми библиотеками, имена, которые IDA присваивает импортированным функциям, могут быть менее ясными. Например, может показаться, что запись PLT называется _memcpy, хотя на самом деле она называется .memcpy, и IDA заменила точку символом подчеркивания, потому что IDA считает точки недопустимыми символами в именах. Еще больше усложняет ситуацию тот факт, что IDA может фактически создать символ с именем memcpy, который находится в разделе, который IDA называет extern. При попытке перечислить перекрестные ссылки на memcpy нас интересует версия PLT символа, потому что это версия, которая вызывается из других функций в программе и, следовательно, версия, на которую будут ссылаться все перекрестные ссылки.

Перечисление экспортируемых функций

В главе 13 мы обсуждали использование idsutils для создания файлов .ids, описывающих содержимое разделяемых библиотек. Напомним, что первый шаг в создании файла .ids включает в себя создание файла .idt, который представляет собой текстовый файл, содержащий описания каждой экспортируемой функции, содержащейся в библиотеке. IDC содержит функции для повторения функций, экспортируемых общей библиотекой. Сценарий, показанный в листинге 15-5, можно запустить для создания файла .idt после открытия общей библиотеки с помощью IDA:

#include <idc.idc>
static main() {
auto entryPoints, i, ord, addr, name, purged, file, fd;
file = AskFile(1, "*.idt", "Select IDT save file");
fd = fopen(file, "w");
entryPoints = GetEntryPointQty();
fprintf(fd, "ALIGNMENT 4\n");
fprintf(fd, "0 Name=%s\n", GetInputFile());
for (i = 0; i < entryPoints; i++) {
ord = GetEntryOrdinal(i);
if (ord == 0) continue;
addr = GetEntryPoint(ord);
if (ord == addr) {
continue; //entry point has no ordinal
}
name = Name(addr);
fprintf(fd, "%d Name=%s", ord, name);
purged = GetFunctionAttr(addr, FUNCATTR_ARGSIZE);
if (purged > 0) {
fprintf(fd, " Pascal=%d", purged);
}
fprintf(fd, "\n");
}
}


Вывод скрипта сохраняется в файл, выбранный пользователем. Новые функции, представленные в этом скрипте, включают GetEntryPointQty, которая возвращает количество символов, экспортированных библиотекой; GetEntryOrdinal, который возвращает порядковый номер (индекс в таблице экспорта библиотеки); GetEntryPoint, который возвращает адрес, связанный с экспортируемой функцией, идентифицированной по порядковому номеру; и GetInputFile, которая возвращает имя файла, загруженного в IDA.

Поиск и маркировка аргументов функции

Версии GCC позже 3.4 используют операторы mov, а не операторы push в двоичных файлах x86, чтобы поместить аргументы функции в стек перед вызовом функции. Иногда это вызывает некоторые проблемы с анализом для IDA (более новые версии IDA лучше справляются с этой ситуацией), потому что механизм анализа полагается на поиск операторов push, чтобы точно определить места, в которые помещаются аргументы для вызова функции. Следующий листинг показывает дизассемблирование IDA, когда параметры помещаются в стек:

.text:08048894 push 0 ; protocol
.text:08048896 push 1 ; type
.text:08048898 push 2 ; domain
.text:0804889A call _socket


Обратите внимание на комментарии, которые IDA разместила на правом поле. Такое комментирование возможно только тогда, когда IDA распознает, что параметры передаются, и когда IDA знает сигнатуру вызываемой функции. Когда операторы mov используются для помещения параметров в стек, результирующая дизассемблирование несколько менее информативно, как показано здесь:

.text:080487AD mov [esp+8], 0
.text:080487B5 mov [esp+4], 1
.text:080487BD mov [esp], 2
.text:080487C4 call _socket


В этом случае IDA не смогла распознать, что три оператора mov, предшествующие вызову, используются для установки параметров вызова функции. В результате мы получаем меньше помощи от IDA в виде автоматических комментариев при дизассемблировании.

Здесь мы имеем ситуацию, когда скрипт может восстановить часть информации, которую мы привыкли видеть в наших кодах. В листинге 15.6 показана первая попытка автоматического распознавания инструкций, устанавливающих параметры для вызовов функций:

#include <idc.idc>
static main() {
auto addr, op, end, idx;
auto func_flags, type, val, search;
search = SEARCH_DOWN | SEARCH_NEXT;
addr = GetFunctionAttr(ScreenEA(), FUNCATTR_START);
func_flags = GetFunctionFlags(addr);
if (func_flags & FUNC_FRAME) { //Is this an ebp-based frame?
end = GetFunctionAttr(addr, FUNCATTR_END);
for (; addr < end && addr != BADADDR; addr = FindCode(addr, search)) {
type = GetOpType(addr, 0);
if (type == 3) { //Is this a register indirect operand?
if (GetOperandValue(addr, 0) == 4) { //Is the register esp?
MakeComm(addr, "arg_0"); //[esp] equates to arg_0
}
}
else if (type == 4) { //Is this a register + displacement operand?
idx = strstr(GetOpnd(addr, 0), "[esp"); //Is the register esp?
if (idx != -1) {
val = GetOperandValue(addr, 0); //get the displacement
MakeComm(addr, form("arg_%d", val)); //add a comment
}
}
}
}
}


Скрипт работает только с фреймами на основе EBP и опирается на тот факт, что когда параметры перемещаются в стек до вызова функции, GCC генерирует ссылки на память относительно esp. Сценарий перебирает все инструкции в функции; для каждой инструкции, которая записывает в ячейку памяти, используя esp в качестве базового регистра, сценарий определяет глубину стека и добавляет комментарий, указывающий, какой параметр перемещается. Функция GetFunctionFlags предлагает доступ к различным флагам, связанным с функцией, например, использует ли функция кадр стека на основе EBP. Выполнение сценария в листинге 15-6 приводит к аннотированному дизассемблированию, показанному здесь:

.text:080487AD mov [esp+8], 0 ; arg_8
.text:080487B5 mov [esp+4], 1 ; arg_4
.text:080487BD mov [esp], 2 ; arg_0
.text:080487C4 call _socket


Комментарии не особо информативны. Однако теперь мы можем с первого взгляда сказать, что три оператора mov используются для помещения параметров в стек, что является шагом в правильном направлении. Немного расширив сценарий и исследуя некоторые дополнительные возможности IDC, мы можем создать сценарий, который предоставляет почти столько же информации, сколько и IDA, когда он правильно распознает параметры. Вывод конечного продукта показан здесь:

.text:080487AD mov [esp+8], 0 ; int protocol
.text:080487B5 mov [esp+4], 1 ; int type
.text:080487BD mov [esp], 2 ; int domain
.text:080487C4 call _socket


Расширенная версия сценария в листинге 15.6, которая способна включать данные из сигнатур функций в комментарии, доступна на веб-сайте этой книги.

Эмуляция поведения языка ассемблера

Существует ряд причин, по которым вам может понадобиться написать сценарий, эмулирующий поведение анализируемой программы. Например, программа, которую вы изучаете, может быть самомодифицирующейся, как многие вредоносные программы, или программа может содержать некоторые закодированные данные, которые расшифровываются, когда это необходимо во время выполнения. Без запуска программы и извлечения измененных данных из памяти запущенного процесса, как можно понять поведение программы? Ответ может заключаться в сценарии IDC. Если процесс декодирования не слишком сложен, вы можете быстро написать сценарий IDC, который выполняет те же действия, что и программа при ее запуске. Использование сценария для декодирования данных таким образом избавляет от необходимости запускать программу, когда вы не знаете, что она делает, или у вас нет доступа к платформе, на которой вы можете запустить программу. Пример последнего случая может возникнуть, если вы исследуете двоичный файл MIPS с помощью вашей версии IDA для Windows. Без какого-либо оборудования MIPS вы не сможете запустить двоичный файл MIPS и наблюдать за любым декодированием данных, которое он может выполнить. Однако вы можете написать сценарий IDC, чтобы имитировать поведение двоичного файла и внести необходимые изменения в базу данных IDA, и все это без необходимости в среде выполнения MIPS. Следующий код x86 был извлечен из двоичного файла DEFCON9 Capture the Flag.

.text:08049EDE mov [ebp+var_4], 0
.text:08049EE5
.text:08049EE5 loc_8049EE5:
.text:08049EE5 cmp [ebp+var_4], 3C1h
.text:08049EEC ja short locret_8049F0D
.text:08049EEE mov edx, [ebp+var_4]
.text:08049EF1 add edx, 804B880h
.text:08049EF7 mov eax, [ebp+var_4]
.text:08049EFA add eax, 804B880h
.text:08049EFF mov al, [eax]
.text:08049F01 xor eax, 4Bh
.text:08049F04 mov [edx], al
.text:08049F06 lea eax, [ebp+var_4]
.text:08049F09 inc dword ptr [eax]
.text:08049F0B jmp short loc_8049EE5


Этот код декодирует закрытый ключ, встроенный в двоичный файл программы. Используя сценарий IDC, показанный в листинге 15-7, мы можем извлечь закрытый ключ, не запуская программу:

auto var_4, edx, eax, al;
var_4 = 0;
while (var_4 <= 0x3C1) {
edx = var_4;
edx = edx + 0x804B880;
eax = var_4;
eax = eax + 0x804B880;
al = Byte(eax);
al = al ^ 0x4B;
PatchByte(edx, al);
var_4++;
}


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

1. Для каждой переменной стека и регистра, используемых в ассемблерном коде, объявите переменную IDC.

2. Для каждого оператора языка ассемблера напишите оператор IDC, который имитирует его поведение.

3. Чтение и запись переменных стека эмулируется чтением и записью соответствующей переменной, объявленной в вашем сценарии IDC.

4. Чтение вне стека выполняется с помощью функции Byte, Word или Dword, в зависимости от объема считываемых данных (1, 2 или 4 байта).

5. Запись в расположение вне стека выполняется с помощью функции PatchByte, PatchWord или PatchDword, в зависимости от объема записываемых данных.

6. В общем, если код содержит цикл, условие завершения которого не очевидно сразу, проще всего начать с бесконечного цикла, такого как while (1) {}, а затем вставлять оператор break, когда встречаются операторы. которые приводят к прекращению цикла.

7. Когда ассемблерный код вызывает функции, все становится сложнее. Чтобы правильно имитировать поведение ассемблерного кода, вы должны найти способ имитировать поведение вызванной функции, включая предоставление возвращаемого значения, которое имеет смысл в контексте моделируемого кода. Один только этот факт может помешать использованию IDC в качестве инструмента для эмуляции поведения последовательности языка ассемблера.

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

auto var_4, addr;
for (var_4 = 0; var_4 <= 0x3C1; var_4++) {
addr = 0x804B880 + var_4;
PatchByte(addr, Byte(addr) ^ 0x4B);
}


В качестве альтернативы, если мы не хотим каким-либо образом модифицировать базу данных, мы можем заменить функцию PatchByte вызовом Message, если мы имеем дело с данными ASCII, или в качестве альтернативы мы можем записать данные в файл, если мы имели дело с бинарными данными.

IDAPython

IDAPython — это надстройка, разработанная Gergely Erdelyi, которая интегрирует интерпретатор Python в IDA. В сочетании с предоставленными привязками Python этот подключаемый модуль позволяет писать сценарии Python с полным доступом ко всем возможностям языка сценариев IDC. Одним из явных преимуществ IDAPython является доступ к собственным возможностям Python по обработке данных, а также ко всему набору модулей Python. Кроме того, IDAPython предоставляет значительную часть функций IDA SDK, позволяющая создавать гораздо более мощные сценарии, чем это возможно при использовании IDC. IDAPython приобрел множество поклонников в сообществе IDA. Блог Ильфака содержит множество интересных примеров решения проблем с помощью скриптов Python, а вопросы, ответы и многие другие полезные скрипты IDAPython часто публикуются на форумах OpenRCE.org. Кроме того, сторонние инструменты, такие как BinNavi от Zynamics, полагаются на IDA и IDAPython для выполнения различных подзадач, требуемых этими инструментами.

Начиная с IDA 5.4 Hex-Rays включает IDAPython в качестве стандартного подключаемого модуля. Исходный код подключаемого модуля доступен для загрузки на странице проекта IDA-Python, а документация по API доступна на веб-сайте Hex-Rays. IDA включает подключаемый модуль только в том случае, если Python установлен на компьютере, на котором запущена IDA. Версия IDA для Windows поставляется с совместимой версией Python и устанавливает ее, в то время как версии IDA для Linux и OS X оставляют правильную установку Python на ваше усмотрение. В Linux текущая версия IDA (6.1) ищет Python 2.6. IDAPython совместим с Python 2.7, и IDA будет работать нормально, если вы создадите символические ссылки из необходимых библиотек Python 2.6 на существующие библиотеки Python 2.7. Если у вас Python 2.7, команда, подобная следующей, создаст символическую ссылку, которая порадует IDA:

# ln –s /usr/lib/libpython2.7.so.1.0 /usr/lib/libpython2.6.so.1


Пользователи OS X могут обнаружить, что версия Python, поставляемая с OS X, старше, чем требуется IDA. В этом случае подходящий установщик Python следует загрузить с сайта www.python.org.

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

IDAPython связывает код Python с IDA, предоставляя доступ к трем модулям Python, каждый из которых служит определенной цели. Доступ к основному API IDA (представленному через SDK) предоставляется с помощью модуля idaapi. Все функции, присутствующие в IDC, доступны в модуле IDAPython idc. Третий модуль, поставляемый с IDAPython, — это idautils, который предоставляет ряд служебных функций, многие из которых выдают списки Python различных объектов, связанных с базой данных, таких как функции или перекрестные ссылки. Модули idc и idautils автоматически импортируются для всех скриптов IDAPython. С другой стороны, если вам нужен idaapi, вы должны импортировать его самостоятельно.

При использовании IDAPython имейте в виду, что подключаемый модуль встраивает один экземпляр интерпретатора Python в IDA. Этот интерпретатор не будет уничтожен, пока вы не закроете IDA. В результате вы можете просматривать все свои скрипты и операторы, как если бы они выполнялись в рамках одного сеанса оболочки Python. Например, после того как вы впервые импортировали модуль idaapi в сеансе IDA, вам не нужно будет импортировать его снова, пока вы не перезапустите IDA. Точно так же инициализированные переменные и определения функций сохраняют свои значения до тех пор, пока они не будут переопределены или пока вы не выйдете из IDA.

Существует несколько стратегий изучения Python API IDA. Если у вас уже есть некоторый опыт использования IDC или программирования с помощью IDA SDK, вы должны чувствовать себя как дома с модулями idaapi и idc. Краткий обзор дополнительных функций модуля idautils — это все, что вам действительно нужно, чтобы начать полноценно использовать IDAPython. Если у вас есть опыт работы с IDC или SDK, вы можете изучить документацию Hex-Ray для API Python, чтобы получить представление о возможностях, которые он предлагает. Помните, что модуль idc в основном отражает IDC API и что вы можете найти список функций IDC во встроенной справке IDA весьма полезным. Точно так же описания функций IDC, представленные ранее в этой главе, в равной степени применимы к соответствующим функциям в модуле idc.

Примеры сценариев IDAPython

В качестве примера сравнения IDC и IDAPython в следующих разделах представлены те же примеры, что и ранее при обсуждении IDC.Везде, где это возможно, мы стараемся максимально использовать специфические для Python функции, чтобы продемонстрировать некоторые преимущества, которые можно получить с помощью сценариев на Python.

Перечисление функций

Одной из сильных сторон IDAPython является то, как он использует мощные типы данных Python для упрощения доступа к коллекциям объектов базы данных. В листинге 15-8 мы повторно реализуем сценарий перечисления функций из листинга 15-1 на Python. Напомним, что целью этого скрипта является перебор каждой функции в базе данных и вывод основной информации о каждой функции, включая начальный и конечный адреса функции, размер аргументов функции и размер пространства локальных переменных функции. Весь вывод отправляется в окно вывода.

funcs = Functions()⵼
for f in funcs:⵼
name = Name(f)
end = GetFunctionAttr(f, FUNCATTR_END)
locals = GetFunctionAttr(f, FUNCATTR_FRSIZE)
frame = GetFrame(f) # retrieve a handle to the function’s stack frame
if frame is None: continue
ret = GetMemberOffset(frame, " r") # " r" is the name of the return address
if ret == -1: continue
firstArg = ret + 4
args = GetStrucSize(frame) - firstArg
Message("Function: %s, starts at %x, ends at %x\n" % (name, f, end))
Message(" Local variable area is %d bytes\n" % locals)
Message(" Arguments occupy %d bytes (%d args)\n" % (args, args / 4))



Для этого конкретного скрипта использование Python мало что дает нам с точки зрения эффективности, кроме использования генератора списка функций {1}, который упрощает цикл for в {2}.

Инструкции по перечислению

В листинге 15-9 показано, как сценарий подсчета инструкций из листинга 15-2 может быть написан на Python с использованием генераторов списков, доступных в модуле idautils.

from idaapi import *
func = get_func(here())⵼ # here() is synonymous with ScreenEA()
if not func is None:
fname = Name(func.startEA)
count = 0
IDA Scripting 283
for i in FuncItems(func.startEA)⵼: count = count + 1
Warning("%s contains %d instructions\n" % (fname,count))
else:
Warning("No function found at location %x" % here())


Отличия от версии IDC включают использование функции SDK {1}(доступ к которой осуществляется через idaapi) для извлечения ссылки на объект функции (в частности, func_t) и использование генератора FuncItems {2} (от idautils) для обеспечения простого перебор всех инструкций внутри функции. Поскольку мы не можем использовать функцию Python len в генераторе, мы по-прежнему обязаны проходить по списку генераторов, чтобы считать каждую инструкцию по одной.

Перечисление перекрестных ссылок

Модуль idautils содержит несколько функций-генераторов, которые создают списки перекрестных ссылок более интуитивным способом, чем мы видели в IDC. В ли стинге 15-10 переписывается сценарий перечисления вызовов функций, который мы видели ранее в листинге 15-3.

from idaapi import *
func = get_func(here())
if not func is None:
fname = Name(func.startEA)
items = FuncItems(func.startEA)
for i in items:
for xref in XrefsFrom(i, 0):⵼
if xref.type == fl_CN or xref.type == fl_CF:
Message("%s calls %s from 0x%x\n" % (fname, Name(xref.to), i))
else:
Warning("No function found at location %x" % here())



Новым в этом скрипте является использование генератора XrefsFrom {1}(от idautils) для пошагового просмотра всех перекрестных ссылок из текущей инструкции. XrefsFrom возвращает ссылку на объект xrefblk_t, содержащий подробную информацию о текущей перекрестной ссылке.

Перечисление экспортируемых функций

Листинг 15-11 — это Python-версия скрипта генератора .idt из листинга 15-5.

file = AskFile(1, "*.idt", "Select IDT save file")
with open(file, 'w') as fd:
fd.write("ALIGNMENT 4\n")
fd.write("0 Name=%s\n" % GetInputFile())
for i in range(GetEntryPointQty()):
ord = GetEntryOrdinal(i)
if ord == 0: continue
addr = GetEntryPoint(ord)
if ord == addr: continue #entry point has no ordinal
fd.write("%d Name=%s" % (ord, Name(addr)))
purged = GetFunctionAttr(addr, FUNCATTR_ARGSIZE)
if purged > 0:
fd.write(" Pascal=%d" % purged)
fd.write("\n")


Эти два сценария выглядят удивительно похожими, поскольку IDAPython не имеет функции генератора для списков точек входа, поэтому нам остается использовать тот же набор функций, что и в листинге 15.5. Стоит отметить одно отличие: IDAPython отказывается от функций обработки файлов IDC в пользу встроенных функций обработки файлов Python.

Резюме

Скрипты предоставляют мощные средства для расширения возможностей IDA. На протяжении многих лет сценарии использовались множеством инновационных способов для удовлетворения потребностей пользователей IDA. Многие полезные скрипты доступны для скачивания на сайте Hex-Rays, а также на зеркальном сайте бывшего IDA Palace. Скрипты IDA идеально подходят для небольших задач и быстрой разработки, но они идеально подходят не для всех ситуаций.

Одним из основных ограничений языка IDC является отсутствие поддержки сложных типов данных и отсутствие доступа к более полнофункциональным API, таким как стандартная библиотека C или Windows API. За счет большей сложности мы можем снять эти ограничения, отойдя от скриптовых расширений в сторону скомпилированных расширений. Как мы покажем в следующей главе, скомпилированные расширения требуют использования комплекта разработки программного обеспечения (SDK) IDA, кривая обучения которого более крутая, чем у IDC или IDAPython. Однако мощность, доступная при разработке расширений с помощью SDK, обычно стоит усилий, затраченных на изучение того, как его использовать.
 
IDA SDK

На протяжении всей книги мы использовали такие фразы, как "IDA делает то" и "IDA делает это". Хотя IDA, безусловно, очень много делает для нас, разумнее отнести его к различным модулям, на которые опирается IDA. Например, именно процессорный модуль принимает все решения на этапе анализа, поэтому можно утверждать, что IDA настолько умна, насколько умны процессорные модули, на которые она опирается. Конечно, Hex-Rays прилагает огромные усилия для обеспечения максимально возможной производительности своих процессорных модулей, и для обычного пользователя IDA аккуратно скрывает свою модульную архитектуру под своим пользовательским интерфейсом.

В какой-то момент вам может понадобиться больше возможностей, чем может предложить язык сценариев IDC, либо по соображениям производительности, либо потому, что вы хотите делать то, для чего IDC просто не предназначена. Когда этот момент наступит, пора перейти к использованию комплекта разработки программного обеспечения (SDK) IDA для создания собственных скомпилированных модулей для использования с IDA.

ПРИМЕЧАНИЕ Механизм сценариев IDC построен на основе SDK IDA. Все функции IDC в конечном итоге преобразуются в вызовы одной или нескольких функций SDK, выполняющих реальную работу. Хотя верно то, что если вы можете сделать что-то в IDC, вы можете сделать то же самое с помощью SDK. SDK предлагает гораздо больше возможностей, чем при использовании только IDC, и многие действия SDK не имеют аналога IDC.

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

BELLS, WHISTLES, AND BULLETS TO THE FOOT

При работе с C++ у вас, конечно же, будет доступ к широкому спектру библиотек C++, включая собственные API-интерфейсы вашей операционной системы. При использовании таких библиотек у вас может возникнуть соблазн включить множество сложных функций в любые создаваемые вами модули. Тем не менее, вы должны быть очень осторожны с тем, какую функциональность вы решите включить таким образом, так как это может привести к нестабильности в IDA. Наиболее конкретным примером этого является тот факт, что IDA является однопоточным приложением. Не предпринимается никаких усилий для синхронизации доступа к низкоуровневым структурам базы данных, и SDK не предоставляет для этого средств. Для версий IDA до 5.5 никогда не следует создавать дополнительные потоки, которые могут одновременно обращаться к базе данных. Для версий 5.5 и более поздних вы можете создавать дополнительные потоки, но любые вызовы функций SDK должны ставиться в очередь с помощью функций exec_request_t и execute_sync, описанных в kernwin.hpp. Кроме того, вы должны понимать, что любые блокирующие операции, которые вы выполняете, перестанут отвечать на запросы IDA до тех пор, пока операция не будет завершена.

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

Введение в SDK

SDK IDA распространяется почти так же, как и другие дополнения IDA, которые мы уже обсуждали. Zip-файл, содержащий SDK, можно найти на исходном компакт-диске IDA, или авторизованные пользователи могут загрузить SDK с веб-сайта Hex-Rays. Каждая версия SDK названа в честь версии IDA, с которой она совместима (например, idasdk61.zip идет с IDA версии 6.1). SDK содержит ту же минималистскую документацию, которая обычно встречается в других инструментах, связанных с IDA, что в случае SDK означает файл readme.txt и дополнительные файлы README для подключаемых модулей, процессорных модулей и загрузчиков.

SDK определяет опубликованный программный интерфейс, который модули могут использовать для взаимодействия с IDA. До SDK версии 4.9 эти интерфейсы нередко менялись настолько, что модуль, успешно скомпилированный в SDK 4.8, больше не мог компилироваться в более новом SDK, таком как версия 4.9, без необходимости внесения изменений. С введением версии 4.9 SDK компания Hex-Rays решила стандартизировать существующий API, а это означает, что модули не только не требуют изменений для успешной компиляции с более новыми версиями SDK, но и модули также будут бинарно совместимы с более новыми версиями. Это означает, что пользователям модулей больше не нужно ждать, пока авторы модулей обновят свой исходный код или сделают доступными обновленные двоичные версии своих модулей каждый раз, когда выпускается новая версия IDA. Это не означает, что существующие интерфейсы API полностью заморожены; Hex-Rays продолжает вводить новые функции с каждой новой версией SDK (то есть каждый новый SDK является расширенным набором своего предшественника). Модули, использующие эти новые функции, обычно несовместимы со старыми версиями IDA или SDK. Тем не менее, были случаи, когда по разным причинам функции были переименованы или отмечены как устаревшие. SDK предлагает макросы для разрешения или запрета использования устаревших функций, что позволяет легко определить, когда функция устарела.

Установка SDK

До версии 5.4 Zip-файл, содержащий SDK, не содержал каталога верхнего уровня. Поскольку SDK использует несколько общих имен подкаталогов с IDA, настоятельно рекомендуется создать специальный каталог SDK, например idasdk53, и извлечь содержимое SDK в этот каталог. Это значительно упростит отличие компонентов SDK от компонентов IDA. Начиная с версии 5.4, IDA SDK упаковывается в каталог SDK верхнего уровня, например idasdk61, поэтому этот шаг больше не требуется. Нет необходимости устанавливать SDK в определенном месте относительно <IDADIR>. Независимо от того, где вы решите установить свой SDK, мы будем ссылаться на каталог SDK в целом как <SDKDIR> до конца книги.

Макет SDK

Базовое понимание структуры каталогов, используемой в SDK, будет полезно как для того, чтобы знать, где вы можете найти документацию, так и для того, чтобы знать, где вы можете найти модули, которые вы создаете. Ниже приводится краткое изложение того, что вы можете ожидать найти в SDK.

bin

В этом каталоге примеры сценариев сохраняют свои скомпилированные модули после успешной сборки. Установка модуля включает копирование модуля из соответствующего подкаталога внутри bin в соответствующий подкаталог в <IDADIR>. Более подробно установка модуля будет рассмотрена в главах 17, 18 и 19. Этот каталог также содержит инструмент постобработки, необходимый для создания процессорных модулей.

etc

Этот каталог содержит исходный код двух утилит, необходимых для сборки некоторых модулей SDK. Скомпилированные версии этих утилит также включены в SDK.

include

Этот каталог содержит заголовочные файлы, определяющие интерфейс API IDA. Короче говоря, каждая структура данных API, которую вам разрешено использовать, и каждая функция API, которую вам разрешено вызывать, объявляются в одном из файлов заголовков в этом каталоге. Файл SDK верхнего уровня readme.txt содержит обзор некоторых из наиболее часто используемых заголовочных файлов в этом каталоге. Файлы в этом каталоге составляют большую часть документации (как в "прочитать исходный код") для SDK.

ldr

Этот каталог содержит исходный код и скрипты сборки для нескольких примеров модулей загрузчика. Файл README для загрузчиков — не что иное, как краткое изложение содержимого этого каталога.

directory

Этот каталог содержит ряд подкаталогов, которые, в свою очередь, содержат библиотеки ссылок, необходимые для сборки различных модулей IDA. Подкаталоги названы в честь компилятора, с которым они должны использоваться. Например, x86_win_vc_32 (6.1 и более поздние версии) или vc.w32 (6.0 и более ранние версии) содержат библиотеку для использования с Visual Studio и 32-разрядной IDA в Windows, а x64_mac_gcc_64 (6.1 и более поздние версии) или gcc64.mac64 (6.0 и более ранние версии) содержит библиотеку для использования с 64-битной IDA на платформах OSX.

module

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

plug-ins

Этот каталог содержит исходный код и сценарии сборки для нескольких примеров подключаемых модулей. Файл README для подключаемых модулей содержит общий обзор архитектуры подключаемых модулей.

каталог верхнего уровня

Верхний уровень SDK содержит несколько make-файлов, используемых для сборки модулей, а также основной файл readme.txt для SDK. Несколько дополнительных файлов install_xxx.txt содержат информацию об установке и настройке различных компиляторов (например, install_visual.txt обсуждает настройку Visual Studio).

Имейте в виду, что документация по использованию SDK скудна. Для большинства разработчиков знание SDK было получено путем проб и ошибок и тщательного изучения содержимого SDK. Возможно, вам повезет, если вы опубликуете вопросы на форуме Research & Resources на форумах поддержки Hex-Rays, где другие пользователи IDA, знакомые с SDK, могут ответить на них. Отличным сторонним ресурсом, предоставляющим введение в SDK и написание подключаемых модулей, является руководство Стива Микаллефа под названием "Написание подключаемых модулей IDA на C/C++".

Настройка среды для сборки

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

Все примеры, включенные в SDK, были созданы с использованием инструментов Borland. Из install_make.txt у нас есть следующая цитата от Ильфака:

Версии WIN32 могут быть созданы только Borland C++ CBuilder v4.0.

Возможно и старый BCC v5.2 будет работать, но я не проверял.

При этом другие файлы install_xxx предлагают указатели на то, как успешно собирать модули с помощью других компиляторов. Несколько примеров модулей содержат файлы для сборки с помощью Visual Studio (например, <SDKDIR>/plugins/vcsample), а install_visual.txt предлагает ряд шагов для правильной настройки проектов SDK с помощью Visual C++ Express 2005.

Для создания модулей с помощью инструментов в стиле Unix либо в системе в стиле Unix, такой как Linux, либо в такой среде, как MinGW, SDK предоставляет сценарий с именем idamake.pl, который преобразует файлы make в стиле Borland в файлы в стиле Unix. make файлы до начала процесса сборки. Этот процесс обсуждается в файле install_linux.txt.

ПРИМЕЧАНИЕ

Сценарии сборки командной строки, поставляемые с SDK, предполагают, что переменная среды с именем IDA указывает на <SDKDIR>. Вы можете установить это глобально для всех сценариев, отредактировав <SDKDIR>/allmake.mak и <SDKDIR>/allmake.unx, чтобы установить эту переменную, или добавив переменную среды IDA в глобальную среду.


Руководство Стива Микаллефа также содержит отличные инструкции по настройке среды сборки для создания подключаемых модулей с помощью различных компиляторов. Наше личное предпочтение при создании модулей SDK для версий IDA для Windows — использовать инструменты MinGW gcc и make. Примеры, представленные в главах 17, 18 и 19, включают make-файлы и файлы проектов Visual Studio, которые не зависят от каких-либо сценариев сборки, включенных в SDK, и которые легко изменить в соответствии с потребностями ваших проектов. Конфигурация сборки для конкретного модуля также будет обсуждаться в каждой из этих глав.

Интерфейс прикладного программирования IDA

API IDA определяется содержимым файлов заголовков в <SDKDIR>/include. Единого указателя доступных функций не существует (хотя Стив Микаллеф собрал довольно неплохой подмножество в своем руководстве по написанию подключаемых модулей). Многим будущим программистам SDK сначала трудно смириться с этим фактом. Реальность такова, что никогда не бывает простого ответа на вопрос "Как мне сделать XXX с помощью SDK?" Есть два основных варианта ответов на такие вопросы: опубликовать вопросы на форуме пользователей IDA или попытаться ответить на них самостоятельно, выполнив поиск в документации по API. Какая документация, говорите? Ну, заголовочные файлы, конечно. Конечно, это не самые удобные для поиска документы, но они содержат полный набор функций API. В этом случае grep (или его подходящая замена, желательно встроенная в ваш программный редактор) — ваш друг.

Есть несколько способов попытаться сузить область поиска с помощью API. Первый способ — использовать свои знания языка сценариев IDC и попытаться найти аналогичные функции в SDK, используя ключевые слова и, возможно, имена функций, полученные из IDC. Однако — и это крайне неприятно — хотя SDK может содержать функции, выполняющие задачи, идентичные функциям IDC, имена этих функций редко совпадают. Это приводит к тому, что программисты изучают два набора вызовов API: один для использования с IDC, а другой для использования с SDK. Чтобы разрешить эту ситуацию, в Приложении B представлен полный список функций IDC и соответствующих действий SDK 6.1, которые выполняются для выполнения этих функций.

Второй способ сузить поиск, связанный с SDK, — ознакомиться с содержимым и, что более важно, с назначением различных заголовочных файлов SDK. Как правило, связанные функции и связанные структуры данных группируются в файлы заголовков на основе функциональных групп. Например, функции SDK, позволяющие взаимодействовать с пользователем, сгруппированы в kernwin.hpp. Когда поиск в стиле grep не может найти требуемую возможность, некоторое знание того, какой заголовочный файл относится к этой возможности, сузит ваш поиск и, надеюсь, ограничит количество файлов, которые вам нужно копать глубже.

Обзор заголовочных файлов

В то время как файлы SDK readme.txt содержат общий обзор наиболее часто используемых файлов заголовков, в этом разделе выделена другая полезная информация для работы с этими файлами. Во-первых, в большинстве заголовочных файлов используется суффикс .hpp, а в некоторых — суффикс .h. Это может легко привести к тривиальным ошибкам при именовании файлов заголовков, которые будут включены в ваши файлы. Во-вторых, ida.hpp является основным заголовочным файлом для SDK и должен быть включен во все проекты, связанные с SDK. В-третьих, SDK использует директивы препроцессора, предназначенные для предотвращения доступа к функциям, которые Hex-Rays считает опасными (например, strcpy и sprintf). Полный список этих функций см. в заголовочном файле pro.h. Чтобы восстановить доступ к этим функциям, вы должны определить макрос USE_DANGEROUS_FUNCTIONS перед включением ida.hpp в свои собственные файлы. Пример показан здесь:

#define USE_DANGEROUS_FUNCTIONS
#include <ida.hpp>


Неспособность определить USE_DANGEROUS_FUNCTIONS приведет к ошибке сборки в том смысле, что dont_use_snprintf является неопределенным символом (в случае попытки использовать функцию snprintf). Чтобы компенсировать ограничение доступа к этим так называемым опасным функциям, SDK определяет для каждой из них более безопасные эквиваленты, как правило, в виде функции qstrXXXX, такой как qstrncpy и qsnprintf. Эти более безопасные версии также заявлены в pro.h.

Аналогичным образом SDK ограничивает доступ ко многим стандартным переменным и функциям ввода/вывода файлов, таким как stdin, stdout, fopen, fwrite и fprintf. Это ограничение частично связано с ограничениями компилятора Borland. Здесь снова SDK определяет функции замены в форме аналогов qXXX, таких как qfopen и qfprintf. Если вам требуется доступ к стандартным файловым функциям, вы должны определить макрос USE_STANDARD_FILE_FUNCTIONS перед включением fpro.h (который включается из kernwin.hpp, который, в свою очередь, включается из нескольких других файлов).

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

area.hpp

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

auto.hpp

В этом файле объявляются функции, используемые для работы с автоанализатором IDA. Автоанализатор выполняет, поставленные в очередь задачи анализа, когда IDA не занята обработкой событий пользовательского ввода.

bytes.hpp

В этом файле объявляются функции для работы с отдельными байтами базы данных. Функции, объявленные в этом файле, используются для чтения и записи отдельных байтов базы данных, а также для управления характеристиками этих байтов. Разные функции также предоставляют доступ к флагам, связанным с операндами инструкций, в то время как другие функции позволяют манипулировать обычными и повторяемыми комментариями.

dbg.hpp

Этот файл объявляет функции, предлагающие программное управление отладчиком IDA.

entry.hpp

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

expr.hpp

В этом файле объявляются функции и структуры данных для работы с конструкциями IDC. Можно изменить существующие функции IDC, добавить новые функции IDC или выполнить операторы IDC из модулей.

fpro.h

Этот файл содержит альтернативные функции файлового ввода-вывода, такие как qfopen, рассмотренные ранее.

frame.hpp

Этот заголовок содержит функции, используемые для управления кадрами стека.

funcs.hpp

Этот заголовок содержит функции и структуры данных для работы с дизассемблированными функциями, а также функции для работы с сигнатурами FLIRT.

idp.hpp

Этот файл содержит объявления структур, которые составляют основу модуля процессора.

gdl.hpp

В этом файле объявлены процедуры поддержки для создания графиков с использованием DOT или GDL.

ida.hpp

Это основной заголовочный файл, необходимый для работы с SDK. Этот файл содержит определение структуры idainfo, а также объявление глобальной переменной inf, которая содержит ряд полей, содержащих информацию о текущей базе данных, а также поля, инициализированные из конфигурационного файла settings.les. В этом файле определены глобальная переменная ph, описывающая текущий модуль процессора, и глобальная переменная ash, описывающая текущий ассемблер.

kernwin.hpp

Этот файл объявляет функции для взаимодействия с пользователем и пользовательским интерфейсом. Здесь объявлены SDK-эквиваленты функций IDC AskXXX, а также функции, используемые для установки позиции отображения и настройки ассоциаций горячих клавиш.

lines.hpp

Этот файл объявляет функции для создания форматированных, раскрашенных строк дизассемблирования.

loader.hpp

Этот файл содержит объявления для структур loader_t и plugin_t, необходимых для создания модулей загрузчика и подключаемых модулей соответственно, а также функции, полезные на этапе загрузки файла, и функции для активации подключаемых модулей.

name.hpp

В этом файле объявляются функции для управления именованными местоположениями (в отличие от имен внутри структур или кадров стека, которые описаны в stuct.hpp и funcs.hpp соответственно).

netnode.hpp

Сетевые узлы — это структура хранения самого низкого уровня, доступная через API. Детали сетевых узлов обычно скрыты пользовательским интерфейсом IDA. Этот файл содержит определение класса сетевых узлов и функций для низкоуровневого управления сетевыми узлами.

pro.h

Этот файл включает определения типов и макросы верхнего уровня, необходимые в любом модуле SDK. Вам не нужно явно включать этот файл в свои проекты, так как он включен из ida.hpp. Помимо прочего, в этом файле определен макрос IDA_SDK_VERSION. IDA_SDK_VERSION позволяет определить, с какой версией SDK создается модуль, и его можно протестировать для обеспечения условной компиляции при использовании разных версий SDK. Обратите внимание, что IDA_SDK_VERSION появилась в SDK версии 5.2. До SDK 5.2 не было официального способа определить, какой SDK используется. Неофициальный заголовочный файл, который определяет IDA_SDK_VERSION для более старых версий SDK (sdk_versions.h), доступен на веб-сайте этой книги.

search.hpp

В этом файле объявляются функции для выполнения различных типов поиска в базе данных.

segment.hpp

Этот файл содержит объявление класса segment_t, подкласса area_t, который используется для описания отдельных разделов (.text, .data и т. д.) внутри двоичного файла. Здесь же объявлены функции для работы с сегментами.

struct.hpp

Этот файл содержит объявление класса struc_t и функции для управления структурами в базе данных.

typeinf.hpp

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

ua.hpp

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

xref.hpp

Этот файл объявляет типы данных и функции, необходимые для добавления, удаления и повторения кода и перекрестных ссылок данных.

В приведенном выше списке описана примерно половина файлов заголовков, поставляемых с SDK. Вам рекомендуется ознакомиться не только с файлами в этом списке, но и со всеми остальными заголовочными файлами по мере углубления в SDK. Функции, составляющие опубликованный API, отмечены как ida_export. Только функции, обозначенные как ida_export, экспортируются в библиотеки ссылок, поставляемые с SDK. Не вводите в заблуждение использование idaapi, так как это просто означает, что функция должна использовать соглашение о вызовах stdcall только на платформах Windows. Иногда вы можете столкнуться с интересными функциями, которые не обозначены как ida_export; вы не можете использовать эти функции в своих модулях.

Netnodes

Большая часть API IDA построена на классах C++, которые моделируют различные аспекты дизассемблированного двоичного файла. С другой стороны, класс netnode кажется окутанным тайной, потому что он не имеет прямого отношения к конструкциям в двоичных файлах (разделам, функциям, инструкциям и т. д.).

Netnodes это механизм хранения данных самого низкого уровня и наиболее общего назначения, доступный в базе данных IDA. Вам, как программисту модулей, редко придется работать напрямую с сетевыми узлами. Многие из структур данных более высокого уровня скрывают тот факт, что они в конечном счете полагаются на сетевые узлы для постоянного хранения в базе данных. Некоторые способы использования Netnodes в базе данных подробно описаны в файле nalt.hpp, из которого мы узнаем, например, что информация об общих библиотеках и функциях, которые импортирует двоичный файл, хранится в Netnodes с именем import_node (да, Netnodes могут иметь имена). Сетевые узлы также являются механизмами постоянного хранения, которые облегчают глобальные массивы IDC.

Netnodes подробно описаны в файле netnode.hpp. Но с точки зрения высокого уровня, сетевые узлы — это структуры хранения, используемые внутри IDA для различных целей. Однако их точная структура остается скрытой даже для программистов SDK. Чтобы предоставить интерфейс к этим структурам хранения, SDK определяет класс netnode, который функционирует как непрозрачная оболочка вокруг этой внутренней структуры хранения. Класс netnode содержит единственный член данных, называемый netnodenumber, который представляет собой целочисленный идентификатор, используемый для доступа к внутреннему представлению Netnodes. Каждый Netnodes однозначно идентифицируется своим номером сетевого узла. В 32-битных системах netnodenumber представляет собой 32-битную величину, что позволяет использовать 2^32 уникальных Netnodes. В 64-битных системах netnodenumber представляет собой 64-битное целое число, что позволяет использовать 2^64 уникальных Netnodes. В большинстве случаев netnodenumber представляет собой виртуальный адрес в базе данных, что создает естественное сопоставление между каждым адресом в базе данных и любым сетевым узлом, который может потребоваться для хранения информации, связанной с этим адресом. Текст комментария является примером произвольной информации, которая может быть связана с адресом и, таким образом, храниться в сетевом узле, связанном с этим адресом.

Рекомендуемым способом управления Netnodes является вызов функций-членов класса netnode с использованием экземпляра объекта netnode. Прочитав файл netnode.hpp, вы заметите, что существует ряд функций, не являющихся членами, которые, кажется, поддерживают манипуляции с netnode. Использование этих функций не рекомендуется в пользу функций-членов. Вы заметите, однако, что большинство функций-членов в сетевых узлах netnod вызывается функциями-членами класса netnode с использованием экземпляра класса netnode, которые являются тонкими обертками вокруг одной из функций, не являющихся членами.

Внутри netnode могут использоваться для хранения нескольких различных типов информации. Каждый netnode может быть связан с именем длиной до 512 символов и первичным значением размером до 1024 байт. Функции-члены класса netnode предназначены для извлечения (имени) или изменения (переименования) имени сетевого узла. Дополнительные функции-члены позволяют обрабатывать первичное значение сетевого узла как целое число (set_long, long_value), строку (set, valstr) или произвольный двоичный объект blob2 (set, valobj). Используемая функция по своей сути определяет, как обрабатывается первичное значение.

Здесь все становится немного сложнее. В дополнение к имени и основному значению каждый netnode также может хранить 256 разреженных массивов, в которых элементы массива могут иметь произвольный размер со значениями до 1024 байт каждый. Эти массивы делятся на три пересекающиеся категории. Массивы первой категории индексируются с использованием 32-битных значений индекса и потенциально могут содержать более 4 миллиардов элементов. Вторая категория массивов индексируется с использованием 8-битных значений индекса и, таким образом, может содержать до 256 элементов. Последняя категория массивов на самом деле представляет собой хэш-таблицы, в которых вместо ключей используются строки. Независимо от того, какая из трех категорий используется, каждый элемент массива будет принимать значения размером до 1024 байт. Короче говоря, сетевой узел может хранить огромное количество данных — теперь нам просто нужно узнать, как все это реализовать.

Если вам интересно, где хранится вся эта информация, вы не одиноки. Весь контент netnode хранится в узлах btree в базе данных IDA. Узлы Btree, в свою очередь, хранятся в файле ID0, который, в свою очередь, архивируется в файл IDB, когда вы закрываете базу данных. Любое содержимое сетевого узла, которое вы создаете, не будет отображаться ни в одном из окон отображения IDA; данные принадлежат вам, и вы можете манипулировать ими по своему усмотрению. Вот почему netnode являются идеальным местом для постоянного хранения любых подключаемых модулей и сценариев, которые вы можете использовать для хранения результатов от одного вызова к другому.

Создание netnode

Потенциально сбивающий с толку момент, связанный с netnode, заключается в том, что объявление переменной сетевого узла в одном из ваших модулей не обязательно создает внутреннее представление этого сетевого узла в базе данных. Сетевой узел не создается внутри до тех пор, пока не произойдет одно из следующих событий:

- Сетевому узлу присваивается имя.
- Сетевому узлу присваивается первичное значение.
- Значение сохраняется в одном из внутренних массивов сетевого узла.

Для объявления сетевых узлов в ваших модулях доступны три конструктора. Прототипы каждого из них, извлеченные из netnode.hpp, и примеры их использования показаны в листинге 16-1.

1650485099393.png


В этом примере гарантируется существование только одного сетевого узла (n3) в базе данных после выполнения кода. Сетевые узлы n1 и n2 могут существовать, если они были ранее созданы и заполнены данными. Независимо от того, существовал он ранее или нет, в этот момент n1 способен получать новые данные. Если n2 не существует, а это означает, что сетевой узел с именем $ node 2 не может быть найден в базе данных, то n2 должен быть явно создан ((4) или (5), прежде чем в него можно будет сохранить данные. Если мы хотим гарантировать, что мы можем хранить данные в n2, нам нужно добавить следующую проверку безопасности:

1650485113877.png


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

Важно понимать, что сетевые узлы должны иметь действительный номер сетевого узла, прежде чем вы сможете сохранять данные в сетевом узле. Номер узла сети может быть назначен явно, как в случае с n1, через конструктор, показанный в (2) в предыдущем примере. В качестве альтернативы номер узла сети может быть сгенерирован внутри, когда узел сети создается с использованием флага создания в конструкторе (как в случае n3 с помощью конструктора, показанного в (3)) или с помощью функции создания (как в случае n2). Внутренне назначенные номера сетевых узлов начинаются с 0xFF000000 и увеличиваются с каждым вновь созданным сетевым узлом.

До сих пор в нашем примере мы пренебрегали сетевым узлом n0. В настоящее время n0 не имеет ни номера, ни имени. Мы могли бы создать n0 по имени, используя функцию create аналогично n2. Или мы могли бы использовать альтернативную форму create для создания безымянного сетевого узла с действительным, сгенерированным внутри netnodenumber, как показано здесь:

1650485125387.png


На данный момент можно сохранить данные в n0, хотя у нас нет возможности получить эти данные в будущем, если мы не запишем где-нибудь присвоенный номер узла сети или не назначим n0 имя. Это демонстрирует тот факт, что к сетевым узлам легко получить доступ, когда они связаны с виртуальным адресом (аналогично n1 в нашем примере). Для всех других сетевых узлов присвоение имени позволяет выполнять поиск по имени для всех будущих ссылок на сетевой узел (как для n2 и n3 в нашем примере).

Обратите внимание, что для наших именованных сетевых узлов мы решили использовать имена с префиксом "$", что соответствует практике, рекомендованной в netnode.hpp, для предотвращения конфликтов с именами, которые IDA использует внутри.

Хранение данных в netnode

Теперь, когда вы понимаете, как создать сетевой узел, в котором вы можете хранить данные, давайте вернемся к обсуждению возможности хранения внутреннего массива сетевых узлов. Чтобы сохранить значение в массиве в сетевом узле, нам нужно указать пять частей информации: значение индекса, размер индекса (8 или 32 бита), значение для сохранения, количество байтов, содержащихся в значении, и массив. (один из 256 доступных для каждой категории массива), в котором будет храниться значение. Параметр размера индекса задается неявно функцией, которую мы используем для хранения или извлечения данных. Остальные значения передаются в эту функцию в качестве параметров. Параметр, который выбирает, в каком из 256 возможных массивов хранится значение, обычно называется тегом и часто указывается (хотя это и не обязательно) с помощью символа. Документация netnode различает несколько специальных типов значений, называемых altvals, supvals и hashvals. По умолчанию каждое из этих значений обычно связано с определенным тегом массива: "A" для значений altval, "S" для значений supval и "H" для значений hash. Четвертый тип значения, называемый charval, не связан ни с каким конкретным тегом массива.

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

Altval предоставляет простой интерфейс для хранения и извлечения целочисленных данных в сетевых узлах. Altval могут быть сохранены в любом массиве в сетевом узле, но по умолчанию это массив 'A'. Независимо от того, в каком массиве вы хотите хранить целые числа, использование функций, связанных с altval, значительно упрощает дело. Код в листинге 16-2 демонстрирует хранение и извлечение данных с использованием altval.

1650485145241.png


В этом примере вы видите шаблон, который будет повторяться для других типов значений сетевых узлов, а именно использование функции XXXset (в данном случае altset) для сохранения значения в сетевом узле и функции XXXval (в данном случае altval) для получения значения из сетевого узла. Если мы хотим хранить целые числа в массивах, используя 8-битные значения индекса, нам нужно использовать немного другие функции, как показано в следующем примере.

1650485157121.png


Здесь вы видите, что общее практическое правило использования 8-битных значений индекса заключается в использовании функции с суффиксом _idx8. Также обратите внимание, что ни одна из функций _idx8 не предоставляет значения по умолчанию для параметра тега массива.

Supvals представляют собой наиболее универсальное средство хранения и извлечения данных в сетевых узлах. Supvals представляют собой данные произвольного размера, от 1 байта до 1024 байт. При использовании 32-битных значений индекса массивом по умолчанию для хранения и извлечения значений supval является массив 'S'. Однако опять же, supvals можно сохранить в любом из 256 доступных массивов, указав соответствующее значение тега массива. Строки представляют собой распространенную форму данных произвольной длины, и поэтому им предоставляется специальная обработка в функциях манипуляции с поддержкой. Код в листинге 16-3 предоставляет примеры сохранения supval в сетевом узле.

1650485175243.png

Для функции supset требуется индекс массива, указатель на некоторые данные, длина данных (в байтах) и тег массива, который по умолчанию равен 'S', если он опущен. Если параметр длины опущен, по умолчанию он равен нулю. Когда длина указана равной нулю, supset предполагает, что сохраняемые данные являются строкой, вычисляет длину данных как strlen(data) + 1 и сохраняет нулевой символ завершения вместе со строковыми данными.

Извлечение данных из Supvals требует некоторой осторожности, так как вы можете не знать объем данных, содержащихся в Supvals, до того, как попытаетесь их извлечь. Когда вы извлекаете данные из supval, байты копируются из сетевого узла в предоставленный пользователем выходной буфер. Как вы гарантируете, что ваш выходной буфер имеет достаточный размер для получения данных supval? Первый метод заключается в извлечении всех данных supval в буфер размером не менее 1024 байт. Второй метод заключается в предварительном задании размера выходных буферов, запросив размер файла supval. Для получения supval доступны две функции. Функция supval используется для получения произвольных данных, а функция supstr предназначена для получения строковых данных. Каждая из этих функций ожидает указатель на ваш выходной буфер вместе с размером буфера. Возвращаемое значение для supval — это количество байтов, скопированных в выходной буфер, а возвращаемое значение для supstr — это длина строки, скопированной в выходной буфер, без учета ограничителя null, даже если ограничитель null копируется в буфер. Каждая из этих функций распознает особый случай, когда указатель NULL предоставляется вместо указателя выходного буфера. В таких случаях supval и supstr возвращают количество байтов памяти (включая любой ограничитель null), необходимое для хранения данных supval. В листинге 16-4 показано извлечение данных supval с помощью функций supval и supstr.

1650485189530.png


Используя supvals, можно получить доступ к любым данным, хранящимся в любом массиве в сетевом узле. Например, функции supval можно использовать для хранения и извлечения данных altval, ограничивая операции supset и supval размером altval. Читая netnode.hpp, вы увидите, что это действительно так, наблюдая за встроенной реализацией функции altset, как показано здесь:

1650485201924.png


Hashvals предлагают еще один интерфейс для сетевых узлов. Hashvals связаны не с целочисленными индексами, а со строками ключей. Перегруженные версии функции hashset упрощают связывание целочисленных данных или данных массива с хеш-ключом, в то время как функции hashval, hashstr и hashval_long позволяют извлекать хэш-значения при наличии соответствующего хеш-ключа. Значения тегов, связанные с функциями hashXXX, фактически выбирают одну из 256 хэш-таблиц, при этом таблица по умолчанию имеет значение "H". Альтернативные таблицы выбираются путем указания тега, отличного от "H".

Последний интерфейс для сетевых узлов, который мы упомянем, — это интерфейс charval. Функции charval и charset предлагают простые средства для хранения однобайтовых данных в массиве сетевых узлов. Нет массива по умолчанию, связанного с хранением и поиском charval, поэтому вы должны указать тег массива для каждой операции charval. Charval хранятся в тех же массивах, что и altvals и supvals, а функции charval являются просто обертками над 1-байтовыми supvals.

Еще одна возможность, предоставляемая классом netnode, — это возможность перебирать содержимое массива сетевых узлов (или хеш-таблицы). Итерация выполняется с использованием функций XXX1st, XXXnxt, XXXlast и XXXprev, доступных для altvals, supvals, hashvals и charvals. Пример в листинге 16-5 иллюстрирует итерацию по массиву altvals по умолчанию ('A').

Итерация по supvals, charvals и hashvals выполняется очень похожим образом; однако вы обнаружите, что синтаксис варьируется в зависимости от типа значений, к которым осуществляется доступ. Например, итерация по хеш-значениям возвращает хэш-ключи, а не индексы массива, которые затем необходимо использовать для получения хэш-значений.

1650485216149.png


NETNODES И ГЛОБАЛЬНЫЕ МАССИВЫ IDC

Вы, возможно, помните из главы 15, что язык сценариев IDC предоставляет постоянные глобальные массивы. Сетевые узлы обеспечивают резервное хранилище для глобальных массивов IDC. Когда вы указываете имя функции IDC CreateArray, строка $ idc_array добавляется к имени, которое вы указываете, чтобы сформировать имя сетевого узла. Номер сетевого узла только что созданного сетевого узла возвращается вам в качестве идентификатора массива IDC. Функция IDC SetArrayLong сохраняет целое число в массив altvals ("A"), а функция SetArrayString сохраняет строку в массив supvals ("S"). Когда вы извлекаете значение из массива IDC с помощью функции GetArrayElement, предоставляемые вами теги (AR_LONG или AR_STR) представляют собой теги для используемых массивов altval и supval. для хранения соответствующих целочисленных или строковых данных.

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


Удаление NETNODES и их данных

Класс netnode также предоставляет функции для удаления отдельных элементов массива, всего содержимого массива или всего содержимого сетевого узла. Удалить весь сетевой узел довольно просто.

1650485231401.png


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

1650485242604.png


Обратите внимание на сходство синтаксиса удаления всего содержимого массива альтернативных значений по умолчанию (1) и синтаксиса удаления одного элемента из массива альтернативных значений по умолчанию (2). Если по какой-то причине вы не указали индекс, когда хотите удалить один элемент, вы можете удалить весь массив. Аналогичные функции существуют для удаления данных supval, charval и hashval.

Полезные типы данных SDK

API IDA определяет ряд классов C++, предназначенных для моделирования компонентов, обычно встречающихся в исполняемых файлах. SDK содержит классы для описания функций, разделов программы, структур данных, отдельных инструкций языка ассемблера и отдельных операндов в каждой инструкции. Определены дополнительные классы для реализации инструментов, которые IDA использует для управления процессом дизассемблирования. Классы, попадающие в эту последнюю категорию, определяют общие характеристики базы данных, характеристики модуля загрузчика, характеристики модуля процессора и характеристики подключаемого модуля, а также определяют синтаксис ассемблера, который будет использоваться для каждой дизассемблированной инструкции.

Здесь описаны некоторые из наиболее распространенных классов общего назначения. Мы откладываем обсуждение классов, более специфичных для подключаемых модулей, загрузчиков и процессорных модулей, до соответствующих глав, посвященных этим темам. Наша цель — представить классы, их назначение и некоторые важные элементы данных каждого класса. Полезные функции для управления каждым классом описаны в разделе "Часто используемые функции SDK" на странице 304.

area_t (area.hpp)

Эта структура описывает диапазон адресов и является базовым классом для нескольких других классов. Структура содержит два члена данных, startEA (включительно) и endEA (исключительно), которые определяют границы диапазона адресов. Определены функции-члены, которые вычисляют размер диапазона адресов и могут выполнять сравнения между двумя областями.

func_t (funcs.hpp)

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

segment_t (сегмент.hpp)

Класс segment_t является еще одним подклассом area_t. Дополнительные поля данных описывают имя сегмента, действующие в сегменте разрешения (доступно для чтения, записи, выполнения), тип сегмента (код, данные и т. д.) и количество битов, используемых в адресе сегмента ( 16, 32 или 64).

idc_value_t (выражение.hpp)

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

idainfo (ida.hpp)

Эта структура заполняется характеристиками, описывающими открытую базу данных. Одна глобальная переменная с именем inf и типом idainfo объявлена в ida.hpp. Поля в этой структуре описывают имя используемого модуля процессора, тип входного файла (например, f_PE или f_MACHO через перечисление filetype_t), точку входа в программу (beginEA), минимальный адрес в двоичном файле (minEA), максимальный адрес в двоичном файле (maxEA), порядок байтов текущего процессора (mf) и ряд параметров конфигурации, взятых из ida.cfg.

struc_t (struct.hpp)

Этот класс описывает размещение структурированных данных в дизассемблированном виде. Он используется для описания структур в окне "Структуры", а также для описания состава кадров стека функций. Структура struc_t содержит флаги, описывающие атрибуты структуры (например, является ли она структурой или объединением, свернута или развернута структура в окне отображения IDA), а также содержит массив элементов структуры.

member_t (struct.hpp)

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

op_t (ua.hpp)

Этот класс описывает один операнд внутри дизассемблированной инструкции. Класс содержит отсчитываемое от нуля поле для хранения номера операнда (n), поле типа операнда (type) и ряд других полей, значение которых зависит от типа операнда. Поле типа устанавливается в одну из констант optype_t, определенных в ua.hpp, и описывает тип операнда или режим адресации, используемый для операнда.

insn_t (ua.hpp)

Этот класс содержит информацию, описывающую одну дизассемблированную инструкцию. Поля внутри класса описывают адрес инструкции в дизассемблированном виде (ea), тип инструкции (itype), длину инструкции в байтах (size) и массив из шести возможных значений операнда (Operands) типа op_t (IDA ограничивает каждую инструкцию до шести операндов). Поле itype задается процессорным модулем. Для стандартных процессорных модулей IDA в поле itype задается одна из перечисленных констант, определенных в файле allins.hpp. При использовании процессорного модуля стороннего производителя список возможных значений itype необходимо получить у разработчика модуля. Обратите внимание, что поле itype обычно не имеет никакого отношения к двоичному коду операции для инструкции.

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

Часто используемые функции SDK

Хотя SDK написан с использованием C++ и определяет ряд классов C++, во многих случаях SDK отдает предпочтение традиционным не являющимся членами функциям в стиле C для манипулирования объектами в базе данных. Для большинства типов данных API чаще встречаются функции, не являющиеся членами, которым требуется указатель на объект, чем функция-член, которая манипулирует объектом нужным вам образом.

В последующих сводках мы рассмотрим функции API, которые обеспечивают функциональность, подобную многим функциям IDC, описанным в главе 15. К сожалению, функции, выполняющие одинаковые задачи, называются в IDC одно, а в API — другое.

Базовый доступ к базе данных

Следующие функции, объявленные в bytes.hpp, обеспечивают доступ к отдельным байтам, словам и двойным словам в базе данных.

uchar get_byte(ea_t адрес)

Считывает текущее значение байта из виртуального адреса addr.

ushort get_word(ea_t addr)

Читает текущее значение слова из виртуального адреса addr.

ulong get_long (ea_t addr)

Считывает текущее значение двойного слова из виртуального адреса addr.

get_many_bytes (ea_t addr, void *buffer, ssize_t len)

Копирует len байтов из адреса в предоставленный буфер.

patch_byte (ea_t addr, ulong val)

Устанавливает значение байта по виртуальному адресу addr.

patch_word(длинный адрес, ulonglong val)

Устанавливает значение слова по виртуальному адресу addr.

patch_long (длинный адрес, длинный адрес)

Устанавливает значение двойного слова в виртуальном адресе addr.

patch_many_bytes(ea_t addr, const void *buffer, size_t len)

Исправляет базу данных, начиная с addr, с помощью len байтов из заданного пользователем буфера.

ulong get_original_byte (ea_t addr)

Считывает исходное значение байта (до исправления) из виртуального адреса addr.

ulonglong get_original_word (адрес ea_t)

Считывает исходное значение слова из виртуального адреса addr.

ulonglong get_original_long (адрес ea_t)

Считывает исходное значение двойного слова из виртуального адреса addr.

bool isLoaded (адрес ea_t)

Возвращает true, если адрес содержит допустимые данные, иначе false.

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

После второго патча доступно как текущее значение, так и исходное значение, но нет возможности получить второе значение (которое было установлено с первым патчем).

Функции пользовательского интерфейса

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

msg(char *format, ...)

Печатает отформатированное сообщение в окно сообщений. Эта функция аналогична функции printf языка C и принимает строку формата в стиле printf.

warning(char *format, ...)

Отображает отформатированное сообщение в диалоговом окне.

char *askstr(int hist, char *default, char *format, ...)

Отображает диалоговое окно ввода, предлагающее пользователю ввести строковое значение. Параметр hist определяет способ заполнения выпадающего списка истории в диалоговом окне и должен быть установлен в одну из констант HIST_xxx, определенных в kernwin.hpp. Строка формата и любые дополнительные параметры используются для формирования строки приглашения.

char *askfile_c(int dosave, char *default, char *prompt, ...)

Отображает диалоговое окно сохранения файла (dosave = 1) или открытия файла (dosave = 0), изначально отображая каталог и маску файла, указанные по умолчанию (например, C:\\windows\\*.exe). Возвращает имя выбранного файла или NULL, если диалог был отменен.

askyn_c(int default, char *prompt, ...)

Запрашивает у пользователя вопрос "да" или "нет", выделяя ответ по умолчанию (1 = да, 0 = нет, −1 = отменить). Возвращает целое число, представляющее выбранный ответ.

AskUsingForm_c (const char * form, ...)

Параметр формы представляет собой строковую спецификацию диалогового окна и связанных с ним элементов ввода в формате ASCII. Эта функция может использоваться для создания настраиваемых элементов пользовательского интерфейса, когда ни одна из других удобных функций SDK не соответствует вашим потребностям. Формат строки формы подробно описан в kernwin.hpp.

get_screen_ea()

Возвращает виртуальный адрес текущего местоположения курсора.

jumpto(ea_t адрес)

Переходит в окно дизассемблирования по указанному адресу.

При использовании API доступно гораздо больше возможностей пользовательского интерфейса, чем при использовании сценариев IDC, включая возможность создания настраиваемых диалоговых окон выбора из списка с одним и несколькими столбцами. Пользователям, заинтересованным в этих возможностях, следует обратиться к kernwin.hpp и, в частности, к функциям Choose и Choose2.

Управление именами баз данных

Для работы с именованными местоположениями в базе данных доступны следующие функции:

get_name(ea_t from, ea_t addr, char *namebuf, size_t maxsize)

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


set_name(ea_t addr, char *name, int flags)

Присваивает данное имя данному адресу. Имя создается с атрибутами, указанными в битовой маске флагов. Возможные значения флагов описаны в файле name.hpp.

get_name_ea(ea_t funcaddr, char *localname)

Ищет заданное локальное имя в функции, содержащей funcaddr. Возвращает адрес имени или BADADDR (-1), если такое имя не существует в данной функции.

Управление функциями

Функции API для доступа к информации о дизассемблированных функциях объявлены в funcs.hpp.Ф ункции для доступа к информации о кадре стека объявлены в файле frame.hpp. Некоторые из наиболее часто используемых функций описаны здесь:

func_t *get_func(ea_t адрес)

Возвращает указатель на объект func_t, описывающий функцию, содержащую указанный адрес.

size_t get_func_qty()

Возвращает количество функций, присутствующих в базе данных.

func_t *getn_func(size_t n)

Возвращает указатель на объект func_t, представляющий n-ю функцию в базе данных, где n находится в диапазоне от нуля (включительно) до get_func_qty() (включительно).

func_t *get_next_func(ea_t адрес)

Возвращает указатель на объект func_t, который описывает следующую функцию после указанного адреса.

get_func_name(ea_t addr, char *name, size_t namesize)

Копирует имя функции, содержащей указанный адрес, в предоставленный буфер имен.

struc_t *get_frame(ea_t адрес)

Возвращает указатель на объект struc_t, описывающий кадр стека для функции, содержащей указанный адрес.

Управление структурой

Класс struc_t используется для доступа к кадрам стека функций, а также к структурированным типам данных, определенным в библиотеках типов. Здесь описаны некоторые основные функции для взаимодействия со структурами и их ассоциированными элементами. Многие из этих функций используют тип данных с идентификатором типа (tid_t). API включает функции для сопоставления struc_t со связанным tid_t и наоборот. Обратите внимание, что классы struc_t и member_t содержат элемент данных tid_t, поэтому получить информацию об идентификаторе типа просто, если у вас уже есть указатель на допустимый объект struc_t или member_t.

tid_t get_struc_id(char *имя)

Ищет идентификатор типа структуры по ее имени.

struc_t *get_struc(tid_t id)

Получает указатель на struc_t, представляющий структуру, заданную данным идентификатором типа.

asize_t get_struc_size(struc_t *s)

Возвращает размер данной структуры в байтах.

member_t *get_member (struc_t *s, asize_t offset)

Возвращает указатель на объект member_t, который описывает элемент структуры, находящийся по указанному смещению в данной структуре.

member_t *get_member_by_name (struc_t *s, char *name)

Возвращает указатель на объект member_t, описывающий элемент структуры, идентифицированный по заданному имени.

tid_t add_struc (индекс uval_t, символ * имя, логическое значение is_union = false)

Добавляет новую структуру с заданным именем в список стандартных структур. Структура также добавляется в окно "Структуры" с заданным индексом. Если индекс имеет значение BADADDR, структура добавляется последней в окне "Структуры".

add_struc_member(struc_t *s, char *name, смещение ea_t, flags_t flags, typeinfo_t *info, размер asize_t)

Добавляет новый элемент с заданным именем в заданную структуру. Элемент либо добавляется по указанному смещению в структуре, либо добавляется в конец структуры, если смещение равно BADADDR. Параметр flags описывает тип данных нового члена. Действительные флаги определяются с использованием констант FF_XXX, описанных в bytes.hpp. Параметр info предоставляет дополнительную информацию для сложных типов данных; он может быть установлен в NULL для примитивных типов данных. Тип данных typeinfo_t определен в nalt.hpp. Параметр size указывает количество байтов, занимаемых новым элементом.

Манипуляции с сегментами

Класс segment_t хранит информацию, относящуюся к различным сегментам в базе данных (например, .text и .data), как указано в окне View → Open Subviews → Segments. Напомним, что IDA термины сегменты часто называют разделами различных форматов исполняемых файлов, таких как PE и ELF. Следующие функции обеспечивают базовый доступ к объектам segment_t. Дополнительные функции, связанные с классом segment_t, объявлены в segment.hpp.

segment_t *getseg(ea_t addr)

Возвращает указатель на объект segment_t, содержащий заданный адрес.

segment_t *ida_export get_segm_by_name(char *name)

Возвращает указатель на объект segment_t с заданным именем.

add_segm(ea_t para, ea_t start, ea_t end, char *name, char *sclass)

Создает новый сегмент в текущей базе данных. Границы сегмента задаются начальным (включительно) и конечным (исключающим) адресными параметрами, а имя сегмента задается параметром name. Класс сегмента в общих чертах описывает тип создаваемого сегмента. Предопределенные классы включают CODE и DATA. Полный список предопределенных классов можно найти в segment.hpp. Параметр para описывает базовый адрес раздела, когда используются сегментированные адреса (seg:offset), и в этом случае начало и конец интерпретируются как смещения, а не как виртуальные адреса. Когда сегментированные адреса не используются или все сегменты основаны на 0, этот параметр должен быть установлен на 0.

add_segm_ex(segment_t *s, char *name, char *sclass, int flags)

Альтернативный метод создания новых сегментов. Поля s должны быть установлены так, чтобы отражать диапазон адресов сегмента. Сегменту присваивается имя и тип в соответствии с параметрами name и sclass. Для параметра flags должно быть установлено одно из значений ADDSEG_XXX, определенных в segment.hpp.

int get_segm_qty() Возвращает количество разделов в базе данных.

segment_t *getnseg(int n) Возвращает указатель на объект segment_t, заполненный информацией о n-ом разделе программы в базе данных.

int set_segm_name(segment_t *s, char *name, ...)

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

get_segm_name(ea_t addr, char *name, size_t namesize)

Копирует имя сегмента, содержащего данный адрес, в введенное пользователем буфер имен. Обратите внимание, что имя может быть отфильтровано для замены символов, которые IDA считает недействительными (символы, не указанные как NameChars в ida.cfg), фиктивным символом (обычно символом подчеркивания, как указано в SubstChar в ida.cfg).

get_segm_name(segment_t *s, char *name, size_t namesize)

Копирует потенциально отфильтрованное имя данного сегмента в предоставленный пользователем буфер имен.

get_true_segm_name (segment_t *s, char *name, size_t namesize)

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

Для фактического создания сегмента необходимо использовать одну из функций add_segm. Простое объявление и инициализация объекта segment_t фактически не создает сегмент в базе данных. Это верно для всех классов-оболочек, таких как func_t и struc_t. Эти классы просто предоставляют удобные средства для доступа к атрибутам базовой сущности базы данных. Для внесения постоянных изменений в базу данных необходимо использовать соответствующие функции для создания, изменения или удаления реальных объектов базы данных.

Перекрестные ссылки кода

Ряд функций и перечисляемых констант определен в xref.hpp для использования с перекрестными ссылками кода. Некоторые из них описаны здесь:

get_first_cref_from(ea_t от)

Возвращает первое местоположение, которому данный адрес передает управление. Возвращает BADADDR (-1), если данный адрес не ссылается ни на какие другие адреса.

get_next_cref_from(ea_t от, ea_t текущий)

Возвращает следующее местоположение, на которое данный адрес (от) передает управление, учитывая, что текущее уже было возвращено предыдущим вызовом get_first_cref_from или get_next_cref_from. Возвращает BADADDR, если больше нет перекрестных ссылок.

get_first_cref_to (ea_t to)

Возвращает первое местоположение, передающее управление на указанный адрес. Возвращает BADADDR (-1), если нет ссылок на данный адрес.

get_next_cref_to(ea_t до, ea_t текущий)

Возвращает следующее местоположение, которое передает управление на заданный адрес (to), учитывая, что текущее уже было возвращено предыдущим вызовом get_first_cref_to или get_next_cref_to. Возвращает BADADDR, если больше нет перекрестных ссылок на данное местоположение.

Перекрестные ссылки данных

Функции для доступа к информации о перекрестных ссылках данных (также объявленные в xref.hpp) очень похожи на функции, используемые для доступа к информации о перекрестных ссылках кода. Эти функции описаны здесь:

get_first_dref_from(ea_t from)

Возвращает первое местоположение, к которому данный адрес относится к значению данных. Возвращает BADADDR (-1), если данный адрес не ссылается ни на какие другие адреса.

get_next_dref_from(ea_t от, ea_t current)

Возвращает следующее местоположение, к которому данный адрес (от) относится как значение данных, учитывая, что текущее уже было возвращено предыдущим вызовом get_first_dref_from или get_next_dref_from. Возвращает BADADDR, если больше нет перекрестных ссылок.

get_first_dref_to (ea_t to)

Возвращает первое местоположение, которое ссылается на данный адрес как на данные. Возвращает BADADDR (-1), если нет ссылок на данный адрес.

get_next_dref_to(ea_t ), ea_t current)

Возвращает следующее местоположение, которое ссылается на данный адрес (to) как на данные, учитывая, что текущее уже было возвращено предыдущим вызовом get_first_dref_to или get_next_dref_to. Возвращает BADADDR, если больше нет перекрестных ссылок на данное местоположение.

SDK не содержит эквивалента функции IDC XrefType. Переменная с именем lastXR объявлена в файле xref.hpp; однако он не экспортируется. Если вам нужно определить точный тип перекрестной ссылки, вы должны выполнить итерацию перекрестных ссылок, используя структуру xrefblk_t. xrefblk_t описан в разделе "Перечисление перекрестных ссылок " на стр. 311.

Методы итерации с использованием IDA API

Используя IDA API, часто существует несколько различных способов перебора различных объектов базы данных. В следующих примерах мы демонстрируем некоторые общие методы итерации:

Перечисление функций

Первый способ перебора функций в базе данных имитирует способ, которым мы выполняли ту же задачу с помощью IDC:

1650485397169.png


В качестве альтернативы мы можем просто перебирать функции по номерам индексов, как показано в следующем примере:

1650485407680.png


Наконец, мы можем работать на несколько более низком уровне и использовать структуру данных, называемую areaacb_t, также известную как блок управления областью, определенную в area.hpp. Блоки управления областью используются для ведения списков связанных объектов area_t. Глобальная область areaacb_t с именем funcs экспортируется (в funcs.hpp) как часть IDA API. Используя класс areaacb_t, предыдущий пример можно переписать следующим образом:

1650485419704.png


В этом примере функция-член get_next_area (1) и (2) повторно используются для получения значений индекса для каждой области в блоке управления функциями. Указатель на каждую связанную область func_t получается путем предоставления каждого значения индекса функции-члену getn_area (3). В SDK объявлено несколько глобальных переменных areaacb_t, в том числе глобальная переменная segs, представляющая собой блок управления областью, содержащий указатели segment_t для каждой секции в двоичном файле.

Перечисление членов структуры

В SDK кадры стека моделируются с использованием возможностей класса struc_t. Пример в листинге 16-6 использует итерацию элемента структуры как средство печати содержимого кадра стека.

1650485433798.png


Этот пример обобщает фрейм стека функции, используя информацию из объекта func_t функции и связанного с ним struc_t, представляющего фрейм стека функции. Поля frsize и и frregs определяют размер части локальной переменной кадра стека и количество байтов, выделенных для сохраненных регистров, соответственно. Сохраненный адрес возврата можно найти в кадре, следующем за локальными переменными и сохраненными регистрами. Внутри самого фрейма поле memqty указывает количество определенных элементов, содержащихся в структуре фрейма, что также соответствует размеру массива элементов. Цикл используется для извлечения имени каждого члена и определения того, является ли член локальной переменной или аргументом на основе его начального смещения (soff) в структуре фрейма.

Перечисление перекрестных ссылок

В главе 15 мы видели, что можно перечислить перекрестные ссылки из сценариев IDC. Те же возможности есть и в SDK, хотя и в несколько иной форме. В качестве примера давайте вернемся к идее перечисления всех вызовов определенной функции (см. листинг 15.4 на стр. 274). Следующая функция почти работает.

1650485457344.png


Причина, по которой эта функция почти работает, заключается в том, что невозможно определить тип перекрестной ссылки, возвращаемой для каждой итерации цикла (напомним, что нет эквивалента SDK для XrefType IDC). В этом случае мы должны убедиться, что каждая перекрестная ссылка на данную функцию на самом деле является перекрестной ссылкой типа вызова (fl_CN или fl_CF).

Когда вам нужно определить тип перекрестной ссылки в SDK, вы должны использовать альтернативную форму итерации перекрестной ссылки, облегчаемую структурой xrefblk_t, которая описана в xref.hpp. Базовый макет xrefblk_t показан в следующем листинге. (Для получения полной информации см. xref.hpp.)

1650485473972.png


Функции-члены xrefblk_t используются для инициализации структуры (1) и (2) и выполнения итераций (3) и (4), а элементы данных используются для доступа к информации о последней полученной перекрестной ссылке. Значение флага, требуемое функциями first_from и first_to, определяет, какой тип перекрестных ссылок должен быть возвращен. Допустимые значения параметра flags включают следующее (из xref.hpp):

1650485486085.png


Обратите внимание, что значение флага не ограничивает возвращаемые ссылки только кодом. Если вас интересуют перекрестные ссылки кода, вы должны либо сравнить поле типа xrefblk_t с конкретными типами перекрестных ссылок (например, fl_JN), либо проверить поле iscode, чтобы определить, была ли последняя возвращенная перекрестная ссылка перекрестной ссылкой кода. Следующая модифицированная версия функции list_callers демонстрирует использование итерационной структуры xrefblk_t.

1650485496499.png


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

Резюме

Функции и структуры данных, описанные в этой главе, лишь поверхностно касаются API IDA. Для каждой из описанных функциональных категорий существует гораздо больше функций API, которые выполняют более специализированные задачи и обеспечивают гораздо более тонкий контроль над различными элементами базы данных, чем это может быть реализовано с помощью IDC. В следующих главах мы подробно рассмотрим создание подключаемых модулей, модулей загрузчика и процессорных модулей, а также продолжим расширять представление о возможностях SDK.
 


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