R0 CREW

Сажаем паразитов Ольге. Расширяем функционал любимого отладчика

Сажаем паразитов Ольге. Расширяем функционал любимого отладчика

Лет 7 назад, листая в остаток обеденного времени статью на тогда еще кряклабе, моя память зацепилась за такую фразу и тогда я вспомнил о том, что меня никогда не подводило. Речь тогда шла о языке C и Visual Studio в противовес выбесивших автора возможностям OllyScript. Пару лет спустя, когда меня окончательно достало пересчитывать адреса в модулях, слинкованных на стандартный базовый адрес 0x10000000 между IDA и OllyDbg, оставшиеся в живых после длительной интоксикации алкоголем нейроны забили тревогу, подталкивая к правильному ответу. Поиск информации на интересующую тему уперся в отсутствие специализированных статей, что в прочем компенсировалось наличием грамотного на первый взгляд help’а в SDK и наличием комментариев в header’е.

В статье максимально подробно (возможно, даже, чересчур) рассказывается как писать плагины для любимого большинством Ring-3 отладчика OllyDbg 1.10. Статью даже можно было назвать “Руководство полного идиота…”. Основной целью является дать необходимый минимум начальных знаний для старта самостоятельного изучения API help’а. Для этого на большом количестве примеров, поясняя чуть ли не каждую деталь, объясняются фундаментальные принципы и особенности их применения. В тексте содержится много критики в адрес плагинного интерфейса, посему слабонервным и беременным читать запрещается. Собственно, критика продукта 10-летней давности врядли является актуальной и с каждым шагом к релизу PDKv2 (в котором большинство недостатков учтено и исправлено) покрывается плесенью в подвалах истории.

[0xFFFFFFFF Когда и почему пишут плагины для отладчика]

В каких случаях необходимо расчехлять компилятор? Вопрос философский (как и камень). Безусловно найдутся фанатики, которые для простейших автоматизированных действий внутри OllyDbg выбирают плагинную систему. Спорить с такими индивидами глупо и бесполезно, поэтому просто попытаюсь определить стандартные ситуации при которых разумно писать своё расширение.

  1. Необходимо обеспечить функционал более-менее вменяемым пользовательским интерфейсом. Да, действительно, скриптовые языки уже давно обзавелись расширениями, позволяющими натянуть на код шкурку какой-либо (чаще Qt) кросс-платформенной графической библиотеки. Но гораздо заманчивее и продуктивнее выглядит возможность получить прелести системного API.
  2. Ограничения скриптового языка. Поскольку Python я не знаю, да и знать хочу только в теории, судить о том что пользователи довольны приходится по комментариям на бордах. Поддержка Perl’а, так же реализована, но мой энтузиазм утих вместе с установкой плагина (для чего пришлось шаманить с переменной среды PATH, чтобы удовлетворить зависимости) и написанием простенького Hello world. НО!!! У последних двух хотя бы поддержка ввода-вывода реализована и работает, чего никак нельзя сказать про OllyScript (да и систему команд в стиле императивного программирования сложно назвать языком). И если команды дампа DM и DMA лечатся элементарно добавлением нужного флага при открытии файла (хорошо помню как пришлось править DLL, чтобы добавить флаг std::ios_base::binary), то элементарная операция по загрузке из файла списка адресов для бряка выглядит как миссия, мать ее, невозможна. Но, несмотря на всю критику, того же OllyScript вполне хватает для рутины, вроде остановится на нужной итерации цикла, учитывая несколько условий.
  3. Доступ к неэкспортируемым API и внутренностям отладчика. Что делать, когда необходимо обратится к вызову, который программист скриптового интерфейса забыл или посчитал ненужным? Или когда нужно прочитать/записать что-либо не из/в отлаживаемого процесса, а адресного пространства отладчика? Обе опции несложно добавить самому при условии наличия исходников плагина и владея матчастью. Правда, со второй опцией надо быть осторожней - не дай бог дебилом назовут, несущим людям вместо добра дыры.
  4. О том, что производительность компилируемого кода выше, чем у интерпретируемого тоже забывать не стоит.

Для всех остальных случаев, как говорится, существует мастеркард скриптинг.

[0x00000000 То, что нужно знать перед началом]

Условимся считать, что все сказанное справедливо для PDK (Plugin Development Kit; так официально зовется SDK) 1.10, т.е. финальной версии отладчика первой ветки. Вообще, если взглянуть на сайт продукта сложно поверить в существование PDK v2; гугл внушает мысль о полулегальном распространении этого чуда на tuts4you с именами вида OllyDbg2 PDK VC6 VC9 v0.1.rar и OllyDbg2 PDK VC2010 v0.2.rar. Однако, заглядывая внутрь архивов, убеждаешься в том, что по крайней мере хидер был написан автором. Но все куда проще. Зайдя на ollydbg.de, переходим по ссылке VERSION 2.XX. Там покоится история развития проекта в виде ленты блога, где к каждому или почти каждому сообщению сверху цепляют ссылки на материалы (их можно принять за теги). Найдя что-то вроде sample plugin, можно быть уверенным, что там будет и PDK (в виде хидера и библиотеки импорта под разные компиляторы), а plugin API даже ведет на некоторые зачатки документации нового интерфейса. Такая “секретность” говорит о неготовности PDK v2 предстать в production виде, а то малое что мы получаем предназначено для портирования существующего кода и тестирования нового интерфейса.

В качестве компилятора для примеров я использую VS 2003, но процесс сборки наврядли будет сильно отличаться при использовании других версий (насчет других сред программирования не уверен и неисключено, что придется править хидер). Скрытые причины моей “некрофилии” обсуждать не будем, каждый использует то, что ему удобней.

Забегая вперед, вынужден честно и прямо предупредить - при работе у тебя не единожды возникнет очущение, что по крайней мере плагинный интерфейс для отладчика писал обдолбанный среднестатистический европейский школьник (мои извинения Олегу, но против правды…). В качестве аргументов можно привести, например, то что в callback’и плагина передается указатель на фиксированный в размерах буфер, а именование функций-хелперов говорит о том что автор бессознательно забил на стандарты оформления кода (ага, к чертям венгерскую нотацию и все эти бесовские понятия; привыкший к строгому оформлению программист по-началу будет тупо втыкать в монитор, пытаясь сообразить “что это, xxxть, за идентификатор, который не найден”).

Теперь по рекомендациям. Общение отладчика с плагином организовано так, что вынуждает использовать множество [в том числе вложенных] switch’ей. Поэтому стоит приучить себя выносить, по-возможности, код из case’ов в отдельные функции, а если религия и ситуация позволяют, то оформлять в виде классов. Поскольку debug плагина подразумевает перезапуск отладчика, внушительные объемы кода лучше писать и отлаживать в отдельном приложении с тестовыми данными, при желании используя условную компиляцию для облегчения переноса (наловчившись можно даже компилировать один и тот же файл).

[0x00000001 Займемся этим]

Первое, что нужно сделать - это создать скелет плагина, который можно будет наполнить смымслом. Качаем PDK, осматриваем внутренности. В архиве лежат необходимый хидер (Plugin.h), DEF-файл, документация по API и на плагин CMDLINE, исходники которого тут же, рядом с иходником другого плагина Bookmark и две папки с проектами (для каждого плагина) для VS 5.0 и BC 5.5.

Теперь любым удобным способом создаем проект DLL. Вытаскиваем Plugin.h из архива и прописываем его #include в исходнике (кстати, перед подключением Plugin.h необходимо прописать #include <windows.h>). Если сейчас попытаться скомпилировать проект - процесс прервется с ошибкой в результате вот такого заклинания:

  #ifndef _CHAR_UNSIGNED               // Verify that character is unsigned
    #error Please set default char type to unsigned (option /J)
  #endif

Поэтому в свойствах проекта идем по адресу Configuration Properties=>C/C++=>Language и указываем Default Char Unsigned в Yes (/J). После этого DLL с плагином должна быть собрана без вопросов.

Для того, чтобы Olly опознала в твоей длл плагин, необходимо экспортировать 2 callback’а (все остальные колбеки не являются обязательными):

extc int _export cdecl ODBG_Plugindata(char shortname[32]) {
 ...
}

extc int _export cdecl ODBG_Plugininit(int ollydbgversion, HWND hw, ulong *features) {
...
}

Но это еще не все. При первой же попытке обратится к любой из ф-ций хелперов (ulong thid = Getcputhreadid();, например) символ не будет найден. Попытка подсунуть ollydbg.lib из директории Vc50 PDK приводит к такому же результату. Ок. Люди все грамотные. Запускаем консоль Visual Studio и собираем свою библиотеку C:\Src\OllyDbg>lib /MACHINE:X86 /DEF:ollydbg.def. Но и этот путь обламывает простого крепостного. Присмотревшись к внутренностям DEF файла, замечаем, по всей видимости, лишнии префиксы у каждого импортируемого имени. Убираем как получится - можно скрипт набросать, лично я воспользовался опцией multi-line editing (Shift+Alt/Up, Shift+Alt/Down) в Notepad++. Пробуем снова собрать библиотеку и вуаля!

Осталось объяснить назначение колбэков.

extc int _export cdecl ODBG_Plugindata(char shortname[32]) ;

Вызывая ODBG_Plugindata (вызывается первой) Olly надеется получить внутреннее имя плагина в shortname и версию отладчика для которой он скомпилирован в качестве результата. Имя, которое ты поместишь в shortname будет гарцевать в подменю Plugins главного меню отладчика. Типичное использование такое:

extc int _export cdecl ODBG_Plugindata(char shortname[32]) {
  strcpy(shortname,"r4ue1");
  return PLUGIN_VERSION;
}

extc int _export cdecl ODBG_Plugininit(int ollydbgversion, HWND hw, ulong *);

Вызывается вторым. Olly ожидает, что в этом callback’е осуществляется первоначальная настройка внутренних переменных плагина. Параметр ollydbgversion указывает версию отладчика (именно отладчика, а не то, что ты вернул из ODBG_Plugindata). hw - хэндл главного окна отладчика. Документация рекомендует его сохранять, но за все время работы с PDK мне это реально понадобилось только 1 раз. Если все хорошо и плагин может работать - возвращаем 0, иначе -1. Ну и как используют:

extc int _export cdecl ODBG_Plugininit(int ollydbgversion, HWND hw, ulong *) {
    if (ollydbgversion < PLUGIN_VERSION)
        return -1;
// тут у нас инициализация
    return 0;
}

Отлично, теперь все есть, чтобы написать первый плагин.

[0x00000002 Приступаем к развлечениям]

Попробуем нацепить на наш скелет какой-нибудь легковесный функционал. Работа с регистрами подойдет идеально. Не сказать, что часто, но бывает, что возникают ситуации, когда трассируя код нужно “вернуться назад” и более “внимательно” (кто часами что-то отлаживал тот поймет) проследить что именно происходит в коде. Перезапускать отладку или ставить бряк и дожидаться чтобы IP снова пришел в тоже место иногда лень. Поэтому для себя я давно написал соответствующий функционал, а объем кода затраченного на это такой, что данный пример является идеальным “Hello world” среди плагинов. (У OllyDbg есть опция “New origin here”, но она меня бесит своими подозрительными warning’ами). Сначала теория.

Процесс взаимодействия между ядром отладчика и плагином при участии пользователя традиционно происходит с помощью расширения меню. Перед каждым отображением меню Olly вызывает callback ODBG_Pluginmenu (если он экспортирован, конечно) и добавляет соответствующие пункты, которые попросил добавить плагин. Исключение составляет только главное меню (подменю Plugins), запрос пунктов для него вызывается только 1 раз после успешной загрузки плагина. Кстати, именно поэтому плагин OllyScript добавляет последние выполненные скрипты к истории только после перезапуска отладчика.

extc int _export cdecl ODBG_Pluginmenu(int origin, char data[4096], void *item);

int origin - соответствует одной из перечисленных констант и указывает для какого именно окна отладчика требуется получить пункты.

#define PM_MAIN        0               // главное меню
#define PM_DUMP        10              // любое окно дампа
#define PM_MODULES     11              // окно "Executable modules"
#define PM_MEMORY      12              // окно "Memory dump"
#define PM_THREADS     13              // окно со списком потоков
#define PM_BREAKPOINTS 14              // окно со списком soft бряков
#define PM_REFERENCES  15              // окно со списком ссылок
#define PM_RTRACE      16              // окно Run trace
#define PM_WATCHES     17              // окно Watches
#define PM_WINDOWS     18              // список окон
#define PM_DISASM      31              // окно дизассемблера
#define PM_CPUDUMP     32              // окно дампа (под окном дизассемблера)
#define PM_CPUSTACK    33              // окно стека
#define PM_CPUREGS     34              // окно регистров

void* item - соответствует данным в конкретном окне. Для PM_MAIN всегда NULL. Например, в окне дизассемблера item необходимо приводить к t_dump*. Полное соответствие констант и типов к которым необходимо приводить item, а так же описание всех почти всех типов смотри в Plugins.hlp.

char data[4096] - сюда необходимо записать строку определенной структуры, которую движок отладчика в последствии разберет и добавит в меню определенные в этой строке пункты. Синтаксис достаточно простой. Сначала идет числовой идентификатор пункта - с его помощью различают что именно выбрал пользователь. Идентификатор должен принимать значения от 0 до 63 включительно. Идентификатор может быть уникальным в пределах плагина (т.е., конечно, чаще используют разные идентификаторы, но если ты желаешь сделать “Включить/Выключить” с одним и тем же идентификатором, причем самостоятельно выясняя текущее состояние - никто тебе этого не запретит, разве что психиатры). Сразу за идентификатором следует текст пункта (идентификатор Olly вырезает из текста):

