Описание функций встроенного языка
Скачать 2.86 Mb.
|
Возвращает значение регистра SP (ESP) в произвольной точке функции относительно его оригинального значения. IDA использует достаточно простой алгоритм, отслеживающий только основные команды модифицирующие стековый регистр. Для специфичных случаев предусмотрена ручная коррекция (смотри описание функции SetSpDiff) но в большинстве случаев IDA и сама справляется с этой задачей. Пример использования: .text:004010FF __amsg_exit proc near .text:004010FF .text:004010FF .text:004010FF arg_0 = dword ptr 4 .text:004010FF .text:004010FF cmp dword_0_408758, 2 .text:00401106 jz short loc_0_40110D .text:00401108 call __FF_MSGBANNER .text:0040110D .text:0040110D loc_0_40110D: .text:0040110D push [esp+arg_0] .text:00401111 call __NMSG_WRITE .text:00401116 push 0FFh .text:0040111B call off_0_408050 .text:00401121 pop ecx .text:00401122 pop ecx .text:00401123 retn .text:00401123 __amsg_exit endp .text:00401123 Message(“%d \n”, GetSpd(0x4010FF) ); 0 Message(“%d \n”, GetSpd(0x401111) ); -4 Message(“%d \n”, GetSpd(0x401116) ); -8 Message(“%d \n”, GetSpd(0x401122) ); -4 Message(“%d \n”, 190 GetSpd(0x401123) ); 0 В точке входа в функцию значение SP (ESP) всегда равно нулю. Затем, в нашем примере, оно изменяется командой push, заносящей в стек двойное слово. Обратите внимание, что значение ESP изменяется только после завершения команды – то есть с адреса начала следующей. Относительное значение ESP Адрес Инструкция 0 .text:0040110D push [esp+arg_0] -4 .text:00401111 call __NMSG_WRITE В точке выхода из функции значение SP (ESP) так же должно равняться нулю. В противном случае стек был бы несбалансированным, и команда возврата вытолкнула из стека не адрес возврата, а что-то совсем иное. В таком случае вероятнее всего, что IDA не смогла отследить все инструкции, модифицирующие значения стекового регистра (или сделала это неправильно). Рекомендуется обнаружить это место и скорректировать его вызовом SetSpDiff. Операнд Пояснения Ea Линейный адрес в теле функции Return Относительное значение стекового регистра SP (ESP) long GetSpDiff(long ea); Возвращает относительное изменение стекового регистра SP (ESP) командой, расположенной по линейному адресу ‘ea’. Например: .text:004010FF __amsg_exit proc near .text:004010FF .text:004010FF .text:004010FF arg_0 = dword ptr 4 .text:004010FF .text:004010FF cmp dword_0_408758, 2 .text:00401106 jz short loc_0_40110D .text:00401108 call __FF_MSGBANNER .text:0040110D .text:0040110D loc_0_40110D: .text:0040110D push [esp+arg_0] .text:00401111 call __NMSG_WRITE .text:00401116 push 0FFh .text:0040111B call off_0_408050 .text:00401121 pop ecx .text:00401122 pop ecx .text:00401123 retn .text:00401123 __amsg_exit endp Message(“%d \n”, 191 GetSpd(0x4010FF) ); 0 Message(“%d \n”, GetSpd(0x401111) ); -4 Message(“%d \n”, GetSpd(0x401116) ); -8 Message(“%d \n”, GetSpd(0x401122) ); -4 Message(“%d \n”, GetSpd(0x401123) ); 0 Относительное значение ESP Адрес Инструкция 0 .text:0040110D push [esp+arg_0] -4 .text:00401111 call __NMSG_WRITE Как и в случае с GetSpd необходимо задавать адрес начала следующей команды или точнее, конца текущей. Операнд Пояснения Ea Линейный адрес конца команды в теле функции Return Относительное изменение стекового регистра SP (ESP) success SetSpDiff(long ea,long delta); Задает изменение стекового регистра SP (ESP) командой, лежащей по указанному линейному адресу. Дело в том, что IDA использует достаточно простой алгоритм, отслеживания SP (ESP), который не учитывает ряда особенностей некоторых экзотических команд. Однако, в настоящее время этот механизм настольно усовершенствован, что практически невозможно придумать в каком случае команда SetSpDiff могла бы оказаться полезной. Возьмем следующий, достаточно надуманный пример: 192 seg000:0000 000 public start seg000:0000 start proc near seg000:0000 push ax seg000:0001 002 push ax seg000:0002 004 push bp seg000:0003 006 mov bp, sp seg000:0005 006 mov word ptr [bp+2], 2 seg000:000A 006 pop bp seg000:000B 004 mov bp, sp seg000:000D 004 mov cx, [bp+0] seg000:0010 004 pop ax seg000:0011 002 add sp, cx seg000:0013 002 push ax seg000:0014 004 add sp, cx seg000:0016 004 retn seg000:0016 start endp ; sp = -4 Message(“%d \n”, GetSpDiff(0x10013) ); 0 Message(“%d \n”, GetSpDiff(0x10016) ); 0 Что бы узнать значение SP после завершения команды add sp, cx IDA, очевидно, должна знать чему равен регистр CX. Что бы его отследить пришлось бы включать в дизассемблер полноценный эмулятор 0x86 процессора. Пока это еще не реализовано и IDA предполагает, что значение CX равно нулю и, таким образом, уже неправильно определяет значение SP во всех нижележащих точках функции. Исправить положение можно ручной коррекцией значения SP. Функция SetSpDiff задает изменение регистра SP после выполнения команды. Для этого необходимо передать линейный адрес конца, а не начала команды. В нашем случае необходимо скорректировать величину изменения SP командами ADD SP, CX расположенными по адресам seg000:0011 и seg000:0014. Линейные адреса команд соответственно равны seg000:0013 и seg000:0016. Их и необходимо передать функции вместе с действительной величиной изменения SP. SetSpDiff(0x10013,2); SetSpDiff(0x10016,2); seg000:0000 000 public start seg000:0000 start proc near seg000:0000 push ax seg000:0001 002 push ax seg000:0002 004 push bp 193 seg000:0003 006 mov bp, sp seg000:0005 006 mov word ptr [bp+2], 2 seg000:000A 006 pop bp seg000:000B 004 mov bp, sp seg000:000D 004 mov cx, [bp+0] seg000:0010 004 pop ax seg000:0011 002 add sp, cx seg000:0013 000 push ax seg000:0014 002 add sp, cx seg000:0016 000 retn seg000:0016 start endp Операнд Пояснения Ea Линейный адрес конца команды delta Величина изменения SP указанной командой Завершение Пояснения 1 Успешно Return 0 Ошибка success MakeLocal(long start,long end,char location,char name) версия 3.74 и старше С версии 3.74 IDA поддерживает локальные переменные, которые в большинстве же случаев распознает и создает автоматически. Но иногда она не способна правильно их распознать, и тогда эта миссия ложиться на плечи пользователя. Подробнее о локальных переменных можно прочитать в специальной главе «Локальные переменные» посвященной непосредственно им. 'MakeLocal' полный аналог («Edit\Functions\Stack variables»). В прототипе функции 'MakeLocal' указывается область видимости локальной переменной ('start' и 'end'), однако существующие версии IDA (вплоть до IDA 4.0) не поддерживает такой возможности и область видимости локальной переменной распространяется целиком на всю функцию. Функция принимает следующие операнды: операнд Пояснения end Этот операнд игнорируется. Обычно его оставляют равным нулю, но из соображений совместимости с последующими версиями рекомендуются задавать конец функции или константу 'BADADDR' - тогда область локальной переменной будет определена IDA автоматически. start Этот операнд в существующих версиях должен совпадать с началом функции, иначе MakeLocal возвратит ошибку (в последующих версиях start должен определять адрес начала видимости локальной переменной) location Смешение переменной в кадре стека, задаваемое в виде строкового выражения "[BP+XX]", где "xx" представлено в шестнадцатеричном исчислении. Спецификатор 'x' можно ставить, а можно не ставить - все равно значение будет трактоваться как шестнадцатеричное. Интересной недокументированной особенностью является 194 возможность задавать другие регистры, помимо BP, например 'AX', однако это не возымеет никакого значения, все равно будет трактоваться как 'BP' name Это есть суть имя создаваемой переменной со всеми ограничениями, наложенными на имена и метки. Признаком хорошего тона является выбор такой нотации, что бы локальные переменные легко визуально отличались от остальных. IDA всем автоматически всем создаваемым локальным переменным присваивает имя 'var_xx'. Return ==return пояснения ==1 Локальная переменная успешно создана ==0 Ошибка Hot Key Menu Кроме локальных переменных этой же функцией можно создавать и размещенные в стеке аргументы, т.к. фактически это те же локальные переменные, только размещенные по другую сторону кадра стека (с положительным смещением, а не отрицательным). IDA в большинстве случаев самостоятельно автоматически определяет аргументы функций (называя их 'arg_xx') и вмешательство пользователя обычно не требуется. Пример: MakeLocal(ScreenEA(),0,"[bp+0x4]","MyVar"); .text:00401124 sub_0_401124 proc near .text:00401124 .text:00401124 MyVar = dword ptr 4 .text:00401124 .text:00401124 push [esp+MyVar] success SetReg (long ea,char reg,long value); Функция устанавливает значение сегментных регистров. IDA автоматически отслеживает их значение (и изменение) и хранит его для каждой точки кода. Этот механизм достаточно совершенен и обычно вмешательства не требуется, однако в некоторых случаях IDA неправильно вычисляет значение сегментых регистов, например, если модификацией управляет отдельный поток и тогда требуется вмешательство пользователя. SetReg генерирует директиву ассемблера ASSUME, помещаемую в исходный код. При этом регистр должен указывать на начало сегмента. Все существующие ассемблеры поддерживают именно такой режим, но программистам иногда требуется установить сегментный регистр по произвольному адресу внутри сегмента (например, для организации плавающего кадра окна для преодоления 64 КБ барьера реального режима на сегмент) SetReg нормально принимает такие значения. Например: dseg:0000 start proc near dseg:0000 mov ax, seg dseg dseg:0003 mov ds, ax dseg:0005 assume ds:dseg dseg:0005 mov dx, offset aHelloSailor ; dseg:0008 call WriteLn 195 dseg:000B mov ax, ds dseg:000D inc ax dseg:000E mov ds, ax dseg:0010 assume ds:nothing dseg:0010 mov dx, 2Fh ; '/' dseg:0013 call WriteLn dseg:0016 mov ah, 4Ch dseg:0018 int 21h dseg:0018 start endp dseg:0020 aHelloSailor db 'Hello,Sailor',0Dh,0Ah,'$' dseg:002F db '$$$$$$$$$$$$$$$$' dseg:003F aHelloIda db 'Hello,IDA!',0Dh,0Ah,'$' dseg:003F dseg ends dseg:003F dseg:003F dseg:003F end start Смещение 0x2F в строке dseg:0x10 на самом деле указывает на строку dseg:0x3F, т.к. перед этим значение DS было увеличено на единицу (один параграф равен шестнадцати байтам) Как «объяснить» это дизассемблеру? Переведем курсор на строку ‘dseg:0x10’ и используем следующую команду: SetReg (SreenEA (),”DS”, 0x1001); Теперь если преобразовать операнд в смещение получиться следующее: dseg:0010 loc_0_10: ; DATA XREF: start+10o dseg:0010 mov dx, offset aHelloIda - offset loc_0_10 ; "Hello,IDA!\r\n$" Заметим по комментарию, что теперь IDA правильно определила ссылку. Однако, сгенерировала неверный код. «offset aHelloIda - offset loc_0_10 » будет работать только до тех пор, пока метка loc_o_10 будет расположена по смещению 0x10, и нам необходимо заменить ее константой 0x10. Для этого воспользуется, например, функций OpAlt. SetReg изменяет значение сегментного регистра в каждой точке до следующего ‘ASSUME’ или конца сегмента. Операнд пояснения ‘ea’ линейный адрес ‘reg’ символьное название регистра. (“CS”,”DS”,”ES” и т.д.) ‘value’ значение регистра в параграфах Return ==return пояснения ==1 Операция была выполнена успешно ==0 Ошибка Функция SetReg эквивалентна команде меню «EDIT\Segments\Change segment register value». long GetReg (long ea,char reg); Возвращает значение сегментных регистров в произвольной точке программы. Подробнее об этом можно прочитать в описании функции SetReg. 196 операнд Пояснения ‘ea’ линейный адрес, в котором необходимо определить значение регистра ‘reg’ символьное имя регистра. Например “DS”, “GS” и так далее Return ==return Пояснения !=0xFFFF Значение сегментного регистра в параграфах ==0xFFFF Ошибка Функция возвращает 16-битное целое, содержащие значение сегментного регистра в параграфах. В 32-битных программах функция обычно возвращает не непосредственное значение, а селектор. Для получения искомого адреса необходимо воспользоваться функцией AskSelector. Поскольку селекторы «визуально» неотличимы от адресов сегментов, то для уверенности необходимо вызывать AskSelector всякий раз для проверки на принадлежность возвращаемого значения к селекторам. Если селектор с указанным номером не существует, то это непосредственное значение. Если регистр не существует (например “MS”) или не определен, то функция и в том и другом случае вернет ошибку 0xFFFF, а не BADADDR, как утверждает прилагаемая к IDA документация. Пример использования: seg000:0000 seg000 segment byte public 'CODE' use16 seg000:0000 assume cs:seg000 Message (“%x \n”, GetReg (0x10000,”CS”) ); 1000 .text:00401000 _text segment para public 'CODE' use32 .text:00401000 assume cs:_text Message (“%x \n”, GetReg (ScreenEA (),”CS”) ); 1 Message (“%x \n”, AskSelector (1) ); 0 ПЕРЕКРЕСТНЫЕ ССЫЛКИ 197 ЧТО ТАКОЕ ПЕРЕКРЕСТНЫЕ ССЫЛКИ? Долгое время SOURCER лидировал среди других дизассемблеров в умении находить и восстанавливать перекрестные ссылки. На этом, правда, его основные достоинства и оканчивались, но все равно он активно использовался для исследования программного обеспечения. Что же такое перекрестные ссылки и почему они так важны для популярности дизассемблера? Покажем это на следующем примере. Рассмотрим простейший случай. Допустим, исходный файл выглядел так: .MODEL TINY .CODE ORG 100h Start: MOV AH,9 LEA DX,s0 INT 21h RET s0 DB "Hello, Sailor!",0Dh,0Ah,'$' END Start После ассемблирования он будет выглядеть следующим образом: seg000:0100 start proc near seg000:0100 mov ah, 9 seg000:0102 mov dx, offset aHelloSailor ; "Hello, Sailor!\r\n$" seg000:0105 int 21h seg000:0105 seg000:0107 retn seg000:0107 start endp seg000:0107 seg000:0107 ; --------------------------------------------------- ------------------------ seg000:0108 aHelloSailor db 'Hello, Sailor!',0Dh,0Ah,'$' seg000:0108 seg000 ends Допустим, мы хотим узнать, какой код выводит эту строку на экран. Когда-то для этого приходилось кропотливо изучать весь листинг, и то не было шансов, что с первого раза выводящий строку код удастся рассмотреть. Поэтому, эту задачу возложили на плечи дизассемблера. Так, что бы машина сама анализировала выполнение программы и восстанавливала все связи. Это невероятно упростило анализ программ, как впрочем, и взлом защит. Стало достаточно только найти в строку, которая защита выводит в случае неудачного завершения проверки (ключевой дискеты ли, или пароля – совершенно не важно), как дизассемблер поможет мгновенно найти, вводящий ее код, а значит, и локализовать защиту каким бы длинной программа не оказалась. Как правило, где-то неподалеку расположен условный переход, передающий управление этой ветви. Стоит только изменить его на противоположный, как защиту можно считать взломанной. Но ведь же не для хакерства же были придуманы перекрестные ссылки! Разумеется, нет! Помощь хакерам это только побочный эффект (хотя и очень приятный для них). Значительно важнее поддержка перекрестных ссылок чтобы правильно дизассемблировать код! Покажем это на следующем примере: 198 .MODEL TINY .CODE ORG 100h Start: LEA AX,s0 PUSH AX CALL Print RET s0 DB 'Hello, Sailor!',0Dh,0Ah,'$' Print: POP AX POP DX PUSH AX MOV AH,9 INT 21h RET END Start Первые четыре строки любой дизассемблер разберет без труда. Но вот дальше начнутся сложности. Как узнать, что следом идет текстовая строка, а не исполняемый код? Если же дизассемблировать как код из соображений одной лишь надежды на это, то полуученый результат станет похожим на бред и вся программа окажется дизассемблированной неправильно. Поэтому приходится эмулировать ее исполнение и отслеживать все косвенные и непосредственные переходы. Если ассемблер сумеет распознать вызов подпрограммы, то ссылку на строку он восстановит с куда большей легкостью. Однако, тут скрывается один подводный камень. Эмуляция (даже частичная) требует больших накладных расходов, и если каждый раз ее выполнять «налету», то никаких вычислительных ресурсов не хватит для нормальной работы! Поэтому приходится прибегать к компиляции. Да, именно к компиляции. Ведь компиляция это только перевод с одного языка в другой, а не обязательно в машинные коды. В данном случае, как раз запутанный язык ассемблера (а точнее одних лишь ссылок) преобразуется к максимально производительно и компактной форме записи. Или другими словами можно сказать, что перекрестные ссылки – это сохраненный результат работы эмулятора. Кроме того, если выполнение программы однонаправлено, то есть часто невозможно сказать, выполнение какой инструкции предшествовало текущей, то перекрестные ссылки предоставляют такую возможность! Можно начать изучение программы с любой точки, свободно двигаясь по ходу ее исполнения как взад, так и вперед. Это, в самом деле, очень удобно. Ведь в большинстве случаев не требуется изучить всю программу целиком, а только один из ее компонентов. Например, тот, что отвечает за взаимодействие с интересующим вас форматом файла. Предположим, что в заголовке находится некая сигнатура, которая проверятся исследуемой программой и находится в дизассемблируемом листинге в «прямом виде». Тогда можно по перекрестным ссылкам, перейти на процедуру загрузки файла и начать ее изучение. И это не единственный пример. Перекрестные ссыпки активно используются при дизассемблировании программ любой сложности и относятся к незаменимым компонентам дизассемблера. Однако, как нетрудно догадаться, что гарантировано можно отслеживать только непосредственные ссылки, такие как CALL 0x666, а уже MOV DX,0x777 может быть как смещением, так и константой, а про инструкции типа CALL BX говорить и вовсе не приходится – для вычисления значения регистра BX потребуется весьма не хилый эмулятор процессора. 199 Поэтому не удивительно, что большинство ассемблеров отслеживало перекрестные ссылки из рук вон плохо. Даже первые версии IDA не были совершенны в этом плане. То есть поддержка перекрестных ссылок имелась, но их созданием приходилось заниматься человеку (ведь IDA изначально планировалась как интерактивная среда!), а не машине. Но с течением времени ситуация изменялась и интеллектуальные механизмы IDA улучшались. С версии 3.7 она уже значительно превосходила в этом отношении все остальные существующие в мире ассемблеры, включая SOURCER, и продолжала совершенствоваться! |