• Приглашаем на KubanCTF

    Старт соревнований 14 сентября в 10:00 по москве

    Ссылка на регистрацию в соревнованиях Kuban CTF: kubanctf.ru

    Кодебай является технологическим партнером мероприятия

Статья ASM. РЕ файл – ломаем стереотипы. Часть-5 Форвардинг.

Marylin

Mod.Assembler
Red Team
05.06.2019
310
1 398
BIT
416
Logo.png

Данная статья является последней в цикле «Исследование формата РЕ-файлов», и по большей части будет посвящена динамическим библиотекам DLL, а точнее механизму «перенаправления вызовов функций», что на английский манер звучит как «Forwarding». Остальные 4 части цикла можно найти здесь. Разрешение ссылок из импорта исполняемых файлов на сторонние библиотеки, позволяет обходить некоторые инструменты анализа двоичных файлов, и если вас интересует данная тема, то прошу под кат.


Общие сведения о форвардинге

Для начала ответим на вопрос, зачем вообще нужно форвардить вызовы API, и что это даёт нам как кодокопателям?
Если посмотреть на это дело с практической точки зрения – фишка бесполезная, т.к. требует накладных расходов. Суть в том, что когда мы загружаем какую-либо библиотеку(А) с предусмотренным в ней перенаправлением к библиотеке(В), система будет вынуждена подгрузить и вторую в память нашего процесса, поскольку фактически код вызываемой функции API находится именно в библиотеке(В), а в первой прописывается лишь указатель-ссылка на неё. Раньше такой механизм представлял собой проблему, т.к. памяти ОЗУ было мало, и в критических случаях системе приходилось освобождать страницы памяти в своп на жёсткий диск, что влекло за собой тормоза. В качестве примера можно привести DLL пользовательского режима Kernel32.dll, некоторые функции которой как-раз и перенаправляются к нативной библиотеке системы Ntdll.dll:

Forvard_1.png

Таким образом, Kernel32.dll не содержит в свой тушке исполняемого кода той-же функции HeapAlloc(), а её вызовы будут транзитом уходить к расположенной в Ntdll.dll RtlAllocateHeap(). В случае именно с Ntdll.dll ситуация не так критична, т.к. эта библиотека (хотим мы того или нет) всегда загружается во-все пользовательские процессы. Однако Kernel32 это не единственная либа с форвардингом в системе – как минимум 30% dll в оси имеют этот механизм. Вот здесь и всплывают наружу плюсы перенаправления!

Возьмём к примеру хорошо известную всем библиотеку для работы с сетевыми сокетами Wsock32.dll, которую инженеры Microsoft прописали в штат оси ещё на Win98. С той поры уже несколько раз ужесточалась политика безопасности системы, включая полностью обновлённый гардероб сети. Устаревшая Wsock32.dll не удовлетворяла теперь системным требованиям, но к моменту выхода WinXP на этой библиотеке было уже написано огромное кол-во софта. Как результат, инженерам пришлось оставить данную DLL, а вызовы функций из неё просто форвардить в более усовершенствованную и защищённую ws2_32.dll. Чтобы не было путаницы, даже имена API было решено оставить прежними, что продемонстрировано на скрине ниже:

Wsock32.png

Здесь становится очевидно, что где это возможно, всегда нужно импортировать в своё приложение более новые DLL, т.к. это позволит экономно расходовать выделенную нашему процессу память. И правда, зачем тянуть Wsock32.dll, если полезный код всё-равно находится в другой библиотеке? С другой стороны, достаточно импортировать (в данном случае) всего одну либу Wsock32.dll, как Mswsock и ws2_32.dll на автомате подгрузятся в память. В общем это палка с двумя концами, и мы сами вольны выбирать удобную схему импорта.


Определение «Proxy-DLL»

Библиотеки с форвардингом на борту принято называть «Proxy-DLL», что как нельзя лучше характеризует механизм перенаправления (proxy, посредник). Если рассматривать такие либы с колокольни малвари, то злоумышленник создаёт свою собственную DLL под зарегистрированным в системе именем (например User32.dll), форвардит все имеющиеся в ней легальные функции API, и в качестве пайлоада добавляет в эту фиктивную DLL несколько своих функций.