0Enable

Несколько пунктов определяются через запятую:

0Start,1Stop,2rm-rf /

Подпункты можно указать спомощью фигурных скобок (причем для главного меню никаких скобок не нужно - все пункты, прописанные через запятую, станут подпунктами твоего плагина):

Actions{0Start,1Stop,2rm-rf /}

Сепаратор можно получить двумя способами:

Actions{0Start|,1Stop,2rm-rf /}

и

#Actions{0Start,#1Stop,2rm-rf /}

В первом случае разделитель появится между пунктами Start и Stop, во втором - перед Actions и перед Stop.

Теперь о неприятном. Я не нашел способа передать вертикальную черту в тексте пункта ("|" не желает вылезать в стандартных комбинациях “|” и “||”, которые используют в таких случаях; будь осторожен - использование этого символа в столь невинном, по-твоему мнению, месте может стать причиной нервного срыва, поскольку если “|” не находится между пунктами - меню вообще не появится). То же самое можно утверждать и про символ “,”. Указанный размер (char data[4096]) даже близко не соответствует заявленному. У меня буфер кончился примерно на 2500 символах. Более того, во время того же самого эксперимента выяснилось, что движок парсера явно писался с бодуна. Добавим конкретики для понимания. Мне однажды понадобилось визуально представить одну структуру данных со множеством вложенных полей (других структур и объединений). Причем желательно как с перемещением относительно начала этой структуры заданной в окне CPU Dump к началу поля, так и с возможностью выделения поля целиком без перемещения курсора. Т.е. масштаб проблемы понять можно. После нескольких часов нервозных предположений по типу “а что если…” пришлось признать очевидное - дело не во мне. Поэтому не советую использовать этот инструмент коммуникаций для создания сложных многократно вложенных подменю.

Второе слагаемое в этом процессе - ODBG_Pluginaction. Этот callback вызывается отладчиком при выборе пользователем пункта, определенного плагином.

extc void _export cdecl ODBG_Pluginaction(int origin, int action, void *item);

Параметры origin и item имеют такое же назначение как и у ODBG_Pluginmenu, а int action тот самый идентификатор.

На данный момент мы уже знаем как взаимодействовать с пользователем, поэтому можно приступить к решению озвученного примера. Последовательность действий, при которой нужно указать в окне дизассемблера адрес команды и выполнить действие выглядит логичной. Так и сделаем.

#define CMD_gotohere 0
...
extc int _export cdecl ODBG_Pluginmenu(int origin, char data[4096], void *item)
{
    switch(origin)
    {
        case PM_DISASM:

            if(Getstatus() != STAT_STOPPED)
                return 0;

            sprintf(data,"r4ue1{#%dGoto here}",CMD_gotohere);
            return 1;

        break;

        default:
            return 0;
    }
}

Функция Getstatus выглядит так:

t_status Getstatus(void);

// состояния отлаживаемого процесса
typedef enum t_status {
  STAT_NONE=0,                         // Ничего в данный момент не отлаживается
  STAT_STOPPED,                        // Поток остановлен для отладки
  STAT_EVENT,                          // Процесс остановлен и обрабатывается debug event
  STAT_RUNNING,                        // Процесс выполняется
  STAT_FINISHED,                       // Отладка процесса завершена
  STAT_CLOSING                         // Производится завершение отладки процесса
} t_status;

Т.к. регистр IP привязан к контексту потока, а поток должен быть остановлен для отладки как раз и производится вызов Getstatus, чтобы отсечь недоразумения. А теперь реакция на событие:

extc void _export cdecl ODBG_Pluginaction(int origin, int action, void* item)
{
    t_dump *dump;
    t_thread* thread;

    switch(origin)
    {
        case PM_DISASM:

            switch(action)
            {
                case CMD_gotohere:

                    if(Getstatus() != STAT_STOPPED)
                        return;

                    if(item == NULL)
                        return;

                    dump = (t_dump*)item; // кастуем к нужному типу

                    thread = Findthread(Getcputhreadid()); // получаем поток

                    if(thread == NULL)
                        return;

                    thread->reg.ip = dump->sel0; // изменяем регистр IP

                break;
            }

        break;
    }
}

Все просто… Прототипы двух новых функций, думаю, в пояснениях не нуждаются:

t_thread* Findthread(ulong threadid);
ulong Getcputhreadid(void);

По поводу t_thread, t_dump и t_reg лучше прочитать в хелпе, скажу только, что:

typedef struct t_dump {
...
  ulong      sel0;              // здесь адрес начала выделения
  ulong      sel1;              // а здесь адрес первого байта, которое выделение НЕ включает
  ...
} t_dump;

Важно помнить, что sel1-sel0 = кол-ву выделенных байт.

Наконец, обсудим другие callback’и, которые могут тебе понадобится, но которые я не планирую баловать отдельным вниманием.

extc void _export cdecl ODBG_Pluginmainloop(DEBUG_EVENT* dbgevt);

Документация клянется, что эта функция вызывается при каждом проходе основного цикла. Правда, что это за цикл не уточняется, хоть и можно предположить по параметру, что это цикл выборки отладочных сообщений. Ясно одно - использовать нужно редко и не пытаться в этом вызове высчитывать число PI до миллионного знака. На какую-либо периодичность тоже рассчитывать не приходится и не стоит заблуждаться и принимать этот вызов как примитивный таймер. Значение параметра разъяснит Microsoft).

extc int _export cdecl ODBG_Pluginshortcut(int origin, int ctrl, int alt, int shift, int key, void* item);

Вызывается каждый раз, когда пользователь нажимает комбинацию клавиш, которую Olly не смог распознать как свою. Причем обычно делается сразу 2 вызова: один “глобальный” с origin = PM_MAIN, второй относительно текущего окна владеющего фокусом с соответствующим origin. Параметры ctrl, alt, shift (не уверен, но по всей видимости=) указывают состояния соответствующих клавиш. key - виртуальный код клавиши (VK_F1 или обычная ‘h’). Ну а item ты уже должен уметь кастовать к нужному типу. Если плагин опознает комбинацию как свою - возвращает 1, иначе 0.

Теперь особенности. Если Getstatus() == STAT_NONE, то никаких хоткеев не вызывается (даже PM_MAIN). Алгоритм вызова такой: сначала опрашивается plugin1 с PM_MAIN, если комбинация не опознана - опрашиваем plugin2 с PM_MAIN, если никто не опознал - делаем тоже самое только с PM_XXX и валидным item. Комбинации Ctrl+Alt считаются невалидными, т.е. Olly тупо подавляет alt, делая вид что нажатия на нее не существует. Можно комбинировать Ctr+Shift или Alt+Shift, но если будет Ctrl+Alt+Shift на входе ты получишь ctrl=1, alt=0, shift=1.

Немного критики. Автор прямым текстом угрожает в документации, что будет добавлять новые хоткеи Ольге и его не волнует что ты из себя представляешь. Поэтому звучит совет предоставлять альтернативный доступ к функционалу. Подход кто первый встал - того и тапки никому и никогда не нравился. Это как отрицать теорию дарвинизма - выживает не сильнейший, а тот кто первым взял в руки палку. Более удачным архитектурным решением стал бы менеджер хоткеев, которым управляет ядро отладчика, принимает запросы на комбинации от плагинов и разрешает конфликты, предоставляя возможность пользователю самому ввести удобный shortcut.

extc void _export cdecl ODBG_Pluginreset(void);

Вызывается каждый раз, когда Olly хочет, чтобы плагин сбросил внутренние переменные. Ситуаций всего несколько: запуск отлаживаемого приложения, после завершения отладки приложения и при аттаче к процессу. Что интересно, даже если, ничего не отлаживая, нажать Alt+F2 то вызов все равно будет.

extc int _export cdecl ODBG_Pluginclose(void);

Вызывается, когда пользователь закрывает отладчик. Забавно,что в документации возвращаемое значение указано как void, вместо int, но тут же дописано, что если по каким-либо причинам необходимо отменить процесс закрытия - можно вернуть любое не нулевое значение. Данный callback настоятельно рекомендуют для сохранения настроек в ini (о настройках поговорим позже отдельно). Необходимо отметить, что окна созданные плагином на данный момент еще существуют.

extc int _export cdecl ODBG_Plugindestroy(void);

Вызывается один раз перед завершением работы отладчика. Все окна, созданные плагином уже уничтожены. Это лучший момент для освобождения ресурсов, захваченных плагином для работы во время всей сессии отладки.

[0x00000003 Игры в сторону]

Ранее был проведен увеселительный экскурс в азы плагиностроения для Ольги. Теперь же пора приступать к более серьезным вещам, которые не спасут жизни, зато могут помочь сберечь по-крайней мере чье-то время. Помнишь пример из введения про пересчет адресов? Его то сейчас и реализуем, тем более, что при определенном везении он и сам мог претендовать на звание самого простого “Hello world”.

extc void _export cdecl ODBG_Pluginaction(int origin, int action, void* item)
{
    t_dump *dump;
    t_module* module = NULL;
    char buf[TEXTLEN];
    ulong rva;

    switch(origin)
    {
        case PM_DISASM:
        case PM_CPUDUMP:

            switch(action)
            {
                case CMD_origaddr:

                    if(Getstatus() == STAT_NONE)
                        return;

                    if(item == NULL)
                        return;

                    dump = (t_dump*)item; // кастуем к нужному типу

                    module = Findmodule(dump->sel0); // получаем модуль

                    if(module == NULL)
                        return;

                    /*
                    вычисляем адрес, учитывая ImageBase из PE заголовка
                    и помещаем его в буфер
                    */
                    rva = dump->sel0 - module->base;
                    sprintf(buf, "0x%08X", module->fixupbase + rva);
                    /*
                    выводим адрес в окне, чтобы его можно было скопировать
                    */
                    Gettext("Original address:", buf, 0, NM_NONAME, FIXEDFONT);

                break;
            }

        break;
    }
}

Код ODBG_Pluginmenu почти совпадает с кодом из первого примера, поэтому смотрим пример или усердно додумываем. Самое главное здесь - структура t_module, которая содержит много полезной информации включающей данные из PE-заголовка и рабочие переменные отладчика привязанные к конкретному PE-файлу (например, таблица строк в модуле или информация анализатора). Полное описание приводить не стану. base - это фактический ImageBase, а fixupbase - ImageBase из заголовка.

t_module* Findmodule(ulong addr);

Findmodule принимает адрес, который должен относится к модулю и возвращает NULL, если адреса не существует или он не относится к модулю.

int Gettext(char *title, char *text, char letter, int type, int fontindex);

Отображает небольшое диалоговое окно для запроса у пользователя строки. Как это часто бывает, default string можно указать, чем и можно воспользоваться, чтобы дать пользователю скопировать выведенный адрес.

  • char* title - заголовок окна
  • char* text - буфер размером TEXTLEN(256 байт)
  • char letter - первый символ, т.е. когда ф-ция действительно используется для ввода и запрос диалога происходит при реакции на нажатие пользователя (как в окне дизассемблера при начале редактирования выделенной команды)
  • int type - тип имени, можно пока не заморачиваться и писать NM_NONAME; позднее про имена я еще расскажу
  • int fontindex - индекс шрифта отладчика; рекомендуют или использовать константу FIXEDFONT, или (если Plugingetvalue(VAL_WINDOWFONT) != 0) индекс шрифта родительского окна (надо полагать индекс из t_table)

У функции Gettext есть и расширенный вариант, позволяющий задать положение окна диалога:

int Gettextxy(char *title, char *text, char letter, int type, int fontindex, int x, int y);

Дальше пишем небольшой скрипт на IDA C и наслаждаемся прозрачной трансляцией адресов между OllyDbg и IDA:

#include <idc.idc>

extern newbase;
extern oldbase;

/*
осуществляем переход по адресу с фактическим (из отладчика) image base
*/
static M_gotoaddr()
{
    auto ea;

    // запрашиваем фактический image base
    if(newbase == 0)
    {
        newbase = AskAddr(newbase, "Input new base");

        if(newbase == BADADDR)
        {
            newbase = 0;
            return;
        }
    }

    // запрашиваем адрес для перехода
    ea = AskAddr(newbase, "Input addr");

    if(ea != BADADDR)
    {
        // считаем и переходим
        ea = ea - newbase;
        ea = ea + oldbase;
        Jump(ea);
    }

}
/*
выводим адрес, который можно использовать в отладчике
*/
static M_getaddr()
{
    auto ea;

    if(newbase == 0)
    {
        newbase = AskAddr(newbase, "Input new base");

        if(newbase == BADADDR)
        {
            newbase = 0;
            return;
        }
    }

    // считаем фактический адрес и выводим для копирования
    ea = ScreenEA();
    ea = ea - oldbase;
    ea = ea + newbase;
    AskAddr(ea, "addr");
}
/*
модуль загружен по новому image base
*/
static M_clearbase()
{
    newbase = 0;
}

static main()
{
    newbase = 0;
    oldbase = 0x10000000; // image base pe файла
    AddHotkey("Ctrl+Shift+A", "M_getaddr");
    AddHotkey("Ctrl+Shift+C", "M_clearbase");
    AddHotkey("Ctrl+Shift+G", "M_gotoaddr");
}

Открою секрет. Я отлично знаю про Edit->Segments->Rebase program…, но этот вариант мне не подходит.

