Описание функций встроенного языка
Скачать 2.86 Mb.
|
1 ОБРАЗ МЫШЛЕНИЯ – ДИЗАССЕМБЛЕР IDA Pro ТОМ I ОПИСАНИЕ ФУНКЦИЙ ВСТРОЕННОГО ЯЗЫКА IDA Pro Аннотация Подробный справочник по функциям встроенного языка, интерфейсу и архитектуре дизассемблера IDA Pro 4.01 с уточнением особенностей младших версий. Показывает приемы эффективного использования IDA Pro для исследования зашифрованного кода, π-кода, самомодифицирующегося кода и кода, защищенного антиотладочными приемами. Ориентирован на системных программистов средней и высокой квалификации в совершенстве владеющих языком ассемблера микропроцессоров серии Intel 80x86 и работающих с операционными системами фирмы Microsoft. Введение. Об этой книге Цель этой книги – частично компенсировать информационный голод, окутывающий один из популярнейших дизассемблеров современности – IDA Pro. Сведения, содержащиеся в документации, подавляемой вместе с этим продуктом, весьма отрывочные и безнадежно устаревшие. Самостоятельное же освоение IDA Pro требует значительных усилий, длительного времени и постоянных консультаций с ее разработчиками. Появлению этой книги предшествовал большой объем работы, проделанной автором. Первоначально когда замышлялось написать книгу, обобщающую достижения современного реинженеринга, планировалось скоромное издание страниц максимум в пятьсот, но в течение работы над проектом выяснилось: описание одних лишь функций встроенного языка IDA Pro значительно превышает этот объем. Поэтому весь материал пришлось разбить на три тома – «Описание функций встроенного языка IDA Pro», «Приемы эффективной работы с IDA Pro» и «Технологии дизассемблирования». Функции ядра IDA находят применение не только во встроенном языке, – сама IDA активно использует их для дизассемблирования, а большинство пунктов меню эквивалентны соответствующим функциям ядра. Поэтому, любую операцию можно выполнить не только последовательными нажатиями «горячих» клавиш, но и совокупностью команд встроенного языка. Эта книга так же рассказывает и об архитектуре, затрагивая вопросы внутреннего устройства IDA Pro без понимания которых полноценная работа с дизассемблера невозможна. ОБРАЩЕНИЕ АВТОРА К ЧИТАТЕЛЮ: когда писались первые строки этой книги ее автор еще не обладал тем опытом, который необходим для написания справочной литературы подобного типа. В результате, из-под пера вылезло нечто ужасное, и все пришлось переписывать заново. К сожалению, сроки издания нельзя бесконечно оттягивать (читатели нервничают, издатель сердится) и в том издании, что вы держите сейчас в руках, «доведены до ума» лишь десять первых глав из двадцати, а остальные даны в первозданном варианте. 2 Автор просит читателя извинить его за такую ситуацию и, положа руку на сердце, обещает, что в следующем издании (если только одно будет это следующее издание – это ж от читателей зависит) все огрехи будут исправлены. Крис Касперски. февраль 2001 Серверный Кавказ. Версии IDA Pro Дизассемблер IDA Pro относится к интенсивно развивающимся продуктам, – постоянные совершенствования и бесконечные изменения, вносимые разработчиками, породили множество версий, из которых наибольшее распространение получили 3.84, 3,84b, 3,85, 4.0, а некоторые до сих пор предпочитают использовать IDA 3.6. К сожалению, даже близкие версии плохо совместимы между собой – прототипы и поведение функций встроенного языка находится в постоянном изменении, затрудняя создание переносимых скриптов. Приводимый в книге материал в основном рассчитан на IDA Pro 4.0.1, но в ряде случаев оговариваются особенности поведения и других версий дизассемблера. Существуют три различных пакета поставки дизассемблера – стандартная (IDA Pro Standard), прогрессивная (IDA Pro Advanced) и демонстрационная (IDA Pro Demo). Отличие между IDA Pro Standard и IDA Pro Advanced заключается в количестве поддерживаемых процессоров, полный перечень которых можно найти в прилагаемой к IDA документации. Демонстрационный пакет представляет собой усеченный вариант полнофункционального продукта и обладает рядом существенных ограничений: никакие процессоры кроме семейства Intel 80x86 и типы файлов за исключением win32 PE не поддерживаются; в поставку входят сигнатуры всего лишь двух компиляторов – Microsoft Visual C++ 6.0 и Borland C++ Builder; функция сохранения результатов работы заблокирована, а максимальное время продолжительности одного сеанса работы с дизассемблером ограничено. В каждый пакет поставки (за исключением демонстрационного) входят две ипостаси – одна графическая под Windows-32 (в дальнейшем обозначаемая как IDAG) и три консольных для MS-DOS, OS/2 и Windows-32. В демонстрационный пакет входит лишь одна графическая ипостась. Обе ипостаси обладают сходными функциональными возможностями, поэтому в книге будет описана лишь одна из них, скомпилированная для исполнения в среде Windows-32 (в дальнейшем обозначаемая как IDAW) Покупка IDA Pro Standard или IDA Pro Advanced дает право на бесплатное приобретение IDA SDK (Software Development Kit) – программного пакета, позволяющего пользователю самостоятельно разрабатывать процессорные модули, файловые загрузчики и плагины (внешние самостоятельные модули). Это неограниченно расширяет возможности IDA Pro, позволяя анализировать любой код, для какого бы микропроцессора и операционной системы он ни предназначался. SDK различных версий не совместимы друг с другом и созданные пользователем модули могут не работать в другой версии IDA Pro, поэтому, прежде чем переходить на очередную версию IDA Pro, настоятельно рекомендуется убедиться в сохранении работоспособности всех скриптов, модулей и плагинов и т.д. Рисунок 1 ”ida.console.view” Так выглядит консольная ипостась IDA Pro 4.01 Рисунок 2 “ida.gui.view” Так выглядит графическая ипостась IDA Pro 4.01 Рисунок 3 “ida.gui.view.4.14.bmp” Так выглядит графическая ипостась IDA Pro 4.14 Demo 3 Кратное введение в дизассемблирование Одним из способов изучения программ в отсутствии исходных текстов является дизассемблирование, – перевод двоичных кодов процессора в удобочитаемые мнемонические инструкции. С перового взгляда кажется: ничего сложного в такой операции нет, и один дизассемблер не будет сильно хуже любого другого. На самом же деле, ассемблирование – однонаправленный процесс с потерями, поэтому автоматическое восстановление исходного текста невозможно. Одна из фундаментальных проблем дизассемблирования заключается в синтаксической неотличимости констант от адресов памяти (сегментов и смещений). Потребность распознавания смещений объясняется необходимостью замены конкретных адресов на метки, действительное смещение которых определяется на этапе ассемблирования программы. Сказанное можно проиллюстрировать следующим примером: рассмотрим исходную программу (a). При ассемблировании смещение строки s0, загружаемое в регистр s0 заменяется его конкретным значением, в данном случае равным 108h, отчего, команда “MOV DX, offset s0” приобретает вид “MOV DX, 108h”. Это влечет за собой потерю информации – теперь уже нельзя однозначно утверждать как выглядел исходный текст, т.к. ассемблирование “…offset s0” и “…108h” дает одинаковый результат, т.е. функция ассемблирования не инъективна 1 Если все машинные инструкции исходного файла, перевести в соответствующие им символьные мнемоники (назовем такую операцию простым синтаксическим дизассемблированием), в результате получится (b). Легко видеть – программа сохраняет работоспособность лишь до тех пор, пока выводимая строка располагается по адресу 108h. Если модификация кода программы (c) нарушает такое равновесие, на экране вместо ожидаемого приветствия появляется мусор – теперь выводимая строка находится по адресу 0x10C, но в регистр DX по прежнему загружается прежнее значение ее смещения – 0x108 (d). mov ah,9 mov ah,9 mov ah,09 mov dx, offset s0 mov dx,0108h mov dx,0108h int 21h Æ int 21h Æ int 21h ret ret xor ax,ax s0 DB 'Hello,World!',0Dh,0Ah,'$' s0 DB 'Hello,World!',0Dh,0Ah,'$' int 16h ret s0 DB 'Hello,World!',0Dh,0Ah,'$' (а) Исходная программа (b) Дизассемблированная программа (с) Модифицированная программа :0100 start proc near :0100 mov ah, 9 :0102 mov dx, 108h ─┐ :0105 int 21h │ :0107 xor ax, ax │ :0109 int 16h ◄────┘ :010B retn :010B aHelloWorld db 'Hello,World!',0Dh,0Ah,'$' :010C end start (d) Неработоспособный результат Аналогичная проблема возникает и переводе с одного языка на другой – фраза «это ключ» в зависимости от ситуации может быть переведена и как “this is key”, и “this is clue”, и “this is switch”… Для правильного перевода мало простого словаря - подстрочечника, необходимо еще понимать о чем идет речь, т.е. осмысливать переводимый текст. Человек легко может определить, что содержимое регистра DX в данном случае 1 Функция f(x) = y называется инъективной, если уравнение f(y) = x, имеет только один корень и, соответственно, наоборот. 4 является именно смещением, а не чем ни будь иным, поскольку, его ожидает функция 0x9 прерывания 0x21. Но дизассемблеру для успешной работы мало знать одних прототипов системных и библиотечных функций, – он должен еще уметь отслеживать содержимое регистров, а, следовательно, «понимать» команды микропроцессора. Создание такого дизассемблера (часто называемого контекстным) очень сложная инженерная задача, тесно граничащая с искусственным интеллектом, которая на сегодняшний день еще никем не решена. Существует более или менее удачные разработки, но ни одна из них не способна генерировать 100%-работоспособные листинги. Например, путь в исходной программе имелся фрагмент (а), загружающий в регистр AX смещение начала таблицы, а в регистр BX индекс требуемого элемента. Ассемблер, заменяя оба значения константами (b), создает неразрешимую задачу, – легко видеть, что один из регистров содержит смещение, а другой индекс, но как узнать какой именно? MOV AX,offset Table BB 00 02 MOV AX,0010 MOV BX,200h ; Index 01 D8 MOV BX,0200 ADD AX,BX Æ 8B 07 Æ ADD AX,BX MOV AX,[BX] B8 10 00 MOV AX,Word ptr [BX] (а) Исходная программа (b) Машинный код (c) Дизассемблированный текст Другая фундаментальная проблема заключается в невозможности определения границ инструкций синтаксическим дизассемблером. Путь в исходной программе (a) имелась директива выравнивания кода по адресам кратным четырем, тогда ассемблер (b) вставит в этом месте несколько произвольных символов (как правило нулей), а дизассемблер «не зная» об этом, примет их за часть инструкций, в результате чего сгенерирует ни на что ни годный листинг (c). JMP Label 00: E90100 Align 4 03: 00 Label: XOR AX,AX 04: 33 C0 RET 06: C3 (а) Исходная программа (b) Машинный код 00: E9 01 00 jmp 04 03: 00 33 add [bp][di],dh 05: C0 C3 rol bl,-070; (c) Дизассемблированный текст Контекстные дизассемблеры частично позволяют этого избежать, поскольку, способны распознавать типовые способы передачи управления, но если программист использует регистровые переходы, дизассемблеру придется эмулировать выполнение программы, для определения значений регистров в каждой точке программы. Это не только технически сложная, но и ресурсоемкая задача, решение которой еще предстоит найти. Дизассемблирование – творческий процесс, развивающий интуицию и абстрактное мышление, возможно, даже особый вид искусства, позволяющего каждому проявить свою индивидуальность. На сегодняшний день не существует ни одного полностью автоматического дизассемблера, способного генерировать безупречно работоспособный листинг и доводить полученный ими результат до готовности приходится человеку. Таким образом, встает вопрос о механизмах взаимодействия человека с дизассемблером. По типу реализации интерфейса взаимодействия с пользователем, существующие дизассемблеры можно разделить на две категории – автономные и интерактивные. Автономные дизассемблеры требуют от пользования задания всех указаний до начала дизассемблирования и не позволяют вмешиваться непосредственно в сам процесс. Если же конечный результат окажется неудовлетворительным, пользователь либо вручную правит полученный листинг, либо указывает дизассемблеру на его ошибки и повторяет всю 5 процедуру вновь и вновь, порой десятки раз! Такой способ общения человека с дизассемблером непроизводителен и неудобен, но его легче запрограммировать. Интерактивные дизассемблеры обладают развитым пользовательским интерфейсом, благодаря которому приобретают значительную гибкость, позволяя человеку «вручную» управлять разбором программы, помогая автоматическому анализатору там, где ему самому не справится – отличать адреса от констант, определять границы инструкций и т.д. Примером автономного дизассемблера является SOURCER, а интерактивного – IDA. Преимущество SOURCER-а заключается в простоте управления, в то время как работа с IDA требует высокой квалификации и навыков системного программирования. Неопытные пользователи часто предпочитают SOURCER, лидирующий среди других дизассемблеров, на небольших проектах. Но он очень плохо справляется с анализом большого, порядка нескольких мегабайт, заковыристого файла, а с шифрованным или π-кодом не справляется вообще! И тогда на помощь приходит IDA, которая, будучи виртуальной программируемой машиной, может абсолютно все – стоит лишь разработать и ввести соответствующий скрипт. А как это сделать и рассказывает настоящая книга. Первые шаги с IDA Pro С легкой руки Дениса Ричи повелось начинать освоение нового языка программирования с создания простейшей программы “Hello, World!”, -- и здесь не будет нарушена эта традиция. Оценим возможности IDA Pro следующим примером (для совместимости с книгой рекомендуется откомпилировать его с помощью Microsoft Visual C++ 6.0 вызовом “cl.exe first.cpp” в командной строке): #include { cout<<"Hello, Sailor!\n"; } a) исходный текст программы first.cpp Компилятор сгенерирует исполняемый файл размером почти в 40 килобайт, большую часть которого займет служебный, стартовый или библиотечный код! Попытка дизассемблирования с помощью таких дизассемблеров как W32DASM (или аналогичных ему) не увенчается быстрым успехом, поскольку над полученным листингом размером в пятьсот килобайт (!) можно просидеть не час и не два. Легко представить сколько времени уйдет на серьезные задачи, требующие изучения десятков мегабайт дизассемблированного текста. Попробуем эту программу дизассемблировать с помощью IDA. Если все настройки оставить по умолчанию, после завершения анализа экран (в зависимости от версии) должен выглядеть следующим образом: Рисунок 4 “0x000.bmp” Так выглядит результат работы консольной версии IDA Pro 3.6 Рисунок 5 “0x001.bmp” Так выглядит результат работы консольной версии IDA Pro 4.0 Рисунок 6 “0x002.bmp” Так выглядит результат работы графической версии IDA Pro 4.0 6 С версии 3.8x 2 в IDA появилась поддержка «сворачивания» (Collapsed) функций. Такой прием значительно упрощает навигацию по тексту, позволяя убрать с экрана не интересные в данный момент строки. По умолчанию все библиотечные функции сворачиваются автоматически. Развернуть функцию можно подведя к ней курсор и нажав <+> на дополнительной цифровой клавиатуре, расположенной справа. Соответственно, клавиша <-> предназначена для сворачивания. По окончании автоматического анализа файла “first.exe”, IDA переместит курсор к строке “.text:00401B2C” – точке входа в программу. Среди начинающих программистов широко распространено заблуждение, якобы программы, написанные на Си, начинают выполняться с функции “main”, но в действительности это не совсем так. На самом деле сразу после загрузки файла управление передается на функцию “Start”, вставленную компилятором. Она подготавливает глобальные переменные _osver (билд), _winmajor (старшая версия операционной системы), _winminor (младшая версия операционной системы), _winver (полная версия операционной системы), __argc (количество аргументов командной строки), __argv (массив указателей на строки аргументов), _environ (массив указателей на строки переменных окружения); инициализирует кучи (heap); вызывает функцию main, а после возращения управления завершает процесс с помощью функции Exit. Наглядно продемонстрировать инициализацию переменных, совершаемую стартовым кодом, позволяет следующая программа. #include #include { int a; printf(">Версия OS:\t\t\t%d.%d\n\ >Билд:\t\t\t%d\n\ >Количество агрументов:\t%d\n",\ _winmajor,_winminor,_osver,__argc); for (a=0;a<__argc;a++) printf(">\tАгрумент %02d:\t\t%s\n",a+1,__argv[a]); a=!a-1; while(_environ[++a]) ; printf(">Количество переменных окружения:%d\n",a); while(a) printf(">\tПеременная %d:\t\t%s\n",a,_environ[--a]); } a) исходный текст программы CRt0.demo.c Прототип функции main как будто указывает, что приложение не принимает ни каких аргументов командной строки, но результат работы программы доказывает обратное и на машине автора выглядит так (приводится в сокращенном виде): >Версия OS: 5.0 >Билд: 2195 >Количество агрументов: 1 > Агрумент 01: CRt0.demo >Количество переменных окружения: 30 > Переменная 29: windir=C:\WINNT >... b) результат работы программы CRt0.demo.c Очевидно, нет никакой необходимости анализировать стандартный стартовый код 2 А может и чуточку раньше 7 приложения, и первая задача исследователя – найти место передачи управления на функцию main. К сожалению, гарантированное решение это задачи требует полного анализа содержимого функции “Start”. У исследователей существует множество хитростей, но все они базируются на особенностях реализации конкретных компиляторов 3 и не могут считаться универсальными. Рекомендуется изучить исходные тексты стартовых функций популярных компиляторов, находящиеся в файлах CRt0.c (Microsoft Visual C) и c0w.asm (Borland C) – это упросит анализ дизассемблерного листинга. Ниже, в качестве иллюстрации, приводится содержимое стартового кода программы “first.exe”, полученное в результате работы W32Dasm: //******************** Program Entry Point ******** :00401B2C 55 push ebp :00401B2D 8BEC mov ebp, esp :00401B2F 6AFF push FFFFFFFF :00401B31 6870714000 push 00407170 :00401B36 68A8374000 push 004037A8 :00401B3B 64A100000000 mov eax, dword ptr fs:[00000000] :00401B41 50 push eax :00401B42 64892500000000 mov dword ptr fs:[00000000], esp :00401B49 83EC10 sub esp, 00000010 :00401B4C 53 push ebx :00401B4D 56 push esi :00401B4E 57 push edi :00401B4F 8965E8 mov dword ptr [ebp-18], esp Reference To: KERNEL32.GetVersion, Ord:0174h | :00401B52 FF1504704000 Call dword ptr [00407004] :00401B58 33D2 xor edx, edx :00401B5A 8AD4 mov dl, ah :00401B5C 8915B0874000 mov dword ptr [004087B0], edx :00401B62 8BC8 mov ecx, eax :00401B64 81E1FF000000 and ecx, 000000FF :00401B6A 890DAC874000 mov dword ptr [004087AC], ecx :00401B70 C1E108 shl ecx, 08 :00401B73 03CA add ecx, edx :00401B75 890DA8874000 mov dword ptr [004087A8], ecx :00401B7B C1E810 shr eax, 10 :00401B7E A3A4874000 mov dword ptr [004087A4], eax :00401B83 6A00 push 00000000 :00401B85 E8D91B0000 call 00403763 :00401B8A 59 pop ecx :00401B8B 85C0 test eax, eax :00401B8D 7508 jne 00401B97 :00401B8F 6A1C push 0000001C :00401B91 E89A000000 call 00401C30 :00401B96 59 pop ecx Referenced by a (U)nconditional or (C)onditional Jump at Address: 3 Например, Microsoft Visual C всегда, независимо от прототипа функции main передает ей три аргумента – указатель на массив указателей переменных окружения, указатель на массив указателей аргументов командной строки и количество аргументов командной строки, а все остальные функции стартового кода принимают меньшее количество аргументов 8 |:00401B8D(C) | :00401B97 8365FC00 and dword ptr [ebp-04], 00000000 :00401B9B E8D70C0000 call 00402877 Reference To: KERNEL32.GetCommandLineA, Ord:00CAh | :00401BA0 FF1560704000 Call dword ptr [00407060] :00401BA6 A3E49C4000 mov dword ptr [00409CE4], eax :00401BAB E8811A0000 call 00403631 :00401BB0 A388874000 mov dword ptr [00408788], eax :00401BB5 E82A180000 call 004033E4 :00401BBA E86C170000 call 0040332B :00401BBF E8E1140000 call 004030A5 :00401BC4 A1C0874000 mov eax, dword ptr [004087C0] :00401BC9 A3C4874000 mov dword ptr [004087C4], eax :00401BCE 50 push eax :00401BCF FF35B8874000 push dword ptr [004087B8] :00401BD5 FF35B4874000 push dword ptr [004087B4] :00401BDB E820F4FFFF call 00401000 :00401BE0 83C40C add esp, 0000000C :00401BE3 8945E4 mov dword ptr [ebp-1C], eax :00401BE6 50 push eax :00401BE7 E8E6140000 call 004030D2 :00401BEC 8B45EC mov eax, dword ptr [ebp-14] :00401BEF 8B08 mov ecx, dword ptr [eax] :00401BF1 8B09 mov ecx, dword ptr [ecx] :00401BF3 894DE0 mov dword ptr [ebp-20], ecx :00401BF6 50 push eax :00401BF7 51 push ecx :00401BF8 E8AA150000 call 004031A7 :00401BFD 59 pop ecx :00401BFE 59 pop ecx :00401BFF C3 ret a) стартовый код программы “first.exe”, полученный дизассемблером W32Dasm Иначе выглядит результат работы IDA, умеющей распознавать библиотечные функции по их сигнатурам (приблизительно по такому же алгоритму работает множество антивирусов). Поэтому, способности дизассемблера тесно связаны с его версией и полнотой комплекта поставки – далеко не все версии IDA Pro в состоянии работать с программами, сгенерированными современными компиляторами. (Перечень поддерживаемых компиляторов можно найти в файле “%IDA%/SIG/list”). 00401B2C start proc near 00401B2C 00401B2C var_20 = dword ptr -20h 00401B2C var_1C = dword ptr -1Ch 00401B2C var_18 = dword ptr -18h 00401B2C var_14 = dword ptr -14h 00401B2C var_4 = dword ptr -4 00401B2C 00401B2C push ebp 00401B2D mov ebp, esp 00401B2F push 0FFFFFFFFh 00401B31 push offset stru_407170 00401B36 push offset __except_handler3 00401B3B mov eax, large fs:0 9 00401B41 push eax 00401B42 mov large fs:0, esp 00401B49 sub esp, 10h 00401B4C push ebx 00401B4D push esi 00401B4E push edi 00401B4F mov [ebp+var_18], esp 00401B52 call ds:GetVersion 00401B58 xor edx, edx 00401B5A mov dl, ah 00401B5C mov dword_4087B0, edx 00401B62 mov ecx, eax 00401B64 and ecx, 0FFh 00401B6A mov dword_4087AC, ecx 00401B70 shl ecx, 8 00401B73 add ecx, edx 00401B75 mov dword_4087A8, ecx 00401B7B shr eax, 10h 00401B7E mov dword_4087A4, eax 00401B83 push 0 00401B85 call __heap_init 00401B8A pop ecx 00401B8B test eax, eax 00401B8D jnz short loc_401B97 00401B8F push 1Ch 00401B91 call sub_401C30 ; _fast_error_exit 00401B96 pop ecx 00401B97 00401B97 loc_401B97: ; CODE XREF: start+61↑j 00401B97 and [ebp+var_4], 0 00401B9B call __ioinit 00401BA0 call ds:GetCommandLineA 00401BA6 mov dword_409CE4, eax 00401BAB call ___crtGetEnvironmentStringsA 00401BB0 mov dword_408788, eax 00401BB5 call __setargv 00401BBA call __setenvp 00401BBF call __cinit 00401BC4 mov eax, dword_4087C0 00401BC9 mov dword_4087C4, eax 00401BCE push eax 00401BCF push dword_4087B8 00401BD5 push dword_4087B4 00401BDB call sub_401000 00401BE0 add esp, 0Ch 00401BE3 mov [ebp+var_1C], eax 00401BE6 push eax 00401BE7 call _exit 00401BEC ; ------------------------------------------------------ 00401BEC 00401BEC loc_401BEC: ; DATA XREF: _rdata:00407170↓o 00401BEC mov eax, [ebp-14h] 00401BEF mov ecx, [eax] 00401BF1 mov ecx, [ecx] 00401BF3 mov [ebp-20h], ecx 00401BF6 push eax 10 00401BF7 push ecx 00401BF8 call __XcptFilter 00401BFD pop ecx 00401BFE pop ecx 00401BFF retn 00401BFF start endp ; sp = -34h b) стартовый код программы “first.exe”, полученный дизассемблером IDA Pro 4.01 С приведенным примером IDA Pro успешно справляется, о чем свидетельствует стока “Using FLIRT signature: VC v2.0/4.x/5.0 runtime” в окне сообщений |