Суббота, 11.05.2024, 10:37
Главная
Регистрация
Вход
Софтер
Приветствую Вас Гость | RSS
Меню сайта
Категории раздела
Мои статьи [34]
Партнер
Реклама
Каталог сайтов Каталог сайтов :: Развлекательный портал iTotal.RU
Radio D-FM Club
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
Главная » Статьи » Мои статьи

Написание эмулирующего отладчика для IDA

1. Вступление

Как-то в Интернете, наткнулся я на отрывок книги Криса Касперски. Вот цитата:

" !IDA DEBUGGER!

В чем новизна и удобство идеи? А в том, что можно сделать не полный, а _контекстный_ отладчик! Что это такое? Обычный дебаpег отлаживает всю программу целиком. Это хоpошо, но чаще всего нас интересуют только выбранные фрагменты. В IDA можно подогнать куpсоp к нужному месту, задать начальные значения pегистpов и пусть на выполнение эмулятоp. Такой подход пpежде всего упоpщает задачу, т.к. тут скоpость не тpубуется. А как удобно! Можно видеть pаботу кода в динамике! И не ломать голову какие будут значения pегистpов\флагов на выходе из такого и такого фpагмента и куда метнеться условный пеpеход. Можно просто прогнать чисто локальный кусок с любой его точки."

Я понимаю, что отладчик для IDA, начиная с версии 4.5 существует, но во-первых не у всех она есть, во-вторых создать отладчик своими руками очень даже интересное дело, поможет поближе ознакомится с устройством IDA и её плагинов. Информация по написанию плагинов для дизассемблера есть в примерах SDK. Сразу надо сказать, что это будет контекстный эмулирующий отладчик 32 разрядного режима. Он будет тесно взаимодействовать с пользователем по части работы. Ну например если эмуляция какой-то команды отсутствует, то предлагается, что пользователь сам проэмулирует её и скорректирует значения регистров, стека.

Предлагаю свой проект, как один из вариантов. Писал я его из-за желания поглубже узнать IDA. Если его довести до ума, то может получится очень полезный program, который может помочь при ручной распаковке и анализе программ.

2. Общее построение

Лучше всего показать на рисунке. В принципе, подойдет не только для отладчика, но и для любого другого плагина.

Чтобы можно было корректно эмулировать, надо дополнительно создать сегмент стека в виртуальном пространстве дизассемблера-это необходимо для работы команд со стеком. Как его создать будет показано ниже.

Управление ведется с консоли дизассемблера-как вызов функций. Также можно организовать "горячие" клавиши. Вывод информации на экран - посредством окна сообщений и информативных окошек. Самое интересное - эмуляция команд. После подвода курсора к нужному адресу и ввода начальных значений в регистры, начинается работа отладчика.

В качестве подопытной была взята IDA 4.5 internal (лежала на www.crackbest.com). Также необходим SDK. Без SDK в создании плагинов делать нечего. Команды управления отладчиком были введены при помощи IDC скрипта - "deb_comm.idc". Этот скрипт компилируется во время инициализации плагина (скрипт должен лежать в директории idc дизассемблера), если потерпит неудачу, то можно попытаться загрузить скрипт вручную из меню File\IDC file. Именно в этом скрипте находятся вызовы функций отладчика, можете повесить на функции "горячие клавиши", и добавить новые. Здесь показан формат вызова команды отладчика:

 #define MY_FUNC_ID xx
static MY_NEW_COMMAND()
{
RunPlugin("IDA_deb",MY_FUNC_ID);
}

Которую после добавления в "deb_comm.idc" можно будет вызывать прямо с консоли, например так: MY_NEW_COMMAND(); . Только незабудьте перед этим вставить в функцию run() плагина обработчик этой команды, которая будет обрабатываться, если аргумент будет равен MY_FUNC_ID.

3. Немного об виртуальной памяти IDA

Для написания отладчика надо знать, об виртуальной памяти. Подробнее смотрите "Образ мышления IDA" Криса Касперски. Прошу простить меня за такое представление виртуального адресного пространства IDA и её базы данных, это упрощено, но отладчик работает именно с данными в виртуальной памяти, в которой лежит база данных. На рисунке показано соотношения между адресами (названия взяты от самого создателя IDA):

