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

  • АРХИТЕКТРУА ПЕРЕКРЕСТНЫХ ССЫЛОК 203В предыдущей главе говорилось, что не зависимо от типа, перекрестная ссылка состоит из двух разных частей – источника

  • Ordinary flow

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


    Скачать 2.86 Mb.
    НазваниеОписание функций встроенного языка
    Анкорobraz_mihlenia_-_dizassembler_IDA
    Дата29.09.2022
    Размер2.86 Mb.
    Формат файлаpdf
    Имя файлаobraz_mihlenia_-_dizassembler_IDA.pdf
    ТипСправочник
    #704305
    страница20 из 39
    1   ...   16   17   18   19   20   21   22   23   ...   39

    ALMA MATER
    Из предыдущей главы можно сделать вывод, что реально поддержка того, что подразумевается под термином «перекрестные ссылки» состоит как минимум из механизма отслеживания перекрестных ссылок и механизма работы с перекрестными ссылками, сохраненными в некотором внутреннем формате дизассемблера.
    Устройство и способности интеллектуального анализатора IDA тема для отдельного разговора, здесь же будет говориться исключительно о работе с уже созданными перекрестными ссылками.
    С первого взгляда возможно будет даже не понятно, о чем идет речь. Если перекрестная ссылка уже создана, то какие могут быть проблемы? На самом деле все не так просто. Перекрестная ссылка может быть создана (и при том не в единственном числе), а может быть, и нет. Как узнать это наверняка? И как получить адрес, на который перекрестная ссылка ссылается?
    Вот для этого и предусмотрено почти два десятка функций, поддерживающих работоспособность IDA. Все они ниже будут подробно рассмотрены, но сначала рассмотрим механизмы взаимодействия с перекрестными ссылками, не углубляясь в детали.
    Итак, любая перекрестная ссылка состоит из источника и приемника. В контекстной помощи IDA источник обозначается как ‘from’, а приемник – как ‘to’.
    Источником называется операнд, ссылающийся на примем ник, но не наоборот!
    Разберем в свете этого приведенный выше пример:
    1. .MODEL TINY
    2. .CODE
    3. ORG 100h
    4. Start:
    5. MOV AH,9
    ;
    6. LEA DX,s0
    ;

    7. INT 21h
    ;
    8. RET ;
    9. s0 DB "Hello, Sailor!",0Dh,0Ah,'$' ;

    10.
    END Start
    Так в строке 8 должна быть создан источник перекрестной ссылки, а в строке 11 – приемник.
    Часто вызывает путаницу, что IDA создает комментарий к перекрестной ссылке только возле приемника. Это, разумеется, правильно, потому что источник в комментариях не нуждается – в большинстве случаев очевидно на что ссылается операнд (хотя в случае инструкций подобных CALL BX этого сказать нельзя), а вот по виду приемника источник установить невозможно.
    Вот IDA и отображает его в виде комментария:

    200
    seg000:0100 public start seg000:0100 start proc near seg000:0100 mov ah, 9 seg000:
    0102 mov dx, offset aHelloSailor 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,'$'
    ; DATA
    XREF: start+2o seg000:0108 seg000 ends
    Обратите внимание, что IDA ничем не выделила строку 0x102 – создается иллюзия, что здесь ничего нет. Но на самом деле именно с этим адресом и связана перекрестная ссылка, а точнее, ее источник.
    Практически для всех манипуляций с перекрестными ссылками нужно знать пару значений – линейные адреса источника и приемника. Впрочем, есть и такие функции, что возвращают источник (источники) для указанного адреса приемника. Но об них поговорим позднее, а пока остановимся на том факте, что одновременно по одному и тому же адресу может располагаться несколько как приемников, так и источников.
    Начнем в первого, как более простого для понимания. seg000:0100 org 100h seg000:0100 assume es:nothing, ss:nothing, ds:seg000, fs:nothing, gs:nothing seg000:0100 seg000:0100 public start seg000:0100 start: ; "Hello, World!\r\n$" seg000:0100 push offset aHelloWorld seg000:0103 call Print seg000:0106 push offset aHelloSailor ; "Hello, Sailor!\r\n$" seg000:0109 call Print seg000:010C retn seg000:010D seg000:010D; _____________ S U B R O U T I N E _______________________________________ seg000:010D seg000:010D seg000:010D Print proc near ;
    CODE XREF: seg000:0103p seg000:010D ; seg000:0109p seg000:010D pop ax seg000:010E pop dx seg000:010F push ax seg000:0110 mov ah, 9 seg000:0112 int 21h ; DOS - PRINT STRING seg000:0112 ; DS:DX -> string terminated by "$" seg000:0114 retn seg000:0114 Print endp ; sp = 2 seg000:0114 seg000:0114 ; ------------------------------------------------------------------------ seg000:0115 aHelloWorld db 'Hello, World!',0Dh,0Ah,'$' ; DATA XREF: seg000:0100o seg000:0125 aHelloSailor db 'Hello, Sailor!',0Dh,0Ah,'$' ; DATA XREF: seg000:0106o seg000:0125 seg000 ends
    -0001010D: sub_0_10D
    В этом примере процедура Print вызывалась из двух точек кода, о чем свидетельствуют две перекрестные ссылки, проставленные IDA как комментарии.
    Следовательно, один приемник может иметь и более одного источника.
    Что бы изучить вызывающий эту процедуру код достаточно только подвести курсор в границы адреса, указанного в перекрестной ссылке и нажать Enter и при желании возвратиться назад по .

    201
    Так же можно переместить курсор в любое место строки 0x10D и выбрать пункт меню

    View \ Cross references. Появится окно следующего вида:
    Поскольку довольно часто встречается, что на один приемник ссылаются десятки
    (а то и больше!) различных источников, то IDA считает не рациональным отображать их в виде комментариев и показывает по умолчанию лишь две первые из них (перекрестные ссылки отсортированы по линейным адресам источников – от младших адресов, к старшим), то просматривать остальные приходится именно в таком окне.
    Как получить программно линейный адрес источника будет рассказано несколько позже, поскольку это делает по разному в зависимости от типов перекрестных ссылок.
    Сейчас же рассмотрим ту ситуацию, когда один источник имеет более одного приемника. С первого взгляда это абсурдно, но выйти за рамки непосредственных операндов, то можно вообразить себе следующую ситацию: seg000:0002 mov ds, ax seg000:0004 assume ds:seg000 seg000:0004 mov ah, 6 seg000:0006 mov di, offset off_0_25 seg000:0009 jmp short Print seg000:0009 ; --------------------------------------------------- seg000:000C seg000:000C Def_1: ; CODE XREF: start+1Bu seg000:000C ; DATA XREF: seg000:0025o seg000:000C mov dl, 31h ; '1' seg000:000E retn seg000:000F ; ---------------------------------------------------- seg000:000F seg000:000F Def_2: ; CODE XREF: start+1Bu seg000:000F ; DATA XREF: seg000:0027o seg000:000F mov dl, 32h ; '2' seg000:0011 retn seg000:0012 ; ----------------------------------------------------- seg000:0012 seg000:0012 Print: ; CODE XREF: start+9j

    202
    seg000:0012 ; start+1Fj seg000:0012 mov bx, [di] seg000:0014 add di, 2 seg000:0017 or bx, bx seg000:0019 jz loc_0_21
    seg000:001B call bx
    seg000:001D int 21h seg000:001F jmp short Print seg000:0021 ; ------------------------------------------------------ seg000:0021 seg000:0021 loc_0_21: ; CODE XREF: start+19j seg000:0021 mov ah, 4Ch seg000:0023 int 21h seg000:0023 start endp ; AL = exit code seg000:0023 seg000:0023 ; ---------------------------------------------------- seg000:0025 off_0_25 dw offset
    Def_1
    ; DATA XREF: start+6o seg000:0027 dw offset
    Def_2
    seg000:0029 dw offset def_3
    seg000:002B dw offset def_4
    seg000:002D dw 0 seg000:002F ; ----------------------------------------------------- seg000:002F seg000:002F def_3: ; CODE XREF: start+1Bu seg000:002F ; DATA XREF: seg000:0029o seg000:002F mov dl, '3' seg000:0031 retn seg000:0032 def_4: seg000:0032 seg000:0032 mov dl, '4' seg000:0034 retn
    Подобные примеры не редкость и встречаться с ними приходится довольно таки часто. Обратим внимание на следующую строку:
    seg000:001B call bx
    Она осуществляет последовательную передачу управления функциям, читаемых в цикле из списка, а, следовательно, ссылается более чем на один адрес.
    К сожалению пока IDA не умеет автоматически вычислять значение регистра BX и, следовательно, не может ни создать перекрестных ссылок, ни даже дизассемблировать вызываемые этой строкой функции. Скорее всего они будут помечены как ‘unexplored’.
    Поэтому эта часть работы ложится на плечи пользователя. Часто при этом дизассемблируют код, но забывают создать перекрестные ссылки. Что при этом получается? А то, что вернувшись к дизассемблируемому файлу спустя некоторое время, вы уже не будете помнить какой код вызывает эти функции и анализ придется начинать сначала.
    Но что бы создавать перекрестные ссылки нужно быть осведомленным в их архитектуре, чему и посвящена следующая глава.
    АРХИТЕКТРУА ПЕРЕКРЕСТНЫХ ССЫЛОК

    203
    В предыдущей главе говорилось, что не зависимо от типа, перекрестная ссылка состоит из двух разных частей – источника и приемника. Каждый из них связан с определенным линейным адресом. Причем с любым адресом может быть связано одновременно как несколько приемников, так и несколько источников.
    Другими словами с каждым линейным адресом может быть ассоциирован список источников (приемников). А, следовательно, нужно быть готовыми для работы со списками.
    Но для начала разберемся с тем, какие типы перекрестных ссылок существуют, ибо для работы с ними используются различные функции.
    В первом приближении их всего два. Это ссылки на код и ссылки на данные.
    Ссылки на код встречаются всякий раз, когда какая-то инструкция нарушает нормальное выполнение кода программы и изменяет (возможно, лишь при некоторых обстоятельствах) значение регистра – указателя команд.
    Говоря проще – такие ссылки образуют все команды условного и безусловного перехода и вызова подпрограмм, такие как JMP, CALL, JZ и тому подобные.
    С перекрестными ссылками на данные мы сталкиваемся всякий раз, когда какая-то инструкция обращается к данным, по их смещению. Например, LEA, MOV xx, offset и так далее, в том числе и DW offset MyData.
    Но есть еще и третий тип, который кардинально отличается от первых двух уже тем, что является внутренним типом перекрестных ссылок IDA и грубо говоря, пользователем знать о его существовании, а уж тем более вникать в технические детали реализации совсем необязательно.
    Однако, это помогает лучше понять работу многих команд, поэтому ниже мы его рассмотрим.
    Разумеется, речь идет о «ссылке на следующую команду» (Ordinary flow в терминологии IDA). Именно с помощью его IDA и отслеживает выполнение программы. Это перекрестная ссылка указывает на следующую команду при нормальном исполнении программы. Покажем это на следующем примере: seg000:0012 mov bx, [di] ;

    seg000:0014 add di, 2 ;


    seg000:0017 or bx, bx ;


    seg000:0019 jz loc_0_21 ;


    seg000:001B call bx ;


    seg000:001D int 21h ;


    seg000:001F jmp short Print ;

    seg000:0021 ; ------------------------------------ seg000:0021 seg000:0021 loc_0_21: seg000:0021 mov ah, 4Ch ;

    seg000:0023 int 21h ;

    seg000:0023 start endp
    Два цвета используются только лишь для облечения восприятия, – что бы было можно отличий пары приемник-источник друг от друга. Как видно, цепочка проходит сквозь все команды, пока не доходит до команды безусловного перехода. Тут она и обрывается.
    Адрес перехода можно узнать по перекрестной ссылке типа «код», которая автоматически образуется здесь. Что это дает? Возможность трассировки программы, например, для определения адреса конца функции, который заканчивается, как правило
    RET, то есть так же инструкцией безусловной передачи управления без возврата в текущую последовательность команд, в отличие от CALL, которая после выполнения подпрограммы передает управление следующей за ней команде.
    Словом, если все упростить, то нет никакого смысла выделять ссылку на следующую команду в отдельный тип. Она хорошо описывается одним лишь типом ссылки на код, поскольку текущая инструкция как бы вызывает следующую (ну во всяком случае

    204
    фактически происходит именно так – или другими словами – текущая инструкция передает, или может передавать, управление следующей). Вот в этих случаях и создается ссылка
    Ordinary flow
    “Скрытой” она объявлена по двум причинам. Первая из них очевидна – какой смысл захламнять текст лишней, никому не нужной информацией? Впрочем, IDA все же выделяет перекрестные ссылки на следующую команду. Точнее выделяет их отсутствие.
    Сплошная черта (в нашем примере в строке 0x21) как раз и говорит об том, что ссылка на следующую команду в данном месте отсутствует.
    Вторая причина кроется в оптимизации. Если все остальные ссылки хранятся в bTree, которой обеспечивает не самый быстрый доступ к данным, то ссылки на следующую команду содержаться в флагах (смотри описании виртуальной памяти), что значительно ускоряет работу с ними. А поскольку IDA очень интенсивно использует их, то выигрыш в скорости весьма существенен.
    Таким образом, Ordinary flow можно рассматривать как отдельный, самостоятельный тип ссылок, а с другой стороны, как частный случай раздновидности ссылок на код.
    Предоставленные в распоряжение пользователя функции большей частью скрывают эти различия, но и то часть команд приходится выполнять с огорками, о чем и сказано в их описании.
    Как будет показано ниже, IDA поддерживает еще и «уточняющий» тип – скажем
    Jump, call или offset. Но на самом деле это всего лишь флаг, или атрибут перекрестной ссылки и играет только информационную роль, и никакого другого влияния не оказывает.
    С другой стороны два типа перекрестных ссылок на код и данные можно рассматривать вместе, поскольку операции с ними производятся аналогично, не зависимо от типа, но разными наборами функций.
    Ссылки на следующую инструкцию при этом лучше не модифицировать без особой на то нужны, обращаясь к ним только на чтение, хотя и запись так же доступна.
    Итак, рассмотрим работу со списком, о котором мы говорили выше. Как было сказано ранее, вся архитектура IDA базируется на линейных адресах и связанных с ними объектах и элементах. Но если с каждым линейным адресом мог был связан только один комментарий каждого типа и только одно имя, то источников и приемников у каждого адреса может быть сколько угодно. Причем один и тот же адрес может быть одновременно как источником одной перекрестной ссылки, так и приемником другой.
    Например: seg000:000C jnb loc_0_17
    seg000:000E mov ah, 3Ch ; '<' seg000:0010 xor cx, cx seg000:0012 mov dx, 206h seg000:0015 int 21h seg000:0015 seg000:0015 seg000:0017 seg000:0017 loc_0_17 ;
    CODE XREF: seg00
    seg000:0017 mov ds:
    word_0_1DA
    , ax seg000:01DA*word_0_1DA dw 0 ; DATA XREF: seg000:0017w
    Поэтому необходимо говорить о двух независимых списках – приемников и источников, да еще отдельных для каждого типа ссылок – для кода и для данных.
    Возникает вопрос, – а куда входят ссылки на следующую инструкцию? Ответ – они вообще не входят в упомянутый выше список, так как физически хранятся отдельно. Но некоторые функции IDA эмулируют их присутствие в списке ссылок типа «код», однако, это приводит часто к путанице и запутывает понимание пользователя. Поэтому будем все же

    205
    считать, что инструкции на следующую команду как бы сами по себе. Это значительно упрощает понимание.
    Таким образом, перейдем к рассмотрению организации этого списка и работы с ним (поскольку не зависимо от хранимых данных, – работа со всеми списками идентична).
    Но при ближайшем рассмотрении (и залезании с позволения так сказать интимную область
    IDA) никакого списка нет, а есть только двоичное дерево, в узлах которого и хранятся перекрытые ссылки в виде (from, to). При необходимости IDA просматривает дерево и извлекает все элементы, адреса которых совпадают с запрошенным.
    Но, увы, доступа к Btree IDA не предоставляет, но дает функции, работающие с перекрестными ссылками исключительно, как с односвязным списком. То есть это Rfirst
    (получение первого элемента) и Rnext (получение всех последующих элементов).
    При этом работа идет не с индексами (которых попросту нет), а исключительно со значениями элементов списка. Таким образом, Rnext принимает линейный адрес и, просматривая двоичное дерево, выдает следующую за ним перекрестную ссылку указанного типа или –1, когда список исчерпан.
    Таким образом, Rnext(0) с первого взгляда равносильна Rfist, которая становится попросту не нужна. На самом деле все немного запутаннее. И понять это автор смог только после того, как связался с разработчиком IDA и обратился к нему за разъяснениями.
    На самом деле Rnext никогда не возвращает ссылок на следующую инструкцию.
    Они хранятся отдельно и поэтому выпадают из поля зрения Rnext. Но вот Rfist действует иначе. Она проверяет – существует ли ссылка на следующую инструкцию и если да, то возвращает в первую очередь ее. В противном случае – первый элемент списка.
    А теперь вообразим себе следующую ситуацию. Пусть у нас имеется следующий код: seg000:0000 push ax ; CODE XREF: seg000:2864p seg000:0000 ;
    Å приемник seg000:2864 call bx ;
    Æ источник seg000:2869 loc_0_286 ; CODE XREF: seg000:2864p seg000:2869 inc si ;
    Å приемник seg000:2892 loc_0_2892: ; CODE XREF: seg000:2864p seg000:2892 ;
    Å приемник seg000:2892 cmp byte ptr [si], 22h ;
    '"'
    Что будет если попытаться просмотреть список приемников в строке 0x2864? По логике Rfirs должна возвратить адрес ссылки на следующую команду, то есть 0x2869. Если теперь передать его Rnext то, по логике она должна будет возвратить следующий на ним приемник, то есть 0x2892, а приемник по адресу 0х0 окажется «вне поля зрения». Так ли это? На самом деле нет! Rnext сперва проверяет – является ли переданный ей адрес ссылкой на следующую команду, и если да, то начинает просмотр с начала списка!
    Однако, Функции xfirst0 ведут себя иначе и не выполняют этой дополнительной проверки, в остальном же они ничем другим не отличаются от своих собратьев.
    Зачем знать все эти подробности? Да просто работать программно с перекрестными ссылками придется намного чаще, чем бы этого хотелось. Дело в том, что
    IDA может отображать список приемников, как в виде комментариев, так и в окне списка
    (подробности смотри выше в главе ALMA MATER), но вот никак не отображает источники, считая что «они и так очевидны».

    206
    Но это на самом деле не так, в случае с командами по типу “CALL BX” – и возникает естественная потребность посмотреть, – а куда же передается управление. Конечно, IDA не отслеживает значение регистра BX автоматически и не создает в этом месте перекрестные ссылки, но вот человек это сделать очень даже может.
    А вот оставшуюся мелочь – перейти по требуемому адресу (или посмотреть их список) интерактивно решить, видимо, невозможно. Поэтому приходится прибегать к языку скриптов и самостоятельно просматривать список значений.
    Кстати, для облегчения навигации по файлу его можно добавить в комментарий к инструкции. Это, по-видимому будет наилучшим решением.
    Подробнее об архитектуре перекрестных ссылок рассказано в описании функций, которые приведены ниже.
    1   ...   16   17   18   19   20   21   22   23   ...   39


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