Pull to refresh

Gray Hat Python — Immunity Debugger

Reading time17 min
Views17K

Intro


Рассмотрев создание и использования отладчика на чистом Python’е в виде PyDbg, пришло время изучить Immunity Debugger, который состоит из полноценного пользовательского интерфейса и наимощнейшей Python-библиотекой, на сегодняшний день, для разработки эксплойтов, обнаружения уязвимостей и анализа вредоносного кода. Выпущенный в 2007 году, Immunity Debugger имеет хорошее сочетание возможностей как динамической отладки, так и статического анализа. Помимо этого он имеет полностью настраиваемый графический интерфейс, реализованный на чистом Питоне. В начале этой главы мы кратко познакомимся с отладчиком Immunity Debugger и его пользовательским интерфейсом. Затем начнем постепенное углубление в разработку эксплойта и некоторых методов, для автоматического обхода анти-отладочных приемов, применяемых в вредоносном ПО. Давайте начнем с загрузки Immunity Debugger и его запуска.

5.1 Установка Immunity Debugger


Immunity Debugger распространяется и поддерживается [1] бесплатно, вот ссылка на его скачивание: debugger.immunityinc.com

Просто скачайте и запустите установщик. Если вы еще не устанавливали Python 2.5 (прим. пер. как вам советовалось), то это не большая проблема, поскольку Immunity Debugger поставляется в комплекте с инсталлятором Python 2.5 (прим. пер. на момент перевода статьи версия Питона идущего в составе отлдачика была 2.7.1), которые будет установлен отлдачиком за вас, если возникнет такая необходимость. Сразу после установки и запуска Immunity Debugger – он будет готов к использованию.

5.2 Immunity Debugger 101


Давайте произведем быстрый обзор Immunity Debugger и его интерфейса, а затем перейдем к рассмотрению Python-библиотеки immlib, которая позволяет писать скрипты для отладчика. При первом запуске вы увидите интерфейс показанный на Рис 5-1.

image
Рис. 5-1: Основной интерфейс Immunity Debugger

Основной интерфейс отладчика состоит из пяти основных частей. В верхнем левом углу расположено окно CPU, где отображается ассемблерный код. В верхнем правом углу расположено окно регистров, где отображаются регистры общего назначения, а так же другие регистры процессора. В левом нижнем углу расположено окно дампа памяти, где вы можете видеть шестнадцатеричный дамп любого адресного пространства, выбранного вами. В правом нижнем углу расположено окно стека, в котором отображаются соответствующие вызовы стека; оно так же показывает вам декодированные параметры функций в виде символьной информации (например, какой-нибудь родной вызов Windows API функции). Пятый элемент – это белая панель командной строки, расположенная в самом низу и предназначенная для управления отладчиком, с помощью команд в WinDbg-стиле. Здесь же вы можете выполнять PyCommands, которые мы рассмотрим дальше.

5.2.1 PyCommands

Основной способ выполнения Python-скриптов в Immunity Debugger заключается в использовании PyCommands [2]. PyCommands – это Python-скрипты, которые написаны для выполнения различных задач внутри Immunity Debugger, например, скрипты осуществляющие: различные перехваты, статический анализ или любой другой отладочный фукнционал. Каждый PyCommand должен иметь определенную структуру, для своего правильного выполнения. Следующий фрагмент кода показывает основную структуру PyCommand, которую вы можете использовать в качестве шаблона, для создания собственных PyCommands.

from immlib import *

def main(args):
    # Instantiate a immlib.Debugger instance
    imm = Debugger()

    return "[*] PyCommand Executed!" 

В каждом PyCommand есть две основные составляющие. Первая составляющая, у вас должна быть определена функция main(), которая должна принимать один параметр, являющийся списком аргументов передаваемых в PyCommand. Вторая составляющая, заключается в том, что main() должна возвратить «строку», когда закончит свое выполнение. Этой строкой будет обновлена «строка состояния отладчика» (прим. пер. находящаяся под командной строкой), когда скрипт закончит выполнение.

Когда вы захотите запустить PyCommand, вам следует убедиться в том, что ваш скрипт сохранен в директории PyCommands, которая находится в основном установочном каталоге Immunity Debugger. Для выполнения сохраненного скрипта, просто введите восклицательный знак сопровождаемый именем скрипта, в командной строке отладчика, вот так:

!scriptname

Как только вы нажмете ENTER, ваш скрипт начнет выполняться.

5.2.2 PyHooks

Immunity Debugger поставляется с 13-ю различными видами перехватов, каждый из которых вы можете реализовать либо как отдельный скрипт, либо как внутренний скрипт PyCommand. Могут использоваться следующие типы перехватов:

BpHook/LogBpHook
Когда встречается брейкопйнт – срабатывают эти типы перехватов. Оба перехвата ведут себя одинаково, за исключением того, что когда встречается BpHook, то он в действительности останавливает выполнение отладчика, тогда как LogBpHook не прерывает его выполнение.

AllExceptHook
Любое исключение, которое произойдет в процессоре, вызовет выполнение этого типа перехвата.

PostAnalysisHook
Этот перехват срабатывает после того, как отладчик закончит анализировать загруженный модуль. Это может быть полезно, если у вас есть некоторые задачи статического анализа, которые вы хотите произвести автоматически, сразу после завершения анализа модуля. Важно заметить, что модуль (включая основной исполняемый файл) нужно проанализировать прежде, чем вы сможете декодировать функции и основные блоки, используя immlib.

AccessViolationHook
Этот перехват срабатывает всякий раз, когда происходит нарушение прав доступа; он наиболее полезен для перехвата информации во время выполнения фаззинга.

LoadDLLHook/UnloadDLLHook
Этот перехват срабатывает всякий раз, когда загружается/выгружается DLL.

CreateThreadHook/ExitThreadHook
Этот перехват срабатывает всякий раз, когда создается/уничтожается поток.

CreateProcessHook/ExitProcessHook
Этот тип перехвата срабатывает, когда целевой процесс запускается или заканчивает работу (exited).

FastLogHook/STDCALLFastLogHook
Эти два перехвата используют заглушку, для передачи выполнения маленькому телу кода перехватчика, который может логировать определенное значение регистра или участка памяти во время перехвата. Эти перехваты полезны для перехвата часто вызываемых функций; мы рассмотрим их использование в Главе 6.

Что бы задать PyHook можно использовать следующий шаблон, который использует LogBpHook в качестве примера:

from immlib import *

class MyHook( LogBpHook ):

    def __init__( self ):
        LogBpHook.__init__( self )

    def run( regs ):
        # Executed when hook gets triggered

Мы перегружаем класс LogBpHook и удостоверяемся, что определена функция run(). Когда сработает перехват, функция run() принимает, в качестве единственного аргумента, перечень всех регистров процессора, которые были установлены в момент срабатывания хука, что позволяет нам просмотреть или изменить текущие значения по своему усмотрению. Переменная regs является словарем, который мы можем использовать для доступа к регистрам по именам, вот так:

regs["ESP"]

Теперь мы можем определять перехваты несколькими способами, с помощью PyCommand и PyHooks. Таким образом, можно устанавливать перехваты либо в ручную с помощью PyCommand, либо автоматически с помощью PyHooks (находится в основном установочном каталоге Immunity Debugger). В случае PyCommand, перехват будет установлен всякий раз, как будет выполнен PyCommand. В случае же PyHooks, перехват будет срабатывать автоматически при каждом запуске Immunity Debugger. Теперь давайте перейдем к некоторым примерам использования immlib, встроенной Python-библиотеки Immunity Debugger.

5.3 Разработка эксплойта


Обнаружение уязвимости в программном обеспечении это только начало длинного и трудного путешествия предстоящего вам для получения надежного работающего эксплойта. Immunity Debugger имеет множество конструкторских особенностей, позволяющих пройти путь его разработки немного легче. Мы разработаем некоторые PyCommands, ускоряющие процесс разработки эксплойта, включая способ нахождения инструкций, для получения EIP, а также фильтрацию байтов не пригодных к использованию в шелл-коде. Так же мы будем использовать PyCommand !findatidep, поставляющуюся в комплекте с Immunity Debugger, которая помогает обойти DEP (Data Execution Prevention) [3]. Давайте начнем!

5.3.1 Поиск дружественных эксплойту инструкций

