Skip to content

Whitecat18/apiparser

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ApiParser

Rust License

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.

Key Features

  • ✅ 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.

Installation

Clone and add apiparser to your Cargo.toml dependencies:

git clone https://github.com/Whitecat18/apiparser.git
[dependencies]
apiparser = { path = "./apiparser" } 

Usage

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.

Supported Algorithms

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

References

License

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages