Главная страница
Навигация по странице:

  • ТОЧКИ ВХОДА АРХИТЕКТУРА ТОЧЕК ВХОДА

  • МЕТОДЫ Функция Описание

  • СТРУКТУРЫ ALMA MATER

  • MCB.IsLastBlock ],M; ; Это продолжение цепочки JNZ gmm_z ; --> Конец/Обрыв цепочки gmm_next: ; // Следущий элемент цепочки ADD AX, ES:[MCB.Size

  • Архитектура структур в IDA

  • Описание функций встроенного языка


    Скачать 2.86 Mb.
    НазваниеОписание функций встроенного языка
    Анкорobraz_mihlenia_-_dizassembler_IDA
    Дата29.09.2022
    Размер2.86 Mb.
    Формат файлаpdf
    Имя файлаobraz_mihlenia_-_dizassembler_IDA.pdf
    ТипСправочник
    #704305
    страница23 из 39
    1   ...   19   20   21   22   23   24   25   26   ...   39
    long XrefType(void);
    Эта функция возвращает тип перекрестной ссылки, которая была возвращена последним вызовом функций Rfirst, Rnext, RfirstB, RnextB, Dfirst, Dnext, DfirstB, DnextB.
    Обратите внимание, что функция не принимает никаких параметров, а взаимодействует исключительно с внутренними переменными IDA.
    При этом она имеет одну грубую ошибку (точнее недостаток, который вряд ли будет скоро исправлен) реализации.
    Взращаемое значение принадлежит либо множеству определений fl_x или dr_x.
    Однако как отмечалось выше, типы перекрестных ссылок понятие число условное и та же функция AddCodeXref принимает в качестве параметра определения из множества dr_x, и даже успешно создает такие перекрестные ссылки, однако, являющиеся все равно перекрестными ссылками на код.
    Поэтому невозможно гарантированно определить тип перекрестной ссылки по возвращаемому функцией XrefType значению.
    Например: seg000:014C loc_0_14C: ;
    CODE
    XREF: seg000:0148
    w
    Rfirst(0x10148);
    Message(“0x%X \n”,
    XrefType()
    );
    0x15
    Функция вернула тип dr_W, но это еще не дает возможности утверждать, что эта перекрестная ссылка указывает на данные.
    Пояснения
    Return
    Тип перекрестной ссылки возращенной последним вызовом, манипулирующей с ней функции.
    ТОЧКИ ВХОДА
    АРХИТЕКТУРА ТОЧЕК ВХОДА
    Поддержка точек входа (Entry Point) самый мало проработанный элемент архитектуры IDA. Причиной тому абсолютная ненужность их для пользователя. В большинстве случаев даже не требуется знать, что такое понятие есть и поддерживается
    IDA
    С одной стороны, вполне логично, что каждый файл имеет некоторую точку, с которой начинается его выполнение, причем эта точка может не совпадать с началом файла. Например, exe файл может начинать выполнение с любой своей точки (между прочим, возможно даже выходящей за границы файла, - но это относиться к недокументированным особенностям MS-DOS совместимых операционных систем и поэтому не будет больше заострять на этом внимания)
    Адрес регистра (регистров) – указателя команд в момент передачи управления загруженному файлу и называется точкой входа.
    Таким образом, любой дизассемблер как минимум должен быть осведомлен, как вычислить этот адрес. Чаще всего он присутствует в заголовке файла (значит,

    228
    дизассемблер должен понимать его формат), реже предполагается по умолчанию – так для com файлов он всегда расположен по адресу 0x100, но для бинарных файлов (дампов
    RAM, например), точка входа не может и вовсе не иметь смысла. Поскольку управление может быть передано на множество мест, в зависимости от обстоятельств.
    Поэтому, с первого взгляда, говорить о поддержке точек входа можно только на уровне ядра дизассемблера, скрытом от пользователя, то есть собственно говорить не о чем и не за чем – манипулировать точками входа лучше предоставить ядру.
    В общих чертах так оно и есть – набор функций, взаимодействующий с точками входа, очень ограничен и, откровенно говоря, не полон. Так, например, не предусмотрено функции удаления точек входа, в том числе и созданных пользователем.
    Однако, создавать свои точки входа в большинстве случае нет нужды, а вот получить адреса существующих требуется очень часто – должны же скрипты знать с какого адреса начинается выполнение программы?
    Для этого предусмотрена функция long GetEntryPoint(long ordinal), чем потребности рядового пользователя с лихвой удовлетворяются.
    Но разного рода извращенцам и маньякам этого очень мало. Например, при анализе ПЗУ сталкиваешься с тем, что код может начинать выполняться с десятков разных мест (например, обработчиков прерываний) и хорошим решением будет создать собственные точки входа (IDA, разумеется, бессильна их определить) и потом взаимодействовать с ними как интерактивно, так и программно (из скриптов).
    Впрочем, многие просто создают в нужных местах функции, а точками входа пренебрегают. В чем-то такая позиция верна, поскольку точки входа не дают никаких преимуществ за исключением того, что явно указывают, что с этих адресов может начинаться выполнение программы.
    Еще IDA предваряет их имена директивой public, делая их общедоступными. Но то же можно сделать вручную.
    Подытоживая сказанное выше можно сказать, что вникать в технические подробности организации точек входа необязательно даже опытным пользователям, а тем более самостоятельно манипулировать ими.
    МЕТОДЫ
    Функция
    Описание
    Long GetEntryPointQty(void);
    Возвращает число точке входа success AddEntryPoint(long ordinal,long ea,char name,long makecode)
    Добавляет новую точку входа long GetEntryOrdinal(long index);
    Возвращает ординал точки входа по ее индексу long GetEntryPoint(long ordinal)
    Возвращает адрес точки входа по ординулу success RenameEntryPoint(long ordinal,char name);
    Переименовывает точку входа
    long GetEntryPointQty(void);

    229
    Функция возвращает число точек входа (Entry Points). Обычно IDA создает только одну точку входа, адрес которой извлекается из заголовков исполняемого файла. Но иногда возникает потребность в создании более, чем в одной точке входа.
    Например, PE файл, имеющий DOS-заглушку. Если мы захотим дизассемблировать последнюю, то необходимо добавить новую точку входа «вручную», поскольку IDA предпочитает в большинтстве случаев обходится всего лишь одним Entry
    Point
    Пример использования:
    Message(“0x%X \n”, GetEntryPointQty());
    0x1
    Операнд
    Пояснения
    Return
    Число точек входа
    success AddEntryPoint(long ordinal,long ea,char name,long makecode);
    Добавляет новую точку входа. Будьте внимательны при вызове этой функции, ведь удалить созданную точку входа уже не удастся!
    Для доступа к точке входа необходимо знать ее ординал, который задается пользователем при вызове функции. Если он равен нулю, то IDA установит его равным линейному адресу точки входа (и строго говоря при этом ординал не создается). С одним и тем же ординалом может существовать только одна точка входа.
    При генерации точки входа IDA вставляет директиву ассемблера PUBLIC. seg000:0000 public start seg000:0000 start proc near seg000:0000 push ax seg000:0001 mov cx, 1

    230
    seg000:0004 shl cx, 1 seg000:0006 add sp, cx seg000:0008 push ax seg000:0009 add sp, cx seg000:000B retn seg000:000B start endp ; sp = -4 seg000:000B seg000:000B seg000 ends
    AddEnrtyPoint(1,0x10006,”NewEntryPoint”,0); seg000:0000 public start seg000:0000 start proc near seg000:0000 push ax seg000:0001 mov cx, 1 seg000:0004 shl cx, 1 seg000:0006 seg000:0006 public
    NewEntryPoint seg000:0006
    NewEntryPoint:
    seg000:0006 add sp, cx seg000:0008 push ax seg000:0009 add sp, cx seg000:000B retn seg000:000B start endp ; sp = -4 seg000:000B seg000:000B seg000 ends
    Если попытаться создать более одной точки с идентичными именами, то IDA добавит к последнему знак прочерка и номер имени, начиная с нуля.
    AddEnrtyPoint(2,0x10009,”NewEntryPoint”,0); seg000:0000 public start seg000:0000 start proc near seg000:0000 push ax seg000:0001 mov cx, 1 seg000:0004 shl cx, 1 seg000:0006 seg000:0006 public NewEntryPoint seg000:0006 NewEntryPoint: seg000:0006 add sp, cx seg000:0008 push ax seg000:0009 seg000:0009 public
    NewEntryPoint_0
    seg000:0009
    NewEntryPoint_0:
    seg000:0009 add sp, cx seg000:000B retn seg000:000B start endp ; sp = -4 seg000:000B seg000:000B seg000 ends
    Если попытаться создать точку входа с уже существующим ординалом, то она не будет создана, а функция вернет ошибку.
    Message(“0x%X \n”,
    AddEnrtyPoint(2,0x10009,”MyEntryPoint”,0)
    );

    231 0x0
    Допускается создание точки входа без имени. При этом она не будет отображена на экране, но появится в списке точек входа.
    AddEntryPoint(1,0x122C6,””,1); seg000:22C0 public start seg000:22C0 start proc near 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:002A sub_0_2A proc near seg000:002A mov si, 211h seg000:002D call sub_0_DD seg000:0030 mov si, 2BAh seg000:0033 call sub_0_DD seg000:0036 retn seg000:0036 sub_0_2A endp
    AddEntryPoint(8,0x1002A,”EntryPoint”,0); seg000:002A public EntryPoint seg000:002A
    EntryPoint proc near seg000:002A mov si, 211h seg000:002D call sub_0_DD seg000:0030 mov si, 2BAh seg000:0033 call sub_0_DD seg000:0036 retn seg000:0036
    EntryPoint endp
    Если же по указанному адресу уже существует точка входа, то она не будет затерта новой, и по одному адресу будут расположены две точки входа. При этом имя предыдущей точки входа переместиться в комментарий.
    AddEntryPoint(9,0x1002A,”NewEntryPoint”,0);

    232
    seg000:002A seg000:002A public NewEntryPoint seg000:002A NewEntryPoint proc near ;
    EntryPoint seg000:002A mov si, 211h seg000:002D call sub_0_DD seg000:0030 mov si, 2BAh seg000:0033 call sub_0_DD seg000:0036 retn seg000:0036 NewEntryPoint endp
    Если флаг makecode будет установлен в единицу то IDA при необходимости формирует функцию и дизассемблирует инструкции. seg000:002A db 0BEh seg000:002B db 11h seg000:002C db 2 seg000:002D db 0E8h seg000:002E db 0ADh seg000:002F db 0 seg000:0030 db 0BEh seg000:0031 db 0BAh seg000:0032 db 2 seg000:0033 db 0E8h seg000:0034 db 0A7h seg000:0035 db 0 seg000:0036 db 0C3h
    AddEntryPoint(1,0x1002A,”MyEntryPoint”,1); seg000:002A seg000:002A public MyEntryPoint seg000:002A MyEntryPoint proc near seg000:002A mov si, 211h seg000:002D call sub_0_DD seg000:0030 mov si, 2BAh seg000:0033 call sub_0_DD seg000:0036 retn seg000:0036 MyEntryPoint endp
    Операнд
    Пояснения ordinal Ординал функции
    Ea Линейный адрес конца команды
    Name Имя точки входа
    ==makecode
    Пояснения
    ==1 Преоброзовывать undefine в инструкции makecode
    ==0 Не переобразовывать undefine в инструкции
    Завершение
    Пояснения
    Return
    1 Успешно

    233 0 Ошибка
    long GetEntryOrdinal(long index);
    Возвращает ординал точки входа по порядковому номеру из списка. Index может принимать значения от нуля до GetEntryPointQty()-1. Все точки входа (если их больше одной) хранятся в несортированном списке, расположенные в порядке их создания.
    Если запросить несуществующий индекс, то функция вернет ноль, а не ошибку
    BADADDR, что само по себе достаточно странно, потому что по нулевому линейному адресу теоретически возможно создать точку входа, хотя это случается крайне редко, поскольку для большинства файлов адрес загрузки по умолчанию лежит значительно выше и равен 0x10000
    Ординал точки входа будет необходим в дальнейшем для функций GetEntryPoint и
    RenameEntryPoint.
    Следующий пример выдаст на экран ординалы всех существующих точек входа. auto a,i; i=0; while((a=GetEntryOrdinal(i++)))
    Message("0x%X \n",a);
    0x122C0 0x1 0x2 0x3
    Операнд
    Пояснения index Индекс точки входа в списке (от нуля до GetEntryPointQty()-1)
    Завершение
    Пояснения
    !=0 Ординал точки входа
    Return
    0 Ошибка
    long GetEntryPoint(long ordinal)
    Возвращает адрес точки входа по ординалу. Если указанный ординал не существует, возвращается ошибка BADADDR.

    234
    Следующий пример выдаст на экран адреса всех существующих точек входа. auto a,i; i=0; while((a=GetEntryOrdinal(i++)))
    Message("0x%X %x \n",a, GetEntryPoint(a));
    0x122C0 122c0 0x1 122dd
    0x2 122e5 0x3 1002a
    Операнд
    Пояснения ordinal Ординал точки входа
    Завершение
    Пояснения
    !=BADADDR Адрес точки входа
    Return
    ==BADADDR Ошибка
    success RenameEntryPoint(long ordinal,char name);
    Позволяет изменить имя точки входа по ординалу. При этом предыдущее имя переносится в комментарий. Если операция завершиться неуспешно, то функция вернет неравное нулю число. Такое может, случиться, например, при попытке передать в качестве нового имени пустую строку.
    Пример использования: seg000:22C0 start proc near seg000:22C0 call My_1 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:22CF start endp
    RenameEntryPoint(0x122C0,”main”); seg000:22C0 main proc near
    ;
    start
    seg000:22C0 call My_1 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:22CF main endp

    235
    Операнд
    Пояснения ordinal Ординал точки входа name Новое имя функции
    Завершение
    Пояснения
    !=0 Успешно
    Return
    ==0 Ошибка
    СТРУКТУРЫ
    ALMA MATER
    Строго говоря, в языке процессора – машинном коде - нет ни типов данных, ни тем более структур, - все это привилегии языков высокого уровня. Процессор же оперирует с регистрами и ячейками памяти. Это самый низкий уровень в абстракции данных и приходится вручную разбираться с такими техническими деталями, как интерпретация знаковых битов или разрядностей ячеек.
    Но ассемблер это не машинный код. Это первый высокоуровневый язык, придуманный человечеством, делающий огромный шаг вперед в абстракции данных.
    Современные ассемблеры уже трудно назвать языками низкого уровня, ибо они поддерживают макросы, средства автоматизации проектирования, сложные конструкции, типотизацию данных и даже элементы объективно ориентированного программирования!
    Поддержка структур на этом фоне уже не выглядит чем-то удивительным и активно используется многими программистами. Это позволяет забыть о смещениях и оперировать одними удобочитаемыми метками.
    Рассмотрим, простой пример, - фрагмент кода, который проходить по цепочке MCB блоков памяти MS-DOS.
    CALL .FirstMCB ; Найти первый MCB gmm_while: ; Сканируем цепочку MCB
    MOV
    ES, AX ; ES = first MCB
    CMP
    Byte ptr ES:[
    0
    ],'M'; Это продолжение цепочки?
    JNZ gmm_z ; --> Конец/Обрыв цепочки gmm_next: ; // Следущий элемент цепочки
    ADD AX, ES:[
    3
    ] ; Размер блока
    INC AX
    ; заголовок MC
    JMP Short gmm_while ; --> Цикл
    Красным цветом выделены константы, которые без знания структуры MCB блока делают этот код бессмысленным. Но разве возможно удержать в голове архитектуру всех компонентов современных операционных систем с точностью до смещений?
    Разумеется, нет. Да этого и не требуется, - достаточно лишь заменить их соответствующими символьными именами, которые несложно и запомнить.
    В нашем примере можно поступить так:
    MCB struc ; (sizeof=0x10)
    IsLastBlock db ?
    ParentPSPaddr dw ?
    Size dw ?
    Name db 11 dup(?)
    MCB ends

    236
    То есть мы определили новую структуру ”MCB”, и теперь для доступа к ее членам совсем не обязательно знать их смещения, от начала структуры. Это сделает за нас ассемблер!
    Тогда исходный текст программы будет выглядеть так:
    CALL .FirstMCB ; Найти первый MCB gmm_while: ; Сканируем цепочку MCB
    MOV
    ES, AX ; ES = first MCB
    CMP
    Byte ptr ES:[
    MCB.IsLastBlock
    ],'M';
    ; Это продолжение цепочки?
    JNZ gmm_z ; --> Конец/Обрыв цепочки gmm_next: ; // Следущий элемент цепочки
    ADD AX, ES:[
    MCB.Size
    ] ; Размер блока
    INC AX
    ; заголовок MC
    JMP Short gmm_while ; --> Цикл
    Не правда ли он стал понятнее? Разумеется, то же можно сказать и о дизассемблированном листинге, - чтобы не держать все смещения в памяти и ежесекундно не заглядывать в справочник, лучше определить их как члены структуры, дав им понятные символьные имена.
    После IDA может показаться странным, что далеко не все дизассемблеры поддерживают структуры, а уж тем более, собственноручно определенные пользователем.
    В этом отношении IDA неоспоримый лидер.
    Она обладает развитой поддержкой структур, которые использует для множества целей. Именно так, например, происходит обращение к локальным переменным и аргументам функций.
    Да, это все члены скрытой от пользователя структуры, но программно (то есть на уровне скриптов) ни чем не отличающийся от остальных.
    .text:00403A80 _memset proc near
    .text:00403A80
    .text:00403A80
    .text:00403A80 arg_0 = dword ptr 4
    .text:00403A80 arg_4 = byte ptr 8
    .text:00403A80 arg_8 = dword ptr 0Ch
    Действительно, если только немного изменить синтаксис объявления локальных переменных, то он ничем не будет отличаться от структуры:
    Memset_arg struc
    Save_reg
    DD ?
    Arg_0
    DD
    ?
    Arg_4
    DD
    ?
    Arg_8
    DD
    ?
    Memset_arg ends
    Таким образом, структуры перестают быть всего лишь синтаксической конструкцией целевого ассемблера, а становятся ключевым элементом архитектуры IDA, использующиеся ее ядром для облегчения доступа ко многим сгруппированным по какому- то признаку данных.
    Внешне (то есть интерактивно) для работы со структурами достаточно всего лишь пары команд меню, поэтому создается ложное впечатление, что в поддержке структур ничего сложного нет.

    237
    Однако, на самом деле требуется около двух десяточков высокоуровневых функций, что бы обеспечить реализацию всех необходимых операций. Но прежде чем углубляться в описание каждой из них полезно получить представление об архитектуре структур в целом.
    Архитектура структур в IDA
    Итак, что есть структура с точки зрения IDA? Это, прежде всего элемент bTree, точно как сегмент или функция.
    Но в отличие от перечисленных выше, структура не связана ни с каким линейным адресом. Это самостоятельный объект, существующий вне адресного пространства дизассемблируемого файла.
    В таком случае возникает вопрос, - а как же к ней может осуществляться доступ?
    Приходится выбирать другую уникальную характеристику, которая бы отличала одну структуру от другой.
    Можно было бы использовать имя, или любую производную от него величину, но разработчик IDA выбрал другой путь. Он связал каждую структуру с 32-разрядным целым числом, то есть идентификатором (сокращенно ID), который возвращался при создании структуры.
    Грубо говоря, можно считать идентификатор аналогом дескриптора файла, с которым приходится сталкиваться в современных операционных системах. Различия между ними и в самом деле несущественны, хотя все же существуют – так, например, после закрытия файла, его дескриптор освобождается и может быть повторно присвоен вновь открытому файлу, а идентификаторы уникальны и никогда не присваиваются дважды, – даже если связанный с ними объект был разрушен.
    Однако, идентификаторы неудобны тем, что их приходится не только хранить, но и распределять между несколькими процессами. Ведь чаще всего один скрипт (IDA, пользователь) создает структуру, с которой приходится работать совсем другому скрипту.
    Точно такая проблема стояла и перед разработчикам операционной системы Zerro
    Way (более известной широким кругам как Windows NT). И вот и в этом случае выход был один – использовать помимо идентификаторов,
    1   ...   19   20   21   22   23   24   25   26   ...   39


    написать администратору сайта