Линейный адрес (тип ea_t) - это адрес в виртуальном пространстве IDA Базовый адрес сегмента - это линейный адрес начала сегмента в виртуальном пространстве IDA Виртуальный адрес - это смещение между линейным адресом и базовым адресом сегмента: Виртуальный адрес=Линейный адрес - Базовый адрес сегмента Как вы уже , наверно, знаете, IDA работает со своим виртуальным пространством, а для дизассемблированной программы создаются сегменты со своими атрибутами (база, селектор разрядность и.т.п.), и с ним же будет работать и наш отладчик, через функции get_byte(..), get_word(), get_long, patch_byte, patch_word, patch_long - все они определены в BYTES.HPP, и там ещё много интересного есть. Линейный адрес вычисляется как

(Base<<4+смещение), где Base - база сегмента

4. Как эмулировать?

Думаю, для этого нужно узнать, как работает то, что мы будем эмулировать - т.е. центральный процессор. Если заглянуть в талмуды Intel, то можно найти примерно следующую последовательность действий по исполнению инструкций:

Like the Intel486 CPU, integer instructions traverse a 5 stage pipe-line. The pipe-line stages are as follows:

 PF Prefetch
D1 Instruction Decode
D2 Address Generate
EX Execute - ALU and Cache Access
WB Writeback


Т.е
-предвыборка команды
-декодирование команды
-генерация адреса, для определения адреса операндов в памяти
-выполнение опреаций с помощью АЛУ
запись результата

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

Чтобы приступить к непосредственной эмуляции, давайте разберемся, как IDA хранит инструкции. Для этого имеются несколько структур, они описаны в файле INCLUDE\UA.hpp. Этот файл ОЧЕНЬ важен при написании эмулятора, т.к. в нем описывается формат данных команды, её операндов их типы.


// Хранит информацию о команде

idaman insn_t ida_export_data cmd; // текущая инструкция


// Класс описывающий инструкцию (сокращено)
class insn_t
{
public:

ushort itype; // Код инструкции (Инициализируется в 0 ядром
// IDA все инструкции содержатся в директории
// INCLUDE\allins.hpp

ulong cs; // База сегмента, в котором находится
// инструкция (Инициализируется ядром IDA
ulong ip; // Виртуальный адрес инструкции (адрес ВНУТРИ
// сегмента) блин, от этих виртуальных адресов
// уже сам начинаешь виртуалиться //(Устанавливается ядром
// ИДА, а кем-же ещё)

ea_t ea; // Линейный адрес инструкции

ushort size; // Размер инструкции в байтах


// Сведения об операндах инструкции
#define UA_MAXOP 6
op_t Operands[UA_MAXOP]; // Массив содержит структуру описывающую каждый
// операнд в команде. К нему удобно обратится
#define Op1 Operands[0] // ,например, так: op_t x= cmd.Op1-получаем ин-
#define Op2 Operands[1] // формацию об первом операнде (странно, что не
#define Op3 Operands[2] // нулевой)
#define Op4 Operands[3]
#define Op5 Operands[4]
#define Op6 Operands[5]
};

// Класс - тип операнда, (показано неполностью)
class op_t
{
public:
char n; // Порядковый номер операнда

// Структура описывающая тип операнда (очень интересное поле)
optype_t type;


// Тип значения операнда

char dtyp;

#define dt_byte 0 // 8 bit
#define dt_word 1 // 16 bit
#define dt_dword 2 // 32 bit
#define dt_float 3 // 4 byte
#define dt_double 4 // 8 byte
#define dt_tbyte 5 // variable size (ph.tbyte_size)
#define dt_packreal 6 // packed real format for mc68040
#define dt_qword 7 // 64 bit
#define dt_byte16 8 // 128 bit
#define dt_code 9 // ptr to code (not used?)
#define dt_void 10 // none
#define dt_fword 11 // 48 bit
#define dt_bitfild 12 // bit field (mc680x0)
#define dt_string 13 // pointer to asciiz string
#define dt_unicode 14 // pointer to unicode string
};