Ставка делается на то, что при загрузке импортируемых DLL, системный лоадер осуществляет поиск этих DLL по строго определённому сценарию, а в дефолте поиск начинается именно с текущего каталога, где и расположен сам исполняемый файл. Полная последовательность поиска прилинкованных DLL выглядит так (правда на Win11 порядок уже изменили и пункт(1) переместили в позицию 3, но всё-же..):
  1. Каталог из которого загружается текущее приложение;
  2. Системная папка Windows\System32 (если код х32, то соответственно WoW64);
  3. Папка Windows;
  4. Перечисленные в переменной PATH каталоги.
Таким образом, если злоумышленник узнает путь к нашему приложению, то поместив фиктивную свою DLL рядом, он сможет не только передать управление на вредоносный код, но бонусом заполучить ещё и права того, кто запускает исполняемый файл ..и хорошо, если это будет не админ системы. Атаки подобного рода известны как «DLL Preloading Attack», или атака на подготовительном этапе загрузки DLL в память.

В качестве мер предупреждения подобного рода атак, подсистема безопасности Win предлагает нам широкий перечень услуг, большинством из которых мы, как-правило, пренебрегаем. В частности, в системном реестре есть ветка HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs, где перечислены имена всех приоритетных для Win библиотек (известные Known), среди которых присутствует и указанная выше User32.dll. Если имя библиотеки находится в этом списке, то создать фиктивную DLL с таким-же именем уже не получится, т.к. система всё-равно загрузит либу из папки Win (другими словами, имя User32.dll для организации такой атаки не подходит). Однако в системе имеется огромное кол-во DLL не указанных в этом листе (см.каталог system32), которые и могут послужить хорошими кандидатами на роль «подсадной утки».

В общем случае, при импорте API нужно придерживаться следующих простых правил:

1. При вызове функций LoadLibrary(), LoadLibraryEx(), CreateProcess() и ShellExecute(), в их параметрах желательно указывать полный путь до импортируемых (или загружаемых иным способом) модулей. Когда мы указываем абсолютный путь, в последовательности поиска DLL он становится приоритетным, и вышеуказанная атака будет обречена уже на провал. В большинстве случаях этого вполне достаточно, но не всегда.​
2. Расширенная версия API динамической загрузки библиотек LoadLibraryEx() имеет набор флагов с префиксом LOAD_LIBRARY_SEARCH_xx, которые позволяют сформировать свой порядок поиска DLL системой – указываем SYSTEM32 и можем спать спокойно. Вот список этих флагов с назначенными им константами:​
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 0x00000100
LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200
LOAD_LIBRARY_SEARCH_USER_DIRS = 0x00000400
LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000
3. Если-же вам лень вызывать лишние функции, и вы пребываете в полной нирване, то хотя-бы убедитесь, что в системе включён «безопасный режим поиска DLL». Этот режим заставляет систему парсить текущий каталог юзера позже в процессе поиска, увеличивая вероятность того, что загрузчик найдет валидную DLL раньше зловредной. Безопасный режим управляется ключом реестра HKLM\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode (если такого ключа у вас нет, то создайте его и присвойте бинарную единицу для включения, и нуль для отключения режима). При наличии данного ключа в реестре, им можно оперировать и на программном уровне, посредством вызова функции SetSearchPathMode() с флагом BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE = 0x0001, что позволит отбросить текущий каталог в самый конец списка поиска DLL.​
4. Ну и наконец можно вообще рубануть с плеча и вовсе скрыть текущий каталог, в результате чего загрузчик будет полностью игнорировать его при поиске библиотек. Для этого имеется трюк с функцией SetDllDirectory(), в параметре которой достаточно передать пустую строку "". Отметим, что восстанавливать обратно дефолтный режим совсем необязательно, т.к. действие функции SetDllDirectory() распространяется исключительно на текущий процесс.​


Реализация форвардинга на практике

В практической части попробуем создать примитивную Proxy-DLL, только немного отойдём от привычных правил, и создадим не библиотеку, а Proxy из своего-же EXE. Во-первых это позволит избавиться от лишнего груза в виде кастомной библиотеки (не нужно будет таскать её с собой), а во-вторых нетипичные схемы создания исполняемых файлов вводят в ступор различные инстументы анализа, что несомненно сыграет нам на руку. Связь секции-импорта с экспортом продемонстрирована на рис.ниже:

ImpExp.png