Рассмотрим еще один небольшой пример, который я писал для себя. Суть в том, что в один прекрасный момент (как обычно что-то отлаживая) в голову пришла мысль “как же напрягает выяснять какому блоку памяти соответствует адрес в CPU Dump”, причем тут же возникло чувство многократного дежавю.

typedef struct t_memory {
  ulong          base;                 // базовый адрес блока
  ulong          size;                 // размер блока
  ulong          type;                 // тип и служебная информация (`TY_XXX`)
  ulong          owner;                // базовый адрес модуля, которому принадлежит блок
  ulong          initaccess;           // начальный read/write доступ
  ulong          access;               // actual status and read/write access
  ulong          threadid;             // какому потоку принадлежит блок или 0
  char           sect[SHORTLEN];       // имя секции модуля
  uchar          *copy;                // copy used in CPU window or NULL
  ulong          reserved[8];          // reserved for plugin compatibility
} t_memory;

// Memory-specific types:
#define TY_DEFHEAP     0x00020000      // Contains default heap
#define TY_HEAP        0x00040000      // Contains non-default heap
#define TY_SFX         0x00080000      // Contains self-extractor
#define TY_CODE        0x00100000      // Contains image of code section
#define TY_DATA        0x00200000      // Contains image of data section
#define TY_IMPDATA     0x00400000      // Memory block includes import data
#define TY_EXPDATA     0x00800000      // Memory block includes export data
#define TY_RSRC        0x01000000      // Memory block includes resources
#define TY_RELOC       0x02000000      // Memory block includes relocation data
#define TY_STACK       0x04000000      // Contains stack of some thread
#define TY_THREAD      0x08000000      // Contains data block of some thread
#define TY_HEADER      0x10000000      // COFF header
#define TY_ANYMEM      0x1FFE0000      // Any of the memory flags above
#define TY_GUARDED     0x20000000      // NT only: guarded memory block

void display_memory_block(t_dump* dump)
{
    t_table* table;
    t_memory *memory;
    ulong index;
    ulong addr;
    bool bFound;

    addr = dump->sel0;

    // тянемся к данным
    table = (t_table*)Plugingetvalue(VAL_MEMORY);

    if(table != NULL)
    {
        index = 0;
        bFound = false;

        // массив t_memory
        memory = (t_memory*)table->data.data;

        for(int i = 0; i < table->data.n; i++)
        {
            // если это наш адрес
            if( (addr >= memory->base) && (addr < memory->base + memory->size) )
            {
                index = i;
                bFound = true;
                break;
            }

            memory++;
        }

        if(bFound)
        {
            // получаем "отображаемый" индекс
            index = table->data.index[index];
            // и устанавливаем выделение
            Selectandscroll(table, index, 1);

            // открываем окно Memory Map
            PostMessage((HWND)Plugingetvalue(VAL_HWCLIENT), WM_MDIACTIVATE, (WPARAM)table->hw, 0);
        }
    }
}
...
extc void _export cdecl ODBG_Pluginaction(int origin, int action, void* item)
{
    t_dump* dump;

    switch(origin)
    {
        case PM_CPUDUMP:

            switch(action)
            {
                case CMD_findblock:

                    if(Getstatus() == STAT_NONE)
                        return;

                    if(item == NULL)
                        return;

                    dump = (t_dump*)item; // кастуем к нужному типу
                    /*
                    выполняем поиск блока, устанавливаем указатель на соответствующую
                    запись и открываем окно Memory Map
                    */
                    display_memory_block(dump);

                break;
            }

        break;
    }
}

t_table - это такая небольшая полуофициальная норка в мир самых вкусных данных, а по совместительству еще и описатель табличного окна. Вопрос окон мы еще затронем отдельно, пока стоит просто пояснить очевидное. Содержимое окна Memory Map имеет тип t_memory. До содержимого можно добраться через мембер data структуры t_sorted, которая сама в свою очередь присутствует в виде мембера data структуры t_table. Непосредственно данные необязательно хранятся в том же порядке, в котором отображаются, поэтому необходимо конвертировать индекс, который нашли перебором. Для этого берем найденный индекс и через массив индексов t_sorted.index получаем “отображаемый” индекс. Его то и надо использовать для выделения нужной записи в функции Selectandscroll.

int Plugingetvalue(int type);

Plugingetvalue - еще один источник бесценной информации самого разного типа. Констант VAL_XXX без малого 6 десятков, так что help в зубы и смотреть описание функции. Об этом прямо не сказано в документации, но принято всегда проверять (вдруг подвох) возвращаемые функцией значения. Например, получение HINSTANCE отладчика через VAL_HINST всегда проверяют на NULL.

void Selectandscroll(t_table *pt, int index, int mode);

Функция просто устанавливает выделение на нужной записи в табличном окне. Параметр int mode указывает положение scrollbar’а: 0 - выбранная запись будет установлена сверху, 1 - посередине, 2 - на усмотрение отладчика.

Чтобы не возникало вредных иллюзий - Selectandscroll не открывает указанное окно. Необходимо сделать это самостоятельно. Olly предоставляет функции для создания или активации уже созданных некоторых своих окон, но не каждое удостоилось такой привилегии и список скуден: watch,trace,thread,patch и windows. Поэтому немного Win API не помешает. Любители латекса, анальных шариков и страпона могут вместо посылки сообщения использовать такой код (эмуляция Alt+M):

keybd_event(VK_MENU, 0, 0 ,0);
keybd_event('M', 0, 0 ,0);
keybd_event('M', 0, KEYEVENTF_KEYUP,0);
keybd_event(VK_MENU, 0, KEYEVENTF_KEYUP,0);

[0x00000004 Наши звонкие смешные имена]

В терминологии Olly именем является любая ASCIIZ строка длиною до TEXTLEN, с которой связаны адрес и тип. То есть, у нас есть уникальное 32-битное значение адрес к которому, через уникальное 8-битное значение тип, справа (включаем воображение) можно прицепить строку. На самом деле важно не то, чем является имя, а то как оно используется. Внушительная группа типов позволяет заменить в окне дизассемблера мрачный бездушный адрес в командах вроде call 401000, на call library_func. Не меньше и комментариев, которые могут появится как по прямому указанию пользователя, так и автоматически после работы анализатора. Есть имена, которые фигурируют только применительно к окну диалога (переход по адресу - Ctrl+G, причем для дизассемблера и CPU Dump типы разные, поиск ASM команды, редактрование ASM команды, окно добавления watch expression и т.п.). Наибольший интерес представляют NM_BREAK, NM_BREAKEXPR, NM_BREAKEXPL и NM_PLUGCMD. Но о них позже.

Имена сохраняются в UDD файлах. Те имена, которые относятся к конкретному адресу покоятся в соответствующем модулю UDD файле. Вот их список:

NM_LABEL    User-defined label
NM_EXPORT    Exported (global) name
NM_IMPORT    Imported name
NM_LIBRARY    Name extracted from library, object file or debug data
NM_CONST    User-defined constant (currently not implemented)
NM_COMMENT    User-defined comment
NM_LIBCOMM    Automatically generated comment from library or object file
NM_BREAK    Condition related with breakpoint
NM_ARG    Arguments decoded by analyser
NM_ANALYSE    Comment added by analyser
NM_BREAKEXPR    Expression related with breakpoint
NM_BREAKEXPL    Explanation related with breakpoint
NM_ASSUME    Assume function with known arguments
NM_STRUCT    Code structure decoded by analyzer
NM_CASE    Case description decoded by analyzer
NM_PLUGCMD    Plugin commands to execute at breakpoint

Имена, которые нельзя отнести к какому-то адресу хранятся в UDD файле соответствующем главному модулю:

NM_INSPECT    Several last entered inspect expressions
NM_WATCH    Watch expressions
NM_ASM    Several last entered assembled strings
NM_FINDASM    Several last entered assembler search strings
NM_LASTWATCH    Several last entered watch expressions
NM_SOURCE    Several last entered source search strings
NM_REFTXT    Several last entered reference text search strings
NM_GOTO    Several last expressions to follow in Disassembler
NM_GOTODUMP    Several last expressions to follow in Dump
NM_TRPAUSE    Several last expresions to pause run trace
NM_LABEL|NMHISTORY    Several last entered user-defined labels
NM_COMMENT|NMHISTORY    Several last entered user-defined comments
NM_BREAK|NMHISTORY    Several last entered breakpoint conditions
NM_BREAKEXPR|NMHISTORY    Several last entered breakpoint expressions
NM_BREAKEXPL|NMHISTORY    Several last entered breakpoint explanations

NMHISTORY - специальный флаг, позволяющий добавить имя к соответствующей истории. То есть, если добавить имя через Insertname с флагами NM_LABEL|NMHISTORY, то при добавлении label (команда “:”) новое имя будет в списке истории. Необходимо уточнить, что использование констант

NM_INSPECT,NM_WATCH,NM_ASM,NM_FINDASM,NM_LASTWATCH,
NM_SOURCE,NM_REFTXT,NM_GOTO,NM_GOTODUMP,NM_TRPAUSE

не требует указания флага NMHISTORY.

Есть еще 2 предопределенных типа:

#define NM_NONAME      0x00            // Undefined name
#define NM_ANYNAME     0xFF            // Name of any type

Но ими реально мало пользуются, разве что проверяют возвращаемое функцией Findlabel[byname] значение на NM_NONAME, да разрешают удаление любых имен через Deletenamerange.

Для добавления имени существует два способа.

int Insertname(ulong addr, int type, char *name);
  • ulong addr - адрес с которым связывается имя.
  • int type - одна из констант NM_XXX.
  • char *name - ASCIIZ строка длиной до TEXTLEN.
int Quickinsertname(ulong addr, int type, char *name);

Параметры и значения совпадают у обоих функций. Разница в том, что после выполнения Insertname имя доступно сразу, а в случае нескольких вызовов Quickinsertname (пакетное добавление имен) имена будут доступны только после вызова Mergequicknames. Т.е. второй способ чем-то похож на транзакции в базах данных.

void Mergequicknames(void);

Существует способ и откатить добавленные через Quickinsertname с последнего вызова Mergequicknames имена, если что-то пошло не так.

void Discardquicknames(void);

Отвязать имя от адреса (удалить) есть только один способ.

void Deletenamerange(ulong addr0,ulong addr1,int type);

ulong addr0, ulong addr1 - определяют range в котором удаляются имена, соответствующего типа. При этом addr1 ведет себя так же как t_dump->sel1, т.е. это адрес байта на котором удаление производится уже не будет. int type - тип удаляемых имен. Может быть NM_ANYNAME, тогда имен на указанном пространстве не останется.

Еще одной востребованной возможностью при работе с именами является их поиск. Для этой задачи существует 2-3 функции + несколько функций суррогатов.

int Findname(ulong addr, int type, char *name);
ulong Findnextname(char *name);
int Decodename(ulong addr, int type, char *name);

Findname производит поиск имени по адресу и типу. Увы, но NM_ANYNAME тут не прокатит. Возвращает длину строки имени или 0, если ничего не найдено. Значения параметров должны быть понятны. Findnextname продолжает поиск имени, который был инициирован с помощью Findname. Из параметров только указатель на буфер. Возвращает адрес по которому прячется очередное имя или 0, если больше нет имен заданного в Findname типа. Судя по прототипам можно уверенно предположить, что ни о каком thread-safety мечтать не приходится. Функцией Decodename ты скорей всего и пользоваться никогда не будешь. Она работает как и Findname, НО если найдет в строке имени <XXXXXXXX>, где XXXXXXXX является HEX числом, будет произведена замена всей конструкции на сумму этого числа и ImageBase модуля, которому принадлежит адрес ulong addr.

Суррогаты:

int Findlabel(ulong addr, char *name);
int Findsymbolicname(ulong addr, char *fname);

ищут имя с предопределенными типами, а

int Findlabelbyname(char *name, ulong *addr, ulong addr0, ulong addr1);
uong Findimportbyname(char *name, ulong addr0, ulong addr1);

пытаются отыскать адрес не только фиксированного типа, но и с указанным именем.

#define CMD_checknames 0
#define CMD_insertcomment 1
#define CMD_insertpackcomments 2
#define CMD_packdiscard 3
#define CMD_deleteallnamesonrange 4
#define CMD_decodenameexample 5