Ну и, наконец, описание типа операнда
typedef uchar optype_t;
const optype_t // Поле в op_t
o_void = 0, // нет операнда ----------
o_reg = 1, // Основной регистр (al,ax,es,ds...) reg
o_mem = 2, // Прямое обращение к памяти addr
o_phrase = 3, // Memory Ref [Base Reg + Index Reg] phrase
o_displ = 4, // Memory Reg [Base Reg + Index Reg + Displacement] phrase+addr
o_imm = 5, // Непосредственное значение value
o_far = 6, // Непосредственный дальний адрес addr
o_near = 7, // Непосредственный ближний адрес addr
o_idpspec0 = 8, // IDP specific type
o_idpspec1 = 9, // IDP specific type
o_idpspec2 = 10, // IDP specific type
o_idpspec3 = 11, // IDP specific type
o_idpspec4 = 12, // IDP specific type
o_idpspec5 = 13, // IDP specific type
o_last = 14; // first unused type

char specflag1;
char specflag2;
char specflag3;
char specflag4;
}

В структуре optype_t есть следующие поля:

char specflag1;
char specflag2;
char specflag3;
char specflag4;

Эти флаги заполняются ядром ИДА (точнее процессорным модулем). Как заполняются не сказано, но, проведя эксперименты, с различными инструкциями и способами адресации, удалось выяснить вот что:

Флаг specflag1 и все остальные равен нулю если:
1. Имеется прямая адресация (OpX.type=o_mem). Т.е. например mov eax,[401000]
2. Косвенная адресация (OpX.type=o_phrase) mov eax,[eax]
3. Косвенная адресация со сдвигом (OpX.type=o_displ) mov eax,[eax+402031]
Если specflag1==1, то в specflag2 содержится информация об составляющих операнда (например при индексной адресации с масштабированием) Если OpX.type=o_phrase, то specflag2 будет расшифровываться так:
7
6
5
4
3
2
1
0
Коэффициент масштабирования
Индексный регистр
Базовый регистр


Кодировка регистров в точности, как принята у Intel:

EAX
0
ECX
1
EDX
2
EBX
3
ESP
4
EBP
5
ESI
6
EDI
7

Коэффициент масштабирования определяет степень двойки, на которую нужно умножить значение индексного регистра:

 
Коэф.Умножения
00
1
01
2
10
4
11
8

Например :


lea eax, [ebx+ecx*4]

Op2 type is: 3
Op2 dtyp is: 0
Op2 value is: 0
Op2 reg is: 4
Op2 phrase is: 4
Op2 address is: 0
Op2 offb is: 0
Op2 offo is: 0
Op2 specflag1 is: 1
Op2 specflag2 is: 9B
Op1 specflag3 is: 0

Op2 specflag2 is: 9B 10001011
10-4
011-ebx
001-ecx

Если OpX.type=o_mem, то specflag2 будет расшифровываться так:

7
6
5
4
3
2
1
0
         
1
0
1
Коэффициент масштабирования
Регистр
Точно не выяснил, но всегда равно константе

Если OpX.type=o_displ, то specflag2 будет расшифровываться так:

7
6
5
4
3
2
1
0
Коэффициент масштабирования
Индексный регистр
Базовый регистр

Также в этом случае, в поле OpX.addr, будет находится значение смещения

Однако здесь есть одно исключение для обращения к памяти вида: [esp+xxx] В этом случае specflag1=1; specflag2=0x24

Разберемся с сегментами IDA. Сетменты в IDA-один из её краеугольных камней. И надо ознакомится со структурами, которые их описывают: Нам они понадобятся для создания сегмента стека отладчика. Без него работать, мягко говоря некорректно. Каждый сегмент имеет базовый сегментный адрес, который определяет положение сегмента в виртуальной памяти IDA. В поле Base может находится базовый адрес, а может находится индекс селектора. Селектор содержит 32-битный базовый адрес сегмента в виртуальном пространстве IDA. Подробнее смотрите "Образ мышления IDA" Криса Касперски.

