ApiParser is a lightweight, modern Rust crate designed for dynamic Windows API resolution. It parses the PEB to locate ntdll.dll exports at runtime and resolves API addresses using hashes.
This technique removes the need for static Import Address Table (IAT) entries, making it ideal for red teaming, malware development research, and bypassing basic static analysis.
- ✅ Resolves APIs dynamically at runtime; functions do not appear in the IAT.
- ✅ Comes with 11+ pre-implemented hashing algorithms (CRC32, DJB2, Jenkins, Murmur3, etc.).
- ✅ It Leverages Rust's type system to ensure you match the hash algorithm with the correct hash constants.
Clone and add apiparser to your Cargo.toml dependencies:
git clone https://github.com/Whitecat18/apiparser.git[dependencies]
apiparser = { path = "./apiparser" } Using the resolver is simple. Initialize the parser, select your hashing algorithm, and resolve the API name and address.
use apiparser::{
NtdllResolver,
hash::crc32::crc32ba,
nthash::crc32::NT_HASHES_CRC32
};
fn main() {
println!("[+] Starting the resolver...");
// Initialize the Resolver (Parses PEB & Exports once)
let api = NtdllResolver::new().expect("Failed to parse PEB");
// Resolve 'NtAllocateVirtualMemory' using CRC32
// You can switch 'crc32ba' to 'djb2', 'jenkins', etc. easily!
let nt_alloc = api
.resolve(NT_HASHES_CRC32.nt_allocate_virtual_memory, crc32ba)
.expect("Failed to find API");
// Output
println!("[*] API Found!");
println!(" Name: {}", nt_alloc.name);
println!(" Address: 0x{:X}", nt_alloc.address);
}If you want to implement and generate only particular apis on your own. here's an example.
// generate api hashes.
use apiparser::{hash::crc32ba};
fn main(){
let apis = vec![
"NtAllocateVirtualMemory",
"NtCreateUserProcess",
"EtwEventWrite",
];
for api in &apis {
println!(
"pub const {}_CRC32BA: u32 = 0x{:X?};",
api.to_ascii_uppercase(), crc32ba(api)
);
}
}
// you will get output like:
// pub const NTALLOCATEVIRTUALMEMORY_CRC32BA: u32 = 0x498165AA;
// pub const NTCREATEUSERPROCESS_CRC32BA: u32 = 0x2B09FF3F;
// pub const ETWEVENTWRITE_CRC32BA: u32 = 0x1087670;Now you can use these to get the apis at runtime !
use apiparser::{NtdllResolver, hash::crc32ba};
pub const NTALLOCATEVIRTUALMEMORY_CRC32BA: u32 = 0x498165AA;
pub const NTCREATEUSERPROCESS_CRC32BA: u32 = 0x2B09FF3F;
pub const ETWEVENTWRITE_CRC32BA: u32 = 0x1087670;
fn main() {
let extract = NtdllResolver::new().unwrap();
let nt_alloc = extract
.resolve(NTALLOCATEVIRTUALMEMORY_CRC32BA, crc32ba)
.unwrap();
let nt_create = extract
.resolve(NTCREATEUSERPROCESS_CRC32BA, crc32ba)
.unwrap();
let event_write = extract.resolve(ETWEVENTWRITE_CRC32BA, crc32ba).unwrap();
println!("[+] {} - 0x{:x?}", nt_alloc.name, nt_alloc.address);
println!("[+] {} - 0x{:x?}", nt_create.name, nt_create.address);
println!("[+] {} - 0x{:x?}", event_write.name, event_write.address);
}You can use the address to call or spoof based on your purpose. here is an example.
#![allow(non_snake_case)]
use std::{ffi::c_void, mem::transmute, ptr::null_mut};
use apiparser::{NtdllResolver, hash::crc32ba, nthash::crc32::NT_HASHES_CRC32};
// define the type
type NtAllocateVirtualMemoryFn = unsafe extern "system" fn(
ProcessHandle: isize,
BaseAddress: *mut *mut c_void,
ZeroBits: usize,
RegionSize: *mut usize,
AllocationType: u32,
Protect: u32,
) -> i32;
fn main() {
println!("[+] Starting the resolver");
let api = NtdllResolver::new().unwrap();
let nt_alloc = api
.resolve(NT_HASHES_CRC32.nt_allocate_virtual_memory, crc32ba)
.unwrap();
let mut addr = null_mut::<c_void>();
let mut size = 1024 * 4usize;
let func: NtAllocateVirtualMemoryFn = unsafe { transmute(nt_alloc.address) };
let status = unsafe { func(-1isize, &mut addr, 0, &mut size, 0x3000, 0x04) };
if status != 0 {
println!("[-] Failed");
}
println!("[+] Address: {:?}", addr);
}The above is not feasible to execute in EDR enviroinments. You need to evade EDR hooks. For that you can use syscall methods like Hells, Halos, Tartarus gate.
The crate currently supports the following hashing techniques. You can find them in apiparser::hash and their corresponding constants in apiparser::nthash.
| Algorithm | Module Name | Struct Name |
|---|---|---|
| CRC32 | crc32 |
NT_HASHES_CRC32 |
| DJB2 | djb2 |
NT_HASHES_DJB2 |
| Jenkins | jenkins |
NT_HASHES_JENKINS |
| Murmur3 | murmur3 |
NT_HASHES_MURMUR3 |
| FNV-1a | fnv1a |
NT_HASHES_FNV1A |
| SDBM | sdbm |
NT_HASHES_SDBM |
| LoseLose | loselose |
NT_HASHES_LOSELOSE |
| PJW | pjw |
NT_HASHES_PJW |
| AP | ap |
NT_HASHES_AP |