Rusty Ekko - Sleep Obfuscation in Rust by memN0ps and trickster0

This commit is contained in:
memN0ps
2023-03-16 21:06:37 +13:00
parent 2100213e4a
commit 3260fbe40a
6 changed files with 406 additions and 0 deletions
+10
View File
@@ -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
+44
View File
@@ -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",
]
+21
View File
@@ -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.
+47
View File
@@ -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 MDSecs 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/))
+273
View File
@@ -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<u8>) {
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::<ProperlyAlignedContext>() };
// 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
);
}
+11
View File
@@ -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);
}
}