Фундаментальные основы хакерства

       

пятый. На сцене появляется IDA


"Реальность такова, какой ее описывает язык" тезис лингвистической относительности Б.Л. Уорфа

С легкой руки Дениса Ричи повелось начинать освоение нового языка программирования с создания простейшей программы “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 4.0

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

С версии 3.8x[1] в IDA появилась поддержка «сворачивания» (Collapsed) функций. Такой прием значительно упрощает навигацию по тексту, позволяя убрать с экрана не интересные в данный момент строки. По умолчанию все библиотечные функции сворачиваются автоматически.

Развернуть функцию можно подведя к ней курсор и нажав <+> на дополнительной цифровой клавиатуре, расположенной справа.


Соответственно, клавиша <-> предназначена для сворачивания.
По окончании автоматического анализа файла “first.exe”, IDA переместит курсор к строке “.text:00401B2C” – точке входа в программу. Среди начинающих программистов широко распространено заблуждение, якобы программы, написанные на Си, начинают выполняться с функции “main”, но в действительности это не совсем так. На самом деле сразу после загрузки файла управление передается на функцию “Start”, вставленную компилятором. Она подготавливает глобальные переменные _osver (билд), _winmajor (старшая версия операционной системы), _winminor (младшая версия операционной системы), _winver (полная версия операционной системы), __argc (количество аргументов командной строки), __argv (массив указателей на строки аргументов), _environ (массив указателей на строки переменных окружения); инициализирует кучи (heap); вызывает функцию main, а после возращения управления завершает процесс с помощью функции Exit.
Наглядно продемонстрировать инициализацию переменных, совершаемую стартовым кодом, позволяет следующая программа.
#include <stdio.h>
#include <stdlib.h>
void main()
{
      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
Очевидно, нет никакой необходимости анализировать стандартный стартовый код приложения, и первая задача исследователя – найти место передачи управления на функцию main. К сожалению, гарантированное решение этой
задачи требует полного анализа содержимого функции “Start”. У исследователей существует множество хитростей, но все они базируются на особенностях реализации конкретных компиляторов[2]
и не могут считаться универсальными.
Рекомендуется изучить исходные тексты стартовых функций популярных компиляторов, находящиеся в файлах 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:
|: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
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
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” в окне сообщений
Рисунок 7 "0x003" Загрузка библиотеки сигнатур
Дизассемблер сумел определить имена всех функций вызываемых стартовым кодом, за исключением одной, расположенной по адресу 0х0401BDB. Учитывая передачу трех аргументов и обращение к _exit, после возращения функцией управления, можно предположить, что это main и есть.
Перейти по адресу 0x0401000 для изучения содержимого функции main можно несколькими способами – прокрутить экран с помощью стрелок управления курсором, нажать клавишу <G> и ввести требуемый адрес в появившемся окне диалога, но проще и быстрее всего воспользоваться встроенной в IDA Pro системой навигации. Если подвести курсор в границы имени, константы или выражения и нажать <Enter>, IDA автоматически перейдет на требуемый адрес.
В данном случае требуется подвести к строке “sub_401000” (аргументу команды call) и нажать на <Enter>, если все сделано правильно, экран дизассемблера должен выглядеть следующим образом:
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”:и нажать <Enter>, IDA автоматически перейдет к требуемой строке:
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” подвести курсор и нажать <Enter>, IDA Pro перейдет к следующей строке:
00401003         push   offset aHelloSailor ; "Hello, Sailor!\n"
Нажатие клавиши <Ecs> отменяет предыдущее перемещение, возвращая курсор в исходную позицию. (Аналогично команде “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 (началу функции) и нажать клавишу <N>, в появившемся диалоге ввести “main”.


Конечный результат должен выглядеть так:
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'
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 ; Ñ
...
Необходимо помочь дизассемблеру, указав адрес перехода. Начинающие пользователи в этой ситуации обычно подводят курсор к соответствующей строке и нажимают клавишу <C>, заставляя IDA дизассемблировать код с текущей позиции до конца функции. Несмотря на кажущуюся очевидность, такое решение ошибочно, ибо по-прежнему остается неизвестным куда указывает условный переход в строке :0x103 и откуда код, расположенный по адресу :0x106 получает управление.
Правильное решение – добавить перекрестную ссылку, связывающую строку :0x103, со строкой :0x106. Для этого необходимо в меню “View” выбрать пункт “Cross references” и в появившемся окне диалога заполнить поля “from” и “to” значениями 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” и нажать <Enter>. Важно соблюдать правильность написания – IDA Pro не распознает шестнадцатеричный формат ни в стиле Си (0x106), ни в стиле MASM\TASM (0106h).
Что представляет собой число “114h” в строке :0x106 – константу или смещение? Чтобы узнать это, необходимо проанализировать следующую команду – “LODSW”, поскольку ее выполнение приводит к загрузке в регистр AX слова, расположенного по адресу DS:SI, очевидно, в регистр SI заносится смещение.


seg000:0106                 mov     si, 114h
seg000:0109                 lodsw
Однократное нажатие клавиши <O> преобразует константу в смещение и дизассемблируемый текст станет выглядеть так:
seg000:0106                 mov     si, offset unk_0_114
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

IDA Pro автоматически создала новое имя “unk_0_114”, ссылающееся на переменную неопределенного типа размером в байт, но команда “LODSW” загружает в регистр AX слово, поэтому необходимо перейти к строке :0144 и дважды нажать <D> пока экран не станет выглядеть так:
seg000:0114 word_0_114      dw 18h                  ; DATA XREF: seg000:0106­o
seg000:0116                 db 0D2h ; T
Но что именно содержится в ячейке “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” содержится количество байт, которые необходимо расшифровать. Подведя к ней курсор, нажатием клавиши <N> можно дать ей осмысленное имя, например “BytesToDecrypt”.


После завершения цикла расшифровщика встречается еще один безусловный регистровый переход.
seg000:0112                 jmp     si
Чтобы узнать куда именно он передает управление, необходимо проанализировать код и определить содержимое регистра SI. Часто для этой цели прибегают к помощи отладчика – устанавливают точку останова в строке 0x112 и дождавшись его «всплытия» просматривают значения регистров. Специально для этой цели, IDA Pro поддерживает генерацию map-файлов, содержащих символьную информацию для отладчика. В частности, чтобы не заучивать численные значения всех «подопытных» адресов, каждому из них можно присвоить легко запоминаемое символьное имя. Например, если подвести курсор к строке “seg000:0112”, нажать <N> и ввести “BreakHere”, отладчик сможет автоматически вычислить обратный адрес по его имени.
Для создания map-файла в меню “File” необходимо кликнуть по «Produce output file» и в развернувшемся подменю выбрать «Produce MAP file» или вместо всего этого нажать на клавиатуре «горячую» комбинацию «<Shift-F10»>. Независимо от способа вызова на экран должно появится диалоговое окно следующего вида. Оно позволяет выбрать какого рода данные будут включены в map-файл – информация о сегментах, имена автоматически сгенерированные IDA Pro (такие как, например, “loc_0_106”, “sub_0x110” и т.д.) и «размангленные» (т.е. приведенные в читабельный вид) имена. Содержимое полученного map-файла должно быть следующим:
Start  Stop   Length Name               Class
00100H 0013BH 0003CH seg000             CODE
Address         Publics by Value
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 заносится длина копируемого блока, вычисляемая путем вычитания смещения первого расшифрованного байта от смещения второй команды перемещающего кода. В действительности, истинная длина на три байта короче и по идее от полученного значения необходимо вычесть три. Однако, такое несогласование не нарушает работоспособности, поскольку содержимое ячеек памяти, лежащих за концом расшифрованного фрагмента, не определено и может быть любым.
Пара команд “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 Base
Type Cls  32es   ss   ds                     ^
¦ seg000    00000100 0000013C byte  1000
pub CODE  N FFFF FFFF 1000 00010100 0001013C -
¦                                                                                     -
¦                                                                                     Ў
L1/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.
Для создания скрипта необходимо нажать комбинацию клавиш <Shift-F2> или выбрать в меню “File” пункт “IDC Command” и в появившемся окне диалога ввести исходный текст программы:


г=[¦]================ Notepad =====================¬
¦  Enter IDC statement(s)                          ¦
¦ auto a;                             ^            ¦
¦ for (a=0x116;a<0x12E;a++)           -            ¦
¦ PatchByte([0x1000,a],               -     OK   - ¦
¦ Byte([0x1000,a])^0x66);             -   -------- ¦
¦                                     -            ¦
¦                                     -            ¦
¦                                     -   Cancel - ¦
¦                                     -   -------- ¦
¦                                     -            ¦
¦                                     -            ¦
¦                                     -    Help  - ¦
¦                                     Ў   -------- ¦
¦0===== 5:1 ===<¦-------------------->             ¦
L==================================================-
Рисунок 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
seg000:0110                 loop    loc_0_10C
Сам же зашифрованный фрагмент начинается с адреса seg000:0x116 и продолжается вплоть до seg000:0x12E. Отсюда – цикл расшифровки на языке Си выглядит так: for (a=0x116;a<0x12E;a++) PatchByte([0x1000,a],Byte([0x1000,a])^0x66);
В зависимости от версии IDA для выполнения скрипта необходимо нажать либо <Enter> (версия 3.8x и старше), либо <Ctrl-Enter> в более ранних версиях. Если все сделано правильно, после выполнения скрипта экран дизассемблера должен выглядеть так (b).
Возможные ошибки – несоблюдение регистра символов (IDA к этому чувствительна), синтаксические ошибки, базовый адрес вашего сегмента отличается от 0x1000 (еще раз вызовете окно «Сегменты» чтобы узнать его значение).


В противном случае необходимо подвести курсор к строке “seg000:0116”, нажать клавишу <U> для удаления результатов предыдущего дизассемблирования зашифрованного фрагмента и затем клавишу <C> для повторного дизассемблирования расшифрованного кода.
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                 db  48h ; H
seg000:011F                 db  65h ; e
seg000:0120                 db  6Ch ; l
seg000:0121                 db  6Ch ; l
seg000:0122                 db  6Fh ; o
seg000:0123                 db  2Ch ; ,
seg000:0124                 db  53h ; S
seg000:0125                 db  61h ; a
seg000:0126                 db  69h ; i
seg000:0127                 db  6Ch ; l
seg000:0128                 db  6Fh ; o
seg000:0129                 db  72h ; r
seg000:012A                 db  21h ; !
seg000:012B                 db  0Dh ;
seg000:012C                 db  0Ah ;
seg000:012D                 db  24h ; $
seg000:012E ; ---------------------------------------------------------------------------
b) результат работы скрипта расшифровщика
Цепочку символов, расположенную начиная с адреса “seg000:011E” можно преобразовать в удобочитаемый вид, подведя к ней курсор и нажав клавишу “<A>”. Теперь экран дизассемблера будет выглядеть так:
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 соответствующим смещением. Но ведь выводимая строка на этапе ассемблирования (до перемещения кода) расположена совсем в другом месте! Одно из возможных решений этой проблемы заключается в создании нового сегмента с последующим копированием в него расшифрованного кода – в результате чего достигается эмуляции перемещения кода работающей программы.
Для создания нового сегмента можно выбрать в меню «View» пункт «Segments» и в раскрывшемся окне нажать клавишу <Insert>. Появится диалог следующего вида (см. рис. 10):
г=[¦]============ Create a new segment ================¬
¦                                                      ¦
¦  Start address and end address should be valid.      ¦
¦          End address > Start address                 ¦
¦                                                      ¦
¦ Segment name     MySeg        ¦v¦                    ¦
¦ Start address    0x20100      ¦v¦ C-notation:        ¦
¦ End   address    0x20125      ¦v¦   hex is 0x...     ¦
¦ Base             0x2000       ¦v¦ in paragraphs      ¦
¦ Class                         ¦v¦ (class is any text)¦
¦                                                      ¦
¦  [ ] 32-bit segment                                  ¦
¦                                                      ¦
¦           OK -    Cancel -    F1 - Help -            ¦
¦           ----    --------    -----------            ¦
L======================================================-


Рисунок 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) исходный текст скрипта - копировщика
Для его ввода необходимо вновь нажать <Shift-F2>, при этом предыдущий скрипт будет утерян (IDA позволяет работать не более чем с один скриптом одновременно). После завершения его работы экран дизассемблера будет выглядеть так:
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
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 и нажав клавишу <A>. Экран дизассемблера должен выглядеть так:
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” и нажать клавишу <Ctrl-O> она будет преобразована в смещение:
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”. Нажатие <Ctrl-E> открывает диалог свойств сегмента, содержащий среди прочих полей конечный адрес, который и требуется изменить. Не обязательно указывать точное значение – можно «растянуть» сегмент с небольшим запасом от предполагаемых изменений.
Если попытаться добавить к программе код “XOR AX,AX; INT 16h” он неминуемо затрет начало строки “Hello, Sailor!”, поэтому, ее необходимо заблаговременно передвинуть немного «вниз» (т.е. в область более старших адресов), например, с помощью скрипта следующего содержания «for(a=0x108;a<0x11A;a++) PatchByte([0x2000,a+0x20],Byte([0x2000,a]);».
Пояснение: объявление переменной a для краткости опущено (сами должны понимать, не маленькие :-), длина строки, как водится, берется с запасом, чтобы не утомлять себя лишними вычислениями и перемещение происходит справа налево, поскольку исходный и целевой фрагменты заведомо не пересекаются.
Подведя к курсор к строке :0128 нажатием <A> преобразуем цепочку символов к удобно-читаемому виду; подведем курсор к строке :0102 и, выбрав в меню “Edir” пункт “Path program”, “Assembler”, введем команду “MOV DX,128h”, где «128h» - новое смещение строки, и тут же преобразуем его в смещение нажатием <Ctrl-O>.
Вот теперь можно вводить новый текст – переместив курсор на инструкцию “ret”, вновь вызовем ассемблер и введем “XOR AX,AX<ENTER>INT 16h<Enter>RET<Enter><Esc>”.


На последок рекомендуется произвести «косметическую» чистку – уменьшить размер сегмента до необходимого и переместить строку “Hello, Sailor” вверх, прижав ее вплотную к коду.
Пояснение: удалить адреса, оставшиеся при уменьшении размеров сегмента за его концом можно взводом флажка “Disable Address” в окне свойств сегмента, вызываемом нажатием <Alt-S>
Если все было сделано правильно конечный результат должен выглядеть как показано ниже:
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 ; ---------------------------------------------------------------------------
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.
Ниже приведен пример скрипта, автоматически выполняющего указанные действия. Для его загрузки достаточно нажать <F2> или выбрать в меню “File” пункт “Load file”, “IDC file”.
// Компилятор для файла Crypt


//
static main()
{
auto a,f;
// Открывается файл Crtypt2.com для записи в двоичном режиме
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 – очень удобный инструмент для модификации файлов, исходные тексты которых утеряны или отсутствуют; она практически единственный дизассемблер, способный анализировать зашифрованные программы, не прибегая к сторонним средствам; она обладает развитым пользовательским интерфейсом и удобной системой навигации по исследуемому тексту; она дает может справится с любой мыслимой и немыслимой задачей…
…но эти, и многие другие возможности, невозможно реализовать в полной мере, без владения языком скриптов, что и подтвердил приведенный выше пример.
___Рассказать о языке комментариев. "Дом который построил Джек"
___Трассированное дизасссемблирование
___Большинство защит вскрываются стандартными приемами, которые вовсе не требуют понимания "как это работает". Мой тезка (широко известный среди спектрумистов уже едва ли не десяток лет) однажды сказал "Умение снимать защиту, еще не означает умения ее ставить". Это типично для кракера, которому, судя по всему, ничто не мешает ломать и крушить. Хакер же не ставит целью взлом (т.е. способ любой ценой заставить программу работать), а интересуется именно МЕХАНИЗМОМ: "как оно работает". Взлом для него вторичен.

Содержание раздела