#define CKNM(nm) if(Findname(dump->sel0, nm, buf) != 0) \
    { \
        MessageBoxA(NULL, buf, #nm, MB_OK); \
    }

extc void _export cdecl ODBG_Pluginaction(int origin, int action, void* item)
{
    t_dump* dump;
    char buf[TEXTLEN];
    char buf2[TEXTLEN];

    if(
        (origin == PM_DISASM  || origin == PM_CPUDUMP) 
        && 
        (item == NULL || Getstatus() == STAT_NONE)
        )
        return;

    dump = (t_dump*)item; // кастуем к нужному типу

    switch(origin)
    {
        case PM_DISASM:
        case PM_CPUDUMP:

            switch(action)
            {
                case CMD_checknames:

                    CKNM(NM_LABEL);
                    CKNM(NM_EXPORT);
                    ...
                    CKNM(NM_GOTODUMP);
                    CKNM(NM_TRPAUSE);



                break;

                case CMD_insertcomment:

                    // готовим буфер и просим ввести текст
                    memset(buf, 0, TEXTLEN);
                    Gettext("watch", buf, 0, NM_COMMENT|NMHISTORY,FIXEDFONT);

                    // добавляем имя
                    Insertname(dump->sel0, NM_COMMENT, buf);

                    // говорим, что интрефейс отладчика нуждается в перерисовке
                    Broadcast(WM_USER_CHALL, 0, 0);

                break;

                case CMD_insertpackcomments:

                    // готовим буфер и просим ввести текст
                    memset(buf, 0, TEXTLEN);
                    Gettext("watch", buf, 0, NM_COMMENT|NMHISTORY, FIXEDFONT);

                    // пакетное добавление
                    for(ulong index = 1,i = dump->sel0, j = dump->sel0+10; i < j; i++,index++)
                    {
                        _snprintf(buf2, TEXTLEN, "%s #%d", buf, index);
                        Quickinsertname(i, NM_COMMENT, buf2);
                    }

                    // COMMIT
                    Mergequicknames();
                    Broadcast(WM_USER_CHALL, 0, 0);

                break;

                case CMD_packdiscard:

                    // готовим буфер и просим ввести текст
                    memset(buf, 0, TEXTLEN);
                    Gettext("watch", buf, 0, NM_COMMENT|NMHISTORY, FIXEDFONT);

                    // то же, что и выше
                    ...

                    // ROLLBACK
                    Discardquicknames();
                    Broadcast(WM_USER_CHALL, 0, 0);

                break;

                case CMD_deleteallnamesonrange:

                    // удаляем все имена в указанном пространстве
                    Deletenamerange(dump->sel0, dump->sel1, NM_ANYNAME);
                    Broadcast(WM_USER_CHALL, 0, 0);

                break;

                case CMD_decodenameexample:

                    // устанвливаем имя для демонстрации
                    memset(buf, 0, TEXTLEN);
                    strcpy(buf, "test <+DEADBEEF>");

                    Insertname(dump->sel0, NM_COMMENT, buf);

                    // чтобы все было по-честному
                    memset(buf, 0, TEXTLEN);

                    // а как же работает эта функция?
                    if(Decodename(dump->sel0, NM_COMMENT, buf) != 0)
                    // если базовый адрес модуля,которому принадлежит адрес == 0x400000
                    // то MessageBox выведет "test DEEDBEEF"(0XDEEDBEEF - 0xDEADBEEF == 0x400000)
                        MessageBox(NULL, buf, "", MB_OK);

                    // чистим
                    Deletenamerange(dump->sel0, dump->sel0+1, NM_COMMENT);

                    Broadcast(WM_USER_CHALL, 0, 0);

                break;
            }

        break;
    }
}

[0x00000005 Такие разные breakpoint’ы]

Работа с бряками поддерживается в полном объеме. Ну, почти… На вскидку не хватает доступа к hardware breakpoint’s, т.е. показать пользователю окно можно, а получить тип, размер и слот программно нельзя. Ну как “нельзя”… можно посмотреть как работает API int Hardbreakpoints(int closeondelete);, что приведет к обработчику диалогового окна, у которого при получении сообщения WM_INITDIALOG происходит вызов функции, в которой есть цикл примерно такого вида:

// глобальный массив из 4х HW точек останова
t_hwbreakpoint[4] g_HWbps;
...
// работаем локально
t_hwbreakpoint* hwbp = g_HWbps;

for(int i = 0; i < 4; i++)
{
    // действия по инициализации окна с HW бряками
    hwbp++;
}

Не трудно будет разобрать структуру t_hwbreakpoint, а до нужных данных добираться, нацепив движок дизассемблера. Можно и жестко прописать адрес массива вручную, но при этом нужно помнить об осторожности.

Доступ к установленным software брякам можно получить через Plugingetvalue:

VAL_BREAKPOINTS    (t_table *)    

// описатель INT3 бряка
typedef struct t_bpoint {
  ulong        addr;           // адрес бряка
  ulong        dummy;          // похоже зарезервировано
  ulong        type;           // комбинация флагов TY_xxx
  char         cmd;            // байт, который перезаписан на INT3
  ulong        passcount;      // счетчик, указывающий сколько раз бряк будет проигнорирован (появился в версии 1.10 - стоит учитывать, если планируется совместимость со старыми версиями)
} t_bpoint;
TY_SET - код INT3 записан в память. В документации есть предупреждение, что изменять (понимаю как сбрасывать) флаг нельзя.
TY_ACTIVE - активный пользовательский breakpoint.
TY_DISABLED - временно неактивный breakpoint (надо думать, что TY_SET сброшен при этом).
TY_ONESHOT - One-shot breakpoint.
TY_TEMP - временный бряк, используемый OllyDbg.

TY_ONESHOT и TY_TEMP автоматически удаляются при срабатывании.

Следующий пример демонстрирует работу с бряками с помощью функции Manualbreakpoint, которая основана на интерактивном взаимодействии с пользователем и фактически эмулирует его действия. Стоит обратить внимание, что меню теперь формируется динамически и зависит от наличия пользовательского бряка.

#define CMD_manualbp 0
#define CMD_manualbpcond 1
#define CMD_manualbplogcond 2
#define CMD_manualunset 3

extc int _export cdecl ODBG_Pluginmenu(int origin, char data[4096], void *item)
{
    t_dump* dump = (t_dump*)item;
    ulong bptype;
    char buf[512];

    if(item == NULL)
        return 0;

    switch(origin)
    {
        case PM_DISASM:
        case PM_CPUDUMP:

            if(Getstatus() == STAT_NONE)
                return 0;

            sprintf(data, "r4ue5{");

                sprintf(buf, "Manual{");
                strcat(data, buf);


                // если постоянный breakpoint установлен
                bptype = Getbreakpointtypecount(dump->sel0, NULL);
                if((bptype & TY_ACTIVE) || (bptype & TY_DISABLED))
                {
                    sprintf(buf, "%dUnset", CMD_manualunset);
                }else{
                    sprintf(buf, "Set{%dNormal,%dConditional,%dConditional logging}", CMD_manualbp, CMD_manualbpcond, CMD_manualbplogcond);
                }

                strcat(data, buf);
                strcat(data, "}");

            strcat(data, "}");
            return 1;

        break;

        default:
            return 0;
    }
}

extc void _export cdecl ODBG_Pluginaction(int origin, int action, void* item)
{
    t_dump* dump = (t_dump*)item;
    t_table* table;

    if(item == 0)
        return;

    switch(origin)
    {
        case PM_DISASM:
        case PM_CPUDUMP:

            switch(action)
            {
                case CMD_manualbp:

                    // это код
                    Manualbreakpoint(dump->sel0, VK_F2, 0, 0, FIXEDFONT);

                break;

                case CMD_manualbpcond:

                    // в комментариях
                    Manualbreakpoint(dump->sel0, VK_F2, 1, 0, FIXEDFONT);

                break;

                case CMD_manualbplogcond:

                    // не нуждается
                    Manualbreakpoint(dump->sel0, VK_F4, 1, 0, FIXEDFONT);

                break;
                case CMD_manualunset:

                    table = (t_table*)Plugingetvalue(VAL_BREAKPOINTS);

                    if(table == NULL)
                        return;

                    Deletesorteddata(&table->data, dump->sel0);

                break;
            }

        break;
    }
}

Как уже было сказано Manualbreakpoint фактически эмулирует действия пользователя по установке бряка:

int Manualbreakpoint(ulong addr, int key, int shiftkey, ulong nametype, int font);

Возвращает 0, если все прошло хорошо и установка BP состоялась, иначе -1.

  • ulong addr - адрес.
  • int key - VK_F2 или VK_F4.
  • shiftkey - 1, для установки conditional или logging conditional бряка.
  • int nametype - документация не утруждает себя объяснениями параметра, указывая, что плагин здесь должен указывать 0. Верим, чего уж.
  • int font - с этим параметром уже знакомились однажды.
ulong Getbreakpointtype(ulong addr);
ulong Getbreakpointtypecount(ulong addr, ulong *passcount);

Обе функции, как не трудно догадаться, возвращают набор флагов TY_XXX, которыми снабжен breakpoint. Если бряк не существует - вернется TY_INVALID.

  • ulong addr - адрес.
  • ulong passcount* - если не NULL, указывает куда следует скопировать текущее значение поля t_bpoint.passcount.

Для удаления отладчиком экспортируется, но не описывается в документации функция:

void Deletebreakpoints(ulong addr0, ulong addr1, ulong);

Которая основана на Deletesorteddatarange. И ей даже пользуются в примере плагина CMDLINE. Но я рекомендовал бы использовать Deletesorteddata[range], по-крайней мере, пока значение третьего параметра не прояснится, т.к. в Deletebreakpoints его содержимое сразу переносят в глобальную переменную. А от этой переменной, на секунду, зависит возвращаемое в ряде случаев значение какой-то внутренней функции. Впрочем можно перекреститься и делать так как в примере CMDLINE:

Deletebreakpoints(dump->sel0, dump->sel0+1, 0);

Текст примера с Tempbreakpoint приводить не стану, поскольку там все тоже самое, что и выше.

void Tempbreakpoint(ulong addr, int mode);

Устанавливает temporary или one-shot breakpoint, хотя есть версия, что этими двумя типами она не ограничивается. По возможности использует HW breakpoint’s, если такой возможности нет - INT3.

  • ulong addr - адрес.
  • int mode - флаги. Среди рекомендуемых комбинаций:
TY_ONESHOT|TY_KEEPCOND    One-shot breakpoint. 
TY_ONESHOT|TY_KEEPCOND|TY_STOPAN Тоже, что и первая комбинация, только во время трассировки - бряк срабатывает и завершает анимацию или трассировку.
TY_TEMP|TY_KEEPCOND    Просто temporary breakpoint. Отработал и удалился.

Если опустить флаг TY_KEEPCOND, то при установке бряка на уже существующем conditional[ logging] breakpoint он перестанет быть таким.

Установка memory breakpoint’а так же возможна, как и удаление. А вот с получением информации о существовании такового и в случае существования адреса, размера и типа возникают проблемы.

int Setmembreakpoint(int type, ulong addr, ulong size);

Функция возвращает 0 в случае успеха и -1 при неудаче. Существовавший ранее memory breakpoint отправляется к праотцам, так и не оставив следа в истории.

  • int type - тип. Документация утверждает(нагло врет), что разрешается использовать MEMBP_READ (чтение,выполнение) и MEMBP_READ|MEMBP_WRITE (запись, чтение, выполнение). Отлаживая Olly (можно в нем же), легко убеждаешься, что сам отладчик использует для пункта меню Memory, on access комбинацию MEMBP_READ|MEMBP_WRITE, а для Memory, on write - MEMBP_WRITE.
  • ulong addr - адрес.
  • ulong size - размер контролируемой области.

Для удаления необходимо использовать Setmemorybreakpoint(0,0,0).

К memory breakpoint’ам при желании, но с оговорками, можно отнести так же технологию break-on-access. Которая позволяет поставить “бряк”, удаляемый при первом срабатывании, на целый блок памяти, для чего необходимо зайти в Memory map, выбрать блок и нажать F2. Изнутри break-on-access реализуется через специальный аттрибут типа памяти TY_GUARDED и экспортируемой функцией Guardmemory.

int Guardmemory(ulong base, ulong size, int guard);

Guardmemory устанавливает памяти отлаживаемого процесса необходимые аттрибуты. При этом поле t_memory.type должно обновляться отдельно - т.е. процесс установки break-on-access состоит из вызова функции и установки флага. Если функция отработала удачно - вернется 0, если что-то пошло не так - значение отличное от нуля.

  • ulong base - базовый адрес блока памяти.
  • ulong size - размер блока.
  • int guard - если требуется поставить break-on-access, то нужно передать 1, снять - 0.

При наличии желания, можно составить список версий отладчика и соответствующих жестко зашитых адресов нужных данных (не забывая проверять CRC, конечно). Для нахождения этих адресов в дизассемблере берем экспортируемую функцию _Setmemorybreakpoint и находим такое место:

.text:0041939E                 mov     eax, esi        ; ulong addr
.text:004193A0                 mov     edx, edi        ; ulong size
.text:004193A2                 mov     dwMemoryBrkAddr, eax
.text:004193A7                 mov     ecx, eax
.text:004193A9                 add     eax, edx
.text:004193AB                 and     ecx, 0FFFFF000h
.text:004193B1                 add     eax, 0FFFh
.text:004193B6                 mov     dwSizeMemoryBrk, edx
.text:004193BC                 and     eax, 0FFFFF000h
.text:004193C1                 mov     dwMemoryBrkPageAddr0, ecx
.text:004193C7                 test    bh, 10h
.text:004193CA                 mov     dwMemoryBrkPageAddr1, eax

,содержимое которого на форуме реверсеров даже стыдно объяснять=). В порядке бреда можно предположить такой код:

#define CMD_memaccess 8
#define CMD_memwrite 9
#define CMD_memunset 10
#define CMD_membrkonaccess 11
#define CMD_memunsetbrkonaccess 12

/*
эти структуры нужно восстанавливать в дизассемблере
*/
typedef struct s_memortybrk
{
    BOOL bIsSFX;
    ulong dwBrkAddr;
    ulong dwBrkSize;
    ulong dwBrkPageAddr0;
    ulong dwBrkPageAddr1;
    ulong dwBrkType;
}t_memorybrk;

t_memorybrk* membrk;

extc int _export cdecl ODBG_Plugininit(int ollydbgversion, HWND hw, ulong *) {
    ...
    // такого в твоем плагине быть не должно
    // по крайней мере CRC проверить нужно
    membrk = (t_memorybrk*)0x004D8138;
    ...
}

