Написание Plugin модулей для IDA. Основы. — Архив WASM.RU
На данный момент IDA является пожалуй самым мощным дизассемблером, обладающим огромным количеством возможностей. Но всё это сводилось бы на нет, если бы ни ещё одна - возможность расширения стандартных функций посредством дополнительных модулей, в простонародии - плагинов. Рассмотрим теперь подробнее как же эти плагины писать.
Ну во первых для всего этого вам потребуется сам дизассемблер, который IDA называется, а также пакет SDK для вашей версии. Главная часть SDK - это директория include со включаемыми файлами, в которых содержаться все константы и прототипы функций, которые используются при написании плагинов, а также библиотека импорта - ida.lib. Мы здесь будем рассматривать компиляторы, которые совместимы с VC++, а именно Intel C++. Выбрал я его потому, что в SDK нет его встроенной поддержки(например для компиляторов Borland C++ и самого VC++ в SDK входят файлы инсталляции), а это позволяет лучше понять рассматриваемую тему. Файл ida.lib является библиотекой импорта от ida.wll(расположен в директории с IDA). Все функции оттуда экспортируются по ординалам, а сопоставление имён ординалам производится в ida.lib. Вот почему лучше использовать SDK от той же версии, что и IDA, т.к. ординалы экспортируемых функций могут меняться от версии к версии. Ну ладно, допустим у вас всё есть, приступим.
Что же представлят собой этот самый плагин? А представляет он собой, как ни странно, обычную DLL, у которой экспортируется следующая структура под именем PLUGIN:
struct plugin_t
{int version; //Показывает версию IDA, для которой пишеться плагин, обычно это поле равно IDP_INTERFACE_VERSION - константе, показывающей, какой версии SDK. Если значение этого поля не совпадёт с используемой версией IDA, то плагин загружен не будет.
int flags //Проще будет перевести, это просто флаги. Проще всего заполнить это поле нулём, но можно сказать, какие здесь есть возможности.
PLUGIN_MOD - Установка этого флага значит, что плагин меняет базу с листингом.(Честно говоря мало плагинов, которые её не меняют). Ставиться этот флаг затем, что если в модуле процессора(здесь не рассматривается) запрещается изменять базу, то плагин нельзя будет использовать. В общем в модуле x86 всё разрешается, вообще этот флаг нам не нужен, ровно как и остальные.
PLUGIN_DRAW - Установка этого флага указывает IDA на то, что после вызова плагина надо перерисовать(т.е. обновить) своё окно.
PLUGIN_SEG - Плагин может вызываться, если адрес, на котором стоит курсор, принадлежит какому-нибудь сегменту. Тоже не особо нужен.
PLUGIN_UNL - Если установить этот флаг, то после использования плагин будет выгружаться из памяти. Это конечно хорошо для отладки, но нам не очень подходит.
Так как это флаги, то их можно комбинировать с помощью логического или(or по нашему или | в языке С), а также через простое сложение. Но проще, как я уже говорил, заполнить это поле красивым словом NULL.
void (idaapi* init)(void); //А это функция инициализации, вызывается при попытке загрузить плагин, и если эта функция вернёт что-то плохое, то плагин не загрузиться.
void (idaapi* term)(void); //Функция, вызываемая при выгрузке плагина. Обычно эта пустышка, используется редко.
void (idaapi* run)(int arg); //Наверное самое важное поле - рабочая функция, которая запускается при каждом вызове плагина и содержит основной его код.
char* comment; //комментарий, используется как подсказка.
char* help; //чтож, help есть help.
char* wanted_name; //Предпочитаемое имя плагина.
char* wanted_hotkey; //Предпочитаемая горячая клавиша. Вообще, вне зависимости от того, какие там клавиши предпочитает автор плагина, истинные значения имени и горячей клавиши записываются в файл plugin.cfg, о котором речь пойдёт ниже.
};Вообще то в include файле loader.hpp эта структура объявлена как класс, но для тех, кто не знает, скажу, что в C++ класс и структура в сущности одно и тоже.Ну вроде как настало время и исходник привести. Я привожу здесь простейший плагин, который в базе будет менять следующие бесполезные группы инструкций:
pusha
popa
push eax
push edx
rdtsc
pop edx
pop eax
push edx
push eax
rdtsc
pop eax
pop edx
на "более понятные" инструкции nop. Вот и исходник:#pragma comment(linker,"/NODEFAULTLIB")
#pragma comment(linker,"/Entry:Dllmain") //Что-то не очень мне нравиться Runtime-библиотека, забирает 40kb, а толку от неё почти никакого, особенно в плагине. А если уж её отключать, то и точку входа надо переобозначать.
#define __NT__
#define __IDP__ //А вот это надо прописать ОБЯЗАТЕЛЬНО, а то плагин работать не будет.
typedef unsigned char BYTE; //Стандартные include-файлы здесь не подключаются, а тип BYTE лишним не будет.
#include <ida.hpp>
#include <idp.hpp>
#include <bytes.hpp>
#include <loader.hpp>
#include <kernwin.hpp> //"Джентльменский набор" подключаемых файлов из IDA SDK
//Переобозначенная точка входа
bool Dllmain(void* hInstDLL, int reason, int reserved)
{return true;}
char comment[] = "Experimental plugin";
char help[] = "Experimental plugin";
char wanted_name[] = "ExpPlugin";
char wanted_hotkey[] = "F11";
BYTE rdtsc1[]={0x52, 0x0F, 0x31, 0x5A, 0x58};
BYTE rdtsc2[]={0x50, 0x0F, 0x31, 0x58, 0x5A}; //Это просто опкоды инструкций
//Процедура инициализации плагина
В зависимости от возвращаемого значения IDA может загрузить и не загрузить плагин. Вот, что она может возвращать
PLUGIN_SKIP - Плагин не будет загружен.
PLUGIN_OK - Ну раз OK, то есстественно плагин будет загружен.
PLUGIN_KEEP - Плагин не только будет загружен, но и будет постоянно висеть в памяти
К примеру, чтобы плагин работал только с файлами PE формата, надо в процедуру инициализации прописать следующий код:
if (inf.filetype != f_PE) return PLUGIN_SKIP;
return PLUGIN_OK;
Структура inf здесь также не рассматривается, для изучения подробностей см. SDK. В данном же случае плагин будет загружаться всегда, поэтому код таков:
int init(void)
{return PLUGIN_OK;}
//В unload - процедуре тоже не производиться никаких действий, это пустышка.
void term(void)
{return;}
//Ну и наконец главная процедура
Перед рассмотрением главной процедуры можно для интереса рассмотреть функции для чтения и записи в базу, а также для получения текущего адреса, т.е. адреса, на котором стоит курсор в IDA:
get_byte
get_word
get_long - Чтение байтов из базы. Принимают один параметр - адрес, который отображается в листинге.
patch_byte
patch_word
patch_long - Ну соответственно запись байтов в базу. Параметров два, первый параметр - адрес, второй - значение
Как можно догадаться, функции, у которых на конце написано byte работают(т.е. принимают и возвращают) байт, word - слово, а long - двойное слово.
get_screen_ea - Функция для получения текущего адреса, который она и возвращает.
void run(int arg)
{BYTE cb1;
int i, tmp0;
ea_t CA = get_screen_ea();
cb1=get_byte(CA);
//pusha
//popa
if (cb1==0x60 && get_byte(CA+1)==0x61) //Опкод команды pusha - 60h, а команды popa - 61h
{patch_word(CA, 0x9090);
return;}
//rdtsc
if (cb1==0x50) //Первый байт последовательности - опкод команды push eax
{tmp0=1;
for (i=0; i<5; i++) //Остальные байты в массиве, проверяются в цикле
if (get_byte(CA+i+1)!=rdtsc1[i]) {tmp0=0; break;}
if (tmp0==1)
{patch_long(CA, 0x90909090);
patch_word(CA+4, 0x9090);
return;}}
if (cb1==0x52)
{tmp0=1;
for (i=0; i<4; i++)
if (get_byte(CA+i+1)!=rdtsc2[i]) {tmp0=0; break;}
if (tmp0==1)
{patch_long(CA, 0x90909090);
patch_word(CA+4, 0x9090);
return;}}
};
//Теперь помещаем здесь описанную ранее экспортируемую структуру.
//Прописать extern "C" необходимо, чтобы линкер не извратил имя экспортируемой
//структуры
extern "C" plugin_t PLUGIN = {
IDP_INTERFACE_VERSION,
0,
init,
term,
run,
comment,
help,
wanted_name,
wanted_hotkey
};
Теперь посмотрим, как его надо компилировать. Вот bat файл:
© dragon
icl -Gz /I <Путь к папке с include-файлами из SDK> plugin.cpp ida.lib /link /subsystem:windows /DLL /def:plugin.def /out:exp.plw
del "<Путь к папке с IDA>\plugins\exp.plw"
copy exp.plw "<Путь к папке с IDA>\plugins\"
PAUSE
Как я уже говорил, пример для Intel C++, если у вас VC++, то исходник менять не надо, а командную строку для компиляции придёться переделать (просто-напросто замените icl на cl). Теперь поподробнее.
-Gz говорит интеловскому компилятору, что передача параметров в функции по умолчанию будет stdcall.
/I - Путь к include директории IDA SDK.
plugin.cpp - исходник с плагином.
ida.lib - библиотека импорта из SDK, должна быть в одной папке с исходником.
После параметра /link идут опции линкера, комментировать нет необходимости. Кроме def-файла. Раз уж здесь есть исходник и bat-файл, то уж придёться приводить и def- файл.
LIBRARY exp
EXPORTS PLUGIN
Т.е def файл указывает, что надо экспортировать структуру PLUGIN. В общем теперь всё, плагин готов к использованию, осталось лишь добавить его в IDA. Для начала неплохо бы скопировать его в папку с plugins, которая находится в папке с IDA. Но этого мало, надо ещё его сконфигурировать. Вся конфигурация плагинов храниться в файле plugins.cfg в той же папке plugins. Обычно вначале этого файла идут комментарии, а в конце записи уже установленных плагинов. Чтобы добавить наш exp.plw(плагины для работы под Windows имеют расширение plw), надо добавить в конец файла четыре параметра в следующем порядке:
Имя_плагина Имя_файла Горячая_клавиша Аргумент.
Аргумент этот передаётся в функцию run, лично мне кажеться, что толку от него никакого, поэтому установим его в 0. Горячую клавишу можно установить F11, вроде как она не используется IDA. Значит добавляемая нами строчка должна выглядеть так:
expplugin exp F11 0
Точку с запятой впереди ставить не рекомендую, т.к. это комментарий. Ну в общем можно считать, что работа закончена. Осталось только запустить IDA, открыть какой-нибудь файл, где встречаются инструкции, которые наш плагин обрабатывает(например можно самому быстренько на асме написать и скомпилировать), и проверить. Если всё сделано правильно, то при нажатии F11 или вызова плагина через меню, инструкции должны заменяться nop'ами. Разумеется курсор должен стоять на начале этих scrambled инструкций, если их можно так назвать, всё-таки это мусорные инструкции. Вот собственно и всё.
Написание Plugin модулей для IDA. Основы.
Дата публикации 6 ноя 2003