Описание функций встроенного языка
Скачать 2.86 Mb.
|
окном), превышает размер одной страницы, IDA может загружать в оперативную память несколько страниц одновременно. Соответственно размер окна должен быть кратен размеру страницы. Если окно целиком заполнено, но требуется загрузить еще одну страницу, IDA просматривает буфер на предмет поиска не модифицированной страницы к которой дольше всего не было обращения. А отыскав такую, замещает ее прочитанной с диска. В противном случае (если все страницы со времени последней загрузки были модифицированы), IDA «сбрасывает» на диск самую старую страницу и только затем замещает ее новой. Во избежание потерь информации при сбоях питания, зависаниях дизассемблера и т.д., предусмотрен автоматический «сброс» модифицированных страниц через определенные промежутки времени, длительность которых задается значением поля “AUTOSAVE” файла <idatui.cfg> и <idagui.cfg> - консольной и графической версий соответственно. По умолчанию буфера сохраняются после совершения пользователем ста любых действий или по истечении пяти минут, при этом в окне сообщений появится поясняющая надпись «Flushing buffers, please wait...ok» Замечание: IDA не учитывает «популярность» страницы (т.е. частоту ее использования или количество обращений), а только время последнего обращения (чтения или записи). 31 ??? #Художнику – перерисовать рисунок! Рисунок 14 Окно страничной памяти Увеличение размера страниц увеличивает вероятность того, что очередной запрашиваемый байт окажется уже загруженным в оперативную память, но в то же время, повышает накладные расходы на сброс модифицированных страниц и уменьшает количество страниц в окне. Поскольку, размер окна сверху ограничен объемом доступной оперативной памяти, возникает задача вычисления оптимального соотношения между размер и количестве страниц. Размеры и количество страниц задаются полями “VPAGES” и “VPAGESIZE” файла <ida.cfg> соответственно. Если VPAGES == 0, IDA пытается самостоятельно вычислить оптимальное значение, исходя из количества требующейся для анализа загруженного файла виртуальной памяти. Поскольку каждый флаг виртуальной памяти требует четыре байта страничной памяти (т.к. помимо 8 бит содержимого ячейки хранит 24 бита атрибутов), грубо оценить потребности страничной памяти можно умножением размера загружаемого файла на четыре. Разумеется, в зависимости от формата файла истинное значение может значительно отличается от расчетного и предсказанная оценка окажется неверной. Это не нарушит работоспособности дизассемблера, поскольку недостающая память будет выделена автоматически по мере необходимости, однако, неоптимальное соотношение размера и количества страниц ухудшат производительность, поэтому, в некоторых случаях его выгоднее вычислять вручную. По умолчанию размер страницы (VPAGESSIZE) в зависимости от версии IDA равен либо 4096 либо 8192 байт, а, поскольку, количество страниц выражается 16-разрядным целым числом, доступное адресное пространство виртуальной памяти равно 64 и 128 мегабайт соответственно. Дальнейшее увеличение размера страниц расширяет границы доступного адресного пространства виртуальной памяти, но одновременно с этим ухудшает производительность за счет большей грануляции, поэтому, прибегать к нему рекомендуется в тех и только в тех случаях, когда требуется свыше 128 мегабайт виртуальной памяти. Ввиду станичной адресации базы, IDA не позволяет «на лету» изменять размер страниц и при загрузке файлов *.idb величина размера страниц берется оттуда, а значения поля VPAGESIZE игнорируется. Поэтому, необходимо внимательно отнестись к выбору размера страницы – в дальнейшем изменить его не удастся! Существует возможность необратимо уничтожить результаты своей работы, выбрав слишком малый размер страниц – в этом случае адресного пространства виртуальной памяти может не хватить и попытка выделения очередного блока виртуальной памяти провалится, а сеанс работы с IDA аварийно завершится! 32 Поле VPAGES, задающие размер буфера окна в страницах, по умолчанию равно нулю, и его значение IDA самостоятельно вычисляет по следующему алгоритму (см. таблицу 2). ??? #Верстальщику CreateNewTable Размер файла Размер окна в страницах Размер окна в байтах 0 КБ -- 255 КБ (FILESIZE * 4) / VPAGESIZE 1 МБ 256 КБ – 1023 КБ 1048576 / VPAGESIZE 1 МБ 1024 КБ – 2559 КБ FILESIZE / VPAGESIZE 1 - 2,5 МБ 2560 КБ – 10 МБ 4194304 / VPAGESIZE 4 МБ > 10 МБ (FILESIZE * 2) / (VPAGESIZE *5) > 4 МБ Таблица 2 Алгоритм автоматического выделения памяти Важно понять – размер буфера окна не ограничивает количество доступной страничной памяти! Окно – всего лишь кэш-буфер, и IDA будет работать даже в том случае, если уменьшить его до размера одной страницы (правда, это чрезвычайно снизит производительность). Операционные системы Windows и OS/2 позволяют выделить под окно больше памяти, чем ее имеется в наличии, сбрасывая избыток на диск в файл подкачки. В результате количество обращений к диску удваивается и производительность дизассемблера резко падает. Рекомендуется выбирать размер окна таким образом, чтобы он полностью умещался в физической памяти компьютера. Значения, вычисляемые IDA по умолчанию, рассчитаны на компьютеры, обладающее 16 или менее мегабайтами оперативной памяти, при наличие же большего его количества (на конец 2000 года компьютер типичной конфигурации содержит 32-64 мегабайт оперативной памяти) можно значительно улучить производительности, увеличив размер буфера окна. Помимо виртуальной памяти, содержащей образ загруженного фала, дизассемблеру требуется какое-то количество страничной памяти для хранения меток, имен функций, комментариев и т.д. В терминологии IDA такая память именуется DATEBASE_MEMORY или Memory for b-tree – память базы данных двоичного дерева. Поле “DATEBASE_MEMORY” конфигурационного файла <ida.cfg> позволяет изменять выделенное количество памяти под буфер базы данных. Объем памяти измеряется в байтах, но округляется до целого числа страниц, размер которых в текущих версиях IDA равен 8 килобайт (8.192 байт). Для нормальной работы дизассемблеру необходимо по крайней мере 5 страниц (40 килобайт), в противном случае IDA сообщит о нехватке памяти «bTree error: not enough memory» и аварийно завершит работу. Если DATEBASE_MEMORY = 0, IDA самостоятельно определяет оптимальный размер буфера по следующему алгоритму (см. таблицу 3): Размер файла Размер окна в страницах Размер окна в байтах 0 – 256 КБ 5 256 КБ 256 КБ – 1 МБ 128 1 МБ 1 МБ – 2.5 МБ 128 – 320 1 МБ – 2.5 МБ 2.5 МБ – 5 МБ 512 4 МБ > 5 МБ FILESIZE / 20 / PAGESIZE FILESIZE / 20 Таблица 3 Алгоритм автоматического определения размера окна С целью увеличения производительности, IDA динамически создает список указателей на имена меток, для работы с которым так же требуется некоторое количество страничной памяти. Размер буфера в страницах задается значением поля NPAGES файла 33 <ida.cfg>, а размер страницы значением поля NPAGESSIZE. По умолчанию IDA резервирует 64 страницы, объемом 1024 байт (1 КБ). Каждый указатель занимает 4 байта страничной памяти, следовательно, 64-кб буфер вмещает свыше 16 тысяч имен. Следует отметить, при дизассемблировании программ, написанных на Delphi, IDA генерирует огромное количество имен и увеличение значение поля NPAGES может существенно улучшить производительность. При загрузке файла в окне сообщений выдается отчет о выделении страничной памяти под нужды IDA: окно виртуальной памяти (“allocating memory for virtual array”), буфер двоичного дерева (“allocating memory for b-tree”) и буфер указателей на имена (“allocating memory for name pointers”). Например, выделение памяти при загрузке файла “first.exe” в IDA 3.84 происходит следующим образом: bytes pages size description --------- ----- ---- -------------------------------------------- 262144 32 8192 allocating memory for b-tree... 65536 16 4096 allocating memory for virtual array... 65536 64 1024 allocating memory for name pointers... ----------------------------------------------------------------- Рисунок 15 "Отчет о выделении памяти при загрузке IDA" Взаимодействие с физической памятью Взаимодействие с физической памятью становится возможным благодаря наличию четырех недокументированных функций _peek, _poke, _lpoke и _call, прототипы которых приведены ниже: • long _poke(long ea, long value) • long _lpoke(long ea, long value) • long _peek(long ea, long value) • long _call(long ea) Функции _poke и _lpoke записывают байт и длинное целое value по линейному адресу ea физической памяти соответственно, возвращая прежнее значение ячейки. Функция _peek читает байт по линейному адресу ea физической памяти, а функция _call передает управление на машинный код, расположенный по линейному адресу ea физической памяти. Следует отметить, указанные функции по-разному функционируют в различных версиях IDA и результат их выполнения может быть непредсказуем, поэтому, их использование не рекомендуется. Пример, приведенный ниже, демонстрирует копирование содержимое ПЗУ компьютера в виртуальную память дизассемблера, с последующим анализом обработчика прерывания INT 0x13. Ввиду различной реализации функций низкоуровневой работы с памятью, его успешная работа гарантируется лишь при запуске из MS-DOS-версии IDA Pro. Операционная система Windows 9x эмулирует наличие ПЗУ и позволяет функции _peek обращаться к нему даже из 32-разрядных версий IDA Pro. auto a; SegCreate(0xF0000,0xFFFFF,0x0F000,0,0,0); Message("Ждите... читаю BIOS..."); for (a=0;a<0xFFFF;a++) PatchByte(0xF0000+a, _peek(0xF0000+a)); Message("ОК \n Дизассемблирую обработчик Int 0x13"); MakeCode(0xFEC59); 34 Message("OK \n"); Jump(0xFEC59); По окончании работы скрипта экран дизассемблера должен выглядеть так: seg001:EC59 loc_E000_EC59: ; CODE XREF: seg001:0188↑J seg001:EC59 ; seg001:019B↑J seg001:EC59 jmp loc_E000_EE3B seg001:EC5C ; ──────────────────────────────────────────────────────────────── seg001:EC5C mov al, 0C0h ; '└' seg001:EC5E call sub_E000_EC8E seg001:EC61 jmp short loc_E000_EC66 seg001:EC63 ; ──────────────────────────────────────────────────────────────── seg001:EC63 call loc_E000_EC8A seg001:EC66 seg001:EC66 loc_E000_EC66: ; CODE XREF: seg001:EC61↑j seg001:EC66 call sub_E000_EC6C seg001:EC69 cmp al, ah seg001:EC6B retn Навигатор по функциям Начать изучение виртуальной памяти лучше всего с загрузки двоичного (бинарного) файла. Сначала необходимо создать сам файл – это можно сделать командной “echo Hello, IDA Pro! > tutor.bin”. ╔═[■] Load Binary or User-Defined Format file ════╗ ║ ║ ║ File name: F:\IDAN\SRC\1\tutor.bin ║ ║ ║ ║ (•) Binary file ║ ║ ║ ║ Loading segment 0x1000 ▐↓▌ (in paragraphs)║ ║ Loading offset 0x666 ▐↓▌ ║ ║ ║ ║ Processor: metapc ║ ║ Change processor ▄ ║ ║ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ║ ║ Analysis options ▄ ║ ║ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ║ ║ [ ] Create segments ║ ║ ║ ║ OK ▄ Cancel ▄ F1 - Help ▄ ║ ║ ▀▀▀▀ ▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ ║ ╚═════════════════════════════════════════════════╝ Рисунок 16 Диалог загрузки бинарного файла Дождавшись появления диалога загрузки консольной версии IDA Pro, запомните базовый адрес сегмента, записанный в поле “Loading segment ... (in paragraphs)”, измените адрес загрузки на любой отличный от нуля (нулевое смещение крайне ненаглядно для иллюстрации), скажем, на 0x666, и сбросьте флажок “Create segment” – для предотвращения автоматического создания сегмента (работа с сегментами будет рассмотрена позже, в главе «Сегменты и селекторы»). Замечание: доступные автору графические версии IDA Pro содержат ошибку реализации, всегда создавая новый сегмент при загрузке файла, независимо от установленных настроек. 35 После окончания загрузки файла экран дизассемблера должен выглядеть следующим образом: 0:00010666 ; File Name : F:\IDAN\SRC\1\tutor.bin 0:00010666 ; Format : Binary File 0:00010666 ; Base Address: 1000h Range: 10666h - 10678h Loaded length: 0012h 0:00010666 0:00010666 0:00010666 db 48h ; H 0:00010667 db 65h ; e 0:00010668 db 6Ch ; l 0:00010669 db 6Ch ; l 0:0001066A db 6Fh ; o 0:0001066B db 2Ch ; , 0:0001066C db 20h ; 0:0001066D db 49h ; I 0:0001066E db 44h ; D 0:0001066F db 41h ; A 0:00010670 db 20h ; 0:00010671 db 50h ; P 0:00010672 db 72h ; r 0:00010673 db 6Fh ; o 0:00010674 db 21h ; ! 0:00010675 db 20h ; 0:00010676 db 0Dh ; 0:00010677 db 0Ah ; 0:00010677 0:00010677 end Слева каждой строки указывается ее линейный адрес, причем адрес первого байта равен 0x1000*0x10+0x666, т.е. сумме базового адреса, указанного при загрузке, умноженного на шестнадцать и адреса смещения. Чтение ячеек виртуальной памяти осуществляется функциями – Byte(long ea), Word(long ea) и Dword(long ea) – возвращающими байт, слово и двойное слово соответственно. Если запрошенная ячейка не существует или не инициализирована, функция возвращает 0xFF (при этом следует быть особенно осторожным с функциями Word и Dword, некорректно сигнализирующих об ошибке – подробнее об этом можно прочитать в под главах Word и Dword соответственно). Поэтому, перед чтением ячейки памяти следует убедиться, что она есть и содержит какое-то значение. Это можно осуществить анализом младшего бита поля атрибутов – если он сброшен – ячейка отсутствует или не инициализирована. Получить содержимое поля атрибутов можно вызовом функции GetFlags (см. описание функции GetFlags) следующим образом: if(MS_VAL & GetFlags(ea)) // значение ячейки определенно, можно читать; else // значение ячейки не определено либо ячейка не существует; …или же воспользоваться макросом hasValue(F), определенным в <idc.idc>, который следует вызывать так: if(hasValue(GetFlags(ea))) // значение ячейки определенно, можно читать; else // значение ячейки не определено либо ячейка не существует Более короткий путь предоставляет макрос isLoaded(ea), определенный там же с аналогичным назначением: 36 if(isLoaded(ea)) // значение ячейки определенно, можно читать; else // значение ячейки не определено либо ячейка не существует” Замечание: макрос byteValue(F), определенный в файле употреблении позволяет сократить количество вызов GetFlags, а, следовательно, увеличить производительность программы. Вызывать его следует так: F = GetFlags(ea); if (hasValue(F)) val = byteValue(F); Использование функции Byte обычно требует двух вызов GetFlags – один раз для проверки значения ячейки, второй – для ее чтения. Если же ячейка заведомо существует и гарантировано содержит инициализированное значение, проверку можно опустить – в этом случае макрос byteValue не будет иметь никаких преимуществ перед функцией Byte. Пример использования: auto a; Message(“>”); for (a=0x10666;a<0x10676;a++) if (isLoaded(a)) Message(“%c”,Byte(a)); else Message(“!ОШИБКА!\n”); >Hello, IDA Pro! Модификация ячеек виртуальной памяти осуществляется функциями PatchByte (long ea, long value), PatchWord (long ea, long value) и PatchDword (long ea, long value) записывающих байт, слово и двойное слово соответственно. Попытка модификации несуществующей ячейки виртуальной памяти заканчивается провалом. Следующий пример меняет регистр всех символов на противоположный: 0:00010666 db 48h ; H 0:00010667 db 65h ; e 0:00010668 db 6Ch ; l 0:00010669 db 6Ch ; l 0:0001066A db 6Fh ; o 0:0001066B db 2Ch ; , 0:0001066C db 20h ; 0:0001066D db 49h ; I 0:0001066E db 44h ; D 0:0001066F db 41h ; A 0:00010670 db 20h ; 0:00010671 db 50h ; P 0:00010672 db 72h ; r 0:00010673 db 6Fh ; o 0:00010674 db 21h ; ! 0:00010675 db 20h ; 0:00010676 db 0Dh ; 0:00010677 db 0Ah ; a) исходные данные – требуется заменить регистр всех символов на противоположный auto a; for(a=0x10666;a<0x10674;a++) 37 PatchByte(a, Byte(a) ^ 0x20); b) скрипт, меняющий регистр всех символов на противоположный, посредством вызова функции PatchByte 0:00010666 db 68h ; h 0:00010667 db 45h ; E 0:00010668 db 4Ch ; L 0:00010669 db 4Ch ; L 0:0001066A db 4Fh ; O 0:0001066B db 0Ch ; 0:0001066C db 0 ; 0:0001066D db 69h ; i 0:0001066E db 64h ; d 0:0001066F db 61h ; a 0:00010670 db 0 ; 0:00010671 db 70h ; p 0:00010672 db 52h ; R 0:00010673 db 4Fh ; O 0:00010674 db 21h ; ! 0:00010675 db 20h ; 0:00010676 db 0Dh ; 0:00010677 db 0FFh ; c) результат – регистр всех символов изменен на противоположный Пара функций NextAddr(long ea) и PrevAddr(long ea) позволяют трассировать адресное пространство виртуальной памяти, «проходясь» по цепочке «включенных» адресов. Функция NextAddr(long ea) возвращает первый «включенный» адрес, следующий за “ea”, соответственно PrevAddr(long ea) возвращает первый «включенный» адрес, предшествующий “ea”. Пример использования: auto a; a=0; while(1) { a=NextAddr(a); if (a==BADADDR) break; Message("0:%08X\tdb %x;",a,Byte(a)); if (Byte(a)>0x20) Message("'%c'",Byte(a)); Message("\n"); } a) скрипт, демонстрирующий трассировку адресов 0:00010666○db 68;'h' 0:00010667○db 45;'E' 0:00010668○db 4c;'L' 0:00010669○db 4c;'L' 0:0001066A○db 4f;'O' 0:0001066B○db c; 0:0001066C○db 0; 0:0001066D○db 69;'i' 0:0001066E○db 64;'d' 0:0001066F○db 61;'a' 0:00010670○db 0; 0:00010671○db 70;'p' 0:00010672○db 52;'R' 0:00010673○db 4f;'O' 0:00010674○db 21;'!' |