В силу того, что никто не запрещает нам создавать секцию-экспорта в файлах EXE, мы просто обзываем экспортируюмую из нашего-же приложения функцию на своё усмотрение, но вместо кода просто перенаправляем/форвардим её вызов в любую системную библиотеку DLL. Например на схеме выше, я экспортирую свою функцию MyCreateFile(), но вместо её адреса прописываю указатель на текстовую строку вида Kernel32.CreateFile если экспорт по имени, или-же Kernel32#678 (хэштег вместо точки), если планирую осуществлять экспорт только по ординалу. Вот собственно и пример на ассемблере fasm:

C-подобный:
format   pe64 console
entry    start
include 'win64ax.inc'
;//============================================
section '.data' data readable writeable
text       db  10,' Import Through Export scheme',0

;//=== Секция-экспорта своих функций ==========
section '.edata' export data readable
export  'PeImpFromExp.exe',\
         frwdPrintf, 'ShowToUser',\     ;// перечисляем их..
         frwdGetch,  'UserSpeak',\
         frwdExit,   'GatewayHome'
align 16
         frwdPrintf   db  'msvcrt.printf',0  ;// форвардинг в либу MSVCRT.DLL
         frwdGetch    db  'msvcrt._getch',0
         frwdExit     db  'msvcrt.exit',0

;//=== Импорт из своего-же EXE ================
section '.idata' import data readable
library  peImp,'PeImpFromExp.exe'
import   peImp, ShowToUser, 'ShowToUser',\
                UserSpeak,  'UserSpeak',\
                GatewayHome,'GatewayHome'

;//=== Теперь можем вызывать API ==============
section '.code' code readable executable
start:   sub     rsp,8
frame
        cinvoke  ShowToUser,text
@exit:  cinvoke  UserSpeak
        cinvoke  GatewayHome,0
endf

Как видим, ничего сложного в организации форвардинга нет, только нужно учитывать один важный момент – имя текущего EXE должно обязательно совпадать с тем, что указано в секциях экспорта/импорта. Иначе системный загрузчик не сможет обнаружить от куда экспортируются функции, и мы получим ошибку как на рис.ниже. В данном случае мой исполняемый файл называется PeImpFromExp.exe, и достаточно удалить последний символ(р) в имени, как вся конструкция рухнет:

NameErr.png

Ну и теперь то, ради чего собственно и весь сыр-бор.
Вскормив свежеиспечённый файл дизассемблеру IDA обнаруживаем, что он не смог найти указатель на перенаправленную библиотеку MSVCRT.DLL, и тем более поймать за хвост импортируемые нами функции printf() и прочии – ссылок на них нет ни в импорте, ни в экспорте. Аналогичная участь ждёт и остальные инструменты статического анализа типа DIE и вся их братия. Из этого списка можно выделить софт «PE-Anatomist» и двоичный редактор «HIEW» - форвардинг от них точно не ускользнёт. Но т.к. этой парочкой редко кто пользуется, выходит таким образом можно хоть как-то маскировать импорт API из системных библиотек.

IDA_x.png

А как на счёт отладчиков.. знакомы-ли они с форвардингом? Ну здесь уже без вариантов, и перенаправления API для них не представляют никакую проблему. Учитывая, что отладка – это макс. приближённая к боевым действиям обстановка, адреса уже загруженных в память функций выдают форвардинг с потрохами, чего собственно и следовало ожидать.

x64dbg.png

Заключение

Подводя черту под темой исследования РЕ-файлов хотелось-бы отметить, что при создании исполняемых EXE/DLL/SYS не нужно придерживаться предписанных непонятно кем правил, а лучше ознакомившись как следует с документацией на PE/COFF подключить фантазию, и попытаться сотворить что-то своё. Разработчики инструментов анализа просто не в силах предусмотреть все мелкие особенности РЕ-формата, которых водится в доках с избытком. Наша цель - опытным путём обнаружить такие нюансы, а остальное уже дело техники. Например тот-же форвардинг API можно вообще замкнуть в кольцо по типу «вращающихся дверей», когда экспорт выходит наружу из нашего файла, и прогулявшись по системным DLL опять возвращается в нашу-же тушку, где и располагается полезный код экспортируемой функции. В общем вариантов хоть отбавляй – тут главное запутать следы. В скрепке найдёте скомпилированный выше файл для тестов, до скорого, пока!
 

Вложения

  • PeImpFromExp.zip
    569 байт · Просмотры: 34
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!