Данные и функции работающие с сегментами содержатся в заголовочном файле SEGMENT.HPP . К сегментам можно обращаться разными способами. Тип структуры, описывающею сегмент segment_t. В ней содержится вся информация о сегменте: База, начальный адрес, конечный адрес, имя сегмента, выравнивание сегмента, разрядность 16 или 32 бит, и.т.д.


class segment_t : public area_t
{
public:

long name; // имя сегмента
long sclass; // класс сегмента
long orgbase; // это поле зависит от IDP
uchar align;
uchar comb; // Код комбинирования сегмента с другими
uchar perm; // "Разрешение" сегмента (EXE, READ, WRITE)
uchar use32; // разрядность сегмента
ushort flags; // флаги сегмента
sel_t sel; // селектор сегмента
uchar type; // Тип сегмента
};

Вот как примерно выглядит создание сегмента стека для отладчика. Подробное описание функций смотрите в SEGMENT.HPP


// Находим пустой селектор и проецируем на виртуальную память
// N - селектор для сегмента
sel_t N=allocate_selector(0);
// Создаем сегмент
if(add_segm(N,0xFFFF0000,0xFFFFFFFE,"STCK","STACK"))
{
segment_t DEBUGGER_STACK_SEGMENT, *pDSS;
pDSS = &DEBUGGER_STACK_SEGMENT;

pDSS = get_segm_by_sel(N);
set_segm_addressing(pDSS,1);

msg("Debugger stack created\n");
}
else warning("Cant create segment\n");

Как видите, ничего сложного нет.

5. Об обработчике команд

В эмуляторе всю работу по исполнению инструкций выполняют обработчики команд. Было решено в обработчик передавать тип соотношения операндов (например: один операнд - регистр, один операнд в памяти, один регистр - другой в памяти). В исходниках это выполняет функция get_operand_info(OPER &), которая заполняет структуру OPER. Также она заполняет поля структуры: вычисленными значениями операндов (например, при косвенной адресации). Сам обработчик команды знает о том какие операнды и какие сочетания операндов возможны и в зависимости от этого выполняется. Покажу на примере инструкции push:


// push instruction
BYTE emPUSH(OPER &O){
// Выводить о работе команды, как вы понимаете не обязательно.
// Просто удобно при отладке обработчика
msg("This is a PUSH cmd emulation\n");

// В стеке есть место??
if(!checkESP(-4)) return false;

// Проверка, если один из операндов в памяти, на принадлежность к
// несуществующему адресу.
if(!checkEA(O)) return false;

EIP2NextCmd(O);
// Операнд один - непосредственное значение 32бит
if(O.rel==i32) {
*pemESP-=4; //Уменьшили ESP
return (WriteVMDWORD(*pemESP,O.op0_i32));//Пишем в вирт. память
}

// Операнд один - непосредственное значение 16бит
if(O.rel==i16) {
*pemESP-=2;
return (WriteVMWORD(*pemESP,O.op0_i16));
}
// Операнд один - непосредственное значение 8бит
if(O.rel==i8) {
*pemESP-=2;
return (WriteVMWORD(*pemESP,(WORD)O.op0_i8));
}
// Здесь должна быть проверка на опернды - регистры и операнды, находящиеся в // памяти
// if(O.rel==r32). . .
// if(O.rel==r16). . .

// if(O.rel==m32). . .
// if(O.rel==m16). . .

// Если мы здесь, то операнды неопознаны
warning("UNKNOWN OPERAND IN PUSH CMD");
return INS_BAD_OP;
}

Это одна из "частных" инструкций и для других писать обработчик намного проще. Например эмуляция инструкции cmp


// cmp
void __declspec(naked) emCMP8() {_asm {cmp al,bl
retn}
}
void __declspec(naked) emCMP16(){_asm {cmp ax,bx
retn}
}
void __declspec(naked) emCMP32(){_asm {cmp eax,ebx
retn}
}

///////////////////////////////////////////////////////
// cmp
///////////////////////////////////////////////////////
bool emCMP(OPER &O){
if(!checkEA(O)) return false;

EIP2NextCmd(O);
return two_op_handle(O, emCMP8, emCMP16, emCMP32);
}