После того как вы получили контроль на EIP, нужно передать выполнение на шелл-код. Как правило, у вас будет регистр или смещение от регистра, которое будет указывать на шелл-код. Ваше задание – найти инструкцию, где-нибудь в исполняемом файле или в одном из его загруженных модулей, которая передаст управление нужному адресу. Python-библиотека Immunity Debugger делает это легким делом, предоставляя интерфейс поиска, который позволяет искать интересующие инструкции по всему загруженному бинарному файлу. Давайте на коленке набросаем скрипт, который будет получать инструкцию и возвращаться все адреса, где эта инструкция встречается. Создайте новый файл findinstruction.py и введите следующий код.

findinstruction.py:
from immlib import *

def main(args):

    imm = Debugger()
    search_code = " ".join(args)

     (#1): search_bytes = imm.Assemble( search_code )
     (#2): search_results = imm.Search( search_bytes )

    for hit in search_results:
        # Retrieve the memory page where this hit exists
        # and make sure it's executable
         (#3): code_page = imm.getMemoryPagebyAddress( hit )
         (#4): access = code_page.getAccess( human = True )

        if "execute" in access.lower():
            imm.log( "[*] Found: %s (0x%08x)" % ( search_code, hit ), address = hit )

    return "[*] Finished searching for instructions, check the Log window."

В начале, переведем полученные инструкции в их бинарный эквивалент (#1), а затем используем функцию Search(), для поиска всех инструкций, в памяти загруженного бинарного файла (#2). Далее, в возвращенном списке перебираем все обнаруженные адреса, для получения страницы памяти, где расположена инструкция (#3), после чего удостоверяемся в том, что память помечена как исполняемая (#4). Затем, для каждой инструкции, в исполняемой странице памяти, находим ее адрес и выводим в окно «Log». Для использования скрипта, просто передайте инструкцию, которую вы ищите, в качестве аргумента, вот так:

!findinstruction "instruction to search for"

После выполнения скрипта, с такими параметрами:

!findinstruction jmp esp

Вы увидите результат похожий на Рис. 5-2.

image
Рис. 5-2: Вывод PyCommand !findinstruction

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

5.3.2 Фильтрация плохих символов

Когда вы посылаете строку, содержащую эксплойт, целевой системе – есть некоторые наборы символов, которые вы не сможете использовать в шелл-коде. Например, если мы нашли переполнение стека при вызове функции strcpy(), то наш эксплойт не может содержать символ NULL (0x00), потому что strcpy() перестает копировать данные, как только встречает значение NULL. Поэтому при написании эксплойтов используют shellcode-кодировщики, которые после запуска шелл-кода декодируют и выполняют его. Однако, существую еще некоторые случаи, когда вы можете иметь несколько символов отфильтровывающихся или обрабатывающихся неким специальным образом в уязвимом ПО, что может стать настоящим кошмаром, для определения их в ручную.

Обычно, когда вы поместили шелл-код в уязвимую программу, и он не запустился (вызвав нарушение прав доступа или сбой в программе, до своего полного выполнения) нужно, для начала, убедиться в том, что он скопировался в память именно так, как вы этого хотели. Immunity Debugger может облегчить решение этой задачи. Посмотрите на Рис. 5-3, который показывает стек после переполнения.

image
Рис. 5-3: Immunity Debugger окно стека после переполнения

Мы видим, что регистр EIP в настоящий момент указывает на регистр ESP. Четыре байта 0xCC просто заставят остановиться отладчик, как если бы там был установлен брейкпойнт (помните? 0xCC это инструкция INT3). Сразу же после четырех инструкций INT3, по смещению ESP+0x4, располагается шелл-код. Именно там нужно начать исследование памяти, что бы убедиться, что наш шелл-код точно такой, какой мы его отправили во время нашей атаки на целевую систему. Для исследования шелл-кода, находящегося в памяти, мы просто возьмем оригинал виде ASCII-строки и сравним его (побайтно) с шелл-кодом размещенном в памяти, что бы удостовериться, что шелл-код был загружен правильно. Если мы замечаем различие – выводим плохой байт, который не прошел через программный фильтр, в Log. После чего, мы можем добавить обработку такого символа в shellcode-кодер, до запуска повторной атаки! Для проверки работоспособности этого инструмента, можно взять шелл-код из Metasploit, либо свою собственную домашнюю заготовку. Создайте новый файл badchar.py и введите следующий код.

badchar.py:
from immlib import *

def main(args):

    imm = Debugger()

    bad_char_found = False

    # First argument is the address to begin our search
    address = int(args[0],16)

    # Shellcode to verify
    shellcode = ">>COPY AND PASTE YOUR SHELLCODE HERE<<"
    shellcode_length = len(shellcode)

    debug_shellcode = imm.readMemory( address, shellcode_length )
    debug_shellcode = debug_shellcode.encode("HEX")

    imm.log("Address: 0x%08x" % address)
    imm.log("Shellcode Length : %d" % length)

    imm.log("Attack Shellcode: %s" % canvas_shellcode[:512])
    imm.log("In Memory Shellcode: %s" % id_shellcode[:512])

    # Begin a byte-by-byte comparison of the two shellcode buffers
    count = 0
    while count <= shellcode_length:

        if debug_shellcode[count] != shellcode[count]:
            imm.log("Bad Char Detected at offset %d" % count)
            bad_char_found = True
            break

        count += 1

    if bad_char_found:
        imm.log("[*****] ")
        imm.log("Bad character found: %s" % debug_shellcode[count])
        imm.log("Bad character original: %s" % shellcode[count])
        imm.log("[*****] ")

    return "[*] !badchar finished, check Log window."

В этом скрипте, мы в действительности используем только вызов readMemory() из библиотеки Immunity Debugger, а в остальной части скрипта производится простое сравнение строк. Теперь все что вам нужно сделать, это взять ваш шелл-код как ASCII-строку (например, если у вас байты 0xEB 0x09, тогда ваша строка будет выглядеть как EB09), вставить ее в скрипт и запустить скрипт следующим образом:

!badchar "Address to Begin Search"

В нашем предыдущем примере, мы бы начали поиск c ESP+0x4, абсолютный адрес которого равен 0x00AEFD4C, поэтому запускаем PyCommand следующим образом:

!badchar 0x00AEFD4c

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

5.3.3 Обход DEP

DEP – это мера обеспечения безопасности реализованная в Microsoft Windows (XP SP2, 2003 и Vista), для предотвращения выполнения кода в областях памяти, таких как куча и стек. Это может помешать выполнению шелл-кода в большинстве эксплойтах, потому что большинство эксплойтов хранят свои шелл-коды в куче или стеке. Однако, есть известный прием [4] посредством которого мы можем использовать родные вызовы Windows API, что бы отключить DEP, для текущего процесса в котором мы выполняемся и в котором разрешено безопасно передавать управление на наш шелл-код независимо от того хранится ли он в стеке или в куче. Immunity Debugger поставляется вместе с PyCommand называемой findantidep.py. которая определяет соответствующие адреса, для установки вашего эксплойта, таким образом, что бы отключить DEP и выполнить шелл-код. Рассмотрим небольшую теорию по отключению DEP. Затем перейдем к использованию скрипта PyCommand, позволяющего находит интересующие нас адреса.

Вызов Windows API, который можно использовать, чтобы отключить DEP для текущего процесса, является недокументированной функцией NtSetInformationProcess() [5], которая имеет следующий прототип:

NTSTATUS NtSetInformationProcess(
    IN HANDLE hProcessHandle,
    IN PROCESS_INFORMATION_CLASS ProcessInformationClass,
    IN PVOID ProcessInformation,
    IN ULONG ProcessInformationLength );

Чтобы отключить DEP – нужно вызвать функцию NtSetInformationProcess() с установленным параметрами: ProcessInformationClass в значение ProcessExecuteFlags (0x22) и ProcessInformation в значение MEM_EXECUTE_OPTION_ENABLE (0x2). Проблема с простой установки шелл-кода заключается в том, что вызов этой функции состоит из некоторого количества NULL-параметров, которые являются проблемными для большинства шелл-кодов. Прием позволяющий обойти это ограничение, заключается в размещение шелл-кода в средине функции, которая уже на стеке вызовет NtSetInformationProcess() с необходимыми параметрами. В ntdll.dll есть известное место, которое выполняет это за нас. Посмотрите на дизассемблерный вывод ntdll.dll для Windows XP SP2, полученный с помощью Immunity Debugger.

7C91D3F8     . 3C 01            CMP AL,1
7C91D3FA     . 6A 02            PUSH 2
7C91D3FC     . 5E               POP ESI
7C91D3FD     . 0F84 B72A0200    JE ntdll.7C93FEBA
...
7C93FEBA     > 8975 FC          MOV DWORD PTR SS:[EBP-4],ESI
7C93FEBD     .^E9 41D5FDFF      JMP ntdll.7C91D403
...
7C91D403     > 837D FC 00       CMP DWORD PTR SS:[EBP-4],0
7C91D407     . 0F85 60890100    JNZ ntdll.7C935D6D
...
7C935D6D     > 6A 04            PUSH 4
7C935D6F     . 8D45 FC          LEA EAX,DWORD PTR SS:[EBP-4]
7C935D72     . 50               PUSH EAX
7C935D73     . 6A 22            PUSH 22
7C935D75     . 6A FF            PUSH -1
7C935D77     . E8 B188FDFF      CALL ntdll.ZwSetInformationProcess


Следуя по этому коду видим сравнение AL со значением 1, затем в ESI помещается значение 2. Если AL равен 1, то срабатывает условный переход на 0x7C93FEBA. Там значение из ESI перемещается в переменную стека EBP-4 (помните, что ESI все еще установлена в 2?). Затем проверяется условие по адресу 0x7C91D403, которое проверяет нашу переменную в стеке (она все еще равна 2), что бы убедиться, что она не равна нулю, после чего срабатывает условный переход на 0x7C935D6D. Вот тут начинается самое интересное; видно что значение 4 помещается в стек, переменная EBP-4 (все еще равна 2!) загружается в регистр EAX, затем это значение помещается в стек, далее вталкивается значение 0x22 и значение -1 (-1, дескриптор процесса, который говорит вызову функции, что это текущий процесс, в котором нужно отключить DEP), затем следует вызов ZwSetInformationProcess (псевдоним NtSetInformationProcess). Итак, в действительности то, что случилось в этом куске кода, вызвало функцию NtSetInformationProcess (), со следующими параметрами:

NtSetInformationProcess( -1, 0x22, 0x2, 0x4 )

Perfect! Это отключит DEP для текущего процесса, но для этого нам нужно передать управление на адрес 0x7C91D3F8. Перед тем как мы передадим управление на этот кусок кода, нам нужно убедиться, что AL (младший байт EAX) установлен в 1. После выполнения этих условий, мы сможем передать управление шелл-коду, как и в любом другом переполнении, например, с помощью инструкции JMP ESP. Таким образом нужно три адреса:
  • Адрес, который устанавливает AL в 1, а затем возвращает управление;
  • Адрес, где находится кусок кода для отключения DEP;
  • Адрес для передачи управления в начало нашего шелл-кода.

Обычно вам нужно искать эти адреса в ручную, но разработчики эксплойтов в Immunity создали небольшой Python-скрипт findantidep.py, выполненного виде wizard (мастера), который проведет вас через процесс поиска этих адресов. Он даже создает строку для эксплойта, которую вы можете скопировать и вставить в ваш экплойт. Это позволяет вам использовать найденные адреса вообще без каких-либо усилий. Давайте посмотрим на скрипт findantidep.py, а затем испытаем его.

findantidep.py:
import immlib
import immutils

def tAddr(addr):
    buf = immutils.int2str32_swapped(addr)
    return "\\x%02x\\x%02x\\x%02x\\x%02x" % ( ord(buf[0]) , ord(buf[1]), ord(buf[2]), ord(buf[3]) )

DESC="""Find address to bypass software DEP"""

def main(args):
    imm=immlib.Debugger()
    addylist = []
    mod = imm.getModule("ntdll.dll")

    if not mod:
        return "Error: Ntdll.dll not found!"

    # Finding the First ADDRESS
     (#1): ret = imm.searchCommands("MOV AL,1\nRET")
    if not ret:
        return "Error: Sorry, the first addy cannot be found"

    for a in ret:
        addylist.append( "0x%08x: %s" % (a[0], a[2]) )
        ret = imm.comboBox("Please, choose the First Address [sets AL to 1]", addylist)

    firstaddy = int(ret[0:10], 16)
    imm.Log("First Address: 0x%08x" % firstaddy, address = firstaddy)

    # Finding the Second ADDRESS
     (#2): ret = imm.searchCommandsOnModule( mod.getBase(), "CMP AL,0x1\n PUSH 0x2\n POP ESI\n" )

    if not ret:
        return "Error: Sorry, the second addy cannot be found"

    secondaddy = ret[0][0]
    imm.Log( "Second Address %x" % secondaddy , address= secondaddy )

    # Finding the Third ADDRESS
     (#3): ret = imm.inputBox("Insert the Asm code to search for")
    ret = imm.searchCommands(ret)

    if not ret:
        return "Error: Sorry, the third address cannot be found"

    addylist = []

    for a in ret:
        addylist.append( "0x%08x: %s" % (a[0], a[2]) )

    ret = imm.comboBox("Please, choose the Third return Address [jumps to shellcode]", addylist)

    thirdaddy = int(ret[0:10], 16)

    imm.Log( "Third Address: 0x%08x" % thirdaddy, thirdaddy )

     (#4): imm.Log( 'stack = "%s\\xff\\xff\\xff\\xff%s\\xff\\xff\\xff\\xff" + "A" * 0x54 + "%s" + shellcode ' % ( tAddr(firstaddy), tAddr(secondaddy), tAddr(thirdaddy) ) )

Итак, вначале найдем команды, которые будут устанавливать AL в 1 (#1), затем попросим пользователя выбрать походящий адрес. После чего, произведем поиск набора инструкций в ntdll.dll, которые содержат код отключения DEP (#2). На третьем шаге просим пользователя ввести инструкцию или инструкции, которые должны будут передать управление на шелл-код (#3), и предоставляем пользователю список адресов, где эти инструкции могут быть найдены. Скрипт заканчивается выводом результатов в окно Log (#4). Посмотрите на рисункци 5-4 – 5-6, что бы увидеть, как проходит этот процесс.

image
Рис. 5-4: Вначале выбираем адрес который установит AL в 1

image
Рис. 5-5: Затем вводим набор инструкций, которые передадут управление на шелл-код

image
Рис. 5-6: Теперь выбираем адрес который вернется из шага (#2)[/CENTER]

И в конце концов вы увидите вывод в окне Log, как показано тут:

stack = "\x75\x24\x01\x01\xff\xff\xff\xff\x56\x31\x91\x7c\xff\xff\xff\xff" + "A" * 0x54 + "\x75\x24\x01\x01" + shellcode

Теперь вы можете просто скопировать и вставить эту строку вывода в эксплойт и добавить свой шелл-код. Использование этого скрипта может помочь протировать существующие эксплойты, так чтобы они могли успешно выполняться в системе с включенным DEP или создавать новые эксплойты, которые поддерживали бы отключение DEP из коробки. Это замечательный пример забирающий часы ручного поиска, который превратился в 30-ти секундное упражнение. Теперь вы можете видеть, как некоторые простые Python-скрипты могут помочь вам разрабатывать более надежные и переносимые эксплойты в сжатые сроки. Давайте перейдем к использованию immlib для обхода общих анти-отладочных процедур во вредоносном программном обеспечении.

5.4 Обход анти-отладочных методов


Текущие разновидности вредоносного ПО становятся все более и более запутанными в своих методах заражения, распространения и своих способностях защиты от анализа. Помимо общих методов обфускации кода, таких как использование упаковщиков и крипторов вредоносное ПО обычно применяет анти-отладочные приемы, пытаясь предотвратить свой анализ с помощью отладчика, чтобы затруднить свое исследование. Используя Immunity Debugger и Python можно создать некоторые простые скрипты, позволяющее обойти некоторые из этих анти-отладочных приемов, что бы помочь аналитику при исследовании сэмплов вредоносов. Давайте посмотрим на некоторые из этих наиболее распространенных анти-отладочных методов и на напишем некоторый соответствующий код для их обхода.

5.4.1 IsDebuggerPresent

Безусловно наиболее распространенным анти-отладочным методов является использование функции IsDebuggerPresent() экспортируемой из kernel32.dll. Эта функция вызывается без параметров и возвращает 1 если есть присоединенный отладчики к текущему процессу или 0 если его нет. Если мы дизассемблируем эту функцию, мы увидим следующий кусок кода:

7C813093     >/$ 64:A1 18000000     MOV EAX,DWORD PTR FS:[18]
7C813099     |. 8B40 30             MOV EAX,DWORD PTR DS:[EAX+30]
7C81309C     |. 0FB640 02           MOVZX EAX,BYTE PTR DS:[EAX+2]
7C8130A0     \.                     C3 RETN


Этот код загружает адрес из TIB (Thread Information Block), который всегда располагается по смещению 0x18 от регистра FS. Оттуда он загружает PEB (Process Environment Block), который всегда находится по смещению 0x30 в TIB. Третья инструкция устанавливает EAX в значение из параметра BeingDebugged, который располагается по смещению 0x2 в PEB. Если есть отладчик присоединенный к процессу – этот байт устанавливает в 0x1. Простой обход для этого был опубликован Демианом Гомесом (Damian Gomez) [6] из Immunity, который является всего лишь одной Python-строкой, которая может содержаться в PyCommand или может быть выполнена из Python-шела в Immunity Debugger:

imm.writeMemory( imm.getPEBaddress() + 0x2, "\x00" )

Этот код просто обнуляет флаг BeingDebugged в PEB, и теперь любой зловред, который использует эту проверку, будет обманут, полагая, что нет присоединенного отладчика.

5.4.2 Обход перебора процессов

Вредоносы также пытаются перебирать все запущенные процессы на компьютере, что бы определить запущен ли отладчик. Например, если вы используете Immunity Debugger для исследования вируса, то ImmunityDebugger.exe будет зарегистрирован как работающий процесс. Для перебора запущенных процессов зловред будет использовать функцию Process32First() для получения первого зарегистрированного процесса в списке процессов системы, а затем будет использовать Process32Next() для перебора всех оставшихся процессов. Оба эти вызова функций возвращают булево значение, которое говорит вызывающему коду успешно ли выполнилась функция или нет, поэтому мы можем просто пропатчить две эти функции, так что бы EAX регистр устанавливался в нуль, при возвращении результата функцией. Мы будем использовать мощный встроенный ассемблер Immunity Debugger для достижения этой цели. Посмотрите на следующий код:

(#1): process32first = imm.getAddress("kernel32.Process32FirstW")
process32next = imm.getAddress("kernel32.Process32NextW")

function_list = [ process32first, process32next ]

(#2): patch_bytes = imm.Assemble( "SUB EAX, EAX\nRET" )

for address in function_list:
     (#3): opcode = imm.disasmForward( address, nlines = 10 )
     (#4): imm.writeMemory( opcode.address, patch_bytes )

Вначале находим адреса двух функций перебирающих процессы и сохраняем их в список (#1). Затем переводим некоторые байты в соответствующие им опкоды, которые установят регистр EAX в 0 и вернут управление из функции; в этом и будет заключаться наш патч (#2), Дальше мы проходим 10 инструкций (#3), в нутрии функций Process32First и Process32Next. Делаем мы это потому, что некоторые продвинутые зловреды на самом деле будут проверять несколько первых байт этих функций, что бы убедиться в том, что функция не была пропатчена реверс инженером. Мы обманам их, пропатчив 10-тью инструкциями ниже; правда, если они проверят целостность всей функции, они обнаружат нас. После того как пропатчим байты в функциях (#4), обе функции будут возвращать ложный результат независимо от того, как они будут вызываться.

Мы рассмотрели два примера того, как вы можете использовать Python и Immunity Debugger для создания автоматизированных способов защиты от вредоносных программ, пытающихся определить наличие присоединенного отладчика. Существует намного больше анти-отладочных методов, которые могут быть использованы, поэтому будет написано бесконечное множество Python-скриптов, чтобы справиться с ними! Полученные в этой главе знания помогут насладиться более коротким временем разработки эксплойтов, а так же новым арсеналом инструментов для борьбы против зловредов.

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

Ссылки

[1] For debugger support and general discussions visit http://forum.immunityinc.com.

[2] For a full set of documentation on the Immunity Debugger Python library, refer to http://debugger.immunityinc.com/update/Documentation/ref/.

[3] An in-depth explanation of DEP can be found at http://support.microsoft.com/kb/875352/EN-US/.

[4] See Skape and Skywing’s paper at http://www.uninformed.org/?v=2&a=4&t=txt.

[5] The NtSetInformationProcess() function definition can be found at http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Process/NtSetInformationProcess.html.

[6] The original forum post is located at http://forum.immunityinc.com/index.php?topic=71.0.
Tags:
Hubs:
+37
Comments5

Articles