Анимация
JavaScript


Главная  Библионтека 

[ 0 ] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140

дизассемблер

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

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

Сказанное можно проиллюстрировать следующим примером: рассмотрим исходную программу (a). При ассемблировании смещение строки s0, загружаемое в регистр s0 заменяется его конкретным значением, в данном случае равным 108h, отчего, команда "MOV DX, offset s0" приобретает вид "MOV DX, 108h". Это влечет за собой потерю информации - теперь уже нельзя однозначно утверждать как выглядел исходный текст, т.к. ассемблирование "offset s0" и " 108h" дает одинаковый результат, т.е. функция ассемблирования не инъективна.

Если все машинные инструкции исходного файла, перевести в соответствующие им символьные мнемоники (назовем такую операцию простым синтаксическим дизассемблированием), в результате получится (b). Легко видеть - программа сохраняет работоспособность лишь до тех пор, пока выводимая строка располагается по адресу 108h. Если модификация кода программы (c) нарушает такое равновесие, на экране вместо ожидаемого приветствия появляется мусор - теперь выводимая строка находится по адресу 0x10C, но в регистр DX по прежнему загружается прежнее значение ее смещения - 0x108 (d).

ah.9

dx, offset s0 21h

DB Hello,World!,0Dh,0Ah,$

(а) Исходная программа

mov ah,9 mov dx,0108h

int 21h ret

s0 DB Hello,World!,0Dh,0Ah,$

(b) Дизассемблированная программа

mov ah,09 mov dx,0108h

int 21h

xor ax,ax int 16h

s0 DB Hello,World!,0Dh,0Ah,$ (с) Модифицированная программа

0100

start

proc

near

0100

ah, 9

0102

dx, 108h

0105

0107

ax, ax

0109

16h ◄-1

010B

retn

010B

aHelloWorld

db He

llo,World!,

010C

start

(d) Неработоспособный результат

Аналогичная проблема возникает и переводе с одного языка на другой - фраза «это ключ» в зависимости от ситуации может быть переведена и как "this is key", и "this is clue", и "this is switch" Для правильного перевода мало простого словаря -подстрочечника, необходимо еще понимать о чем идет речь, т.е. осмысливать переводимый текст.

Человек легко может определить, что содержимое регистра DX в данном случае

Функция f(x) = y называется инъективной, если уравнение f(y) = x, имеет только один корень и, соответственно, наоборот.



является именно смещением, а не чем ни будь иным, поскольку, его ожидает функция 0x9 прерывания 0x21. Но дизассемблеру для успешной работы мало знать одних прототипов системных и библиотечных функций, - он должен еще уметь отслеживать содержимое регистров, а, следовательно, «понимать» команды микропроцессора. Создание такого дизассемблера (часто называемого контекстным) очень сложная инженерная задача, тесно граничащая с искусственным интеллектом, которая на сегодняшний день еще никем не решена. Существует более или менее удачные разработки, но ни одна из них не способна генерировать 100%-работоспособные листинги.

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

MOV AX,offset Table MOV BX,200h ; Index ADD AX,BX

MOV AX,[BX] (а) Исходная программа

BB 00 02 MOV AX,0010

01 D8 MOV BX,0200

8B 07 ADD AX,BX

B8 10 00 MOV AX,Word ptr [BX]

(b) Машинный код (c) Дизассемблированный текст

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

JMP Label

E90100

Align 4

Label: XOR AX,AX

33 C0

( а ) Исходная программа

(b) Машинный код

00: E9 01 00 jmp 04

03: 00 33 add [bp][di],dh

05: C0 C3 rol bl,-070;

(c) Дизассемблированный текст

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

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

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



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

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

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

Первые шаги с IDA Pro

С легкой руки Дениса Ричи повелось начинать освоение нового языка программирования с создания простейшей программы "Hello, World!", -- и здесь не будет нарушена эта традиция. Оценим возможности IDA Pro следующим примером (для совместимости с книгой рекомендуется откомпилировать его с помощью Microsoft Visual C++ 6.0 вызовом "cl.exe first.cpp" в командной строке):

#include <iostream.h>

void main()

cout«"Hello, Sailor!\n";

a) исходный текст программы first.cpp

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

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

Рисунок 4 "0x000.bmp" Так выглядит результат работы консольной версии IDA Pro 3.6

Рисунок 5 "0x001.bmp" Так выглядит результат работы консольной версии IDA Pro

Рисунок 6 "0x002.bmp" Так выглядит результат работы графической версии IDA Pro



[ 0 ] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140