Вот и весь обработчик инструкции. Вся работа будет происходить в two_op_handle, которая и учтет типы операдов их типы, и воздействие на флаги.
Перед написанием обработчика инструкции, неплохо бы узнать, что возвращает структура cmd , так как может отличаться для разных инструкций. Например для инструкции lea eax,[ebx] cmd.Op1.dtyp = dt_dword, а cmd.Op1.dtyp = dt_byte. Поэтому ,возможно, придется добавить обработку ситуации в функцию get_operand_info.

При написании обработчиков ОЧЕНЬ неплохо помогает справочник из книги Юрова "Ассемблер", там расписаны все типы операндов команд и алгоритм их работы.

6. Послесловие

На данный момент поддерживается:
1) Точки останова BPX,
2) BPM_R, BPM_W, BPM_RW
3) Эмулировано 122 инструкции x86 (кроме 3-х операндных imul, shld, shrd, enter, leave и др., их можете реализовать сами.)

Команды управления отладчиком можно уточнить как help(); из консоли IDA.

Надо добавить: 1) Создать нормальный показ регистров в отладчике в виде отдельного диалогового окна
2) При желании можно добавить эмуляцию FPU, MMX
Самое серьезное, что непонятно, как эмулировать вызовы WinAPI функций. Похоже, что неполучится, но в принципе в контекстном отладчике это и ненужно, хотя было бы неплохо.

В приложении содержится исходный код отладчика, а также скомпилированная версия плагина для IDA 4.5 Похоже, что файл plugins.cfg необязателен (ИДА 4.5), т.к. и без него плагин вызывался нормально. Подозреваю, что ИДА сканирует директорию с плагинами и узнает параметры через структуру PLUGIN. Если неполучится, то добавьте следующую строку:
CDEx86 CDEx86 F11 0
, также можно добавить горячую клавишу к плагину.

Первые шаги по работе:
1) Скопируйте CDEx86.plw в папку \plugins дизассемблера.
2) Запустите дизассемблер
3) Приведите курсор к куску кода, который хотите изучить, и начинаите трассировку ( по умолчанию клавиша F11)
4) Значения регистров можно посмотреть вызвав r(); из консоли (Shift+F2). В следующей версии ,возможно, будет отдельное окно с регистрами.
5) Если хотите автоматическую трассировку, то подведите курсор к месту, где должна остановится трассировка и введите из консоли точку останова.
bpx();
at();

Точно так устанавливаются виртуальные бряки на чтение\запись в память bpm();
Поработав со своим творением, у меня появились некоторые рекомендации по работе с ним:
1) Отладчик предполагает хорошие навыки работой с IDA.
2) Не надо лезть в вызов WinAPI функции, т.к. ее кода нет в дизассемблере и соответственно нельзя ее трассировать. Лучше всего перепрыгнуть её ( cheip(); ), сбалансировав стек.
3) Отладчик получает инструкцию по адресу курсора IDA. Поэтому если вы прыгнете на середину команды, то получите сообщение Not code. Stay at code first. В этом случае надо сперва превратить в Unknown, а затем превратить в Code.

Ненадо требовать от контекстного отладчика сверхъестественного. Я писал его лишь из-за интереса к внутреннему устройства IDA. Если чего-то нехватает, добавьте сами - исходники есть.

Хотелось бы сказать большое спасибо Крису Касперски, за его отличные книги и статьи.

Категория: Мои статьи | Добавил: Morris (25.10.2009)
Просмотров: 1489 | Комментарии: 1 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Форма входа
Логин:
Пароль:
Поиск
Статьи
[25.10.2009][Мои статьи]
Знакомство с веб программированием (0)
[25.10.2009][Мои статьи]
Работа с файлами (0)
[17.09.2011][Мои статьи]
Продвижение сайтов, поднятие ТИЦ, PR, увеличение посещаемости сайта, увеличение трафика (0)
[10.01.2010][Мои статьи]
коды к игре Dragon Age Origins (0)
[25.10.2009][Мои статьи]
Сессии и cookie в PHP (1)
Навигация
  • Locations of visitors to this page
  • Рейтинг!!!
    Яндекс цитирования

    Рейтинг@Mail.ru


    | Copyright MyCorp © 2024 | Сайт управляется системой uCoz|