extc int _export cdecl ODBG_Pluginmenu(int origin, char data[4096], void *item)
{
    t_dump* dump = (t_dump*)item;
    t_memory *memory;

    ...

                sprintf(buf, ",Memory{");
                strcat(data, buf);

                if(membrk->dwBrkSize == 0 || ( memory != NULL && !(memory->type & TY_GUARDED)))
                {
                    strcat(data, "Set{");

                    if(membrk->dwBrkSize == 0)
                    {
                        sprintf(buf, "%dMemory \"on access\",%dMemory \"on write\"", CMD_memaccess, CMD_memwrite);
                        strcat(data, buf);
                    }

                    if( memory != NULL && !(memory->type & TY_GUARDED))
                    {
                        sprintf(buf, "%s%dSet break-on-access",
                            (membrk->dwBrkSize == 0) ? "," : ""
                            , CMD_membrkonaccess);
                        strcat(data, buf);
                    }

                    strcat(data, "}");
                }

                if(membrk->dwBrkSize != 0 || ( memory != NULL && (memory->type & TY_GUARDED) > 0))
                {
                    if(membrk->dwBrkSize != 0)
                    {

                        sprintf(buf, "%dUnset %s breakpoint[0x%08X/%Xh]", CMD_memunset, (
                            (membrk->dwBrkType == 0x01) ? "on access" : ((membrk->dwBrkType == 0x20) ? "on write" : "unknown")
                            ), membrk->dwBrkAddr, membrk->dwBrkSize
                            );
                        strcat(data, buf);
                    }

                    if( memory != NULL && (memory->type & TY_GUARDED) > 0)
                    {
                        sprintf(buf, "%s%dUnset break-on-access",
                            (membrk->dwBrkSize != 0) ? "," : ""
                            , CMD_memunsetbrkonaccess);
                        strcat(data, buf);
                    }
                }

                strcat(data, "}");

    ...
}

extc void _export cdecl ODBG_Pluginaction(int origin, int action, void* item)
{
    t_dump* dump = (t_dump*)item;
    t_memory* memory;

    ...
                case CMD_memaccess:

                    Setmembreakpoint(MEMBP_READ|MEMBP_WRITE, dump->sel0, dump->sel1 - dump->sel0);

                break;

                case CMD_memwrite:

                    Setmembreakpoint(MEMBP_WRITE, dump->sel0, dump->sel1 - dump->sel0);

                break;

                case CMD_memunset:

                    Setmembreakpoint(0, 0, 0);

                break;

                case CMD_membrkonaccess:

                    memory = Findmemory(dump->sel0);

                    if(memory == NULL)
                        return;

                    if(!Guardmemory(memory->base, memory->size, TRUE))
                        memory->type |= TY_GUARDED;

                break;

                case CMD_memunsetbrkonaccess:

                    memory = Findmemory(dump->sel0);

                    if(memory == NULL)
                        return;

                    if(!Guardmemory(memory->base, memory->size, FALSE))
                        memory->type &= (~TY_GUARDED);

                break;
    ...
}

Теперь обратим свой взор на hardware breakpoint’ы.

int Hardbreakpoints(int closeondelete);

Функция Hardbreakpoints показывает диалоговое окно (меню Debug->Hardware breakpoints). Если произошла ошибка или пользователь нажал отмену - возвращает -1, иначе 0. Параметр int closeondelete указывает окну диалога закрываться, если пользователь удалил хоть один бряк.

int Sethardwarebreakpoint(ulong addr, int size, int type);

Sethardwarebreakpoint устанавливает HW breakpoint. Если все слоты заняты - вызывает окно с мольбой удалить один из существующих бряков (окно, правда, появляется не всегда - у меня 50/50 и это нужно учитывать). Возвращает -1 при ошибке и 0 - если все получилось. Функция не изменят память отлаживаемого процесса, а посему может вызываться при Getstatus() == STAT_RUNNING.

  • ulong addr - адрес.
  • int size - 1, 2 или 4. Для Execution бряка всегда 1.
  • int type - тип бряка. HB_CODE, HB_ACCESS или HB_WRITE.

Для удаления существует целых 2 решения:

int Deletehardwarebreakpoint(int index);
int Deletehardwarebreakbyaddr(ulong addr);

Deletehardwarebreakpoint удаляет HW бряк по индексу слота, в который тот помещен. Возвращает -1 при неудаче, и 0 в случае успеха. int index - индекс слота в диапазоне 0…3 включительно с обоих концов. Учитывая, что Sethardwarebreakpoint не возвращает индекс слота даже через out параметры, то как автор предлагает использовать ф-цию остается загадкой (наверно, нужно генерировать случайное число и надеяться на удачу=). Хотя один идиотский, но хотя бы полурабочий метод предложить можно - после установки бряка(если это не Execution HW) нужно отловить хотя бы одно срабатывание, а затем проверить все 4 слота на соответствие известному адресу. Но не факт, что даже одно срабатывание будет.

Deletehardwarebreakbyaddr удаляет бряк по адресу. Возвращается количество удаленных бряков или 0 при ошибке. Если к адресу привязано несколько HW бряков - удалятся все. Именно поэтому, несмотря на весь геморрой, рекомендую использовать Deletehardwarebreakpoint, предварительно разреверсив сам отладчик.

#define CMD_hwsete 13
#define CMD_hwsetrw1 14
#define CMD_hwsetrw2 15
#define CMD_hwsetrw4 16
#define CMD_hwsetw1 17
#define CMD_hwsetw2 18
#define CMD_hwsetw4 19
#define CMD_hwremove0 20
#define CMD_hwremove1 21
#define CMD_hwremove2 22
#define CMD_hwremove3 23

typedef struct s_hwbreakpoint
{
    ulong addr;
    ulong size;
    ulong type; // 1 - Execute, 2 - Access, 3 - Write, 4 - Port I/O, 5 - One-shot,6 - One-shot, 7 - Temporary
    ulong tmp3;
    ulong tmp4;
    ulong tmp5;
    ulong tmp6;
}t_hwbreakpoint;

t_hwbreakpoint *g_HWbps;

extc int _export cdecl ODBG_Plugininit(int ollydbgversion, HWND hw, ulong *) {
...
    g_HWbps = (t_hwbreakpoint*)0x004D8D70;
...
}

