Внедрение DLL с помощью CreateRemoteThread

Если вы посмотрите на следующий рабочий код простой инъекции DLL:

  //Open the target process with read , write and execute priviledges
   Process = OpenProcess(PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_READ|PROCESS_VM_WRITE|PROCESS_VM_OPERATION, FALSE, ID); 

   //Get the address of LoadLibraryA
   LoadLibrary = (LPVOID)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA"); 

   // Allocate space in the process for our DLL 
   Memory = (LPVOID)VirtualAllocEx(Process, NULL, strlen(dll)+1, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 

   // Write the string name of our DLL in the memory allocated 
   WriteProcessMemory(Process, (LPVOID)Memory, dll, strlen(dll)+1, NULL); 

   // Load our DLL 
   CreateRemoteThread(Process, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibrary, (LPVOID)Memory, NULL, NULL); 

   //Let the program regain control of itself
   CloseHandle(Process); 

Меня смущает то, что GetProcAddress возвращает LoadLibraryA функциональный адрес текущего процесса, как вы можете передать его в качестве параметра CreateRemoteThread и ожидать, что целевой процесс запустит его?


person James King    schedule 30.03.2014    source источник
comment
Потому что CreateRemoteThread принимает LoadLibrary в качестве параметра и вызывает его. Поскольку он также принимает Memory в качестве параметра, этот параметр передается в LoadLibrary.   -  person Brandon    schedule 31.03.2014
comment
Это все еще не может объяснить, зачем вам нужен LoadLibrary адрес функции текущего процесса. Memory - это адрес для имени dll. Почему бы просто не передать LoadLibrary в виде строки, если вы просто хотите ее вызвать?   -  person James King    schedule 31.03.2014
comment
Потому что смещение будет точно таким же в другом процессе. Если другой процесс — x32, а ваш процесс — x32, смещение от kernel32 будет таким же. Если ваш процесс x64, а другой процесс x64, смещение снова будет таким же. Если другой процесс — x32, а ваш — x64 или наоборот, смещение будет другим, и внедрение завершится ошибкой. Я считаю, что User32.dll также всегда загружается с одним и тем же смещением. Похоже на: Kernel32.dll   -  person Brandon    schedule 31.03.2014


Ответы (4)


Это работает случайно. Это очень частая случайность. Microsoft прилагает огромные усилия, чтобы убедиться, что DLL операционной системы, такие как kernel32.dll, имеют базовый адрес, не конфликтующий ни с какими другими DLL. Дальнейшее улучшение за счет того, что kernel32.dll загружается очень рано при инициализации процесса, поэтому вероятность того, что ему приходится бороться за получение своего предпочтительного базового адреса, низка.

Вы легко отделаетесь. Примечательно, что в прошлом это было не так, было обновление безопасности XP, которое привело к перемещению gdi32.dll и к падению многих машин при загрузке. Правильный способ довольно болезненный, CreateToolhelp32Snapshot() + Module32First/Next(), чтобы найти смещение перемещения, не доставляет большого удовольствия. Честно говоря, вам, вероятно, вообще не следует этого делать, если операционная система такая «странная».

person Hans Passant    schedule 30.03.2014
comment
подождите... так это работает, потому что LoadLibrary удовлетворяет сигнатуре LPTHREAD_START_ROUTINE и оказывается по одному и тому же адресу во всех процессах? поэтому DllMain вызывается внутри другого пространства процесса... Я должен попробовать. - person Dmitry; 22.01.2017

Рандомизация адресного пространства (ASLR) — это функция защиты от эксплойтов, управляемая Windows, которая позволяет перераспределять адреса, чтобы помочь злоумышленникам определить адрес для использования чего-либо в памяти (предотвращает жесткое кодирование адресов/смещений). Однако модули Windows меняют свои адреса только за сеанс.

Если у вас есть процесс, использующий kernel32.dll (не все процессы используют kernel32.dll, и я объясню это далее через несколько минут), адрес подпрограммы может быть, например, 55AA1122 (это недопустимый примерный адрес). Теперь следующий процесс с kernel32.dll будет иметь тот же адрес 55AA1122 для той же подпрограммы, что и предыдущая... Только если процессы имеют одинаковую архитектуру.

32-разрядные процессы будут иметь те же адреса для экспорта kernel32.dll, среди других экспортов модулей Windows (например, NTDLL, USER32 и т. д.). 64-битные процессы будут иметь адреса, отличные от 32-битных процессов, однако все 64-битные процессы будут иметь одинаковые адреса для модулей Windows!

Создание удаленного потока не было «случайностью», Microsoft намеренно внедрила его. Почему? Microsoft часто использует его в самой Windows, а также для асинхронных вызовов процедур. Microsoft также часто вносит исправления для своих собственных подпрограмм в качестве антиреверсивного трюка или, если они теряют исходный код для своих собственных проектов, ха-ха.

Что касается загрузки kernel32.dll в процесс, то она загружается только в процессы, использующие Win32 API. Это включает в себя 99% программ в мире, однако можно скомпилировать собственный процесс, который не будет его использовать. Однако это заставит вас полностью использовать Native API, а не Win32 API, и процесс Windows, называемый smss.exe, делает именно это. Вы также можете скомпилировать Native DLL, которые даже не имеют обычной процедуры ввода DLL Win32 API.

Короче говоря, адреса подпрограмм модуля Windows меняются один раз при загрузке. Он сохранится до следующей перезагрузки и так далее. 32-разрядные процессы имеют свои собственные общие адреса модулей Windows для каждого процесса, как и 64-разрядные процессы. Таким образом, вы не можете использовать адрес LoadLibraryA 64-разрядного процесса при целевом внедрении DLL для 32-разрядного процесса, если вы не используете 32-разрядный адрес LoadLibraryA ядра Kernel32.dll. В любом случае, лучше было бы использовать LdrLoadDll или просто внедрить в шелл-код заглушку загрузчика рефлексивной DLL.

person PspSetProcessPpmPolicy    schedule 20.12.2017

LoadLibraryA живет в kernel32.dll, модуле, который всегда загружается в каждый процесс и также загружается по одному и тому же адресу в каждом процессе.

person 500 - Internal Server Error    schedule 30.03.2014
comment
LoadLibraryA ... happens to also be loaded at the same address in every process Есть ли какие-либо документы или ссылки на это предложение? - person James King; 31.03.2014
comment
Возможно, хотя у меня нет ссылки под рукой. Поскольку ntdll.dll и kernel32.dll автоматически загружаются Windows в качестве первых двух модулей помимо основного исполняемого файла, нет никакой возможности, чтобы предпочтительный адрес загрузки для этих двух модулей был занят чем-то другим. - person 500 - Internal Server Error; 31.03.2014
comment
Несколько ссылок: blogs.msdn.com/b/calvin_hsia/archive/2007/07/27/ и nynaeve.net/?p=198 Но было бы лучше найти некоторые документы из официального MSDN, а не только некоторые блоги. - person James King; 31.03.2014
comment
@500-InternalServerError: Это неправильная причина. ASLR заставит их не загружаться по предпочтительному адресу. Но ASLR меняет адрес загрузки только один раз за сеанс. - person Ben Voigt; 31.03.2014
comment
@BenVoigt: я исправлен. Спасибо, что напомнили мне об ASLR. Однако, как вы указываете, эффект остается прежним. - person 500 - Internal Server Error; 31.03.2014
comment
К вашему сведению: упомянутый выше блог Кельвина Хсиа теперь находится по адресу здесь (после архива блога MSDN). - person OzgurH; 10.04.2020

Если вы запускаете Visual Studio, создайте пустой проект, добавьте новый файл main.cpp, пропишите:

#include <windows.h>
void main()
{
}

А затем скомпилируйте эту программу, не ожидайте, что созданный исполняемый файл ничего не делает, это не так.

Возможно, компилятор Visual C++ не записал в объектный файл какую-либо команду, потому что в исходном коде команды нет, но компоновщик записал в начале основной процедуры исполняемого файла LoadLibrary вызовы user32.dll, kernel32.dll, gdi32.dll и т. д.

Таким образом, каждое приложение, написанное в Visual Studio C++, изначально выполняет множество одинаковых вызовов LoadLibrary в одном и том же порядке, независимо от исходного кода этого исполняемого файла.

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

Таким образом, каждое приложение Visual Studio C++ загружает LoadLibrary из kernel32.dll в начале выполнения, поэтому LoadLibrary имеет одинаковую точку входа или адрес во всех процессах относительно адреса процесса.

Теоретически вы можете заставить инжектор, вредоносную программу, которая пытается внедрить вашу программу, потерпеть неудачу, если вы скомпонуете какой-нибудь kernel32.lib, который не загружает процедуру LoadLibrary kernel32.dll в память вашей программы.

Инжектор не может заставить вашу программу динамически во время выполнения загружать некоторые dll, если ваша программа не может динамически во время выполнения загружать dll, потому что процедура, которая предполагает это сделать, LoadLibrary, отсутствует в памяти процесса.

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

Но возможно, что злоумышленник использует VirtualAllocEx для создания некоторого блока в памяти жертвы, WriteProcessMemory для некоторого исполняемого кода, а затем CreateRemoteThread для его выполнения.

person user9820482    schedule 26.06.2018