
Invoque dinamicamente código não gerenciado arbitrário
Porte Rust de Dinvoke. DInvoke_rs pode ser usado para muitos propósitos, como análise de PE, resolução dinâmica de funções exportadas, carregamento dinâmico de plugins PE em tempo de execução, evasão de hooks de API e muito mais.
Recursos:
Todos os créditos vão para os criadores da implementação original em C# desta ferramenta:
Importe esta crate para o seu projeto adicionando a seguinte linha ao seu cargo.toml:```rust
[dependencies]
dinvoke_rs = "0.2.2"
# Exemplos
## Resolvendo APIs Exportadas
O exemplo abaixo demonstra como usar DInvoke_rs para encontrar e chamar dinamicamente as exportações de uma DLL (neste caso, `ntdll.dll`).
1) Obtenha o endereço base da ntdll.
2) Use `get_function_address()` para encontrar uma exportação dentro de `ntdll.dll` pelo nome. Isso é feito percorrendo e analisando o EAT da DLL.
3) Você também pode encontrar uma exportação por ordinal chamando `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);
}
}
}
No exemplo abaixo, usamos DInvoke_rs para chamar dinamicamente RtlAdjustPrivilege a fim de habilitar SeDebugPrivilege para o token do processo atual. Esse tipo de execução contornará quaisquer ganchos de API presentes no Win32. Além disso, não criará nenhuma entrada na Tabela de Endereços de Importação do PE final, dificultando a detecção do comportamento do PE sem executá-lo.```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!"),
}
}
}
}
## Executando syscall indireto
No próximo exemplo, usamos DInvoke_rs para executar o syscall correspondente à função `NtQueryInformationProcess`. Como a macro `execute_syscall!()` aloca e executa dinamicamente o shellcode necessário para realizar o syscall desejado, todos os hooks presentes em `ntdll.dll` são contornados. A memória alocada é liberada assim que o syscall retorna, evitando a presença permanente de páginas de memória com permissão de execução.```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."),
}
}
}
Neste exemplo, DInvoke_rs é usado para mapear manualmente uma cópia nova de ntdll.dll, sem nenhum hook de EDR. Então essa cópia nova de ntdll.dll pode ser usada para executar qualquer função desejada.
Este mapeamento manual também pode ser executado a partir da memória (use manually_map_module() nesse caso), permitindo realizar a clássica injeção de DLL reflexiva.```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();