commit 0a2896519b2abfb0fa334a098269886a5e7a4ac0 Author: flux <49762827+0xflux@users.noreply.github.com> Date: Wed Jun 12 08:09:24 2024 +0100 Rework from last few weeks local diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2491af4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*/target +/target \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4dc33c3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "shellcode" + ] +} \ No newline at end of file diff --git a/builder/Cargo.lock b/builder/Cargo.lock new file mode 100644 index 0000000..c545746 --- /dev/null +++ b/builder/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "builder" +version = "0.1.0" diff --git a/builder/Cargo.toml b/builder/Cargo.toml new file mode 100644 index 0000000..74d6934 --- /dev/null +++ b/builder/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "builder" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/builder/src/main.rs b/builder/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/builder/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/shellcode/.cargo/config b/shellcode/.cargo/config new file mode 100644 index 0000000..1db95b2 --- /dev/null +++ b/shellcode/.cargo/config @@ -0,0 +1,21 @@ +[build] +target = "x86_64-pc-windows-msvc" + +rustflags = [ + # pre-link + "-Z", "pre-link-arg=/NODEFAULTLIB", # not to use the default libraries + "--emit", "asm", # instructs the compiler to emit assembly code in addition to binary + + # post-link + "-C", "link-arg=/ENTRY:main", + "-C", "link-arg=/MERGE:.edata=.rdata", + "-C", "link-arg=/MERGE:.rustc=.data", + "-C", "link-arg=/MERGE:.rdata=.text", + "-C", "link-arg=/MERGE:.pdata=.text", + "-C", "link-arg=/DEBUG:NONE", # disables generation of debug information + "-C", "link-arg=/EMITPOGOPHASEINFO", # generates phase information for profile-guided optimisation + "-C", "target-feature=-mmx,-sse,+soft-float" # disable mmx and sse +] + +[unstable] +build-std = ["core", "alloc"] \ No newline at end of file diff --git a/shellcode/Cargo.lock b/shellcode/Cargo.lock new file mode 100644 index 0000000..17259b4 --- /dev/null +++ b/shellcode/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "shellcode" +version = "0.1.0" diff --git a/shellcode/Cargo.toml b/shellcode/Cargo.toml new file mode 100644 index 0000000..37eaf48 --- /dev/null +++ b/shellcode/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "shellcode" +version = "0.1.0" +edition = "2021" + +[dependencies] + +[profile.dev] +panic = "abort" +opt-level = "z" +lto = true + +[profile.release] +panic = "abort" +opt-level = "z" +lto = true \ No newline at end of file diff --git a/shellcode/src/main.rs b/shellcode/src/main.rs new file mode 100644 index 0000000..f603fb9 --- /dev/null +++ b/shellcode/src/main.rs @@ -0,0 +1,79 @@ +#![no_std] +#![no_main] +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] +#![allow(non_upper_case_globals)] +#![allow(overflowing_literals)] + + mod resolver; + +use core::{arch::asm, ffi::c_void, mem::transmute, panic::PanicInfo, ptr::null}; + +#[link(name = "vcruntime")] +extern {} + +#[link(name = "ucrt")] +extern {} + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + loop {} +} + +/// FFI binding for MessageBoxA +type MessageBoxA = extern "system" fn( + handle: *const u8, + lp_text: *const u8, + lp_caption: *const u8, + t: usize, +) -> *const c_void; + +/// FFI for LoadLibraryA +type LoadLibraryA = extern "system" fn( + lib_name: *const u8, +) -> isize; + +/// FFI for +type GetProcAddress = extern "system" fn( + handle: *const usize, + proc_name: *const u8, + ) -> *mut c_void; + +#[no_mangle] +pub extern "C" fn main() { + // stack strings + let kernel_32 = b"KERNEL32.DLL\0"; + let load_library_a = b"LoadLibraryA\0"; + let get_proc_addr = b"GetProcAddress\0"; + let message_box_a = b"MessageBoxA\0"; + let user_32 = b"User32.dll\0"; + + // get virtual addresses + let load_library_a = resolver::get_function_from_exports(kernel_32, load_library_a).unwrap(); + let get_proc_addr = resolver::get_function_from_exports(kernel_32, get_proc_addr).unwrap(); + + // obtaining User32.dll + let load_library_a: LoadLibraryA = unsafe { transmute(load_library_a.address) }; + let user_32_dll = load_library_a(user_32 as *const u8); + let get_proc_address: GetProcAddress = unsafe { transmute(get_proc_addr.address) }; + + // get msg box fn + let message_box_address = get_proc_address(user_32_dll as *const usize, message_box_a as *const u8); + let message_box_a:MessageBoxA = unsafe { transmute(message_box_address) }; + + // align the stack to divisible by 16 + unsafe { asm!("and rsp, ~0xf") }; + + let msg = b"Injected!\0"; + + // unsafe { asm!("int3") }; + message_box_a( + null(), + msg as *const u8, + msg as *const u8, + 0x0, + ); +} + + + diff --git a/shellcode/src/resolver.rs b/shellcode/src/resolver.rs new file mode 100644 index 0000000..46ed497 --- /dev/null +++ b/shellcode/src/resolver.rs @@ -0,0 +1,334 @@ +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] +#![allow(dead_code)] + +use core::{arch::asm, ffi::c_void, ops::Add, slice::from_raw_parts, str::{from_utf8, from_utf8_mut}}; + +/// A structure containing the module name, function name, and export address for each function loaded +/// into the portable executable on x64 systems only. +pub struct ExportResolver<'a> { + pub module: &'a str, + pub base_address: usize, // to prevent repeat reads of the peb + pub function: &'a str, + pub address: usize, +} + +/// Get the base address of a specified module. Obtains the base address by reading from the TEB -> PEB -> +/// PEB_LDR_DATA -> InMemoryOrderModuleList -> InMemoryOrderLinks -> DllBase +/// +/// Returns the DLL base address as a Option +#[allow(unused_variables)] +#[allow(unused_assignments)] +fn get_module_base(module_name: &[u8]) -> Option { + + let module_name: &str = from_utf8(module_name).unwrap(); + + let mut peb: usize; + let mut ldr: usize; + let mut in_memory_order_module_list: usize; + let mut current_entry: usize; + + unsafe { + // get the peb and module list + asm!( + "mov {peb}, gs:[0x60]", + "mov {ldr}, [{peb} + 0x18]", + "mov {in_memory_order_module_list}, [{ldr} + 0x10]", // points to the Flink + peb = out(reg) peb, + ldr = out(reg) ldr, + in_memory_order_module_list = out(reg) in_memory_order_module_list, + ); + + // set the current entry to the head of the list + current_entry = in_memory_order_module_list; + + // iterate the modules searching for + loop { + // get the attributes we are after of the current entry + let dll_base = *(current_entry.add(0x30) as *const usize); + let module_name_address = *(current_entry.add(0x60) as *const usize); + let module_length = *(current_entry.add(0x58) as *const u16); + + // check if the module name address is valid and not zero + if module_name_address != 0 && module_length > 0 { + // read the module name from memory + let dll_name_slice = from_raw_parts(module_name_address as *const u16, (module_length / 2) as usize); + let mut buffer = [0u8; 512]; + if let Some(dll_name) = wide_to_str(dll_name_slice, &mut buffer) { + // do we have a match on the module name? + if dll_name.eq_ignore_ascii_case(module_name) { + return Some(dll_base); + } + } + + } else { + return None; + } + + // dereference current_entry which contains the value of the next LDR_DATA_TABLE_ENTRY (specifically a pointer to LIST_ENTRY + // within the next LDR_DATA_TABLE_ENTRY) + current_entry = *(current_entry as *const usize); + + // If we have looped back to the start, break + if current_entry == in_memory_order_module_list { + return None; + } + } + } +} + +/// Get the function address of a function in a specified DLL from the DLL Base. +/// +/// # Parameters +/// * dll_name -> the name of the DLL / module you are wanting to query +/// * needle -> the function name (case sensitive) of the function you are looking for +/// +/// # Returns +/// Option<*const c_void> -> the function address as a pointer +pub fn get_function_from_exports<'a>(dll_name: &'a [u8], needle: &'a [u8]) -> Option> { + + let dll_name = strip_null_terminator(dll_name); + let needle = strip_null_terminator(&needle); + + let needle = from_utf8(&needle).unwrap(); + + // if the dll_base was already found from a previous search then use that + // otherwise, if it was None, make a call to get_module_base + let dll_base: *mut c_void = match get_module_base(dll_name) { + Some(a) => a as *mut c_void, + None => { + return None; + }, + }; + + let dll_name = from_utf8(dll_name).unwrap(); + + // check we match the DOS header, cast as pointer to tell the compiler to treat the memory + // address as if it were a IMAGE_DOS_HEADER structure + let dos_header: IMAGE_DOS_HEADER = unsafe { read_memory(dll_base as *const IMAGE_DOS_HEADER) }; + if dos_header.e_magic != IMAGE_DOS_SIGNATURE { + return None; + } + + // check the NT headers + let nt_headers = unsafe { read_memory(dll_base.offset(dos_header.e_lfanew as isize) as *const IMAGE_NT_HEADERS64) }; + if nt_headers.Signature != IMAGE_NT_SIGNATURE { + return None; + } + + // get the export directory + // https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_data_directory + // found from first item in the DataDirectory; then we take the structure in memory at dll_base + RVA + let export_dir_rva = nt_headers.OptionalHeader.DataDirectory[0].VirtualAddress; + let export_offset = unsafe {dll_base.add(export_dir_rva as usize) }; + let export_dir: IMAGE_EXPORT_DIRECTORY = unsafe { read_memory(export_offset as *const IMAGE_EXPORT_DIRECTORY) }; + + // get the addresses we need + let address_of_functions_rva = export_dir.AddressOfFunctions as usize; + let address_of_names_rva = export_dir.AddressOfNames as usize; + let ordinals_rva = export_dir.AddressOfNameOrdinals as usize; + + let functions = unsafe { dll_base.add(address_of_functions_rva as usize) } as *const u32; + let names = unsafe { dll_base.add(address_of_names_rva as usize) } as *const u32; + let ordinals = unsafe { dll_base.add(ordinals_rva as usize) } as *const u16; + + // get the amount of names to iterate over + let number_of_names = export_dir.NumberOfNames; + + for i in 0..number_of_names { + // calculate the RVA of the function name + let name_rva = unsafe { *names.offset(i.try_into().unwrap()) as usize }; + // actual memory address of the function name + let name_addr = unsafe { dll_base.add(name_rva) }; + + // read the function name + let function_name = unsafe { + let char = name_addr as *const u8; + let mut len = 0; + // iterate over the memory until a null terminator is found + while *char.add(len) != 0 { + len += 1; + } + + from_raw_parts(char, len) + }; + + let function_name = from_utf8(function_name).unwrap_or("Invalid UTF-8"); + if function_name.eq("Invalid UTF-8") { + return None; + } + + // if we have a match on our function name + if function_name.eq(needle) { + + // calculate the RVA of the function address + let ordinal = unsafe { *ordinals.offset(i.try_into().unwrap()) as usize }; + let fn_rva = unsafe { *functions.add(ordinal) as usize }; + // actual memory address of the function address + let fn_addr = unsafe { dll_base.add(fn_rva) } as *const c_void; + + let result = ExportResolver { + module: dll_name, + base_address: dll_base as usize, + function: needle, + address: fn_addr as usize, + }; + + return Some(result); + } + } + + None +} + +/// Read memory of any type +unsafe fn read_memory(address: *const T) -> T { + core::ptr::read(address) +} + +fn wide_to_str<'a>(wide: &[u16], buffer: &'a mut [u8]) -> Option<&'a str> { + let mut len = 0; + + for &w in wide { + // convert each u16 character to u8 and store in the byte array + if w == 0 { break; } // stop at null terminator + if w > 0xFF { + return None; // non-ASCII characters are not handled here + } + if len >= buffer.len() { + return None; // buffer overflow protection + } + buffer[len] = w as u8; + len += 1; + } + + // Convert the byte array to &str + match from_utf8_mut(&mut buffer[..len]) { + Ok(s) => Some(s), + Err(_) => None, + } +} + +fn strip_null_terminator(bytes: &[u8]) -> &[u8] { + if let Some(pos) = bytes.iter().position(|&x| x == 0) { + &bytes[..pos] + } else { + bytes + } +} + +#[repr(C, packed(2))] +pub struct IMAGE_DOS_HEADER { + pub e_magic: u16, + pub e_cblp: u16, + pub e_cp: u16, + pub e_crlc: u16, + pub e_cparhdr: u16, + pub e_minalloc: u16, + pub e_maxalloc: u16, + pub e_ss: u16, + pub e_sp: u16, + pub e_csum: u16, + pub e_ip: u16, + pub e_cs: u16, + pub e_lfarlc: u16, + pub e_ovno: u16, + pub e_res: [u16; 4], + pub e_oemid: u16, + pub e_oeminfo: u16, + pub e_res2: [u16; 10], + pub e_lfanew: i32, +} + +pub const IMAGE_DOS_SIGNATURE: u16 = 23117u16; + +#[repr(C)] +pub struct IMAGE_NT_HEADERS64 { + pub Signature: u32, + pub FileHeader: IMAGE_FILE_HEADER, + pub OptionalHeader: IMAGE_OPTIONAL_HEADER64, +} + +#[repr(C)] +pub struct IMAGE_FILE_HEADER { + pub Machine: IMAGE_FILE_MACHINE, + pub NumberOfSections: u16, + pub TimeDateStamp: u32, + pub PointerToSymbolTable: u32, + pub NumberOfSymbols: u32, + pub SizeOfOptionalHeader: u16, + pub Characteristics: IMAGE_FILE_CHARACTERISTICS, +} + +#[repr(C, packed(4))] +pub struct IMAGE_OPTIONAL_HEADER64 { + + pub Magic: IMAGE_OPTIONAL_HEADER_MAGIC, + pub MajorLinkerVersion: u8, + pub MinorLinkerVersion: u8, + pub SizeOfCode: u32, + pub SizeOfInitializedData: u32, + pub SizeOfUninitializedData: u32, + pub AddressOfEntryPoint: u32, + pub BaseOfCode: u32, + pub ImageBase: u64, + pub SectionAlignment: u32, + pub FileAlignment: u32, + pub MajorOperatingSystemVersion: u16, + pub MinorOperatingSystemVersion: u16, + pub MajorImageVersion: u16, + pub MinorImageVersion: u16, + pub MajorSubsystemVersion: u16, + pub MinorSubsystemVersion: u16, + pub Win32VersionValue: u32, + pub SizeOfImage: u32, + pub SizeOfHeaders: u32, + pub CheckSum: u32, + pub Subsystem: IMAGE_SUBSYSTEM, + pub DllCharacteristics: IMAGE_DLL_CHARACTERISTICS, + pub SizeOfStackReserve: u64, + pub SizeOfStackCommit: u64, + pub SizeOfHeapReserve: u64, + pub SizeOfHeapCommit: u64, + pub LoaderFlags: u32, + pub NumberOfRvaAndSizes: u32, + pub DataDirectory: [IMAGE_DATA_DIRECTORY; 16], +} + +#[repr(transparent)] +pub struct IMAGE_FILE_MACHINE(pub u16); + +#[repr(transparent)] +pub struct IMAGE_FILE_CHARACTERISTICS(pub u16); + +#[repr(transparent)] +pub struct IMAGE_OPTIONAL_HEADER_MAGIC(pub u16); + +#[repr(transparent)] +pub struct IMAGE_SUBSYSTEM(pub u16); + +#[repr(transparent)] +pub struct IMAGE_DLL_CHARACTERISTICS(pub u16); + +#[repr(C)] +pub struct IMAGE_DATA_DIRECTORY { + pub VirtualAddress: u32, + pub Size: u32, +} + +pub const IMAGE_NT_SIGNATURE: u32 = 17744u32; + +#[repr(C)] +pub struct IMAGE_EXPORT_DIRECTORY { + pub Characteristics: u32, + pub TimeDateStamp: u32, + pub MajorVersion: u16, + pub MinorVersion: u16, + pub Name: u32, + pub Base: u32, + pub NumberOfFunctions: u32, + pub NumberOfNames: u32, + pub AddressOfFunctions: u32, + pub AddressOfNames: u32, + pub AddressOfNameOrdinals: u32, +} \ No newline at end of file