Описание функций встроенного языка
Скачать 2.86 Mb.
|
Рисунок 7 "0x003" Загрузка библиотеки сигнатур Дизассемблер сумел определить имена всех функций вызываемых стартовым кодом, за исключением одной, расположенной по адресу 0х0401BDB. Учитывая передачу трех аргументов и обращение к _exit, после возращения функцией управления, можно предположить, что это main и есть. Перейти по адресу 0x0401000 для изучения содержимого функции main можно несколькими способами – прокрутить экран с помощью стрелок управления курсором, нажать клавишу В данном случае требуется подвести к строке “sub_401000” (аргументу команды call) и нажать на 00401000 ; -------------- S U B R O U T I N E ---------------------- 00401000 00401000 ; Attributes: bp-based frame 00401000 00401000 sub_401000 proc near ; CODE XREF: start+AF ↓ p 00401000 push ebp 00401001 mov ebp, esp 00401003 push offset aHelloSailor ; "Hello, Sailor!\n" 00401008 mov ecx, offset dword_408748 0040100D call ??6ostream@@QAEAAV0@PBD@Z ; ostream::operator<<(char const *) 00401012 pop ebp 00401013 retn 00401013 sub_401000 endp Дизассемблер сумел распознать строковую переменную и дал ей осмысленное имя “aHelloSailor”, а в комментарии, расположенном справа, для наглядности привел оригинальное содержимое “Hello, Sailor!\n”. Если поместить курсор в границы имени “aHelloSailor”:и нажать 00408040 aHelloSailor db 'Hello, Sailor!',0Ah,0 ; DATA XREF: sub_401000+3↑o Выражение “DATA XREF: sub_401000+3 ↑o” называется перекрестной ссылкой и свидетельствует о том, что в третей строке процедуры sub_401000, произошло обращение к текущему адресу по его смещению (“o” от offset), а стрелка, направленная вверх, указывает на относительное расположение источника перекрестной ссылки. Если в границы выражения “sub_401000+3” подвести курсор и нажать IDA Pro перейдет к следующей строке: 00401003 push offset aHelloSailor ; "Hello, Sailor!\n" Нажатие клавиши 11 исходную позицию. (Аналогично команде “back” в web-браузере). Смещение строки “Hello, Sailor!\n”, передается процедуре “??6ostream@@QAEAAV0@PBD@Z”, представляющей собой оператор “<<” языка С++. Странное имя объясняется ограничениями, наложенными на символы, допустимые в именах библиотечных функций. Поэтому, компиляторы автоматически преобразуют (замангляют) такие имена в “абракадабру”, пригодную для работы с линкером, и многие начинающие программисты даже не догадываются об этой скрытой “кухне”. Для облегчения анализа текста, IDA Pro в комментариях отображает «правильные» имена, но существует возможность заставить ее везде показывать незамангленные имена. Для этого необходимо в меню “Options” выбрать пункт “Demangled names” и в появившемся окне диалога переместить радио кнопку на “Names”, после этого вызов оператора “<<” станет выглядеть так: 0040100D call ostream::operator<<(char const *) На этом анализ приложения “first.cpp” можно считать завершенным. Для полноты картины остается переименовать функцию “sub_401000” в main. Для этого необходимо подвести курсор к строке 0x0401000 (началу функции) и нажать клавишу 00401000 ; --------------- S U B R O U T I N E --------------------------------------- 00401000 00401000 ; Attributes: bp-based frame 00401000 00401000 main proc near ; CODE XREF: start+AF↓p 00401000 push ebp 00401001 mov ebp, esp 00401003 push offset aHelloSailor ; "Hello, Sailor!\n" 00401008 mov ecx, offset dword_408748 0040100D call ostream::operator<<(char const *) 00401012 pop ebp 00401013 retn 00401013 main endp Для сравнения результат работы W32Dasm выглядит следующим образом (ниже приводится лишь содержимое функции main): :00401000 55 push ebp :00401001 8BEC mov ebp, esp Possible StringData Ref from Data Obj ->"Hello, Sailor!" | :00401003 6840804000 push 00408040 :00401008 B948874000 mov ecx, 00408748 :0040100D E8AB000000 call 004010BD :00401012 5D pop ebp :00401013 C3 ret Другое важное преимущество IDA – способность дизассемблировать зашифрованные программы. В демонстрационном примере ??? “/SRC/Crypt.com” использовалась статическая шифровка, часто встречающаяся в “конвертных” защитах. Этот простой прием полностью “ослепляет” большинство дизассемблеров. Например, результат обработки файла “Crypt.com” SOURCER-ом выглядит так: Crypt proc far 7E5B:0100 start: 7E5B:0100 83 C6 06 add si,6 7E5B:0103 FF E6 jmp si ;* ;*No entry point to code 7E5B:0105 B9 14BE mov cx,14BEh 7E5B:0108 01 AD 5691 add ds:data_1e[di],bp ; (7E5B:5691=0) 7E5B:010C 80 34 66 xor byte ptr [si],66h ; 'f' 12 7E5B:010F 46 inc si 7E5B:0110 E2 FA loop $-4 ; Loop if cx > 0 7E5B:0112 FF E6 jmp si ;* ;* No entry point to code 7E5B:114 18 00 sbb [bx+si],al 7E5B:116 D2 6F DC shr byte ptr [bx-24h],cl ; Shift w/zeros fill 7E5B:119 6E 67 AB 47 A5 2E db 6Eh, 67h,0ABh, 47h,0A5h, 2Eh 7E5B:11F 03 0A 0A 09 4A 35 db 03h, 0Ah, 0Ah, 09h, 4Ah, 35h 7E5B:125 07 0F 0A 09 14 47 db 07h, 0Fh, 0Ah, 09h, 14h, 47h 7E5B:12B 6B 6C 42 E8 00 00 db 6Bh, 6Ch, 42h, E8h, 00h, 00h 7E5B:131 59 5E BF 00 01 57 db 59h, 5Eh, BFh, 00h, 01h, 57h 7E5B:137 2B CE F3 A4 C3 db 2Bh, CEh, F3h, A4h, C3h Crypt endp SOURCER половину кода вообще не смог дизассемблировать, оставив ее в виде дампа, а другую половину дизассемблировал неправильно! Команда “JMP SI” в строке :0x103 осуществляет переход по адресу :0x106 (значение регистра SI после загрузки com файла равно 0x100, поэтому после команды “ADD SI,6” регистр SI равен 0x106). Но следующая за “JMP” команда расположена по адресу 0x105! В исходном тексте в это место вставлен байт-пустышка, сбивающий дизассемблер с толку. Start: ADD SI,6 JMP SI DB 0B9h ; LEA SI,_end ; На начало зашифрованного фрагмента SOURCER не обладает способностью предсказывать регистровые переходы и, встретив команду “JMP SI” продолжает дизассемблирование, молчаливо предполагая, что команды последовательно расположены вплотную друг к другу. Существует возможность создать файл определений, указывающий, что по адресу:0x105 расположен байт данных, но подобное взаимодействие с пользователем очень неудобно. Напротив, IDA изначально проектировалась как дружественная к пользователю интерактивная среда. В отличие от SURCER-подобных дизассемблеров, IDA не делает никаких молчаливых предположений, и при возникновении затруднений обращается за помощью к человеку. Поэтому, встретив регистровый переход по неизвестному адресу, она прекращает дальнейший анализ, и результат анализа файла “Crypt.com” выглядит так: seg000:0100 start proc near seg000:0100 add si, 6 seg000:0103 jmp si seg000:0103 start endp seg000:0103 seg000:0103 ; ------------------------------------------------------------------------ seg000:0105 db 0B9h ; ¦ seg000:0106 db 0BEh ; - seg000:0107 db 14h ; seg000:0108 db 1 ; seg000:0109 db 0ADh ; í seg000:010A db 91h ; Ñ Необходимо помочь дизассемблеру, указав адрес перехода. Начинающие пользователи в этой ситуации обычно подводят курсор к соответствующей строке и нажимают клавишу Правильное решение – добавить перекрестную ссылку, связывающую строку :0x103, со строкой :0x106. Для этого необходимо в меню “View” выбрать пункт “Cross references” и в появившемся окне диалога заполнить поля “from” и “to” значениями 13 seg000:0103 и seg000:0106 соответственно. После этого экран дизассемблера должен выглядеть следующим образом (в IDA версии 4.01.300 содержится ошибка, и добавление новой перекрестной ссылки не всегда приводит к автоматическому дизассемблированию): seg000:0100 public start seg000:0100 start proc near seg000:0100 add si, 6 seg000:0103 jmp si seg000:0103 start endp seg000:0103 seg000:0103 ; ----------------------------------------------------------------------- seg000:0105 db 0B9h ; ¦ seg000:0106 ; ----------------------------------------------------------------------- seg000:0106 seg000:0106 loc_0_106: ; CODE XREF: start+3↑u seg000:0106 mov si, 114h seg000:0109 lodsw seg000:010A xchg ax, cx seg000:010B push si seg000:010C seg000:010C loc_0_10C: ; CODE XREF: seg000:0110↓j seg000:010C xor byte ptr [si], 66h seg000:010F inc si seg000:0110 loop loc_0_10C seg000:0112 jmp si seg000:0112 ; ---------------------------------------------------------------------- seg000:0114 db 18h ; seg000:0115 db 0 ; seg000:0116 db 0D2h ; T seg000:0117 db 6Fh ; o Поскольку IDA Pro не отображает адреса-приемника перекрестной ссылки, то рекомендуется выполнить это самостоятельно. Такой примем улучшит наглядность текста и упростит навигацию. Если повести курсор к строке :0x103 нажать клавишу <:>, введя в появившемся диалоговом окне любой осмысленный комментарий (например “переход по адресу 0106”), то экран примет следующий вид: seg000:0103 jmp si ; Переход по адресу 0106 Ценность такого приема заключается в возможности быстрого перехода по адресу, на который ссылается “JMP SI”, - достаточно лишь подвести курсор к числу “0106” и нажать Pro не распознает шестнадцатеричный формат ни в стиле Си (0x106), ни в стиле MASM\TASM (0106h). Что представляет собой число “114h” в строке :0x106 – константу или смещение? Чтобы узнать это, необходимо проанализировать следующую команду – “LODSW”, поскольку ее выполнение приводит к загрузке в регистр AX слова, расположенного по адресу DS:SI, очевидно, в регистр SI заносится смещение. seg000:0106 mov si, 114h seg000:0109 lodsw Однократное нажатие клавиши … seg000:0114 unk_0_114 db 18h ; ; DATA XREF: seg000:0106↑o seg000:0115 db 0 ; seg000:0116 db 0D2h ; T seg000:0117 db 6Fh ; o … 14 IDA Pro автоматически создала новое имя “unk_0_114”, ссылающееся на переменную неопределенного типа размером в байт, но команда “LODSW” загружает в регистр AX слово, поэтому необходимо перейти к строке :0144 и дважды нажать Но что именно содержится в ячейке “word_0_144”? Понять это позволит изучение следующего кода: seg000:0106 mov si, offset word_0_114 seg000:0109 lodsw seg000:010A xchg ax, cx seg000:010B push si seg000:010C seg000:010C loc_0_10C: ; CODE XREF: seg000:0110↓j seg000:010C xor byte ptr [si], 66h seg000:010F inc si seg000:0110 loop loc_0_10C В строке :0x10A значение регистра AX помещается в регистр CX, и затем он используется командой “LOOP LOC_010C” как счетчик цикла. Тело цикла представляет собой простейший расшифровщик – команда “XOR” расшифровывает один байт, на который указывает регистр SI, а команда “INC SI” перемещает указатель на следующий байт. Следовательно, в ячейке “word_0_144” содержится количество байт, которые необходимо расшифровать. Подведя к ней курсор, нажатием клавиши После завершения цикла расшифровщика встречается еще один безусловный регистровый переход. seg000:0112 jmp si Чтобы узнать куда именно он передает управление, необходимо проанализировать код и определить содержимое регистра SI. Часто для этой цели прибегают к помощи отладчика – устанавливают точку останова в строке 0x112 и дождавшись его «всплытия» просматривают значения регистров. Специально для этой цели, IDA Pro поддерживает генерацию map-файлов, содержащих символьную информацию для отладчика. В частности, чтобы не заучивать численные значения всех «подопытных» адресов, каждому из них можно присвоить легко запоминаемое символьное имя. Например, если подвести курсор к строке “seg000:0112”, нажать Для создания map-файла в меню “File” необходимо кликнуть по «Produce output file» и в развернувшемся подменю выбрать «Produce MAP file» или вместо всего этого нажать на клавиатуре «горячую» комбинацию «Shift-F10». Независимо от способа вызова на экран должно появится диалоговое окно следующего вида. Оно позволяет выбрать какого рода данные будут включены в map-файл – информация о сегментах, имена автоматически сгенерированные IDA Pro (такие как, например, “loc_0_106”, “sub_0x110” и т.д.) и «размангленные» (т.е. приведенные в читабельный вид) имена. Подробнее о сегментах рассказывается в главе «Сегменты и селекторы», об авто генерируемых и замангленных именах - в главе «Настойки IDA Pro”. Содержимое полученного map-файла должно быть следующим: Start Stop Length Name Class 00100H 0013BH 0003CH seg000 CODE Address Publics by Value 15 0000:0100 start 0000:0112 BreakHere 0000:0114 BytesToDecrypt Program entry point at 0000:0100 Такой формат поддерживают большинство отладчиков, в том числе и популярнейший Soft-Ice, в поставку которого входит утилита “msym”, запускаемая с указанием имени конвертируемого map-файла в командной стоке. Полученный sym-файл необходимо разместить в одной директории с отлаживаемой программой, загружаемой в загрузчик без указания расширения, т.е., например, так “WLDR Crypt”. В противном случае символьная информация не будет загружена! Затем необходимо установить точку останова командой “bpx BreakHere” и покинуть отладчик командной “x”. Спустя секунду его окно вновь появиться на экране, извещая о достижении процессором контрольной точки. Посмотрев на значения регистров, отображаемых по умолчанию вверху экрана, можно выяснить, что содержимое SI равно 0x12E. С другой стороны, это же значение можно вычислить «в уме», не прибегая к отладчику. Команда MOV в строке 0x106 загружает в регистр SI смещение 0x114, откуда командой LODSW считывается количество расшифровываемых байт – 0x18, при этом содержимое SI увеличивается на размер слова – два байта. Отсюда, в момент завершения цикла расшифровки значение SI будет равно 0x114+0x18+0x2 = 0x12E. Вычислив адрес перехода в строке 0x112, рекомендуется создать соответствующую перекрестную ссылку (from: 0x122; to: 0x12E) и добавить комментарий к строке 0x112 (“Переход по адресу 012E”). Создание перекрестной ссылки автоматически дизассемблирует код, начиная с адреса seg000:012E и до конца файла. seg000:012E loc_0_12E: ; CODE XREF: seg000:0112u seg000:012E call $+3 seg000:0131 pop cx seg000:0132 pop si seg000:0133 mov di, 100h seg000:0136 push di seg000:0137 sub cx, si seg000:0139 repe movsb seg000:013B retn Назначение команды “CALL $+3” (где $ обозначает текущее значение регистра указателя команд IP) состоит в заталкивании в стек содержимого регистра IP, откуда впоследствии оно может быть извлечено в любой регистр общего назначения. Необходимость подобного трюка объясняется тем, что в микропроцессорах серии Intel 80x86 регистр IP не входит в список непосредственно адресуемых и читать его значение могут лишь команды, изменяющие ход выполнения программы, в том числе и call. Для облегчения анализа листинга можно добавить к стокам 0x12E и 0x131 комментарий – “MOV CX, IP”, или еще лучше – сразу вычислить и подставить непосредственное значение – “MOV CX,0x131”. Команда “POP SI” в строке 0x132 снимает слово из стека и помещает его в регистр SI. Прокручивая экран дизассемблера вверх в строке 0x10B можно обнаружить парную ей инструкцию “PUSH SI”, заносящую в стек смещение первого расшифровываемого байта. После этого становится понятным смысл последующих команд “MOV DI, 0x100\SUB CX,SI\REPE MOVSB”. Они перемещают начало расшифрованного фрагмента по адресу, начинающегося со смещения 0x100. Такая операция характерна для «конвертных» защит, накладывающихся на уже откомпилированный файл, который перед запуском должен быть размещен по своим «родным» адресам. Перед началом перемещения в регистр CX заносится длина копируемого блока, вычисляемая путем вычитания смещения первого расшифрованного байта от смещения второй команды перемещающего кода. В действительности, истинная длина на три байта 16 короче и по идее от полученного значения необходимо вычесть три. Однако, такое несогласование не нарушает работоспособности, поскольку содержимое ячеек памяти, лежащих за концом расшифрованного фрагмента, не определено и может быть любым. Пара команд “0x136:PUSH DI” и “0x13B:RETN” образуют аналог инструкции “CALL DI” – “PUSH” заталкивает адрес возврата в стек, а “RETN” извлекает его оттуда и передает управление по соответствующему адресу. Зная значение DI (оно равно 0x100) можно было бы добавить еще одну перекрестную ссылку (“from:0x13B; to:0x100”) и комментарий к строке :0x13B – “Переход по адресу 0x100”, но ведь к этому моменту по указанным адресам расположен совсем другой код! Поэтому, логически правильнее добавить перекрестную ссылку “from:0x13B; to:0x116” и комментарий “Переход по адресу 0x116”. Сразу же после создания новой перекрестной ссылки IDA попытается дизассемблировать зашифрованный код, в результате чего получится следующее: seg000:0116 loc_0_116: ; CODE XREF: seg000:013Bu seg000:0116 shr byte ptr [bx-24h], cl seg000:0119 outsb seg000:011A stos word ptr es:[edi] seg000:011C inc di seg000:011D movsw seg000:011E add cx, cs:[bp+si] seg000:0121 or cl, [bx+di] seg000:0123 dec dx seg000:0124 xor ax, 0F07h seg000:0127 or cl, [bx+di] seg000:0129 adc al, 47h seg000:0129;────────────────────────────────────────────────────── seg000:012B db 6Bh ; k seg000:012C db 6Ch ; l seg000:012D db 42h ; B seg000:012E;────────────────────────────────────────────────────── Непосредственное дизассемблирование зашифрованного кода невозможно – предварительно его необходимо расшифровать. Подавляющее большинство дизассемблеров не могут модифицировать анализируемый текст налету и до загрузки в дизассемблер исследуемый файл должен быть полностью расшифрован. На практике, однако, это выглядит несколько иначе – прежде чем расшифровывать необходимо выяснить алгоритм расшифровки, проанализировав доступную часть файла. Затем выйти из дизассемблера, тем или иным способом расшифровать «секретный» фрагмент, вновь загрузить файл в дизассемблер (причем предыдущие результаты дизассемблирования окажутся утеряны) и продолжить его анализ до тех пор, пока не встретится еще один зашифрованный фрагмент, после чего описанный цикл «выход из дизассемблера – расшифровка – загрузка - анализ» повторяется вновь. Достоинство IDA заключается в том, что она позволяет выполнить ту же задачу значительно меньшими усилиями, никуда не выходя из дизассемблера. Это достигается за счет наличия механизма виртуальной памяти, подробно описанного в главе «Виртуальная память». Если не вдаваться в технические тонкости, упрощенно можно изобразить IDA в виде «прозрачной» виртуальной машины, оперирующей с физической памятью компьютера. Для модификации ячеек памяти необходимо знать их адрес, состоящий из пары чисел – сегмента и смещения. Слева каждой строки указывается ее смещение и имя сегмента, например “seg000:0116”. Узнать базовый адрес сегмента по его имени можно, открыв окно «Сегменты» выбрав в меню «View» пункт «Segments». ╔═[■]═══════════════════════════ Program Segmentation ══════════════════════════3═[↑]═╗ ║ Name Start End Align |