Rust 宏系统实现高阶 Win32 API 动态导入
2022-9-11 00:5:53 Author: 0x4d5a(查看原文) 阅读量:38 收藏

0. TL; DR

手动导入 API 更隐蔽,但写起来比通过 IAT 调用麻烦,同时为了隐蔽性,我们还会将动态导入的模块名和函数名编码或哈希。本文介绍借助 Rust 的宏系统,来实现上述两个需求,做到兼具隐蔽性与开发效率

本文的代码是笔者从个人项目中抠出来的一个 Demo,只保留了核心部分,不包含复杂功能和错误处理等。完整 Demo 在 win32api-practice/dyn-import[1]

本例向读者展示在武器化开发时,借助 Rust 的高阶语言设施,能够在具备等同于 C / C++ 底层能力的同时,实现一些零开销的 magic 功能。

1. 动态导入

首先是对动态导入的宏实现,该实现是受标准库启发:

macro_rules! dyn_import {    ($module:literal ($calling_convention:literal): $(        $(#[$meta:meta])*        fn $symbol:ident($($argname:ident: $argtype:ty),*) -> $rettype:ty;    )*) => ($(        $(#[$meta])*        #[warn(non_snake_case)]        pub mod $symbol {            use super::*;
type F = unsafe extern $calling_convention fn($($argtype),*) -> $rettype;
static mut PTR: *const c_void = ptr::null();
unsafe fn get_f() -> *const c_void { let ptr = winapi::GetProcAddress(winapi::LoadLibraryW(macros::wss_z!($module).as_ptr()), macros::ss_z!($symbol).as_ptr()); if !ptr.is_null() { return ptr; }
#[cfg(debug_assertions)] panic!(stringify!($symbol));
#[cfg(not(debug_assertions))] panic!(); }
#[allow(dead_code)] #[allow(clippy::too_many_arguments)] pub unsafe fn call($($argname: $argtype),*) -> $rettype { if PTR.is_null() { PTR = get_f(); }
mem::transmute::<*const c_void, F>(PTR)($($argname),*) } }
$(#[$meta])* pub use $symbol::call as $symbol; )*);}

macro_rules 是示例宏(macros by example),功能相对受限。代码细节就不解释了,读者感兴趣可以自己研究。

2. 符号名编码或哈希

上节的例子中的这段代码通过宏将模块名和符号名转为栈上的字节数组(该功能也可用于 shellcode 开发)。

winapi::GetProcAddress(winapi::LoadLibraryW(macros::wss_z!($module).as_ptr()), macros::ss_z!($symbol).as_ptr());

wss_z 的含义为 Wide Stack String with Zero terminated,实现是:

/// Stack string in UTF16-LE terminated by null#[proc_macro]pub fn wss_z(item: TokenStream) -> TokenStream {    let tt: Vec<TokenTree> = item.into_iter().take(1).collect();
let mut s = tt[0] .to_string() .strip_prefix('"') .unwrap() .strip_suffix('"') .unwrap() .replace("\\\\", "\\") .to_string(); s.push('\0');
let mut output = Vec::<String>::new();
s.to_string() .as_bytes() .iter() .map(|&c| { output.push(format!("0x{:x}_u16", c)); }) .for_each(mem::drop);
format!("[{}]", output.join(", ")).parse().unwrap()}

该功能无法通过 macro_rules 实现,而是通过过程宏(proc macros)实现。过程宏以编译器插件形式工作,它可以直接操作语法分析后的 token。

这个宏获取的输入是模块名和符号名的 token,可以直接将其改为字节数组、或是一个哈希值。


在我实际使用中是将符号哈希,并结合自实现 GetProcAddress 使用的,读者如有需要可以自行改写:

let hmod = winapi::load_library(core::str::from_utf8_unchecked(&macros::ss!($module)));let ptr = winapi::get_proc_address(hmod, winapi::Thunk::Hash(macros::hsh!($symbol))) {

3. 使用示例和宏展开

使用 dyn_import 宏声明只需要标注模块名,调用约定,符号名和函数签名。

dyn_import! {    "kernel32" ("system"):
fn LocalAlloc(uflags: u32, ubytes: usize) -> isize; fn GetModuleFileNameA(hmod: *const c_void, lpfilename: *mut u8, size: u32) -> u32;}
dyn_import! { "msvcrt" ("C"):
fn fwrite(ptr: *const u8, size: usize, nmemb: usize, f: *const FILE) -> usize; fn fflush(f: *const FILE) -> i32;}

除了导入函数,也可以导入变量,比如 _iob

dyn_import! {    "msvcrt":
sym<FILE> _iob;}

通过 dyn_import 宏导入的函数的使用和普通函数没有区别。

let addr = LocalAlloc(LPTR, 1 << 10);let stdout = _iob().offset(1);
let n = GetModuleFileNameA(ptr::null(), addr as _, 1 << 10);
let output = format!("alloc @ 0x{addr:x}\nstdout {stdout:?}\n");
fwrite(output.as_ptr(), output.len(), 1, stdout);fwrite(addr as _, n as _, 1, stdout);fflush(stdout);

输出为:

alloc @ 0x24d3c6a1a40stdout 0x7fff82cdd1f0\\Mac\Home\Downloads\win32api-practice\dyn-import\target\x86_64-pc-windows-gnu\debug\dyn-import.exe

dyn_import 宏展开后的效果是:

#[warn(non_snake_case)]pub mod LocalAlloc {    use super::*;    type F = unsafe extern "system" fn(u32, usize) -> isize;    static mut PTR: *const c_void = ptr::null();    unsafe fn get_f() -> *const c_void {        let ptr =            winapi::GetProcAddress(winapi::LoadLibraryW([0x6b_u16, 0x65_u16,                                0x72_u16, 0x6e_u16, 0x65_u16, 0x6c_u16, 0x33_u16, 0x32_u16,                                0x0_u16].as_ptr()),                [0x4c_u8, 0x6f_u8, 0x63_u8, 0x61_u8, 0x6c_u8, 0x41_u8,                            0x6c_u8, 0x6c_u8, 0x6f_u8, 0x63_u8, 0x0_u8].as_ptr());        if !ptr.is_null() { return ptr; }        ::core::panicking::panic_fmt(::core::fmt::Arguments::new_v1(&["LocalAlloc"],                &[]));    }    #[allow(dead_code)]    #[allow(clippy :: too_many_arguments)]    pub unsafe fn call(uflags: u32, ubytes: usize) -> isize {        if PTR.is_null() { PTR = get_f(); }        mem::transmute::<*const c_void, F>(PTR)(uflags, ubytes)    }}pub use LocalAlloc::call as LocalAlloc;

References

[1] win32api-practice/dyn-import: https://github.com/EddieIvan01/win32api-practice/tree/master/dyn-import


文章来源: http://mp.weixin.qq.com/s?__biz=Mzg5MjU2NTc1Mg==&mid=2247483783&idx=1&sn=9145686b826e3d7cd4be263b323aa9cd&chksm=c03d6010f74ae9065028ce24eaef36fbc85874bf1713370c7d8703559b4023e5860b23d7f27e#rd
如有侵权请联系:admin#unsafe.sh