extc int _export cdecl ODBG_Pluginmenu(int origin, char data[4096], void *item)
{
...
                strcat(data, ",HW{");

                sprintf(buf, "Set{%dOn execute,Write{%d 1 byte,%d 2 bytes,%d 4 bytes},Access{%d 1 byte,%d 2 bytes,%d 4 bytes}}",
                    CMD_hwsete, CMD_hwsetw1, CMD_hwsetw2, CMD_hwsetw4, CMD_hwsetrw1, CMD_hwsetrw2, CMD_hwsetrw4);
                strcat(data, buf);

                if(g_HWbps[0].type != 0 || g_HWbps[1].type != 0 || g_HWbps[2].type != 0 || g_HWbps[3].type != 0)
                {
                    const int cmds[4] = {CMD_hwremove0, CMD_hwremove1, CMD_hwremove2, CMD_hwremove3};
                    bool bAlreadyOut = false;

                    strcat(data, ",Unset{");

                    for(int i = 0; i < 4; i++)
                    {
                        if(g_HWbps[i].type != 0)
                        {

                            if(bAlreadyOut == true)
                                strcat(data, ",");

                            sprintf(buf, "%d slot %d", cmds[i], i);
                            strcat(data, buf);

                            switch(g_HWbps[i].type)
                            {
                                case 1:

                                    sprintf(buf, " execute at %08X", g_HWbps[i].addr);

                                break;

                                case 2:

                                    sprintf(buf, " access at %08X size:%d", g_HWbps[i].addr, g_HWbps[i].size);

                                break;

                                case 3:

                                    sprintf(buf, " write at %08X size:%d", g_HWbps[i].addr, g_HWbps[i].size);

                                break;

                                case 4:

                                    sprintf(buf, " Port I/O");

                                break;

                                case 5:
                                case 6:

                                    sprintf(buf, " One-shot at %08X", g_HWbps[i].addr);

                                break;

                                case 7:

                                    sprintf(buf, " Temporary at %08X", g_HWbps[i].addr);

                                break;

                                default:

                                    sprintf(buf, "unknown");

                                break;
                            }

                            strcat(data, buf);

                            bAlreadyOut = true;
                        }
                    }

                    strcat(data, "}");

...
}

extc void _export cdecl ODBG_Pluginaction(int origin, int action, void* item)
{
...
                case CMD_hwsete:

                    Sethardwarebreakpoint(dump->sel0, 1, HB_CODE);

                break;

                case CMD_hwsetrw1:

                    Sethardwarebreakpoint(dump->sel0, 1, HB_ACCESS);

                break;

                case CMD_hwsetrw2:

                    Sethardwarebreakpoint(dump->sel0, 2, HB_ACCESS);

                break;

                case CMD_hwsetrw4:

                    Sethardwarebreakpoint(dump->sel0, 4, HB_ACCESS);

                break;

                case CMD_hwsetw1:

                    Sethardwarebreakpoint(dump->sel0, 1, HB_WRITE);

                break;

                case CMD_hwsetw2:

                    Sethardwarebreakpoint(dump->sel0, 2, HB_WRITE);

                break;

                case CMD_hwsetw4:

                    Sethardwarebreakpoint(dump->sel0, 4, HB_WRITE);

                break;

                case CMD_hwremove0:

                    Deletehardwarebreakpoint(0);

                break;

                case CMD_hwremove1:

                    Deletehardwarebreakpoint(1);

                break;

                case CMD_hwremove2:

                    Deletehardwarebreakpoint(2);

                break;

                case CMD_hwremove3:

                    Deletehardwarebreakpoint(3);

                break;
...
}

Осталось обсудить две (де-факто одну) функции.

int Setbreakpoint(ulong addr, ulong type, uchar cmd);
int Setbreakpointext(ulong addr, ulong type, uchar cmd, ulong passcount);

Устанавливает новый или изменяет существующий INT3 breakpoint. В случае успеха возвращает 0, иначе - -1.

  • ulong addr - адрес бряка.
  • ulong type - набор флагов, с которыми частично уже знакомы.
  • uchar cmd - перезаписываемый байт команды. Если флаг TY_KEEPCODE не установлен, то этот параметр игнорируется и оригинальный байт будет прочитан из памяти.
  • ulong passcount - тоже уже встречали. Этот счетчик используется так: при бряке Olly проверяет passcount, если он == 0, то выполнение отлаживаемого процесса прервется, иначе счетчик уменьшится на 1 и выполнение процесса продолжится. Флаг TY_SETCOUNT принудительно заставляет обновить значение passcount даже у уже существующего бряка.

С бряками через имена неразрывно связана технология conditional & logging breakpoiont’ов. Так если задать имя NM_BREAK, breakpoint станет conditional. NM_BREAKEXPR и NM_BREAKEXPL задают соответственно значения Expression и Explanation (см. Shift+F4).

#define CMD_softset 24
#define CMD_softsetcond 25
#define CMD_softsetcondlog 26
#define CMD_softunset 27
#define CMD_softdisable 28

extc int _export cdecl ODBG_Pluginmenu(int origin, char data[4096], void *item)
{
...
                strcat(data, ",Soft{");

                bptype = Getbreakpointtypecount(dump->sel0, NULL);

                if((bptype & TY_ACTIVE) || (bptype & TY_DISABLED))
                {

                    sprintf(buf, "%dUnset", CMD_softunset);
                    strcat(data, buf);

                    if((bptype & TY_ACTIVE))
                    {
                        sprintf(buf, ",%dDisable breakpoint", CMD_softdisable);
                        strcat(data, buf);
                    }

                }else{

                    sprintf(buf, "Set{%dSet bp,%dSet conditional bp,%dSet conditional logging bp}",
                        CMD_softset, CMD_softsetcond, CMD_softsetcondlog);

                    strcat(data, buf);

                }

                strcat(data, "}");
...
}

extc void _export cdecl ODBG_Pluginaction(int origin, int action, void* item)
{
...
                case CMD_softset:

                    Setbreakpointext(dump->sel0, TY_ACTIVE|TY_KEEPCOND, 0, 0);

                break;

                case CMD_softsetcond:

                    Setbreakpointext(dump->sel0, TY_ACTIVE|TY_KEEPCOND, 0, 0);

                    // устанавливаем conditional
                    strcpy(buf, "eax == 0");
                    Insertname(dump->sel0, NM_BREAK, buf);

                    // устанавливаем "Pause program" в "On conditional"
                    sprintf(buf, "%c", COND_FILLING);
                    Insertname(dump->sel0, NM_BREAKEXPR, buf);

                    // Pause program = Never
                    /*
                    sprintf(buf, "%c", COND_FILLING | COND_NOBREAK);
                    Insertname(dump->sel0, NM_BREAKEXPR, buf);
                    */

                    // Pause program = Always
                    /*
                    sprintf(buf, "%c", COND_FILLING | COND_BRKALWAYS);
                    Insertname(dump->sel0, NM_BREAKEXPR, buf);
                    */


                break;

                case CMD_softsetcondlog:

                    Setbreakpointext(dump->sel0, TY_ACTIVE|TY_KEEPCOND, 0, 0);

                    // устанавливаем conditional
                    strcpy(buf, "eax == 0");
                    Insertname(dump->sel0, NM_BREAK, buf);

                    // устанавливаем "Pause program" в "On conditional"
                    // устанавливаем "Log value of expression" в "On conditional"
                    // устанавливаем "Log function arguments" в "On conditional"
                    // "Decode value of expression as:" ставим в "Handle of window"
                    sprintf(buf, "%c[esp]\nw=", COND_FILLING|COND_LOGTRUE|COND_ARGTRUE);
                    Insertname(dump->sel0, NM_BREAKEXPR, buf);

                    // устанавливаем Explanation
                    strcpy(buf, "<WinProc>");
                    Insertname(dump->sel0, NM_BREAKEXPL, buf);


                break;

                case CMD_softunset:

                    table = (t_table*)Plugingetvalue(VAL_BREAKPOINTS);

                    if(table == NULL)
                        return;

                    Deletesorteddata(&table->data, dump->sel0);

                break;

                case CMD_softdisable:

                    Setbreakpointext(dump->sel0, TY_DISABLED|TY_KEEPCOND, 0, 0);

                break;
...
}

Из кода видно как с помощью имен можно не только установить conditional, expression и explanation, но и указать условия при которых должна быть произведена остановка или логирование как выражения, так и аргументов. Стоит обратить внимание, что если NM_BREAKEXPR содержит 2 строки через стандартный разделитель \n, то Olly попытается интерпретировать вторую строку в качестве типа выражения.

B= - Boolean value
R= - Identifier of resource string
e= - Error code
w= - Handle of window
VK_X: - Virtual key code
MAKELONG: - Packed pair of 16-bit words
COLORREF: - Packed RGB color
A= - Pointer to ASCII string
N= - Pointer to UNICODE string
m= - Pointer to MSG structure (ASCII)
n= - Pointer to MSG structure (UNICODE)
r= - Pointer to RECT structure

Теперь самое время поговорить о реакции плагина на события извне.

int ODBG_Paused(int reason, t_reg *reg);
int ODBG_Pausedex(int reason, int extdata, t_reg *reg, DEBUG_EVENT *debugevent);
int ODBG_Plugincmd(int reason, t_reg *reg, char *cmd);

Первые два callback’а вызываются (о третьем речь пойдет позже), когда отлаживаемое приложение приостанавливается (после проведения отладчиком всех необходимых действий). На самом деле вызывается только один callback. Приоритет при этом имеет ODBG_Pausedex.

int reason - указывает причину остановки. В этом параметре кодируется общая (для ODBG_Paused и ODBG_Pausedex) и детальная (только для ODBG_Pausedex) информация о событии. Так, к общей информации относятся:

PP_EVENT - отладочное событие
PP_PAUSE - запрос пользователя.
PP_TERMINATED - отлаживаемое приложение завершилось.

Конкретный тип (PP_EVENT, PP_PAUSE, PP_TERMINATED) извлекают с помощью маски PP_MAIN. Детальная информация о событии:

PP_BYPROGRAM - отладочное событие сгенерировано программой.
PP_INT3BREAK - INT3 бряк.
PP_MEMBREAK - memory бряк.
PP_HWBREAK - HW бряк.
PP_SINGLESTEP - очередной шаг трассировки.
PP_EXCEPTION - Exception.
PP_ACCESS - access violation.
PP_GUARDED - столкновение с guarded page.

int extdata - зарезервировано.

t_reg reg - состояние регистров потока на момент возникновения события. Может быть NULL.
DEBUG_EVENT debugevent - здесь объяснят. Так же может быть NULL.

Чтобы отменить перерисовку и продолжить выполнение, плагин должен внутри callback’а вызвать функцию Go и вернуть 1, в остальных случаях возвращать стоит 0.

Любопытно, что для access violation выделен отдельный тип события PP_ACCESS, при этом PP_EXCEPTION не генерируется. Почему PP_ACCESS оказывается такое внимание - не понятно, наверно потому что он черный. Перехватим для примера ошибку stack overflow. Возьмем для этого надуманный пример на FASM:

format PE GUI 4.0
include '%FASMINC%\win32a.inc'
entry _start

section '.text' code readable executable
_start:
call _start
push 0
call dword [ExitProcess]

section '.idata' import readable writeable

library kernel32,'kernel32.dll'

import kernel32,ExitProcess,'ExitProcess'

А со стороны плагина:

extc int _export cdecl ODBG_Pausedex(int reason, int, t_reg* reg, DEBUG_EVENT *dbgevt)
{
...
    if(reason & PP_EXCEPTION)
    {
        if(dbgevt != NULL && dbgevt->dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
        {
            if(dbgevt->u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_STACK_OVERFLOW)
            {
                MessageBoxA(NULL, "Беда насяльника. Стека - кирдык.", "Большой бум-бум", MB_OK | MB_ICONINFORMATION);
            }
        }
    }
...
}

Нужно понимать, что int reason хранит набор флагов. Так чаще всего при Exception reason содержит флаги PP_EXCEPTION и PP_BYPROGRAM, а общий тип при этом (reason & PP_MAIN) == PP_EVENT.

Теперь пример с нарушением доступа.

format PE GUI 4.0
include '%FASMINC%\win32a.inc'
entry _start

section '.text' code readable executable
_start:
mov eax,[0]
push 0
call dword [ExitProcess]

section '.idata' import readable writeable

library kernel32,'kernel32.dll'

import kernel32,ExitProcess,'ExitProcess'

И как это можно обработать в плагине:

extc int _export cdecl ODBG_Pausedex(int reason, int, t_reg* reg, DEBUG_EVENT *dbgevt)
{
    const char *type;
    char buf[512];
    ulong *exceptinf;

    if(reason & PP_ACCESS)
    {
        if(dbgevt != NULL && dbgevt->dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
        {
            if(dbgevt->u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_ACCESS_VIOLATION &&
                dbgevt->u.Exception.ExceptionRecord.NumberParameters == 2)
            {

                exceptinf = &dbgevt->u.Exception.ExceptionRecord.ExceptionInformation[0];

                switch(exceptinf[0])
                {
                    case 0:

                        type = "read";

                    break;

                    case 1:

                        type = "write";

                    break;

                    case 8:

                        type = "DEP";

                    break;

                    default:

                        type = "unknown";

                    break;
                }

                sprintf(buf, "Access violation at %08X, type:%s, addr:%08X", dbgevt->u.Exception.ExceptionRecord.ExceptionAddress, type, exceptinf[1]);
                MessageBox(NULL, buf, "", MB_OK | MB_ICONINFORMATION);
            }
        }
    }
    ...
}

Увы, но такие события, как старт нового потока или загрузка модуля не проходят цензуру и плагину не передаются, даже при включении флажков в настройках. По крайней мере я не знаю других способов, кроме как патчить Olly.

Обработку событий наиболее часто используют для реакции на бряки, которые проставил плагин. И самым важным моментом здесь является опознание своего бряка. Для PP_INT3BREAK сделать это не сложно. Возьмем такой пример:

format PE GUI 4.0
include '%FASMINC%\win32a.inc'
entry _start

section '.text' code readable executable
_start:
mov ecx,100

_next_iter:
dec ecx
jnz _next_iter

push 0
call dword [ExitProcess]

section '.idata' import readable writeable

library kernel32,'kernel32.dll'

import kernel32,ExitProcess,'ExitProcess'

А теперь для демонстрации того, что в ODBG_Pausedex управление передается только при обнулении passcount напишем такое (проверь сам как работает conditional breakpoint в этой ситуации):

#define CMD_int3start 0
#define CMD_int3stop 1

bool bIsINT3;
ulong int3addr;

extc int _export cdecl ODBG_Plugininit(int ollydbgversion, HWND hw, ulong *) {
...
    bIsINT3 = false;
    int3addr = 0;
...
    return 0;
}

extc int _export cdecl ODBG_Pluginmenu(int origin, char data[4096], void *item)
{
...
            if(bIsINT3)
            {

                sprintf(buf, "%dStop INT3", CMD_int3stop);

            }else{

                sprintf(buf, "%dStart INT3", CMD_int3start);

            }
...
}

extc void _export cdecl ODBG_Pluginaction(int origin, int action, void* item)
{
...
                case CMD_int3start:

                    // управление ODBG_Pausedex получит только когда passcount обнулится
                    Setbreakpointext(dump->sel0, TY_ACTIVE|TY_KEEPCOND, 0, 45);
                    bIsINT3 = true;
                    int3addr = dump->sel0;

                break;

                case CMD_int3stop:

                    table = (t_table*)Plugingetvalue(VAL_BREAKPOINTS);

                    if(table == NULL)
                        return;

                    Deletesorteddata(&table->data, dump->sel0);

                    bIsINT3 = false;
                    int3addr = 0;

                break;
...
}

extc int _export cdecl ODBG_Pausedex(int reason, int, t_reg* reg, DEBUG_EVENT *dbgevt)
{
...
    if(bIsINT3 && (reason & PP_INT3BREAK) && reg->ip == int3addr)
    {
        sprintf(buf, "%08X", reg->r[REG_ECX]);
        MessageBox(NULL, buf, "", MB_OK | MB_ICONINFORMATION);
    }
...
}

На dec ecx в окне дизассемблера нажмем правой кнопкой и выберем Start INT3, затем F9. Действительно просто. Разве что не мешало бы еще обработать ODBG_Pluginreset, чтобы предотвратить гипотетическую ситуацию: отлаживаемое приложение завершилось, начинается отладка другого приложения для которого, по иронии судьбы, установлен бряк на тот же самый адрес. Memory и break-on-access breakpoint’ы работают схожим образом. Поэтому и обрабатываются почти одинакого. Пример:

format PE GUI 4.0
include '%FASMINC%\win32a.inc'
entry _start

section '.text' code readable executable
_start:
mov ecx,100

_next_iter:
mov eax,[mycooldata]
dec ecx
jnz _next_iter

mov eax,[mycooldata2]

push 0
call dword [ExitProcess]

section '.data' data readable writeable
mycooldata dd 0DEADBEEFh
mycooldata2 dd ?
section '.idata' import readable writeable

library kernel32,'kernel32.dll'

import kernel32,ExitProcess,'ExitProcess'

И пример плагина:

#define CMD_memstart 2
#define CMD_memstop 3
#define CMD_guardstart 4
#define CMD_guardstop 5

bool bIsMEM;
ulong memaddr0,memsize;

bool bIsGUARD;
ulong guardaddr0,guardsize;

extc int _export cdecl ODBG_Plugininit(int ollydbgversion, HWND hw, ulong *) 
{
...
    bIsMEM = false;
    memaddr0 = memsize = 0;

    bIsGUARD = false;
    guardaddr0 = guardsize = 0;

...
}

extc int _export cdecl ODBG_Pluginmenu(int origin, char data[4096], void *item)
{
...
            if(bIsMEM)
            {

                sprintf(buf, ",%dStop MEM", CMD_memstop);

            }else{

                sprintf(buf, ",%dStart MEM", CMD_memstart);

            }

            strcat(data, buf);

            if(bIsGUARD)
            {

                sprintf(buf, ",%dStop GUARD", CMD_guardstop);

            }else{

                sprintf(buf, ",%dStart GUARD", CMD_guardstart);

            }

            strcat(data, buf);
...
}

extc void _export cdecl ODBG_Pluginaction(int origin, int action, void* item)
{
...
                case CMD_memstart:

                    Setmembreakpoint(MEMBP_READ|MEMBP_WRITE, dump->sel0, dump->sel1 - dump->sel0);

                    bIsMEM = true;
                    memaddr0 = dump->sel0;
                    memsize = dump->sel1 - dump->sel0;

                break;

                case CMD_memstop:

                    if(bIsMEM && membrk->dwBrkAddr == memaddr0 && membrk->dwBrkSize == memsize)
                    {
                        Setmembreakpoint(0, 0, 0);
                    }

                    bIsMEM = false;
                    memaddr0 = memsize = 0;

                break;

                case CMD_guardstart:

                    memory = Findmemory(dump->sel0);

                    if(memory != NULL)
                    {
                        if(!Guardmemory(memory->base, memory->size, TRUE))
                        {
                            memory->type |= TY_GUARDED;

                            bIsGUARD = true;
                            guardaddr0 = memory->base;
                            guardsize = memory->size;
                        }
                    }

                break;

                case CMD_guardstop:

                    if(bIsGUARD)
                    {

                        memory = Findmemory(guardaddr0);

                        if(memory != NULL)
                        {
                            if(!Guardmemory(memory->base, memory->size, TRUE))
                                memory->type &= ~TY_GUARDED;
                        }

                        bIsGUARD = false;
                        guardaddr0 = guardsize = 0;
                    }

                break;
...
}

extc int _export cdecl ODBG_Pausedex(int reason, int, t_reg* reg, DEBUG_EVENT *dbgevt)
{
...
    ulong addr;
...
    if(bIsMEM && (reason & PP_MEMBREAK))
    {
        if(dbgevt != NULL && dbgevt->dwDebugEventCode == EXCEPTION_DEBUG_EVENT &&
            dbgevt->u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_ACCESS_VIOLATION &&
                dbgevt->u.Exception.ExceptionRecord.NumberParameters == 2
            )
        {
            exceptinf = &dbgevt->u.Exception.ExceptionRecord.ExceptionInformation[0];

            addr = exceptinf[1];

            if(memaddr0 <= addr && addr < (memaddr0+memsize))
            {
                MessageBox(NULL, "Catch memory breakpoint", "", MB_OK | MB_ICONINFORMATION);
            }
        }
    }

    if(bIsGUARD && (reason & PP_GUARDED))
    {
        if(dbgevt != NULL && dbgevt->dwDebugEventCode == EXCEPTION_DEBUG_EVENT &&
            dbgevt->u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_GUARD_PAGE &&
                dbgevt->u.Exception.ExceptionRecord.NumberParameters == 2
            )
        {
            exceptinf = &dbgevt->u.Exception.ExceptionRecord.ExceptionInformation[0];

            addr = exceptinf[1];

            if(guardaddr0 <= addr && addr < (guardaddr0 + guardsize))
            {
                MessageBox(NULL, "Catch break-on-access breakpoint", "", MB_OK | MB_ICONINFORMATION);
            }
        }

    }
...
}

Для тестирования кода можно сначала поставить memory бряк (Start MEM) на mycooldata, которая удобно расположилась в окне CPU Dump. Когда убедимся, что бряк ловится и вылезает сообщение - ставим Start MEM уже на mycooldata2, затем тут же ставим обычным способом Breakpoint->Memory, on access. Это приведет к тому, что плагин будет считать, что memory бряк установлен. Запустив, получим срабатывание бряка без MessageBox’а, т.е. распознавание свой-чужой прошло успешно. Проверить GUARD еще проще - ставим Start GUARD в окне CPU Dump. Запускаем - ловим MessageBox. Затем ставим Start GUARD, лезем в Memory map встаем на секцию кода, жмем F2. Запускаем - видим остановку, но не видим MessageBox’а.

О PP_SINGLESTEP тебе нужно знать только то, что это событие возникает при ручной трассировке или остановке автоматической. В обработчике этого события удобно проверять где находится IP и что-то делать, если он лежит в заданных границах.

Для последнего примера, который показывает обработку HW бряков отдельный пример не нужен, поэтом просто возьмем sample4. Для тестирования установим 1 HW бряк на mycooldata. Затем поставим Set HW на mycooldata2, запустим и будем смотреть как чужие бряки не опознаются (чтобы не надоело жать F9 в цикле по доступу к mycooldata - можно удалить HW breakpoint после пары срабатываний), а установленный обрабатывается.

#define CMD_hwstart 6
#define CMD_hwstop 7

bool bIsHW;
ulong hwaddr0,hwsize,hwindex;

extc int _export cdecl ODBG_Plugininit(int ollydbgversion, HWND hw, ulong *)
{
...
    bIsHW = false;
    hwaddr0 = hwsize = hwindex = 0;
...
}

extc int _export cdecl ODBG_Pluginmenu(int origin, char data[4096], void *item)
{
...
            if(bIsHW)
            {

                sprintf(buf, ",%dStop HW", CMD_hwstop);

            }else{

                sprintf(buf, ",%dStart HW", CMD_hwstart);

            }

            strcat(data, buf);
...
}

extc void _export cdecl ODBG_Pluginaction(int origin, int action, void* item)
{
...
                case CMD_hwstart:

                    if(bIsHW)
                        return;

                    if(!Sethardwarebreakpoint(dump->sel0, 1, HB_ACCESS))
                    {
                        for(int i = 0; i < 4; i++)
                        {
                            if(g_HWbps[i].type == 2 && g_HWbps[i].addr == dump->sel0 && g_HWbps[i].size == 1)
                            {
                                hwindex = i;
                                break;
                            }
                        }

                        bIsHW = true;
                        hwsize = 1;
                        hwaddr0 = dump->sel0;
                    }

                break;

                case CMD_hwstop:

                    if(bIsHW)
                    {

                        Deletehardwarebreakpoint(hwindex);

                        bIsHW = false;
                        hwaddr0 = hwsize = hwindex = 0;
                    }

                break;
...
}

extc int _export cdecl ODBG_Pausedex(int reason, int, t_reg* reg, DEBUG_EVENT *dbgevt)
{
...
    t_thread* thread;
...
    if(bIsHW && (reason & PP_HWBREAK))
    {
        thread = Findthread(Getcputhreadid());

        if(thread != 0)
        {
            if(thread->context.Dr6 & (1 << hwindex))
            {
                MessageBox(NULL, "О-о-о да, детка!", "", MB_OK | MB_ICONINFORMATION);
            }
        }
    }
...
}

В первый раз мне пришлось крепко подумать, чтобы вытащить индекс сработавшего HW, так что “мотай на ус”. t_reg, который передается в callback и тот который принадлежит t_thread содержат отладочные регистры в полном объеме. Вот только если ознакомится с тем, что там лежит - придешь в недоумение (мусор там). Поэтому тянемся к контексту потока, а вот в нем как раз и находится то, что можно использовать.

Наконец, узнаем как после обработки события продолжить выполнение приложения:

if(Go(Getcputhreadid(), 0, STEP_RUN, 0, 0))
    MessageBox(NULL, "Беда, барыня", "", MB_OK | MB_ICONERROR)

Вообще, вместо функции Go документация рекомендует использовать Sendshortcut, эмулируя реакцию пользователя. Вероятно, это связано с тем, что неверно заданные параметры могут привести к нестабильной работе отладчика.

int Go(ulong threadid, ulong tilladdr, int stepmode, int givechance, int backupregs);

Возвращает 0 при удачном исходе и -1 при ошибке.

  • ulong threadid - идентификатор потока. Если передать 0 - будет взят dwThreadId потока в котором возникло отладочное событие.
  • ulong tilladdr - адрес команды, на которой надо прерваться. Используется только если используется STEP_SKIP. Похоже, что используются temporary бряки, т.е. могут заниматься отладочные слоты. Не рекомендуется (крайне не рекомендуется) передавать адрес, например, середины команды.
  • int stepmode - режим:
STEP_SOME - использовать параметр stepmode переданный при предыдущем вызове.
STEP_RUN - запустить.
STEP_OVER - шаг без захода в функцию.
STEP_IN - шаг с заходом в функцию.
STEP_SKIP - смотрим параметр tilladdr
  • int givechance - если не 0, при возникновении исключения передать управление обработчику исключений отлаживаемого приложения(должно помочь, если плагин предназначен для [полу]автоматической распаковки какой-нибудь бяки).
  • int backupregs - если не 0 - обновляется поле t_thread.oldreg. Olly его использует для подсветки измененных регистров.
void Sendshortcut(int where, ulong addr, int msg, int ctrl, int shift, int vkcode);

Функция эмулирует ввод пользователя.

  • int where - какому окну посылается сообщение (PM_MAIN, PM_DISASM, PM_CPUDUMP, PM_CPUSTACK, PM_CPUREGS).
  • ulong addr - адрес для которого применен shortcut. Т.к. окна неразрывно связаны с адресами (дизассемблер - адрес команды, окно INT3 бряков - адрес бряка), похоже и возникла необходимость в этом параметре. Этот параметр игнорируется, если окно назначения PM_MAIN или PM_CPUREGS.
  • int msg - каким сообщением эмулировать ввод (WM_KEYDOWN, WM_SYSKEYDOWN, WM_CHAR).
  • int ctrl - состояние Ctrl (0 или 1).
  • int shift - состояние Shift (0 или 1).
  • int vkcode - виртуальный код клавиши VK_XXX или просто ‘t’.

С обработкой бряков связано еще одна возможность отладчика. Через специальный callback ODBG_Plugincmd Оля может передавать плагину команды, оформленные в виде ASCIIZ строки. Для этого к адресу, на котором установлен conditional logging бряк цепляется имя с типом NM_PLUGCMD, а содержимое имени и есть текстовая команда плагина. На самом деле под одной командой может скрываться целый выводок, разделенные переводом строки (\x0D\X0A). При этом каждая строка считается отдельной командой и передается callback’у поочередно. Учитывая размер имени, ограниченный константой TEXTLEN, можно догадаться, что команды должны быть краткими. Еще одна неприятная особенность в том, что необходимо учитывать возможность существования других команд. Чего уж говорить, что остается только молиться, чтобы программисты других плагинов, использующие NM_PLUGCMD учитывали и твое существование тоже.

extc int _export cdecl ODBG_Plugincmd(int origin, t_reg* reg, char* cmd);

Если плагин опознает команду - он должен вернуть 1, и тогда поиск хозяина этой команды прервется и начнется поиск следующей. Если плагин не признал команду - возвращаем 0.

  • int origin - только PP_EVENT.
  • t_reg* reg - регистры потока, столкнувшегося с conditional logging breakpoint.
  • char* cmd - ASCIIZ команда.
extc int _export cdecl ODBG_Pluginmenu(int origin, char data[4096], void *item)
{
    if(item == NULL)
        return 0;

    switch(origin)
    {
        case PM_DISASM:

            sprintf(data, "r4ue7{%dInsert cmd}", CMD_insertcmd);

            return 1;
        break;
    }

    return 0;
}

extc void _export cdecl ODBG_Pluginaction(int origin, int action, void* item)
{
    t_dump* dump = (t_dump*)item;

    if(item == 0)
        return;

    switch(origin)
    {
        case PM_DISASM:

            switch(action)
            {
                case CMD_insertcmd:

                    Setbreakpointext(dump->sel0, TY_ACTIVE|TY_KEEPCOND, 0, 0);
                    Insertname(dump->sel0, NM_PLUGCMD, "testcmd1\x0D\x0Atestcmd2");

                break;
            }

        break;
    }
}

extc int _export cdecl ODBG_Plugincmd(int origin, t_reg* reg, char* cmd)
{
    MessageBox(NULL, cmd, "", MB_OK|MB_ICONINFORMATION);
    return 0;
}

Стоит помнить, что команда передается только если условия выполнены. Т.е. если passcount == 0 и остановка разрешена (Pause program - Always или (On conditional и conditional == TRUE)).

Вообще, команда - это как клеймо проститутки. Когда любой желающий клеймит своих девок, например, квадратом, то сбежавшая путана, наткнувшись на любого другого сутенера (которого природа не наделила фантазией) моментально будет опознана как своя и лучшей жизни уже не увидит. В документации прямо говорится, что плагин CMDLINE хватает с улицы любую шлюху, имя которой начинается с точки (’.’). На лицо очередное неудачное архитектурное решение.

На этом длинную, нудную, но важную тему бряков предлагаю закончить.

[0x00000006 Спаси и сохрани]

Olly предлагает два механизма для хранения настроек. Глобальные переменные и данные предлагается сохранять в INI файле (ollydbg.ini). Имеющие же локальный характер (применительно к отлаживаемому модулю) настройки рекомендуют помещать в UDD файлы. Никто, разумеется, не запрещает хранить все что нужно хоть на Dropbox’е, но раз уж механизмы есть, то почему бы ими не воспользоваться?!

Работа с INI ограничена двумя типа данных - int и ASCIIZ. И, соответственно, представлена 4-мя функциями, которые можно вызывать в любом месте плагина:

int Pluginwriteinttoini(HINSTANCE dllinst, char *key, int value);
int Pluginwritestringtoini(HINSTANCE dllinst, char *key, char *s);
int Pluginreadintfromini(HINSTANCE dllinst, char *key, int def);
int Pluginreadstringfromini(HINSTANCE dllinst, char *key, char *s, char *def);

Pluginwriteinttoini и Pluginwritestringtoini возвращают в случае успеха 1, иначе 0. Pluginreadintfromini возвращает в случае успеха прочитанное из INI числовое значение, при неудаче вернется значение аргумента int def. Pluginreadstringfromini возвращает длину строки, записанной в буфер (при удачной операции - прочитанной из файла, при неудачной - из аргумента char* def).

  • HINSTANCE dllinst - HINSTANCE модуля плагина. Судя по всему, Olly хранит соответствия HINSTANCE => t_plugin (абстрактно).
  • char* key - название параметра.
  • int value, char* s (для write функций) - сохраняемое значение int или ASCIIZ типа.
  • int def, char* def - значение по-умолчанию, которое будет доступно, если ключ не будет найден.
  • char* s (для read string) - буфер, куда поместить строку.

Прототип Pluginreadstringfromini так и кричит “переполни меня”. Имя секции для настроек формируется так: “[Plugin %PLGNAME%]”, где %PLGNAME% - имя, возвращаемое плагином через ODBG_Plugindata. Вот и все, что можно сказать на тему хранения в INI.

Для демонстрации работы с UDD предлагаю написать еще один небольшой, но полезный пример. Как часто у тебя возникала необходимость, трассируя цикл, выбирать правой кнопкой в окне дизассемблера Follow in Dump -> Memory address, чтобы лицезреть что именно находится в памяти на данный момент? Например, если мотаешь ты цикл по элементам массива произвольной структуры и приходится каждый раз выполнять одно и тоже действие. Надоедает? Сейчас мы это полечим.

Предположим, мы хотим, чтобы окно дампа по указанному адресу при остановках (PP_SINGLESTEP, PP_HWBREAK и PP_INT3BREAK) показывало память по актуальному адресу (например, в команде mov ebx,dword[ebx+ecx*4+0Ch]). Для этого нужно вести список адресов, по которым такое наблюдение разрешено. Так же предположим, что хотим смотреть не только CPU Dump, но и CPU Stack, тогда к адресу нужно привязать еще и тип наблюдения (Dump или Stack). Получается уже список элементов структур. Экземпляры структур будут сохраняться в UDD файлах модулей, которым эти адреса принадлежат. Ок.

int ODBG_Pausedex(int reason, int, t_reg* reg, DEBUG_EVENT*)
{
    ulong cmdsize;
    char cmd[MAXCMDSIZE];
    t_disasm disasm;
    t_watchds *pwds;
    t_thread* thread;
    t_dump* dump;

    // если работаем по одному из этих событий И с текущим адресом
    if(
        ((reason & PP_INT3BREAK) || (reason & PP_HWBREAK) || (reason & PP_SINGLESTEP)) &&
        is_workaddr(reg->ip,&pwds)
        )
    {
        // получаем актуальный адрес, который нужно отобразить в окне дампа или стека
        cmdsize = Readcommand(reg->ip,cmd);
        Disasm((uchar*)cmd,cmdsize,reg->ip,NULL,&disasm,DISASM_ALL,Getcputhreadid());

        // для доступа к границам стека
        thread = Findthread(Getcputhreadid());


        // если предпочтительно показывать в стеке И адрес содержащийся команде
        // смотрит в стек текущего остановленного потока - тогда и только тогда показываем в стеке
        // stackbottom < stacktop
        if(pwds->wdstype == WDS_STACK && disasm.opaddr[pwds->index] >= thread->stackbottom && 
            disasm.opaddr[pwds->index] < thread->stacktop)
        {

            // устанавливаем адрес отображаемой вершиной стека
            Setcpu(0,0,0,disasm.opaddr[pwds->index],CPU_REDRAW);
            /*
            dump = (t_dump*)Plugingetvalue(VAL_CPUDSTACK);
            dump->addr = disasm.opaddr[pwds->index];
            */

        }else{

            Setcpu(Getcputhreadid(), 0, disasm.opaddr[pwds->index], 0, CPU_DUMPFIRST | CPU_REDRAW);

        }
    }

    return 0;
}

// подчищаем память
void ODBG_Pluginreset(void)
{
    list<t_watchds*>::iterator it;

    EnterCriticalSection(&g_CS);

    it = watchlist.begin();

    while(it != watchlist.end())
    {
        delete* it;
        it++;
    }

    watchlist.clear();

    LeaveCriticalSection(&g_CS);

}

// определяем свои записи и восстанавливаем
int ODBG_Pluginuddrecord(t_module* pmod, int ismain, ulong tag, ulong size, void* data)
{
    t_savewatch *pswds;
    t_watchds *pwds;

    if(tag != MAGIC0 || size != sizeof(t_savewatch))
        return 0;

    pswds = (t_savewatch*)data;

    // дополнительная проверка на вшивость
    if(pswds->magic1 == MAGIC1 && pswds->magic2 == MAGIC2)
    {
        pwds = new t_watchds();
        *pwds = pswds->wds;

        // адрес восстанавливаем по базе
        pwds->addr += pmod->base;

        EnterCriticalSection(&g_CS);

        watchlist.push_back(pwds);

        LeaveCriticalSection(&g_CS);

        return 1;
    }

    return 0;
}

// нужно сохраниться
void ODBG_Pluginsaveudd(t_module* pmod, int ismain)
{
    t_module *pmod2;
    list<t_watchds*>::iterator it;
    t_savewatch swds;

    // глобальные настройки указывают нужно ли сохраняться
    if(g_PlugSettings.bEnableSaveRestore == false)
        return;

    // авада кедавра
    swds.magic1 = MAGIC1;
    swds.magic2 = MAGIC2;

    EnterCriticalSection(&g_CS);

    it = watchlist.begin();

    // перечисляем точки наблюдения
    while(it != watchlist.end())
    {
        pmod2 = Findmodule((*it)->addr);

        // сохраняя только те, которые относятся к сохраняемому модулю
        if(pmod2 != NULL && pmod2->base == pmod->base)
        {
            swds.wds = **it;
            // адреса рекомендуют хранить в виде смещения от базы модуля
            swds.wds.addr -= pmod2->base;
            Pluginsaverecord(MAGIC0, sizeof(t_savewatch), &swds);
        }

        it++;
    }

    LeaveCriticalSection(&g_CS);
}

Это наиболее интересные части. Полный пример смотри в аттаче.

Итак. Работа с UDD строится на двух callback’ах и функции Pluginsaverecord.

void ODBG_Pluginsaveudd(t_module *pmod,int ismainmodule);
int ODBG_Pluginuddrecord(t_module *pmod, int ismainmodule, ulong tag, ulong size, void *data);
int Pluginsaverecord(ulong tag,ulong size,void *data);

ODBG_Pluginsaveudd вызывается, для главного модуля и тех модулей, которые запросили о сохранении настроек в UDD файл. Т.е. нет никаких гарантий, что модуль по которому ты работаешь будет сохраняться. Возможности запросить “вручную” сохранение я как-то не нашел. Конечно, если не извращаться с именами, которые, как мы знаем, как раз таки сохраняются в UDD. Данный callback единственное место, где можно вызывать функцию сохранения Pluginsaverecord.

  • t_module* pmod - модуль для которого сохраняются настройки.
  • int ismainmodule - если не 0, то pmod - главный модуль.

ODBG_Pluginuddrecord вызывается, когда читаются UDD файлы (что, надо полагать, случается при загрузке модулей). Грубо говоря, Olly читает запись из UDD и, если не опознает за свою, начинает опрашивать плагины у которых экспортируется данная функция. Если плагин вернет 1, т.е. опознает запись, то будет прочитана следующая и цикл повторится.
Параметры t_module* pmod и int ismainmodule теже, что и у ODBG_Pluginsaveudd.
ulong tag - уникальное значение, по которому призывают проводить опознание свой-чужой. Думаю, не стоит говорить, что 32-х бит на сегодняшний день маловато, поэтому в коде и реализовано опознание по tag’у и двум дополнительным 32-х битным сигнатурам, цепляемым к каждой записи.

  • ulong size - размер данных (до USERLEN, что соответствует 4096 байт).
  • void *data - сами данные.

Pluginsaverecord вызывают для сохранения одной записи в UDD. Если сохранение прошло удачно - вернется 1, иначе 0. Параметры и их назначение аналогичны одноименным версиям из прототипа ODBG_Pluginuddrecord.

Важным является и то в каком порядке вызываются функции. ODBG_Pluginsaveudd вызывается ДО ODBG_Pluginreset, а ODBG_Pluginuddrecord ПОСЛЕ.

В примере промелькнуло несколько новых функций.

ulong Readcommand(ulong ip, char *cmd);

Readcommand чаще всего используют перед вызовом Disasm, чтобы получить размер команды. Возвращает размер или 0, если память по указанному адресу не может быть прочитана.

  • ulong ip - откуда читать.
  • char* cmd - указатель на буфер размером MAXCMDSIZE.
ulong Disasm(char *src, ulong srcsize, ulong srcip, char *srcdec, t_disasm *disasm, int disasmmode, ulong threadid);

Дизассемблер, который я бы не назвал удачным. Несмотря на это, многие вещи с его помощью все таки можно сделать. Например, можно получить актуальный адрес памяти, к которой обращается один из операндов. Disasm возвращает размер дизассемблированной команды и заполняет структуру t_disasm.

  • char* src - указатель на буфер с прочитанной командой.
  • ulong srcsize - размер буфера. Мои попытки передавать 0 и MAXCMDSIZE, чтобы отказаться от Readcommand не увенчались успехом (может лыжи не едут…). Поэтому передаем размер команды.
  • ulong srcip - адрес команды.
  • char* srcdec - указатель на буфер для данных анализатора (таблицы switch, константы и строки). Если не нужен - NULL. Интересно, что не указано какого размера этот буфер нужен. Или хотя бы как узнать какой размер требуется.
  • t_disasm *disasm - сюда будут помещены данные.
  • int disasmmode - режим дизассемблирования. Одна из констант DISASM_XXX.
  • ulong threadid - идентификатор потока, регистры которого будут использованы (для вычисления эффективного адреса регистры требуются). Может быть NULL.

От режима дизассемблирования зависит какие поля структуры t_disasm будут заполнены (или не заполнены). Доступны режимы:

DISASM_SIZE    - только дизассемблер длин.
DISASM_DATA    - извлекает важные данные, текстовую информацию не берет.
DISASM_TRACE - тоже что и DISASM_DATA, только дополнительно извлекается содержимое целочисленных регистров.
DISASM_FILE - дизассемблирование производится с предположением, что регистры не определены и символьные имена недоступны, т.е. дизассемблируются бинарные данные.
DISASM_CODE - предполагает, что регистры не определены
DISASM_ALL - полное дизассемблирование

В целом Disasm использовать можно, но не во всех случаях можно добится желаемого. Так, мне пришлось внедрять сторонний дизассемблер BeaEngine, который нуждался еще и в перекомпилировании, чтобы изменить имя главной функции (тёзки) Disasm. На такой шаг меня толкнуло то, что Disasm Ольги крайне неохотно отвечал на вопрос “существует ли в одном из операндов обращение к памяти?”. В инструкции lea eax,[label] по его мнению вообще таких обращений нет, а предположение, что тип операнда (t_disasm.optype[x] и t_disasm.op[x].optype) может помочь, уперлось в какой-то дикий набор флагов DEC_XXX (применение которых, в свою очередь, регулярно путало понятия регистр и память).

void Setcpu(ulong threadid, ulong asmaddr, ulong dumpaddr, ulong stackaddr, int mode);

Функция Setcpu обновляет содержимое окна CPU, позволяя установить текущие адреса стека, дизассемблера и дампа.

  • ulong threadid - идентификатор потока, который нужно отобразить в CPU. Или 0, если поток изменять не надо. Если этот параметр не 0, параметры asmaddr и stackaddr игнорируются, а отображаемые адреса устанавливаются в соответствии с регистрами EIP и ESP указанного потока. Если threadid не является валидным идентификатором потока, будет произведено переключение на главный поток.
  • ulong asmaddr - адрес для отображения в окне дизассемблера. Если 0 - адрес не меняется. Если threadid != 0, этот параметр игнорируется.
  • ulong dumpaddr - адрес для отображения в окне CPU Dump. Если 0 - адрес не меняется.
  • ulong stackaddr - адрес для отображения в окне CPU Stack. Если 0 - адрес не меняется. Если threadid != 0, этот параметр игнорируется.
  • int mode - флаги CPU_XXX.
CPU_ASMHIST - добавить изменения в историю дизассемблера (видимо историю переходов клавишами "+"/"-").
CPU_ASMCENTER - спозиционировать указаный адрес в окне дизассемблера по центру.
CPU_ASMFOCUS - переместить фокус на окно дизассемблера.
CPU_DUMPHIST - см. CPU_ASMHIST. В документации примечание "(currently not available)".
CPU_DUMPFIRST - спозиционировать адрес dumpaddr первым байтов в окне CPU Dump.
CPU_DUMPFOCUS - переместить фокус на окно CPU Dump.
CPU_REGAUTO    - Automatically change Registers mode to FPU/MMX/3DNow! Хз - что имеется ввиду, но звучит красиво. Ясно только, что это не имеет никакого отношения к смене фокуса.
CPU_RUNTRACE - еще один параметр, значение которого не до конца ясно.
CPU_NOCREATE - если окно `CPU` не существует, то не создавать его.
CPU_REDRAW - перерисовать.
CPU_NOFOCUS - не передавать принудительно фокус главному окну. Как этот флаг работает опять же не ясно. Предположительно, можно использовать в ситуациях, когда пользователь находится в другом окне (например, бряков) и нет желания помогать ему с переключением. Но тогда флаг имеет смысл, только если `Getstatus() == STAT_RUNNING`, поскольку любое debug event моментально вручит CPU фокус.

В качестве альтернативы Setcpu можно использовать, например, такой метод (устанавливает с помощью t_dump отображаемый верхним адрес в стеке):

dump = (t_dump*)Plugingetvalue(VAL_CPUDSTACK);
dump->addr = disasm.opaddr[pwds->index];

На этом предлагаю закончить. Приведенной информации хватит в 80% случаев, поскольку наиболее часто к PDK обращаются для того, чтобы написать трейсер или логгер (и не исключено, что вместе). Полные коды примеров из статьи смотри в аттаче. Там же ищи отдельный плагин r4u со всеми полезностями описанными в тексте. Чтобы антивирусы не верещали на FASM’овые примеры, они запаролены reverse4you. Объемная тема окон, которая была обещана, но не затронута, будет в следующей серии (конечно, если это хоть кому-нибудь нужно)…

m0r1 (c) r0 Crew

Статья отличная, спасибо m0r1. Продолжай в том-же духе.

Offtop

MDCODE - на панели редактирования белый квадрат, со стрелочкой вниз. Отдаленно похоже на М.

Спасибо на добром слове.

Видимо, второй части не избежать=)

Отличное чтиво и мануал. Жду продолжения)

Очень давно не читал ничего подобного! Автор дай две :slight_smile:
P.S. Даже в PDF сохранил :3

Вообще никак не избежать :wink:

Ребят, версия над второй частью уже идёт. Но… чисто мое мнение - в большинстве случаев Вам хватит и этих знаний.

Да там и без статьи можно с легкостью разобраться. В общей сложности я написал 5 плагов для 1 и 2 ольки, было бы желание. А так что, все прочитали, по-лайкали и забыли, вряд ли кто-то будет писать плаги.

Со статьей вхождение намного проще и быстрее =) Те кто собирается что-либо писать, сначала гуглят тему, читают и только потом уже пишут :wink: Так что статья полезна во всех смыслах.

В “мое время” :slight_smile: не было статей, была справка по апи и несколько плагов с открытыми исходниками. Вот так мы тогда жили )

Статья писалась для тех, кто знаком с реверсом, но мало знаком с плагиностроением под Olly (7 лет назад я всего то знал языки Asm,VBScript,JS,Pascal,Perl,PHP и кое-как C). Кроме того, в статье есть исследования ASM кода отладчика, которые никто не публиковал, но наверняка не я один не единожды исследовал. Вы, уж, извините, условия чтения, специально для Вас, я изменю.

Да не слушай ты его, он у нас вечно плохого полицейского разигрывает.

Зачем, всем же нравится, а подстраиваться под одного человека не стоит.

Оба сообщения (m0r1 и BoRoV) были отредактированы. Вы не правильно друг друга поняли. Прошу не разводить оффтоп, отказаться от перехода на личности и необоснованных / преждевременных выводов.