Dynamic Binary Instrumentation в ИБ

        Сложность программного обеспечения растет – программы становятся более динамическими, и их поведение возможно оценить только в процессе выполнения. Производить оценку безопасности (поиск уязвимостей, недокументированных возможностей и т.д.) таких приложений значительно сложнее. Использовать только статические подходы анализа становится невозможным, так как из-за динамически генерируемого кода мы даже не можем гарантировать полное покрытие кода при анализе. На помощь приходят динамические методы анализа.

        Есть такая замечательная технология, как динамическая бинарная инструментация (Dynamic Binary Instrumentation, DBI), которая заключается во вставке в бинарный исполняющийся код анализирующих (в общем случае) процедур. Основная прелесть данного подхода заключается в том, что нет необходимости в исходном коде анализируемого приложения – работа происходит непосредственно с бинарным файлом.

        Инструментацией называют процесс модификации исследуемой программы с целью ее анализа. За вставку дополнительного кода обычно отвечают инструментирующие процедуры, которые вызываются только раз при возникновении необходимого события и модифицируют целевую программу. Добавленный код представляет собой анализирующие процедуры. Эти процедуры отвечают за проведение необходимого анализа, модификации и мониторинга исследуемой (целевой) программы и вызываются каждый раз при достижении определенного участка кода или возникновения в программе определенного события (создание процесса, возникновения исключения и т.д.). Инструментация бинарного приложения может быть выполнена на разных уровнях гранулярности программы:
        • инструкции;
        • базового блока;
        • трассы;
        • процедуры;
        • секции бинарного файла;
        • бинарного образа.

        В итоге для создания таких инструментов, работающих во время выполнения программы, были разработаны специальные фреймворки для динамической бинарной инструментации. Инструменты же, которые можно создать с их помощью, называют динамическими бинарными анализаторами (DBA, Dynamic Binary Analysis).

        Можно выделить четыре самых популярных фреймворка: PIN, DynamoRIO, DynInst и Valgrind. Подробнее о каждой библиотеке можно узнать из моей презентации “DBI:Intro” с конференции ZeroNights.

        В данной статье хотелось бы остановиться на библиотеке PIN от Intel, которую мы активно используем в рамках проектов нашего исследовательского центра DSecRG, и рассмотреть, как ее можно использовать в целях решения задач, возникающих при работе с бинарными приложениями без исходных кодов.

        Данная библиотека активно развивается и поддерживается. Вокруг нее сложилось достаточно большое сообщество – pinheads, где можно получить ответ на любой интересующий вас вопрос по поводу PIN.

        Активно DBI используют такие крутые исследовательские центры по безопасности, как Immunity, Zynamics, Rapid7, SourceFire VRT, Coseinc и т.д. Знание DBI уже сейчас встречается как одно из требований к специалистам по оценке безопасности ПО и разработке эксплойтов. Вот пример требований из вакансии на должность Exploit Engineer в команду разработчиков Metasploit:



        В качестве примера работы с PIN рассмотрим реализацию Shadow Stack, суть которого заключается в создании собственного стека адресов возвратов программы. Благодаря этому можно идентифицировать переполнения буфера в стеке, когда адрес возврата перезаписывается. Алгоритм такого инструмента очень прост: перед вызовом каждой функции запоминаем адрес возврата в Shadow Stack, а перед выходом из функции сравниваем адрес возврата со значением, сохраненным в Shadow Stack.

    #include <stdio.h>
    #include "pin.H"
    #include <stack>
    
    typedef struct
    {
        ADDRINT address;
        ADDRINT value;
    } pAddr;
    
    //  Shadow Stack
    stack<pAddr> protect;   
    
    FILE * logfile;                
    //--------------------------------------------------------------------------------------
    VOID Fini(INT32 code, VOID *v)
    {
        fclose(logfile);
    }
    //--------------------------------------------------------------------------------------
    VOID RtnEntry(ADDRINT esp, ADDRINT addr)
    {
        pAddr tmp;
        tmp.address = esp;
        tmp.value = *((ADDRINT *)esp);
    	
    	// Заносим адрес возврата наверх  Shadow Stack
        protect.push(tmp);
    }
    //--------------------------------------------------------------------------------------
    VOID RtnExit(ADDRINT esp, ADDRINT addr)
    {
        // Обработка пустого списка  Shadow Stack
    	if (protect.empty())
        {
            fprintf(logfile, "WARNING! protection list empty\n");
            return;
        }
    
        pAddr orig = protect.top();
        ADDRINT cur_val = (*((ADDRINT *)orig.address));
        
    	// Сравниваем адрес возврата из стека и Shadow Stack
    	if (orig.value != cur_val)
        {
            fprintf(logfile, "Overwrite at: %x old value: %x, new value: %x\n", 
                           orig.address, orig.value, cur_val );        
        }
    	
    	// Выносим адрес возврата с вершины Shadow Stack
        protect.pop();
    }
    //--------------------------------------------------------------------------------------
    // Вызывается для каждой процедуры
    VOID Routine(RTN rtn, VOID *v)
    {
        RTN_Open(rtn);
        SEC sec = RTN_Sec(rtn);
        IMG img = SEC_Img(sec);
    
        if ( IMG_IsMainExecutable(img) && (SEC_Name(sec) == ".text") )
        {	
    		// Функция, выполняющаяся до вызова процедуры (RtnEntry)
            RTN_InsertCall(rtn, IPOINT_BEFORE,(AFUNPTR)RtnEntry, 
                            IARG_REG_VALUE, REG_ESP, 
                            IARG_INST_PTR, 
                            IARG_END);
    		
    		// Функция, выполняющаяся после вызова процедуры (RtnExit)
            RTN_InsertCall(rtn, IPOINT_AFTER ,(AFUNPTR)RtnExit, 
                            IARG_REG_VALUE, REG_ESP, 
                            IARG_INST_PTR, 
                            IARG_END);
        }
    
        RTN_Close(rtn);
    }
    //--------------------------------------------------------------------------------------
    INT32 Usage()
    {
        PIN_ERROR( "This Pintool logs function return addresses in main module and reports modifications\n" 
                  + KNOB_BASE::StringKnobSummary() + "\n");
        return -1;
    }
    //--------------------------------------------------------------------------------------
    int main(int argc, char *argv[])
    {
        // Инициализация обработки символов
        PIN_InitSymbols();
    	
    	// Инициализация библиотеки PIN
        if (PIN_Init(argc, argv))
    	{	
    		return Usage();
    	}
    
        // Открываем лог-файл
        logfile = fopen("protection.out", "w");
    
        // Регистрируем функцию, которая вызывается для инструментации процедур
        RTN_AddInstrumentFunction(Routine, 0);
        
    	// Регистрируем завершающую функцию, которая вызывается при завершении работы приложения
    	PIN_AddFiniFunction(Fini, 0);
    
        // Запуск 
        PIN_StartProgram();
        
        return 0;
    }
    

        Графически работу данного инструмента можно представить так:


        Справедливо можно заметить, что данный инструмент сработает только при перезаписи адреса возврата и ничего не заметит при разрушении данных, находящихся перед адресом возврата. Чтобы заметить разрушение данных внутри функции, необходимо скомпилировать программу с флагом из семейства /RTC, но тут, во-первых, надо иметь исходный код исследуемого приложения, и во-вторых, как правило, программы выходят в релиз без него, так как он сильно сказывается на производительности программы.

        Также можно сказать, что похожую задачу выполняет флаг /GS, добавляющий специальное значение cookie перед адресом возврата и проверяющий это значение перед выходом из функции. Но данный флаг применяется не во всех программах и порой просто не способен защитить от простейшей эксплуатации переполнения буфера в стеке.

        Например, в функции func_bof() вызывается небезопасная функция strcpy(), входные параметры которой никак не фильтруются, что приводит к переполнению буфера в стеке. Как следствие, перезаписываются параметры функции (переменная b), cookie (если они есть), адрес возврата. Но проверка cookie сразу не сработает, так как программа еще не дошла до конца функции func_bof(), в результате чего перезаписанная переменная b попадает в функцию critical_func(), где может привести, например, к integer overflow или негативно повлиять на другие данные, которые пойдут дальше, в результате чего программа упадет далеко от реального места проблемы. С помощью простейшей модификации вышеприведенного кода можно проверять адрес возврата функции и при вызове дочерних функций (оставим это как домашнее задание), что позволит идентифицировать данную проблему уже перед вызовом critical_func().



        И напоследок приведу список интересных публичных проектов по безопасности, реализованных с помощью PIN:
         — Shellcode dumper – дампер стандартных шеллкодов, принцип работы которых основывается на передаче управления на stack или heap.
         — Moflow-mitigations – прототип, идентифицирующий ROP-шеллкоды и JIT-шеллкоды.
         — Code-coverage-analysis-tools – анализатор покрытия кода.
         — RunTracer – набор инструментов для отслеживания потока управления программы.
         — Kerckhoffs – инструмент для полуавтоматического детектирования криптографических примитивов в программах.
         — VERA – инструмент для визуализации работы программы.
         — Tripoux – анализатор упаковщиков зловредов.
         — Privacy Scope – инструмент для обнаружения утечек критичной информации.
         — Tartetatintools – набор инструментов для анализа вредоносного кода.

        Также есть очень интересные реализации интеграции результатов работы PIN-инструментов с дизассемблером IDA Pro, который играет роль визуализатора:
         — DiffCov – набор инструментов для записи выполнившихся базовых блоков программы.
         — runtime-tracer – создает трейс выполнения программы со значениями регистров и обрабатываемыми участками памяти.



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

        Также всем заинтересовавшимся DBI/PIN в целях информационной безопасности настоятельно советую обратить внимание на workshop “Binary instrumentation for security professionals” (slides, examples) от моего хорошего знакомого Gal Diskin из компании Intel. К данному курсу посчастливилось приложить руку и мне. Он читался на таких хакерских конференциях, как BlackHat, DEFCON и HackLu.
    Метки:
    Digital Security 113,19
    Безопасность как искусство
    Поделиться публикацией
    Похожие публикации
    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое