Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Учебное пособие 50072.doc
Скачиваний:
5
Добавлен:
30.04.2022
Размер:
1.46 Mб
Скачать

1.2.2. Метод интеллектуального дизассемблирования от точки входа

Принцип дизассемблирования от точки входа заключается в том, что дизассемблер ведет список точек входа в подпрограммы, куда первоначально помещается точка входа в программу. Затем из списка точек входа извлекается очередная точка входа в подпрограмму и выполняется ее последовательное дизассемблирование [25, 26].

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

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

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

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

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

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

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

Для функций обратного вызова задача может быть решена путем перечисления всех функций API Windows, которые принимают в качестве параметров указатель на callback-функцию. Это DialogBoxParam (макрос DialogBox), CreateTimer, CreateThread, RegNotifyChangeKeyValue и др.

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

1.2.3. Метод потокового анализа

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

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

Потоковый анализ можно условно разделить на анализ потока управления и анализ потока данных.

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

В рамках анализа потока данных необходимо построение графа связи по данным (def-use graph). Данный граф позволяет описать потоки данных в программе без учета того, как именно эти данные преобразуются. Множества входов и выходов соответствуют интуитивному представлению об аргументах и результатах операторов, а отображение Dеf-use просто описывает, как используются выработанные операторами результаты. Построение данного графа опирается на решение одной из задач из области анализа потоков данных, а именно - задачи о достижимых определениях. Эту задачу можно сформулировать следующим образом: Для каждого вхождения переменной требуется определить множество присваиваний, такое, что для каждого из них существует путь, в котором между ним и данным вхождением отсутствуют другие присваивания той же переменной.

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

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

После построения графа достижимых определений можно сделать выводы о данных, передаваемых в подпрограмму и обратно. Данные, передаваемые в подпрограмму (параметры) можно выявить, анализируя связи графа def-use, выходящие за пределы процедуры.

Параметры в подпрограмму могут передаваться несколькими способами:

  • через стек. При этом перед вызовом подпрограммы находится серия команд push или модификация указателя стека. Внутри подпрограммы, как правило, выполняется последовательность команд push ebp; mov ebp esp; sub esp <размер локальных переменных подпрограммы>. В результате оптимизации последовательность может не присутствовать в коде в явном виде, но семантика операций сохраняется;

  • через регистры общего назначения процессора (так называемые fastcall-подпрограммы). Первые два параметра таких подпрограмм передаются через регистры eax и ebx. Если у подпрограммы больше двух параметров, то они передаются через стек;

  • через глобальные (статические) переменные. Такие переменные выделяются, как правило, в сегментах (секциях) инициализированных или неинициализированных данных исполняемого модуля.

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

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

  • по указателю, переданному как параметр подпрограммы;

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

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