Приемы анализа malware: Распаковка драйверов в Ring3

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


    Вступление

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

    Подготовка драйвера

    Сам драйвер, по своей структуре почти идентичен обычным динамическим библиотекам. И чтоб загрузить его в ring3, нужно изменить пару полей в PE-заголовке, а именно:
    • Тип Subsystem с Native на Windows GUI. (В PeTools кнопка Optional Header);
    • В поле IMAGE_FILE_HEADER.Characteristics выставить атрибут Dll. (В PeTools кнопка File Header, а затем Characteristics);

    Теперь нужно отвязать драйвер от системных низкоуровневых библиотек. Для этого напишем свою dll (или несколько, кому как угодно) с заглушками необходимых функций. Открываем список импортируемых API и ищем их прототипы в MSDN или в других информационных ресурсах.
    В моем случае список следующий:

    Список API до патча
    Чтобы драйвер мог подгрузить именно нашу dll, а не системную, пропатчим имена импортируемых модулей, на подготовленные нами.

    Список API после патча
    Теперь наш драйвер можно загрузить в отладчик пользовательского режима, например OllyDbg.

    Распаковка и решение проблем по мере выполнения

    В данном случае пакер примитивный — xor и разновидность алгоритма LZ. Рассматривать xor-декриптор я не буду, не смотря на то, что он немного замусорен. После дешифровки попадаем на следующий код:

    Код после xor-декриптора
    Вот и первая неприятность. Если просто отпустить код выполняться, то сразу словим exception ACCESS_VIOLATION. Код берет из служебных структур адрес, находящийся внутри ntoskrnl.exe и находит ImageBase модуля. Но так как мы находимся в ring3, то структура, находящаяся в сегменте FS отличается от ядерной. И если потрассировать код, то из fs:[38] считается 0, а на следующей команде будет чтение по адресу 0+4. Естественно, никакого ntoskrnl у нас в памяти тоже нет, поэтому предположим, что обойдемся адресом ntdll (большая часть её API совпадает с ядерными функциями).

    Открываем карту памяти и смотрим что находится в сегменте FS. Должны увидеть TIB — Thread Information Block. Немного посмотрев, можно увидеть в ней и указатель на PEB — Process Environment Block. Выбираем любой подходящий адрес в ntdll (я выбрал PEB.FastPebLock).

    PEB и TIB
    Можно просто заNOPить код, и по ходу трейса подменить адрес на ntdll. Но мы поступим по другому — изменим смещения.


    Следующая проблема с которой мы сталкиваемся по ходу трейса — распаковщик динамически получает адреса необходимых ядерных функций. На скриншоте видно цикл перебора списка имен с псевдофункцией xGetProcAddress, которая аналог системной. Её начало, где она парсит MZ-заголовок, можно увидеть в нижней части.


    При этом EDX указывает на список имен необходимых функций


    Внимательные заметят чуть ниже пожатый MZ-заголовок, но об этом позже.
    Вроде ничего необычного. Все было бы хорошо, да только в ntdll нет некоторых необходимых API, или хотя бы похожих по прототипу. Но если немного подумать, то найдутся таковые в kernel32.dll.

    1. PVOID ExAllocatePool(POOL_TYPE PoolType, SIZE_T NumberOfBytes);
    2. VOID ExFreePool(PVOID P);

    Аккуратно можно заменить на 
    1. HGLOBAL WINAPI GlobalAlloc(UINT uFlags, SIZE_T dwBytes);
    2. HGLOBAL WINAPI GlobalFree(HGLOBAL hMem);

    Меняем имена несуществующих функций, на любые имеющиеся, чтобы просто корректно отработал xGetProcAddress. Я их заменил на NtClose.


    Аккуратно трассируя, подменяем в регистрах адрес NtClose, на необходимые нам адреса из kernel32.dll. После отработки цикла, все необходимые адреса получены, как видно на изображении ниже. С этой проблемой покончено, следуем дальше.


    Убеждаемся, что подмененная нами ExAllocatePool на GlobalAlloc стабильно отрабатывает.


    Незаметно подошли к распаковке.


    Вероятно код писался на assembler’е, так как нет ничего лишнего. Код на скрине делает выделение памяти, распаковку в нее, потом делает подготовку образа, зануляет и чистит память, и в случае успеха прыгает на OEP.
    Алгоритм распаковки я не стал изучать, потому что на вид пожатые данные мне показались похожи на вариант LZW.


    Распаковщик не использует никаких API, поэтому прогоняется быстро и без проблем. Собственно, сразу после этого можно делать дамп региона с чистым драйвером. Но мне было интересно, на сколько можно будет продвинуться в анализе, находясь в ring3.
    Функция PrepareImage подготавливает распакованный образ: делает ремап секций по необходимым смещениям, получает адреса API из импорта, производит пересчет адресов по таблице relocations.
    Очередные палки в колеса нам сует цикл поиска функций для IAT, который не только запрашивает модули, которых у нас нет (ntoskrnl, hal и др), но и соответственно функции.


    Как видно я уже попался и вошел в цикл, но поменяв EIP на 0x008982d7, уменьшив ESP и установив в EAX = 0, более-менее корректно вышел из него. Правка reloc’ов не приносит нам каких-либо неприятностей, и мы наконец выходим на OEP. Но на этом придется остановиться, так как адреса импорта не восстановлены, а писать очередную dll с заглушками я не вижу смысла. Чистый код можно уже проанализировать статически в дизассемблере.

    Вместо вывода

    До:


    …и после:


    Чтобы не мучать Вас вбиванием строк с нижнего скриншота в поиск, скажу сразу, что это одна из версий Rustock’а

    В очередной раз убеждаюсь, что моя лень заставляет извращаться еще дольше, чем это можно было бы сделать решением «в лоб».
    Share post

    Comments 12

      0
      А я всегда думал, что у антисов есть отдельные машины, чтобы запускать всякую малвару, или хотя бы вмваре или виртуалбокс. А у вас жесть.

      «Наша служба и опасна и трудна,
      И на первый взгляд как будто не видна,
      На второй как будто тоже не видна,
      И на третий тоже...»

        +1
        Естествеyно все делается исключительно в VMWare. Не знаю с чего вы решили, что это не так? Нежелание загружать драйвер было в кривой работе syser под виртуалкой.
        0
        И разве русток не был закрыт мелкомягкими? Закрыт, причем с концами, уже несколько месяцев как. blogs.drweb.com/node/824

        Зачем так извращаться, еще и со старыми образцами малваре? Возьмите что-то новенькое, тогда уж, чтоли.
          +2
          Слышал тоже что закрыт, но раз уж пришел образец на анализ, который к тому же не распаковывался движком, пришлось это делать самому и добавлять сигнатуру поверх пакера. Так сказать что приходит, с тем и работаю. Сам поисками редко занимаюсь.
          0
          эх, а hiew постарел на пару-тройку мажорных версий уже…
          спасибо, интересно.
            +4
            > В очередной раз убеждаюсь, что моя лень заставляет извращаться еще дольше, чем это можно было бы сделать решением «в лоб»

            Надеюсь всё это делалось из туалета с коммуникатора и посредством VNC
              0
              >>Чтобы драйвер мог подгрузить именно нашу dll, а не системную, пропатчим имена импортируемых модулей, на подготовленные нами.
              А что из себя представляет Hak.dll? Это пропатченный ntoskrnl.exe или это ваша кастомная?
                0
                Упс. Не заметил:
                >>Для этого напишем свою dll (или несколько, кому как угодно) с заглушками необходимых функций.
                Но на мой взгляд лучше всего делать не в OllyDebug, а связке VMware+VirtualKD -> WinDbg. Если прикрутить pykd это модуль скриптования WinDbg на Python
                +1
                Хм… Интересный изврат :-) Но IDA Pro + VmWare чесслово лучше. Благо что отладчик будет прямо в иде. Есть мануал в нете, как стыковать и настраивать, если что — проконсультирую.

                «Алгоритм распаковки я не стал изучать, потому что на вид пожатые данные мне показались похожи на вариант LZW.» — Это apLib, M8ZP8 — тичное начало упакованных им данных.
                  +1
                  Про связку VMWare+IDA я в курсе, для отладки mbr как раз это и использую. За точное название алгоритма спасибо, буду знать. На сколько понял он тоже основан на LZ
                    0
                    Вот честно скажу: не знаю, основан он на LZ или нет. apLib и дело с концом :-)
                      0
                      >>Вот честно скажу: не знаю, основан он на LZ или нет. apLib и дело с концом
                      apLib? Этож LZ. Встречается в RlPak, PePack и во множестве малвары с обфускацией и заменой констант

                Only users with full accounts can post comments. Log in, please.