Описание функций встроенного языка
Скачать 2.86 Mb.
|
Base Type Cls 32es ss ds ▲ ║ seg000 00000100 0000013C byte 1000 pub CODE N FFFF FFFF 1000 00010100 0001013C ▓ ║ ▓ ║ ▼ 17 ╚1/1 ═════════════════◄■▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒►─┘ Рисунок 8 Окно «Сегменты» Искомый адрес находится в столбце “Base” и для наглядности на приведенной копии экрана выделен жирным шрифтом. Обратится к любой ячейке сегмента поможет конструкция “[segment:offset]”, а для чтения и модификации ячеек предусмотрены функции Byte и PatchByte соответственно. Их вызов может выглядеть, например, так: a=Byte([0x1000,0x100]) – читает ячейку, расположенную по смещению 0x100 в сегменте с базовым адресом 0x1000; PatchByte([0x1000,0x100],0x27) – присваивает значение 0x27 ячейке памяти, расположенной по смещению 0x100 в сегменте с базовым адресом 0x1000. Как следует из названия функций, они манипулируют с ячейками размером в один байт. Существуют так же функции, манипулирующие целыми словами, подробнее о них можно прочесть в главе «Виртуальная память». Знания этих двух функций вполне достаточно для написания скрипта - расшифровщика при условии, что читатель знаком с языком Си. Реализация IDA-Си не полностью поддерживается стандарта – подробнее об этом рассказывается в главе «Язык скриптов IDA-Си», здесь же достаточно заметить, что IDA не позволяет разработчику задавать тип переменной и определяет его автоматически по ее первому использованию, а объявление осуществляется ключевым словом “auto”. Например, “auto MyVar, s0” объявляет две переменных – MyVar и s0. Для создания скрипта необходимо нажать комбинацию клавиш ╔═[■]════════════════ Notepad ═════════════════════╗ ║ Enter IDC statement(s) ║ ║ auto a; ▲ ║ ║ for (a=0x116;a<0x12E;a++) ▓ ║ ║ PatchByte([0x1000,a], ▓ OK ▄ ║ ║ Byte([0x1000,a])^0x66); ▓ ▀▀▀▀▀▀▀▀ ║ ║ ▓ ║ ║ ▓ ║ ║ ▓ Cancel ▄ ║ ║ ▓ ▀▀▀▀▀▀▀▀ ║ ║ ▓ ║ ║ ▓ ║ ║ ▓ Help ▄ ║ ║ ▼ ▀▀▀▀▀▀▀▀ ║ ║☼═════ 5:1 ═══◄■▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒► ║ ╚══════════════════════════════════════════════════╝ Рисунок 9 Встроенный редактор скриптов auto a; for (a=0x116;a<0x12E;a++) PatchByte([0x1000,a],Byte([0x1000,a])^0x66); a) исходный текст скрипта - расшифровщика Пояснение: как было показано выше алгоритм расшифровщика сводится к последовательному преобразованию каждой ячейки зашифрованного фрагмента операцией XOR 0x66, (см. ниже – выделено жирным шрифтом) seg000:010C xor byte ptr [si], 66h seg000:010F inc si 18 seg000:0110 loop loc_0_10C Сам же зашифрованный фрагмент начинается с адреса seg000:0x116 и продолжается вплоть до seg000:0x12E. Отсюда – цикл расшифровки на языке Си выглядит так: for (a=0x116;a<0x12E;a++) PatchByte([0x1000,a],Byte([0x1000,a])^0x66); В зависимости от версии IDA для выполнения скрипта необходимо нажать либо Возможные ошибки – несоблюдение регистра символов (IDA к этому чувствительна), синтаксические ошибки, базовый адрес вашего сегмента отличается от 0x1000 (еще раз вызовете окно «Сегменты» чтобы узнать его значение). В противном случае необходимо подвести курсор к строке “seg000:0116”, нажать клавишу для удаления результатов предыдущего дизассемблирования зашифрованного фрагмента и затем клавишу Цепочку символов, расположенную начиная с адреса “seg000:011E” можно преобразовать в удобочитаемый вид, подведя к ней курсор и нажав клавишу “”. Теперь экран дизассемблера будет выглядеть так: seg000:0116 loc_0_116: ; CODE XREF: seg000:013Bu seg000:0116 mov ah, 9 seg000:0118 mov dx, 108h seg000:011B int 21h ; DOS - PRINT STRING seg000:011B ; DS:DX -> string terminated by "$" seg000:011D retn seg000:011D ; ─────────────────────────────────────────────────────────────────────────── seg000:011E aHelloSailor db 'Hello,Sailor!',0Dh,0Ah,'$' seg000:012E ; ─────────────────────────────────────────────────────────────────────────── с) создание ASCII-строки Команда “MOV AH,9” в строке :0116 подготавливает регистр AH перед вызовом прерывания 0x21, выбирая функцию вывода строки на экран, смещение которой заносится следующей командой в регистр DX. Т.е. для успешного ассемблирования листинга необходимо заменить константу 0x108 соответствующим смещением. Но ведь выводимая строка на этапе ассемблирования (до перемещения кода) расположена совсем в другом месте! Одно из возможных решений этой проблемы заключается в создании нового 19 сегмента с последующим копированием в него расшифрованного кода – в результате чего достигается эмуляции перемещения кода работающей программы. Для создания нового сегмента можно выбрать в меню «View» пункт «Segments» и в раскрывшемся окне нажать клавишу 10): ╔═[■]════════════ Create a new segment ════════════════╗ ║ ║ ║ Start address and end address should be valid. ║ ║ End address > Start address ║ ║ ║ ║ Segment name MySeg ▐↓▌ ║ ║ Start address 0x20100 ▐↓▌ C-notation: ║ ║ End address 0x20125 ▐↓▌ hex is 0x... ║ ║ Base 0x2000 ▐↓▌ in paragraphs ║ ║ Class ▐↓▌ (class is any text)║ ║ ║ ║ [ ] 32-bit segment ║ ║ ║ ║ OK ▄ Cancel ▄ F1 - Help ▄ ║ ║ ▀▀▀▀ ▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ ║ ╚══════════════════════════════════════════════════════╝ Рисунок 10 IDAC: Создание нового сегмента Подробнее о значении каждого из полей рассказано в главе «Сегменты и селекторы», а здесь не рассматривается. Пояснение: Базовый адрес сегмента может быть любым если при этом не происходит перекрытия сегментов seg000 и MySeg; начальный адрес сегмента задается так, чтобы смещение первого байта было равно 0x100; разница между конечным и начальным адресом равна длине сегмента, вычислить которую можно вычитанием смещения начала расшифрованного фрагмента от смещения его конца – 0x13B – 0x116 = 0x25. Скопировать требуемый фрагмент в только что созданный сегмент можно скриптом следующего содержания. auto a; for (a=0x0;a<0x25;a++) PatchByte([0x2000,a+0x100],Byte([0x1000,a+0x116])); a) исходный текст скрипта - копировщика Для его ввода необходимо вновь нажать После завершения его работы экран дизассемблера будет выглядеть так: MySeg:0100 MySeg segment byte public '' use16 MySeg:0100 assume cs:MySeg MySeg:0100 ;org 100h MySeg:0100 assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing MySeg:0100 db 0B4h ; ┤ MySeg:0101 db 9 ; MySeg:0102 db 0BAh ; ║ MySeg:0103 db 8 ; MySeg:0104 db 1 ; MySeg:0105 db 0CDh ; ═ MySeg:0106 db 21h ; ! MySeg:0107 db 0C3h ; ├ MySeg:0108 db 48h ; H MySeg:0109 db 65h ; e 20 MySeg:010A db 6Ch ; l MySeg:010B db 6Ch ; l MySeg:010C db 6Fh ; o MySeg:010D db 2Ch ; , MySeg:010E db 53h ; S MySeg:010F db 61h ; a MySeg:0110 db 69h ; i MySeg:0111 db 6Ch ; l MySeg:0112 db 6Fh ; o MySeg:0113 db 72h ; r MySeg:0114 db 21h ; ! MySeg:0115 db 0Dh ; MySeg:0116 db 0Ah ; MySeg:0117 db 24h ; $ MySeg:0117 MySeg ends b) результат работы скрипта-копировщика Теперь необходимо создать перекрестную ссылку “from:seg000:013B; to:MySeg:0x100”, преобразовать цепочку символов в удобочитаемую строку, подведя курсор к строке MySeg:0108 и нажав клавишу . Экран дизассемблера должен выглядеть так: MySeg:0100 loc_1000_100: ; CODE XREF: seg000:013Bu MySeg:0100 mov ah, 9 MySeg:0102 mov dx, 108h MySeg:0105 int 21h ; DOS - PRINT STRING MySeg:0105 ; DS:DX -> string terminated by "$" MySeg:0107 retn MySeg:0107 ; ─────────────────────────────────────────────────────────────────────────── MySeg:0108 aHelloSailorS db 'Hello,Sailor!',0Dh,0Ah MySeg:0108 db '$' MySeg:0118 MySeg ends с) результат дизассемблирования скопированного фрагмента Результатом всех этих операций стало совпадение смещения строки со значением, загружаемым в регистр DX (в тексте они выделены жирным шрифтом). Если подвести курсор к константе “108h” и нажать клавишу MySeg:0102 mov dx, offset aHelloSailorS ; "Hello,Sailor!\r\n$ш" MySeg:0105 int 21h ; DOS - PRINT STRING MySeg:0105 ; DS:DX -> string terminated by "$" MySeg:0107 retn MySeg:0107 ; ─────────────────────────────────────────────────────────────────────────── MySeg:0108 aHelloSailorS db 'Hello,Sailor!',0Dh,0Ah ; DATA XREF: MySeg:0102o d) преобразование константы в смещение Полученный листинг удобен для анализа, но все еще не готов к ассемблированию, хотя бы уже потому, что никакой ассемблер не в состоянии зашифровать требуемый код. Конечно, эту операцию можно выполнить вручную, после компиляции, но IDA позволит проделать то же самое не выходя из нее и не прибегая к помощи стороннего инструментария. Демонстрация получится намного нагляднее, если в исследуемый файл внести некоторые изменения, например, добавить ожидание клавиши на выходе. Для этого можно прибегнуть к интегрированному в IDA ассемблеру, но прежде, разумеется, необходимо несколько «раздвинуть» границы сегмента MySeg, дабы было к чему дописывать новый код. Выберете в меню “View” пункт “Segments” и в открывшемся окне подведите курсор к стоке “MySeg”. Нажатие Если попытаться добавить к программе код “XOR AX,AX; INT 16h” он неминуемо 21 затрет начало строки “Hello, Sailor!”, поэтому, ее необходимо заблаговременно передвинуть немного «вниз» (т.е. в область более старших адресов), например, с помощью скрипта следующего содержания « for(a=0x108;a<0x11A;a++) PatchByte([0x2000,a+0x20],Byte([0x2000,a]); ». Пояснение: объявление переменной a для краткости опущено (сами должны понимать, не маленькие :-), длина строки, как водится, берется с запасом, чтобы не утомлять себя лишними вычислениями и перемещение происходит справа налево, поскольку исходный и целевой фрагменты заведомо не пересекаются. Подведя к курсор к строке :0128 нажатием преобразуем цепочку символов к удобно-читаемому виду; подведем курсор к строке :0102 и, выбрав в меню “Edir” пункт “Path program”, “Assembler”, введем команду “MOV DX,128h”, где «128h» - новое смещение строки, и тут же преобразуем его в смещение нажатием Вот теперь можно вводить новый текст – переместив курсор на инструкцию “ret”, вновь вызовем ассемблер и введем “ XOR AX,AX ”. На последок рекомендуется произвести «косметическую» чистку – уменьшить размер сегмента до необходимого и переместить строку “Hello, Sailor” вверх, прижав ее вплотную к коду. Пояснение: удалить адреса, оставшиеся при уменьшении размеров сегмента за его концом можно взводом флажка “Disable Address” в окне свойств сегмента, вызываемом нажатием Если все было сделано правильно конечный результат должен выглядеть как показано ниже: seg000:0100 ; File Name : F:\IDAN\SRC\Crypt.com seg000:0100 ; Format : MS-DOS COM-file seg000:0100 ; Base Address: 1000h Range: 10100h-1013Ch Loaded length: 3Ch seg000:0100 seg000:0100 seg000:0100 ; =========================================================================== seg000:0100 seg000:0100 ; Segment type: Pure code seg000:0100 seg000 segment byte public 'CODE' use16 seg000:0100 assume cs:seg000 seg000:0100 org 100h seg000:0100 assume es:nothing, ss:nothing, ds:seg000, fs:nothing, gs:nothing seg000:0100 seg000:0100 ; --------------- S U B R O U T I N E --------------------------------------- seg000:0100 seg000:0100 seg000:0100 public start seg000:0100 start proc near seg000:0100 add si, 6 seg000:0103 jmp si ; Ïåðåõîä ïî àäðåñó 0106 seg000:0103 start endp seg000:0103 seg000:0103 ; --------------------------------------------------------------------------- seg000:0105 db 0B9h ; ¦ seg000:0106 ; --------------------------------------------------------------------------- seg000:0106 mov si, offset BytesToDecrypt seg000:0109 lodsw seg000:010A xchg ax, cx seg000:010B push si seg000:010C seg000:010C loc_0_10C: ; CODE XREF: seg000:0110j seg000:010C xor byte ptr [si], 66h seg000:010F inc si seg000:0110 loop loc_0_10C seg000:0112 seg000:0112 BreakHere: ; Ïåðåõîä ïî àäðåñó 012E seg000:0112 jmp si seg000:0112 ; --------------------------------------------------------------------------- seg000:0114 BytesToDecrypt dw 18h ; DATA XREF: seg000:0106o seg000:0116 ; --------------------------------------------------------------------------- 22 seg000:0116 seg000:0116 loc_0_116: ; CODE XREF: seg000:013Bu seg000:0116 mov ah, 9 seg000:0118 mov dx, 108h ; "Hello,Sailor!\r\n$" seg000:011B int 21h ; DOS - PRINT STRING seg000:011B ; DS:DX -> string terminated by "$" seg000:011D retn seg000:011D ; --------------------------------------------------------------------------- seg000:011E aHelloSailor db 'Hello,Sailor!',0Dh,0Ah,'$' ; DATA XREF: seg000:0118o seg000: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 seg000:013B seg000 ends seg000:013B MySeg:0100 ; --------------------------------------------------------------------------- MySeg:0100 ; =========================================================================== MySeg:0100 MySeg:0100 ; Segment type: Regular MySeg:0100 MySeg segment byte public '' use16 MySeg:0100 assume cs:MySeg MySeg:0100 ;org 100h MySeg:0100 assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing MySeg:0100 MySeg:0100 loc_1000_100: ; CODE XREF: seg000:013Bu MySeg:0100 mov ah, 9 MySeg:0102 mov dx, offset aHelloSailor_0 ; "Hello,Sailor!\r\n$" MySeg:0105 int 21h ; DOS - PRINT STRING MySeg:0105 ; DS:DX -> string terminated by "$" MySeg:0107 xor ax, ax MySeg:0109 int 16h ; KEYBOARD - READ CHAR FROM BUFFER, WAIT IF EMPTY MySeg:0109 ; Return: AH = scan code, AL = character MySeg:010B retn MySeg:010B ; --------------------------------------------------------------------------- MySeg:010C aHelloSailor_0 db 'Hello,Sailor!',0Dh,0Ah,'$' ; DATA XREF: MySeg:0102o MySeg:010C MySeg ends MySeg:010C MySeg:010C MySeg:010C end start a) окончательно дизассемблированный текст Структурно программа состоит из следующих частей – расшифровщика, занимающего адреса seg000:0x100 – seg000:0x113, переменной размером в слово, содержащей количество расшифровываемых байт, занимающей адреса seg000:0x114- seg000:0x116, исполняемого кода программы, занимающего целиком сегмент MySeg и загрузчика, занимающего адреса seg000:0x12E-seg000:0x13B. Все эти части должны быть в перечисленном порядке скопированы в целевой файл, причем исполняемый код программы необходимо предварительно зашифровать, произведя над каждым его байтом операцию XOR 0x66. Ниже приведен пример скрипта, автоматически выполняющего указанные действия. Для его загрузки достаточно нажать // Компилятор для файла Crypt // static main() { auto a,f; // Открывается файл Crtypt2.com для записи в двоичном режиме 23 f=fopen("crypt2.com","wb"); // В файл Crypt2 копируется расшифровщик for (a=0x100;a<0x114;a++) fputc(Byte([0x1000,a]),f); // Определяется и копируется в файл слово, содержащее число // байтов для расшифровки fputc( SegEnd([0x2000,0x100]) - SegStart([0x2000,0x100]),f); fputc(0,f); // Копируется и налету шифруется расшифрованный фрагмент for(a=SegStart([0x2000,0x100]);a!=SegEnd([0x2000,0x100]);a++) fputc(Byte(a) ^ 0x66,f); // Дописывается загрузчик for(a=0x12E;a<0x13C;a++) fputc(Byte([0x1000,a]),f); // Закрывается файл. fclose(f); } a) исходный код скрипта-компилятора Подробное объяснение каждой функции, встретившийся в скрипте, можно найти в главах «Сегменты и селекторы», «Файловый ввод-вывод» и т.д. Выполнение скрипта приведет к созданию файла “Crypt2.com”, запустив который можно убедиться в его работоспособности – он выводит строку на экран и, дождавшись нажатия любой клавиши, завершает свою работу. Огромным преимуществом такого подхода является «сквозная» компиляция файла, т.е. дизассемблированный листинг в действительности не ассемблировался! Вместо этого из виртуальной памяти байт-за-байтом читалось оригинальное содержимое, которое за исключением модифицированных строк доподлинно идентично исходному файлу. Напротив, повторное ассемблирование практически никогда не позволяет добиться полного сходства с дизассемблируемым файлом. IDA – очень удобный инструмент для модификации файлов, исходные тексты которых утеряны или отсутствуют; она практически единственный дизассемблер, способный анализировать зашифрованные программы, не прибегая к сторонним средствам; она обладает развитым пользовательским интерфейсом и удобной системой навигации по исследуемому тексту; она дает может справится с любой мыслимой и немыслимой задачей… …но эти, и многие другие возможности, невозможно реализовать в полной мере, без владения языком скриптов, что и подтвердил приведенный выше пример. |