Описание функций встроенного языка
Скачать 2.86 Mb.
|
char GetFuncOffset(long 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:00401121 __amsg_exit endp .text:00401121 .text:00401122 pop ecx .text:00401123 retn Message(“%s \n”, GetFuncOffset(0x401108) ); __amsg_exit+9 Операнд Пояснения ea Линейный адрес, принадлежащий хотя бы одной функции Завершение Пояснения Return !=”” Смещение относительно начла функции (строка) 178 “” Ошибка Если смещение относительно функции равно нулю, то вызов GetFuncOffset возвратит только одно имя. long FindFuncEnd(long ea); Описание этой функции, приведенное в контекстной помощи, немного запутанное и с первого взгляда назначение этой функции не ясно. Но на самом деле она очень находит широкое применение в автономных скриптах. Основное ее назначение – определение линейного адреса при создании функции. Это сопряжено со следующими трудностями – прежде всего необходимо, что бы адрес конца не превышал линейного адреса следующей за ней функции, поскольку функции перекрываться не могут. Например: seg000:22C0 start: seg000:22C0 call sub_0_22DD seg000:22C3 call sub_0_2325 seg000:22C6 call sub_0_235B seg000:22C9 call sub_0_2374 seg000:22CC call sub_0_23B6 seg000:22CF call sub_0_23F8 seg000:22D2 jnz loc_0_22DA seg000:22D4 nop seg000:22D5 nop seg000:22D6 nop seg000:22D7 call sub_0_2412 seg000:22DA seg000:22DA loc_0_22DA: ; seg000:22DA call halt seg000:22DD seg000:22DD ; _______________ S U B R O U T I N E ___ seg000:22DD seg000:22DD seg000:22DD sub_0_22DD proc near ; seg000:22DD call sub_0_28CC Функция start не завершается командой возврата ret. Вместо этого она перывает выполнение программы, процедурой Halt. Если пытаться создать функцию, определяя линейный адрес ее конца поиском ret, то полученный адрес будет принадлежать функции sub_0_22DD! Следовательно, адрес конца не может превышать линейный адрес следующей функции. Вторая проблема заключается в отождествлении инструкции возврата. Это может быть все что угодно. И RETN, и RETF… Таким образом, определение конца функции «вручную» оказывается слишком утомительно. И тогда стоит прибегнуть к вызову FindFincEnd. Что она делает? Она возвращает линейный адрес на единицу больший линейного адреса конца функции, которая может быть успешно создана. Таким образом, задача создания определения адреса конца функции для ее создания упрощается, тем более, что FindFuncEnd ищет не первую встретившуюся ей инструкцию возврата, а последнюю в цепочке перекрестных ссылок на следующую команду (подробнее об этом рассказано в главе «Перекрестные ссылки»). 179 Отсюда следует тот замечательный факт, что она поддерживает функций со множественными возвратами (а таким, как правило большинство). Например: seg000:0100 start: seg000:0100 mov ax, 3D01h seg000:0103 mov dx, 10Fh seg000:0106 int 21h seg000:0106 seg000:0106 seg000:0106 seg000:0108 jb loc_0_10B seg000:010A retn seg000:010B ; ------------------------------------- seg000:010B seg000:010B loc_0_10B: seg000:010B mov ax, 0FFFFh seg000:010E retn seg000:010E ; ------------------------------------- seg000: 010F aMyfile db 'MyFile',0 Message("0x%X \n", FindFuncEnd(0x10103) ); 0x1010F Обратите внимание, что IDA эмулировала выполнение команды условного перехода и правильно определила точку выхода из функции. Однако, если быть уж совсем «буквоедом», то можно заметить, что строка aMyFIle вероятнее всего принадлежала функции, но IDA автоматически не смогла это распознать. Поэтому иногда результат работы функции все же приходится корректировать. Очень важный факт – линейный адрес должен указывать на начало команды, иначе вызов провалиться. Например: Message("0x%X \n", FindFuncEnd(0x10102) ); 0xFFFFFFFF То же самое произойдет если по указанному адресу будет расположены данные, так что функцию будет создать невозможно. Если же функция уже существует, то вызов FindFuncEnd так же возврат адрес ее конца: seg000:0100 start proc near seg000:0100 mov ax, 3D01h seg000:0103 mov dx, 10Fh seg000:0106 int 21h seg000:0106 seg000:0106 seg000:0106 seg000:0108 jb loc_0_10B seg000:010A retn 180 seg000:010B ; ------------------------------------ seg000:010B seg000:010B loc_0_10B: seg000:010B mov ax, 0FFFFh seg000:010E retn seg000:010E ; ------------------------------------ seg000:010F aMyfile db 'MyFile',0 seg000: 010F start endp Message("0x%X \n", FindFuncEnd(0x10103) ); 0x10116 То, что последний отображаемый адрес равен 0x10F это небольшой баг IDA. На самом деле это адрес начала стоки, но не ее конца. Как нетрудно вычислить, адрес конца строки равен 0x115, следовательно функция FindFuncEnd работает правильно. В описании этой функции в idc.idc утверждается, что требуется передать линейный адрес начала функции, но это не так. С таким же успехом функция принимает любой, принадлежащий ей адрес, если он приходится на начало инструкции. Операнд Пояснения ea Линейный адрес, конца функции Завершени е Пояснения !=BADADD R ID структуры обеспечивающий доступ к локальным переменным и аргументам Return BADADDR Ошибка long GetFrame(long ea); Возвращает ID фрейма функции (если он есть) или BADADDR в противном случае. Это значение может интерпретироваться только IDA, и с токи зрения пользователя лишено всякого смысла (как и всякий дескриптор) Все локальные переменные и аргументы объединены в одну структуру, с которой можно работать, как и с любой с помощью функций, описанных в разделе «Структуры» Если функция не содержит ни одной локальной переменной и не имеет ни одного аргумента, то вызов GetFrame возвратит ошибку BADADDR. Пример использования: .text:004010FF __amsg_exit proc near .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 181 .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(“%x \n”, GetFrame(0x40110D) ); ff000162 Операнд Пояснения ea Линейный адрес, принадлежащий функции Завершени е Пояснения !=BADADD R ID структуры обеспечивающий доступ к локальным переменным и аргументам Return BADADDR Ошибка long GetFrameLvarSize(long ea); Возвращает размер локальных переменных функции (в байтах). Если функция не имеет локальных переменных, то возвращается ноль. Если указанный адрес не принадлежит ни одной функции, возвращается ошибка BADADDR. Например: .text:00401806 __ioinit proc near .text:00401806 var_44 = byte ptr -44h .text:00401806 var_12 = word ptr -12h .text:00401806 var_10 = dword ptr -10h .text:00401806 .text:00401806 sub esp, 44h .text:00401809 push ebx .text:0040180A push ebp Message(“0x%X \n”, GetFrameLvarSize(0x401809) ); 0x44 Операнд Пояснения Ea Линейный адрес, принадлежащий функции Завершени е Пояснения !=0 !=BADADD R Размер локальных переменных функции в байтах 0 Функция не имеет локальных переменных Return BADADDR Ошибка 182 long GetFrameRegsSize(long ea); Возвращает размер сохраненных в стековом фрейме регистров. Для 32-разрядных программ он равен четырем (четыре байта на регистр) и для 16-разрядных соответственно двум (два байта не регистр) Если функция не имеет кадра стека, то возвращается ноль и BADADDR если указанный адрес не принадлежит ни одной функции. Пример использования: .text:0040124A __XcptFilter proc near .text:0040124A .text:0040124A arg_0 = dword ptr 8 .text:0040124A arg_4 = dword ptr 0Ch .text:0040124A .text:0040124A push ebp .text:0040124B mov ebp, esp .text:0040124D push ebx .text:0040124E push [ebp+arg_0] Message(“0x%X \n”, GetFrameRegsSize(0x40124A) ); 4 seg000:2092 sub_0_2092 proc far seg000:2092 seg000:2092 var_40 = byte ptr -40h seg000:2092 seg000:2092 push bp seg000:2093 mov bp, sp Message(“0x%X \n”, GetFrameRegsSize(0x12093) ); 2 Операнд Пояснения Ea Линейный адрес, принадлежащий функции Завершени е Пояснения !=0 !=BADADD R Размер сохраненных регистров в стековом фрейме 0 Функция не имеет кадра стека Return BADADDR Ошибка 183 long GetFrameArgsSize(long ea); Возвращает размер (в байтах) аргументов, передаваемых функции. IDA определят эту величину на основании анализа команд, очищающих стек от локальных переменных. Обычно компиляторы используют два принципиально различных соглашения для этого. Паскаль – соглашение предписывает функции самой очищать стек от локальных переменных перед возвратом из функции. Независимо от способа реализации после возврата из функции стек должен быть сбалансирован. На платформе Intel практически всегда для этого используют команду RET N, где N число байт, которые нужно вытолкнуть с верхушки стека после возврата. В этом случае IDA просто возвращает аргумент, стоящий после RET. Например: Pascal_func: Push bp Mov bp,sp Mov ax,[BP+4] RET 2 Endp PUSH 10 CALL Pascal_func Си – соглашение предписывает очищать локальные переменные вызываемому коду. При выходе из функции стек остается несбалансированным. Поэтому необходимо скорректировать его верхушку. Долгое время не оптимизирующие компиляторы использовали для этого команду ADD SP, N. Где N размер аргументов в байтах. Очевидно, что IDA так же без проблем могла распознать такую ситуацию. Например: C_func: Push bp Mov bp,sp Mov ax,[BP+4] RET Endp PUSH 10 CALL C_func ADD SP,2 Но с появлением оптимизирующих компиляторов все изменилось. Они могли выталкивать аргументы командой POP в неиспользуемые регистры или вовсе оставлять стек несбалансированным на то время пока к нему нет обращений. Поэтому в определении размера аргументов стали возможны ошибки. C_opimize_func: Push bp Mov bp,sp Mov ax,[BP+4] RET Endp PUSH 10 CALL C_optimize_func 184 OR AX,AX JZ xxx MOV AX,[BX] Xxx: POP AX RET Даже для человека с первого взгляда не очевидно назначение команды POP AX. Кроме того, современные компиляторы поддерживают «совмещенные аргументы», что делает задачу определения их размера практически невозможной. Допустим, что по ходу программы необходимо передать один и то же аргумент нескольким функциям. H=open(“MyFile”,”rb”); read(buff,10,H); seek(20,1,H); По идее Си-компилятор должен был бы сгенерировать следующий код. PUSH offset arb PUSH offset aMyFile CALL open ADD SP,4 MOV [offset H],AX PUSH [offset H] PUSH [10] PUSH buff CALL read ADD SP,6 PUSH [offset H] PUSH 1 PUSH 20 CALL seek ADD SP,6 Как нетрудно видеть один и тот же аргумент – дескриптор файла многократно заносится в стек, а потом выталкивается из него. Поэтому оптимизирующие компиляторы поступят, скорее всего, приблизительно так: PUSH offset arb PUSH offset aMyFile CALL open PUSH AX PUSH [10] PUSH buff CALL read ADD SP,4 PUSH 1 PUSH 20 CALL seek ADD SP,10 185 Разобраться сколько аргументов принимает каждая функция одним лишь анализом балансировки стека абсолютно невозможно! После выхода из первой функции стек остается несбалансированным. Вторая функция очищает только два аргумента, оставляя в стеке дескриптор файла для последующих функций. И «отдуваться» за все это приходится третей функции, которая выталкивает из стека аж 5 слов! Но на самом деле размер его аргументов гораздо скромнее. Можно, конечно, попытаться отслеживать аргументы, к которым функция обращается и, выбрав из них тот, что лежит «внизу» всех, вычислить на основе этого размер аргументов. Однако такой подход предполагает, что функции известно число переданных ей аргументов, что в случае с Си - компиляторами неверно. Ведь перенос заботы об очистке стека с самой функции на вызывающий ее код объясняется как раз тем, что в Си функции не знают, сколько точно им параметров было передано. Поэтому можно только удивляться, что даже на оптимизированном коде IDA сравнительно редко ошибается. Операнд Пояснения Ea Линейный адрес, принадлежащий функции Завершени е Пояснения !=0 !=BADADD R Размер аргументов в байтах 0 Функция не имеет кадра стека Return BADADDR Ошибка long GetFrameSize(long ea); Возвращает полный размер стекового фрейма в байтах. Он вычисляется по следующей формуле: FrameSize == FrameLvarSize + FrameArgsSize + FrameRegsSize + ReturnAddresSize То есть сумме размеров локальных переменных, аргументов, сохраненных в стеке регистров и адреса возврата всех вместе взятых. Подробнее о каждой из них можно прочитать в описании функций GetFrameLvaerSize, GetFrameArgsSize, GetFrameRegsSize. Специальной функции, возвращающий значение размера адреса возврата не существует, однако, он может быть вычислен по следующей формуле: ReturnAddresSize == FrameSize - FrameLvarSize + FrameArgsSize + FrameRegsSize 186 Поскольку в стековый фрейм входит и адрес возврата, то независимо от того, имеет ли функция локальные переменные или нет, он не может быть равен нулю. Примеры использования: seg000:0000 start proc near seg000:0000 call sub_0_A seg000:0003 call sub_0_10 seg000:0006 call sub_0_16 seg000:0009 retn seg000:0009 start endp Message(“0x%X \n”, GetFrameSize(0x10000) ); 2 seg000:0010 sub_0_10 proc near seg000:0010 push bp seg000:0011 push ax seg000:0012 mov bp, sp seg000:0014 pop bp seg000:0015 retn seg000:0015 sub_0_10 endp ; sp = -2 Message(“0x%X \n”, GetFrameSize(0x10010) ); 6 Message(“0x%X \n”, GetFrameRegsSize(0x10010) ); 4 Как видно, в последнем случае стековый фрейм состоял из адреса возврата и сохраненных в стеке регистров. Однако, если команды расположить по другому, то результат изменится: seg000:000A sub_0_A proc near seg000:000A push bp seg000:000B mov bp, sp seg000:000D push ax seg000:000E pop bp seg000:000F retn seg000:000F sub_0_A endp ; sp = -2 Message(“0x%X \n”, GetFrameSize(0x1000A) ); 4 187 Message(“0x%X \n”, GetFrameRegsSize(0x1000A) ); 2 Все команды, лежащие «ниже» (то есть в более старших адресах) относительно команды mov bp, sp (которая и определят стековый фрейм) в него не попадают и можно безболезненно заносить (выталкивать) команды из стека, не боясь разрушить стековый фрейм. Операнд Пояснения Ea Линейный адрес, принадлежащий функции Завершени е Пояснения !=BADADD R Размер стекового фрейма в байтах Return BADADDR Ошибка long MakeFrame(long ea,long lvsize,long frregs,long argsize); Создает фрейм стека функции или модифицирует уже существующий. Для этого достаточно указать любой адрес, принадлежащий функции и размеры области для локальных переменных, сохраненных регистров и аргументов. Они расположены во фрейме в следующей последовательности. Стековый фрейм Локальные переменные Сохраненные регистры Аргументы, переданные функции Адрес возврата из функции При успешном завершении функция возвращает ID структуры, обеспечивающий доступ ко всем вышеперечисленным элементам. В противном случае функция вернет ошибку BADADDR. Операнд Пояснения Ea Любой линейный адрес, принадлежащий функции lvsize Размер локальных переменных в стековом фрейме frrgs Размер сохраненных регистров в стековом фрейме argsize Размер аргументов, передаваемых функции Завершение Пояснения !=BADADDR ID структуры, обеспечивающей доступ ко всем элементам стекового фрейма Return BADADDR Ошибка Модифицирование уже существующего фрейма стека может повлечь за собой удаление локальных переменных. Например: .text:00401487 __setargv proc near 188 .text:00401487 .text:00401487 var_8 = dword ptr -8 .text:00401487 var_4 = dword ptr -4 .text:00401487 .text:00401487 push ebp .text:00401488 mov ebp, esp .text:0040148A push ecx .text:0040148B push ecx MakeFrame(0x401487,0,0,0); .text:00401487 __setargv proc near .text:00401487 push ebp .text:00401488 mov ebp, esp .text:0040148A push ecx .text:0040148B push ecx .text:0040148C push ebx Аргументы же функции никогда не удаляются из стекового фрейма, даже при уменьшении размера выделенного для них региона до нуля. Однако, это нарушает целостность всего фрейма и локальных переменных, лежащих «выше» .text:00401520 sub_0_401520 proc near .text:00401520 .text:00401520 .text:00401520 arg_0 = dword ptr 8 .text:00401520 arg_4 = dword ptr 0Ch .text:00401520 arg_8 = dword ptr 10h .text:00401520 arg_C = dword ptr 14h .text:00401520 arg_10 = dword ptr 18h .text:00401520 .text:00401520 push ebp .text:00401521 mov ebp, esp .text:00401523 mov ecx, [ebp+arg_10] .text:00401526 mov eax, [ebp+arg_C] .text:00401529 push ebx MakeFrame(0x401520,0,0,0); .text:00401520 sub_0_401520 proc near .text:00401520 .text:00401520 .text:00401520 arg_0 = dword ptr 8 .text:00401520 arg_4 = dword ptr 0Ch .text:00401520 arg_8 = dword ptr 10h .text:00401520 arg_C = dword ptr 14h .text:00401520 arg_10 = dword ptr 18h .text:00401520 .text:00401520 push ebp .text:00401521 mov ebp, esp .text:00401523 mov ecx, [ebp+arg_10] .text:00401526 mov eax, [ebp+arg_C] .text:00401529 push ebx |