From 3260fbe40a294706e5be5ed3b1a581077eded294 Mon Sep 17 00:00:00 2001 From: memN0ps <89628341+memN0ps@users.noreply.github.com> Date: Thu, 16 Mar 2023 21:06:37 +1300 Subject: [PATCH] Rusty Ekko - Sleep Obfuscation in Rust by memN0ps and trickster0 --- memN0ps/ekko-rs/.gitignore | 10 ++ memN0ps/ekko-rs/Cargo.toml | 44 ++++++ memN0ps/ekko-rs/LICENSE | 21 +++ memN0ps/ekko-rs/README.md | 47 +++++++ memN0ps/ekko-rs/src/ekko.rs | 273 ++++++++++++++++++++++++++++++++++++ memN0ps/ekko-rs/src/main.rs | 11 ++ 6 files changed, 406 insertions(+) create mode 100644 memN0ps/ekko-rs/.gitignore create mode 100644 memN0ps/ekko-rs/Cargo.toml create mode 100644 memN0ps/ekko-rs/LICENSE create mode 100644 memN0ps/ekko-rs/README.md create mode 100644 memN0ps/ekko-rs/src/ekko.rs create mode 100644 memN0ps/ekko-rs/src/main.rs diff --git a/memN0ps/ekko-rs/.gitignore b/memN0ps/ekko-rs/.gitignore new file mode 100644 index 0000000..088ba6b --- /dev/null +++ b/memN0ps/ekko-rs/.gitignore @@ -0,0 +1,10 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/memN0ps/ekko-rs/Cargo.toml b/memN0ps/ekko-rs/Cargo.toml new file mode 100644 index 0000000..74b1628 --- /dev/null +++ b/memN0ps/ekko-rs/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "ekko-rs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[profile.release] +opt-level = "z" # Optimize for size. +lto = true # Enable Link Time Optimization +codegen-units = 1 # Reduce number of codegen units to increase optimizations. +panic = "abort" # Abort on panic +strip = true # Automatically strip symbols from the binary. + +[dependencies] +env_logger = "0.9.0" +log = "0.4.17" +#obfstr = "0.4.3" +ntapi = "0.4.0" +#winapi = { version = "0.3.9", features = ["synchapi"] } + +[dependencies.windows-sys] +version = "0.45.0" +features = [ + "Win32_Foundation", + "Win32_Security", + "Win32_System_Threading", + "Win32_UI_WindowsAndMessaging", + "Win32_System_Memory", + "Win32_System_Diagnostics_Debug", + "Win32_System_SystemServices", + "Win32_System_WindowsProgramming", + "Win32_System_LibraryLoader", + "Win32_NetworkManagement_IpHelper", + "Win32_Networking_WinSock", + "Win32_System_SystemInformation", + "Win32_System_Environment", + "Win32_System_ProcessStatus", + "Win32_Globalization", + "Win32_System_Diagnostics_ToolHelp", + "Win32_System_Kernel", + "Win32_System_Pipes", + "Win32_Storage_FileSystem", + "Win32_System_IO", +] diff --git a/memN0ps/ekko-rs/LICENSE b/memN0ps/ekko-rs/LICENSE new file mode 100644 index 0000000..43a19c8 --- /dev/null +++ b/memN0ps/ekko-rs/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 memN0ps + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/memN0ps/ekko-rs/README.md b/memN0ps/ekko-rs/README.md new file mode 100644 index 0000000..8b36701 --- /dev/null +++ b/memN0ps/ekko-rs/README.md @@ -0,0 +1,47 @@ +# Ekko in Rust + +A small sleep obfuscation technique that uses the `CreateTimerQueueTimer` Win32 API function ported from C https://github.com/Cracked5pider/Ekko/ to Rust. + +## Debugging + +For debugging uncomment `env_logger::init();` in main and set Powershell environment variable to `$Env:RUST_LOG="info"`. + +## Example + +``` +PS C:\Users\memN0ps\Documents\GitHub\ekko-rs\target\debug\ekko-rs.exe +[*] Ekko Sleep Obfuscation by @memN0ps and @trickster0. Full credits to Cracked5pider (@C5pider), Austin Hudson (@SecIdiot), Peter Winter-Smith (@peterwintrsmith) +[+] Queue timers +[+] Wait for hEvent +[+] Finished waiting for event +[+] Queue timers +[+] Wait for hEvent +[+] Finished waiting for event +[+] Queue timers +[+] Wait for hEvent +[+] Finished waiting for event +[+] Queue timers +[+] Wait for hEvent +[+] Finished waiting for event +[+] Queue timers +[+] Wait for hEvent +[+] Finished waiting for event +[+] Queue timers +[+] Wait for hEvent +[+] Finished waiting for event +[+] Queue timers +[+] Wait for hEvent +[+] Finished waiting for event +[+] Queue timers +[+] Wait for hEvent +[+] Finished waiting for event +``` + +## Credits / References + +- [@C5pider](https://twitter.com/C5pider) https://github.com/Cracked5pider/Ekko/ +- [Austin Hudson (@SecIdiot)](https://twitter.com/ilove2pwn_) https://suspicious.actor/2022/05/05/mdsec-nighthawk-study.html / https://web.archive.org/web/20220702162943/https://suspicious.actor/2022/05/05/mdsec-nighthawk-study.html +- Originally discovered by [Peter Winter-Smith](peterwintrsmith) and used in MDSec’s Nighthawk +- Thanks for contributing [@trickster012](https://twitter.com/trickster012) +- https://learn.microsoft.com/ +- Rust Lang Community Discord: https://discord.com/invite/rust-lang-community ([MaulingMonkey](https://github.com/MaulingMonkey/)) \ No newline at end of file diff --git a/memN0ps/ekko-rs/src/ekko.rs b/memN0ps/ekko-rs/src/ekko.rs new file mode 100644 index 0000000..aa81485 --- /dev/null +++ b/memN0ps/ekko-rs/src/ekko.rs @@ -0,0 +1,273 @@ +use std::{ + ffi::c_void, + ptr::{null, null_mut}, +}; +use ntapi::winapi::shared::ntdef::PVOID; +use windows_sys::Win32::{ + Foundation::{GetLastError, HANDLE}, + System::{ + Diagnostics::Debug::{CONTEXT, IMAGE_NT_HEADERS64}, + LibraryLoader::{GetModuleHandleA, GetProcAddress, LoadLibraryA}, + Memory::{PAGE_EXECUTE_READWRITE, PAGE_READWRITE}, + SystemServices::IMAGE_DOS_HEADER, + Threading::{ + CreateEventW, CreateTimerQueue, CreateTimerQueueTimer, DeleteTimerQueue, + WaitForSingleObject, WAITORTIMERCALLBACK, WT_EXECUTEINTIMERTHREAD, + }, + WindowsProgramming::INFINITE, + }, +}; + +// https://doxygen.reactos.org/da/dab/structustring.html +#[repr(C)] +struct UString { + length: u32, + maximum_length: u32, + buffer: PVOID, +} + +// There is a bug in windows-rs/windows-sys and WINAPI: https://github.com/microsoft/win32metadata/issues/1044. Otherwise this is not needed. +#[derive(Clone, Copy)] +#[repr(align(16))] +struct ProperlyAlignedContext(pub CONTEXT); + +impl core::ops::Deref for ProperlyAlignedContext { + type Target = CONTEXT; + fn deref(&self) -> &CONTEXT { + &self.0 + } +} + +impl core::ops::DerefMut for ProperlyAlignedContext { + fn deref_mut(&mut self) -> &mut CONTEXT { + &mut self.0 + } +} + +pub fn ekko(sleep_time: u32, key_buf: &mut Vec) { + let mut h_new_timer: HANDLE = 0; + let mut old_protect: u32 = 0; + + // Creates or opens a named or unnamed event object. + // https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createeventw + let h_event = unsafe { CreateEventW(null(), 0, 0, null()) }; + //log::info!("[+] h_event: {:#x}", h_event); + + if h_event == 0 { + panic!("[!] CreateEventW failed with error: {}", unsafe { GetLastError() }); + } + + // Creates a queue for timers. Timer-queue timers are lightweight objects that enable you to specify a callback function to be called at a specified time. + // https://learn.microsoft.com/en-us/windows/win32/api/threadpoollegacyapiset/nf-threadpoollegacyapiset-createtimerqueue + let h_timer_queue = unsafe { CreateTimerQueue() }; + //log::info!("[+] h_timer_queue: {:#x}", h_timer_queue); + + if h_timer_queue == 0 { + panic!("[!] CreateTimerQueue failed with error: {}", unsafe { GetLastError() }); + } + + let image_base = unsafe { GetModuleHandleA(null_mut()) }; + let dos_header = image_base as *mut IMAGE_DOS_HEADER; + let nt_headers = unsafe { (dos_header as u64 + (*dos_header).e_lfanew as u64) as *mut IMAGE_NT_HEADERS64 }; + let image_size = unsafe { (*nt_headers).OptionalHeader.SizeOfImage }; + + //log::info!("[+] Image Base: {:#x}", image_base as u64); + //log::info!("[+] Image Size: {:#x}", image_size as u64); + + //let mut key_buf: [u8; 16] = [0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55]; + let key = UString { + length: key_buf.len() as u32, + maximum_length: key_buf.len() as u32, + buffer: key_buf.as_mut_ptr() as _, + }; + + let mut data = UString { + length: image_size as u32, + maximum_length: image_size as u32, + buffer: image_base as _, + }; + + let rtl_capture_context = unsafe { GetProcAddress(LoadLibraryA("ntdll\0".as_ptr()), "RtlCaptureContext\0".as_ptr()).unwrap() as u64 }; + let rtl_capture_context_ptr = unsafe { std::mem::transmute::<_, WAITORTIMERCALLBACK>(rtl_capture_context) }; + + let nt_continue = unsafe { GetProcAddress(GetModuleHandleA("ntdll\0".as_ptr()),"NtContinue\0".as_ptr()).unwrap() as u64 }; + let nt_continue_ptr = unsafe { std::mem::transmute::<_, WAITORTIMERCALLBACK>(nt_continue) }; + + let system_function032 = unsafe { GetProcAddress(LoadLibraryA("Advapi32\0".as_ptr()), "SystemFunction032\0".as_ptr()).unwrap() as u64}; + let virtual_protect = unsafe { GetProcAddress(LoadLibraryA("kernel32.dll\0".as_ptr()), "VirtualProtect\0".as_ptr()).unwrap() as u64 }; + let wait_for_single_object = unsafe { GetProcAddress(LoadLibraryA("kernel32.dll\0".as_ptr()), "WaitForSingleObject\0".as_ptr()).unwrap() as u64 }; + let set_event = unsafe { GetProcAddress(LoadLibraryA("kernel32.dll\0".as_ptr()), "SetEvent\0".as_ptr()).unwrap() as u64 }; + + //log::info!("[+] RtlCaptureContext: {:#x}", rtl_capture_context); + //log::info!("[+] NtContinue: {:#x}", nt_continue); + //log::info!("[+] SystemFunction032: {:#x}", system_function032); + //log::info!("[+] VirtualProtect: {:#x}", virtual_protect); + //log::info!("[+] WaitForSingleObject: {:#x}", wait_for_single_object); + //log::info!("[+] SetEvent: {:#x}", set_event); + + //pause(); + //log::info!("[+] Calling CreateTimerQueueTimer with ctx_thread"); + + // Contains processor-specific register data. The system uses CONTEXT structures to perform various internal operations. + // https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-context + let ctx_thread = unsafe { std::mem::zeroed::() }; + + // Creates a timer-queue timer. This timer expires at the specified due time, then after every specified period. When the timer expires, the callback function is called. + // https://learn.microsoft.com/en-us/windows/win32/api/threadpoollegacyapiset/nf-threadpoollegacyapiset-createtimerqueuetimer + let result = unsafe { CreateTimerQueueTimer(&mut h_new_timer, h_timer_queue, rtl_capture_context_ptr, &ctx_thread as *const _ as *const _, 0, 0, WT_EXECUTEINTIMERTHREAD) }; + + if result != 0 { + // Waits until the specified object is in the signaled state or the time-out interval elapses. + // https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject + unsafe { WaitForSingleObject(h_event, 0x32) }; + + // Clone not required as it implements the copy trait + let mut rop_prot_rw = ctx_thread; + let mut rop_mem_enc = ctx_thread; + let mut rop_delay = ctx_thread; + let mut rop_mem_dec = ctx_thread; + let mut rop_prot_rx = ctx_thread; + let mut rop_set_evt = ctx_thread; + + // pub unsafe extern "system" fn VirtualProtect(lpaddress: *const c_void, dwsize: usize, flnewprotect: PAGE_PROTECTION_FLAGS, lpfloldprotect: *mut PAGE_PROTECTION_FLAGS) -> BOOL + // https://docs.rs/windows-sys/latest/windows_sys/Win32/System/Memory/fn.VirtualProtect.html + rop_prot_rw.Rsp -= 8; + rop_prot_rw.Rip = virtual_protect as u64; + rop_prot_rw.Rcx = image_base as *const c_void as u64; + rop_prot_rw.Rdx = image_size as u64; + rop_prot_rw.R8 = PAGE_READWRITE as u64; + rop_prot_rw.R9 = &mut old_protect as *mut _ as u64; + //dump_virtual_protect_context(&rop_prot_rw); + + // https://doxygen.reactos.org/df/d13/sysfunc_8c.html#a66d55017b8625d505bd6c5707bdb9725 + // NTSTATUS WINAPI SystemFunction032(struct ustring *data, const struct ustring *key) + // pub unsafe extern "system" fn SystemFunction032(data: *mut UString, key: *const UString) -> NTSTATUS; + rop_mem_enc.Rsp -= 8; + rop_mem_enc.Rip = system_function032 as u64; + rop_mem_enc.Rcx = &mut data as *mut _ as u64; + rop_mem_enc.Rdx = &key as *const _ as u64; + //dump_system_function032_context(&rop_mem_enc); + + // pub unsafe extern "system" fn WaitForSingleObject(hhandle: HANDLE, dwmilliseconds: u32) -> WIN32_ERROR + // https://docs.rs/windows-sys/latest/windows_sys/Win32/System/Threading/fn.WaitForSingleObject.html + rop_delay.Rsp -= 8; + rop_delay.Rip = wait_for_single_object as u64; + rop_delay.Rcx = -1 as isize as u64; // NtCurrentProcess + rop_delay.Rdx = sleep_time as u64; + //dump_wait_for_single_object_context(&rop_delay); + + // https://doxygen.reactos.org/df/d13/sysfunc_8c.html#a66d55017b8625d505bd6c5707bdb9725 + // NTSTATUS WINAPI SystemFunction032(struct ustring *data, const struct ustring *key) + // pub unsafe extern "system" fn SystemFunction032(data: *mut UString, key: *const UString) -> NTSTATUS; + rop_mem_dec.Rsp -= 8; + rop_mem_dec.Rip = system_function032 as u64; + rop_mem_dec.Rcx = &mut data as *mut _ as u64; + rop_mem_dec.Rdx = &key as *const _ as u64; + //dump_system_function032_context(&rop_mem_dec); + + // pub unsafe extern "system" fn VirtualProtect(lpaddress: *const c_void, dwsize: usize, flnewprotect: PAGE_PROTECTION_FLAGS, lpfloldprotect: *mut PAGE_PROTECTION_FLAGS) -> BOOL + // https://docs.rs/windows-sys/latest/windows_sys/Win32/System/Memory/fn.VirtualProtect.html + rop_prot_rx.Rsp -= 8; + rop_prot_rx.Rip = virtual_protect as u64; + rop_prot_rx.Rcx = image_base as *const c_void as u64; + rop_prot_rx.Rdx = image_size as u64; + rop_prot_rx.R8 = PAGE_EXECUTE_READWRITE as u64; + rop_prot_rx.R9 = &mut old_protect as *mut _ as u64; + //dump_virtual_protect_context(&rop_prot_rx); + + // https://docs.rs/windows-sys/latest/windows_sys/Win32/System/Threading/fn.SetEvent.html + // pub unsafe extern "system" fn SetEvent(hevent: HANDLE) -> BOOL + rop_set_evt.Rsp -= 8; + rop_set_evt.Rip = set_event as u64; + rop_set_evt.Rcx = h_event as u64; + //dump_set_event_context(&rop_set_evt); + + println!("[+] Queue timers"); + unsafe + { + CreateTimerQueueTimer(&mut h_new_timer, h_timer_queue, nt_continue_ptr, &rop_prot_rw as *const _ as *const _, 100, 0, WT_EXECUTEINTIMERTHREAD); + + CreateTimerQueueTimer(&mut h_new_timer, h_timer_queue, nt_continue_ptr, &rop_mem_enc as *const _ as *const _, 200, 0, WT_EXECUTEINTIMERTHREAD); + + CreateTimerQueueTimer(&mut h_new_timer, h_timer_queue, nt_continue_ptr, &rop_delay as *const _ as *const _, 300, 0, WT_EXECUTEINTIMERTHREAD); + + CreateTimerQueueTimer(&mut h_new_timer, h_timer_queue, nt_continue_ptr, &rop_mem_dec as *const _ as *const _, 400, 0, WT_EXECUTEINTIMERTHREAD); + + CreateTimerQueueTimer(&mut h_new_timer, h_timer_queue, nt_continue_ptr, &rop_prot_rx as *const _ as *const _, 500,0, WT_EXECUTEINTIMERTHREAD); + + CreateTimerQueueTimer(&mut h_new_timer, h_timer_queue, nt_continue_ptr, &rop_set_evt as *const _ as *const _, 600, 0, WT_EXECUTEINTIMERTHREAD); + + println!("[+] Wait for hEvent"); + + WaitForSingleObject(h_event, INFINITE); //0xFFFFFFFF + + println!("[+] Finished waiting for event"); + } + } + + // Deletes a timer queue. Any pending timers in the queue are canceled and deleted. + // https://learn.microsoft.com/en-us/windows/win32/api/threadpoollegacyapiset/nf-threadpoollegacyapiset-deletetimerqueue + unsafe { DeleteTimerQueue(h_timer_queue) }; +} + +#[allow(dead_code)] +/// Gets user input from the terminal +fn get_input() -> std::io::Result<()> { + let mut buf = String::new(); + std::io::stdin().read_line(&mut buf)?; + Ok(()) +} + +#[allow(dead_code)] +/// Used for debugging +pub fn pause() { + match get_input() { + Ok(buffer) => println!("{:?}", buffer), + Err(error) => println!("error: {}", error), + }; +} + +#[allow(dead_code)] +fn dump_virtual_protect_context(rop: &ProperlyAlignedContext) { + log::info!( + "[+] RSP: {:#x} RIP: {:#x} -> VirtualProtect({:#x}, {:#x}, {:#x}, {:#x})", + rop.Rsp, + rop.Rip, + rop.Rcx, + rop.Rdx, + rop.R8, + rop.R9 + ); +} + +#[allow(dead_code)] +fn dump_system_function032_context(rop: &ProperlyAlignedContext) { + log::info!( + "[+] RSP: {:#x} RIP: {:#x} -> SystemFunction032({:#x}, {:#x})", + rop.Rsp, + rop.Rip, + rop.Rcx, + rop.Rdx + ); +} + +#[allow(dead_code)] +fn dump_wait_for_single_object_context(rop: &ProperlyAlignedContext) { + log::info!( + "[+] RSP: {:#x} RIP: {:#x} -> WaitForSingleObject({:#x}, {:#x})", + rop.Rsp, + rop.Rip, + rop.Rcx, + rop.Rdx + ); +} + +#[allow(dead_code)] +fn dump_set_event_context(rop: &ProperlyAlignedContext) { + log::info!( + "[+] RSP: {:#x} RIP: {:#x} -> SetEvent({:#x})", + rop.Rsp, + rop.Rip, + rop.Rcx + ); +} \ No newline at end of file diff --git a/memN0ps/ekko-rs/src/main.rs b/memN0ps/ekko-rs/src/main.rs new file mode 100644 index 0000000..609b011 --- /dev/null +++ b/memN0ps/ekko-rs/src/main.rs @@ -0,0 +1,11 @@ +mod ekko; + +fn main() { + //env_logger::init(); + println!("[*] Ekko Sleep Obfuscation by @memN0ps and @trickster0. Full credits to Cracked5pider (@C5pider), Austin Hudson (@SecIdiot), Peter Winter-Smith (@peterwintrsmith)"); + + let mut key_buf = "1234567890ABCDEF\0".as_bytes().to_vec(); + loop { + ekko::ekko(4000, &mut key_buf); + } +}