На главную | Содержание | Назад | Вперёд
Наши друзья

 

 

Создание базового средства для охвата кода

Как уже было указано, во всех средствах охвата кода (как коммерческих, так и бесплатных) отсутствуют важные возможности и методы визуализации данных, ко­торые очень интересуют хакера. Вместо того чтобы "сражаться" с дорогими и неэф­фективными средствами, почему бы не создать аналог самостоятельно? В этом раз­деле речь пойдет о чрезвычайно полезном и в то же время простом средстве охвата кода, которое можно создать, используя отладочные вызовы API. Это средство будет отслеживать все условные ветвления в программном коде. Особо выделяются слу­чаи, если выбор ветви по условию происходит на основе введенных пользователем данных. Очевидно, что цель заключается в определении того, исполняются ли вве­денные данные во всех вероятных ветвях, которые можно контролировать.
В нашем примере это средство запускает процессор в пошаговом режиме и от­слеживает каждую команду с помощью дизассемблера. Нашей основной задачей яв­ляется выявить искомый блок кода (code location). Блок кода представляет собой не­прерывный блок команд без операторов условного перехода. Команды условного перехода соединяют между собой блоки кода. От одного блока кода программа пере­ходит к исполнению другого блока. Нам нужно отследить все "посещенные" блоки кода и определить, обрабатывались ли в них введенные пользователем данные. Для отслеживания блоков кода мы использовали следующую структуру.
//Блок кода struct item <
item () {
subroutine=FALSE; is_conditional=FALSE; isret=FALSE; boron=FALSE; addressab­le ngth=l,* x=0; y=0;
column=0;
m hasdrawn=FALSE;
bool subroutine;
bool is_conditional;
bool isret;
bool boron;
bool m_hasdrawn;      // Для остановки циклических ссылок
int address; int length; int column; int x; int у;
std::string m_disasm; std::string mborons;
std::list<struct item *> mChildren;
Struct item * lookup(DWORD addr) {
std::list<item *>::iterator i - mChildren.begin(); while(i != mChildren.end())

struct item *g if (g->address i + + ;
В каждом блоке кода есть набор указателей ко всем "адресатам" условного пере­хода из этого блока кода. Также в каждом блоке кода есть строка, в которой "отражаются" команды ассемблера, составляющие блок кода. Следующий фрагмент кода исполняется при каждом пошаговом событии.
struct item *anltem ■ NOLL;
// Проверим, что контекст является новым. theThread->GetThreadContext О ;
// Дезассемблируем искомую команду, mdisasm.Disasm( theThread );
// Определим, является ли она целью условного перехода, if(m_next_is_target || m_next_is_calltarget)
anltem - OnBranchTarget( theThread );
SetCurrentItemForThread( theThread->m_thread_id, anltem); m_next_is_target « FALSE; m_next_is_calltarget - FALSE;
//Мы прошли операцию ветвления, поэтому нужно задать // списки родительский/дочерний, if(old^item)
// Определим, .находимся ли мы в дочернем процессе, if(NULL " оld_item->lookup(anltem->address))
old_itera->mChildren.pushback(anltem);
else
anltera = GetCurrentlteraForThread( theThread->ra thread id );
if(anltem)
anltera->ra_disasm +- m_disasm.m_instruction; anltem->ra_disasm +■ '\n';
char *_c " m_disasm.m_instruction; if(strstr(_c, "call"))
m_next_is_calltarget - TRUE;
else if(strstr(_c, "ret"))
ra_next_is_target » TRUE;
if(anltem)  anItern->isret - TRUE;
else if(strstr(_c, "jmp")>
m_next_is_target = TRUE;

old_item = anltem;
Как видим, в коде создается новая структура CONTEXT для потока, который был остановлен на первом шаге. Затем выполнено дизассемблирование команды, на ко­торую указывает указатель команд. Если команда является началом нового блока кода, запрашивается список уже найденных соответствий блоков кода, чтобы не вы­полнять повторных записей. Команда затем сравнивается со списком известных ко­манд условного перехода и в структуре элемента устанавливаются соответствующие

флаги. В завершение делается проверка наличия тегов boron. Код для этой провер­ки представлен в следующем разделе.
Проверка тегов boron
При возникновении пошагового события или точки останова отладчик может за­просить в памяти сведения о наличии тегов boron (т.е. подстроки с данными поль­зователя). С помощью подпрограмм запроса к памяти, которые были представлены выше, мы можем создать довольно интеллектуальные запросы о наличии тегов boron. Поскольку регистры процессора постоянно используются для хранения ука­зателей на данные, есть смысл проверить все регистры процесса на предмет хране­ния в них указателей на выделенные адреса памяти при возникновении точки оста­нова или пошагового события. Если регистр процессора содержит указатель на вы­деленную область памяти, мы можем запросить эту область памяти и выполнить в ней поиск тега boron. Итак, в любом блоке кода, в котором обрабатываются введен­ные пользователем данные, обычно присутствует указатель на эти данные в одном из регистров процессора. Для проверки регистров можно воспользоваться следую­щей программой.
bool check_boron(( CDThread *№eTThrreadl, char *c,  struct item *ip )
{
// Отметим все регистры,   хранящие указатели на буфер пользователя.
DWORD reg;
if(strstr(c, "eax"))
{
reg = theThvread-ftm._ctx.Eax; if(can_read(( theThread,   ((void *)reg ))
{
SIZE_T lpRead;
char string[255];
string[mTagLen]=NULL;
// Выполним чтение указанной области памяти. if(ReadProcess^mory^( theThread->m_hProcess,
(void *)reg,   string,  mTagLen, &lpRead))
{
if(strstr(  string,   mBoronTag ))
{
// Найти строку boron. ip->m_borons += "EAX:   " ; ip->m_borons += c; ip->m_borons += " —> "; ip-ftmjaorons += string; ip->m_borons += '\n';
return TRUE;
}
}
I
// Повторим этот вызов для всех регистров EAX,  ЕВХ,  ECX,  EDX, ESI,  and EDI,
return FALSE;
}
Для экономии места мы не стали приводить программный код для всех регист­ров, а ограничились регистром ЕАХ. Программа должна опросить все регистры, ука­

занные в комментарии. Функция возвращает значение TRUE, если тег boron обна­ружен в области памяти, на которую есть указатель в одном из регистров.
Резюме
Все программы состоят из машинного кода. В действительности, только машин­ный код заставляет программу выполнять те или иные функции. Восстановление исходного кода представляет собой процесс поиска шаблонов в машинном коде. Оп­ределив определенный шаблон в машинном коде, хакер может найти потенциально уязвимые места в программном обеспечении.
В этой главе были изложены базовые концепции и методы декомпиляции. Был проанализирован программный код нескольких устаревших (но все еще мощных) средств в качестве примера. Используя эти средства и методы, можно узнать все не­обходимые сведения о цели, что впоследствии позволит провести ее взлом.

 

На главную | Содержание | Назад | Вперёд
 
Яндекс.Метрика