首页
社区
课程
招聘
[分享]Rust内存工具库 hackit
2021-5-12 21:25 9481

[分享]Rust内存工具库 hackit

2021-5-12 21:25
9481

https://gitee.com/udbg/hackit 是笔者开发的一个memory hack之类的库,中文我不知道咋翻译比较优雅,暂且叫内存工具库

  • 主要封装了一些Windows开发常用的功能,比如枚举进程模块、读写内存、Hook、汇编/反汇编、ntdll层的接口封装等,希望能给想用rust做Windows底层开发的朋友提供一些参考
  • 开发hackit这个库的过程也是我不断学习rust的过程,有些地方的写法可能还不是很规范,有些接口后续可能会变动
  • 本文另一个目的是展示使用rust编写代码的便捷性,如果你没用过rust,希望能让你感到新鲜;如果你是老手,希望能抛砖引玉、多多交流
  • 本文不介绍rust语言基础的知识,如果想要系统地学习rust,推荐官方教程 中文版

如果想运行下面的示例代码,需要在 Cargo.toml 中加入 hackit 依赖,使用nightly版本的rust编译器(rust内联汇编需要nightly版本的编译器),我的rustc版本是rustc 1.52.0-nightly (a8486b64b 2021-02-24)

1
2
[dependencies]
hackit = {git = 'https://gitee.com/udbg/hackit', features = ['std', 'csutil']}

枚举进程、模块

枚举进程

1
2
3
4
5
use hackit::*;
 
for ps in enum_process() {
    println!("{} {}", ps.pid(), ps.name());
}

利用rust的迭代器trait,可以很方便地过滤出自己想要枚举的进程,
更多迭代器方法可参考官方文档 https://doc.rust-lang.org/core/iter/trait.Iterator.html

1
2
3
for ps in enum_process().filter(|p| p.name().eq_ignore_ascii_case("svchost.exe")) {
    println!("{} {}", ps.pid(), ps.name());
}

通过进程名过滤进程,被单独封装成了一个函数 enum_process_filter_name(name: &str)
获得第一个名为 explorer.exe 的进程信息

1
2
let ps = enum_process_filter_name("explorer.exe").next().unwrap();
println!("{} {}", ps.pid(), ps.name());

遍历模块:枚举 explorer 进程中的模块

1
2
3
for m in enum_module(ps.pid()) {
    println!("{:x} {:x} {}", m.base(), m.size(), m.path());
}

进程内存读写

1
2
3
4
5
6
7
8
9
10
11
12
13
// 打开进程对象,第二个参数为访问权限,默认为 PROCESS_ALL_ACCESS
let p = Process::open(ps.pid(), None).expect("open explorer");
// 定位到模块基址
let base = p.enum_module().next().unwrap().base();
// 读取内存
let buf = p.read_bytes(base, 2);
assert_eq!(buf, b"MZ");
// 泛型读取,泛型参数可以是 u16 u32 float double ...
let word = p.read_value::<u16>(base).unwrap();
println!("read u16: 0x{:x}", word);
// 读字符串
let s = p.read_cstring(base, 2);
println!("read string: {:?}", s);

更多读内存的方法参考 trait ReadMemUtil https://gitee.com/udbg/hackit/blob/master/src/mem.rs#L21

部分ntapi接口的封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 枚举驱动模块
for m in system_module_list().unwrap() {
    println!("{:p} {:x} {}", m.ImageBase, m.ImageSize, m.full_path_str());
}
 
// 枚举 explorer 的进程线程信息
for pi in system_process_information().unwrap().filter(|pi| pi.UniqueProcessId as u32 == p.pid) {
    println!("pid: {} image: {} handle-count: {}", pi.UniqueProcessId as u32, pi.ImageName.to_string(), pi.HandleCount);
    for ti in pi.threads() {
        println!(
            "  tid: {} priority: {} wait-time: {} entry: {:p}",
            ti.ClientId.UniqueThread as u32,
            ti.Priority,
            ti.WaitTime,
            ti.StartAddress,
        );
    }
}

字符串编码处理

rust的String是utf8编码,如果需要调用一些Windows原生接口,需要转Unicode字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
// utf8转Unicode
let abc = "abc";
unsafe {
    use core::ptr::*;
    use winapi::um::winuser::MessageBoxW;
 
    MessageBoxW(null_mut(), abc.to_unicode_with_null().as_ptr(), null_mut(), 0);
}
// Unicdoe转utf8
let wstr = abc.to_unicode();
assert_eq!(wstr.to_utf8(), "abc");
// Unicode转ANSI编码
assert_eq!(wstr.to_ansi(), b"abc");

Hook功能

直接贴了测试用例,Hook库是自己实现的,理论上是可以Hook任意地址的(有跳转指令的除外,处理jmp和call的逻辑还有点问题)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#[cfg(test)]
mod hook_test {
    use super::*;
    use std::println;
 
    use winapi::um::winuser::*;
    use winapi::um::libloaderapi::*;
 
    const MAGIC_VALUE: u64 = 1001234;
 
    #[test]
    fn inline_hook() {
        let tp = this_process();
        tp.sym_init(None, true).ok();
 
        type FnRtlQueryPerformanceCounter = extern "system" fn(&mut u64) -> usize;
        let m = unsafe { GetProcAddress(GetModuleHandleA(b"ntdll\0".as_ptr() as *const i8), b"RtlQueryPerformanceCounter\0".as_ptr() as *const i8) };
        assert!(!m.is_null());
        HookManager::instance().inline_hook(m as usize, |args: &mut HookArgs| unsafe {
            let RtlQueryPerformanceCounter = transmute::<_, FnRtlQueryPerformanceCounter>(args.trampoline());
            let regs = &mut args.regs;
            let arg1 = regs.arg(1);
            *regs.ax() = RtlQueryPerformanceCounter(transmute(arg1));
            *(arg1 as *mut u64) = MAGIC_VALUE;
            args.reject = Some(if size_of::<usize>() == 8 { 0 } else { 1 });
        }, true).expect("RtlQueryPerformanceCounter");
 
        let mut counter: u64 = 0;
        unsafe {
            let RtlQueryPerformanceCounter: FnRtlQueryPerformanceCounter = transmute(m);
            RtlQueryPerformanceCounter(transmute(&mut counter));
            assert_eq!(MAGIC_VALUE, counter);
        }
 
        let m = unsafe { GetProcAddress(LoadLibraryA(b"user32\0".as_ptr() as *const i8), b"MessageBoxA\0".as_ptr() as *const i8) };
        assert!(!m.is_null());
 
        static mut CHECK_MSG: u64 = 0;
        unsafe extern "C" fn callback(args: &mut HookArgs) {
            // let msg: extern "system" fn(usize, *const u8, *const u8, u32) = transmute(args.ret);
            // msg(0, b"Hooked\0".as_ptr(), "\0".as_ptr(), 0);
            CHECK_MSG = MAGIC_VALUE;
            args.reject = Some(if cfg!(target_arch = "x86_64") { 0 } else { 4 });
        }
        HookManager::instance().inline_hook(m as usize, callback as HookCallbackFn, true).expect("MessageBoxA");
        unsafe {
            MessageBoxA(std::ptr::null_mut(), b"error\0".as_ptr() as *const i8, b"\0".as_ptr() as *const i8, 0);
            assert_eq!(MAGIC_VALUE, CHECK_MSG);
        }
    }
 
    #[test]
    fn table_hook() {
        static mut CHECK_HOOK: u64 = 0;
 
        unsafe {
            let m = GetProcAddress(LoadLibraryA(b"kernel32\0".as_ptr() as *const i8), b"OutputDebugStringW\0".as_ptr() as *const i8);
            let output_debug_string: extern "win64" fn(LPCWSTR) = transmute(m);
            let address: usize = transmute(&output_debug_string);
 
            HookManager::instance().table_hook(address, |_arg: &mut HookArgs| {
                println!("TableHook success");
                CHECK_HOOK = MAGIC_VALUE;
            }, true).expect("table hook");
 
            let s = "OutputDebugString".to_wide();
            output_debug_string(s.as_ptr());
            assert_eq!(MAGIC_VALUE, CHECK_HOOK);
        }
    }
}

内联汇编、ShellCode

主要是实现了shellcode!这个宏,通过这个宏直接编写汇编代码,并能返回一个指向这段汇编的&[u8](字节数组引用)

 

https://gitee.com/udbg/hackit/blob/master/src/util.rs#L95

 

用法参考Hook库的实现 https://gitee.com/udbg/hackit/blob/master/src/win/hook.rs#L143

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
use hackit::util::*;
 
fn create_hook_handler() -> *const u8 {
    shellcode!(
        "push rsp"      // +80
        "push rax"      // +78
        "push rcx"      // +70
        "push rdx"      // +68
        "push rbx"      // +60
        "push rbp"      // +58
        "push rsi"      // +50
        "push rdi"      // +48
        "push r8"       // +40
        "push r9"       // +38
        "push r10"      // +30
        "push r11"      // +28
        "push r12"      // +20
        "push r13"      // +18
        "push r14"      // +10
        "push r15"      // +8
        "pushfq"        // +0
        "add qword ptr [rsp+0x80], 8"
        "mov rcx, [rsp+0x88]"
        "mov rdx, rsp"
        "sub rsp, 0x18"
        "call hook_handler"
        "add rsp, 0x18"
        "popfq"
        "pop r15"
        "pop r14"
        "pop r13"
        "pop r12"
        "pop r11"
        "pop r10"
        "pop r9"
        "pop r8"
        "pop rdi"
        "pop rsi"
        "pop rbp"
        "pop rbx"
        "pop rdx"
        "pop rcx"
        "pop rax"
        "pop rsp"
        "ret"
    ).as_ptr()
}

二进制搜索

主要是实现了sig!这个宏,可以直接写BinaryPattern,搜索算法使用了最简单的顺序匹配

 

宏实现: https://gitee.com/udbg/hackit/blob/master/src/util.rs#L152

 

算法实现: https://gitee.com/udbg/hackit/blob/master/src/util.rs#L118

1
2
3
4
5
6
7
8
9
10
use hackit::util::*;
 
let pattern = BinCode::new(sig![0x48 0x8B 0x0D ?? ?? ?? ?? 0xE8]);
// 随便写了一段shellcode以作示例,并不能编过
pattern.search(shellcode!(
    "push rbp"
    "mov rcx, qword_180165420"
    "call    RtlAllocateHeap"
    "pop rbp"
));

常用的rust库

还有一些功能就不一一介绍了,感兴趣的朋友自然会去看源码了解,如果用着有啥问题,也欢迎给我提issue,再推荐一些搞memory hack常用的第三方库

  • winapi: 主要是 Windows API 的Rust声明,这个库出的比较早,已经有很多项目在用了,最近微软官方也出了一个windows-rs
  • ntapi: 主要包含ntdll里常用的API及数据结构
  • capstone: 知名的全平台反汇编库
  • vmprotect: 非官方的 VMP Rust SDK
  • dynasm: 动态汇编库,可以用于动态生成代码
  • keystone: 汇编库
  • iced_x86: x86平台的汇编、反汇编库,支持 no_std
  • goblin: 全平台的可执行文件解析库,支持 PE、ELF、MACH-O等格式

[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2021-5-17 19:44 被metaworm编辑 ,原因:
收藏
点赞7
打赏
分享
最新回复 (2)
雪    币: 453
活跃值: (129)
能力值: (RANK:0 )
在线值:
发帖
回帖
粉丝
同志们好啊 2021-5-12 22:40
2
0
好东西,加油.
雪    币: 1115
活跃值: (1921)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
Oday小斯 2021-5-12 23:17
3
0
牛逼,感谢分享
游客
登录 | 注册 方可回帖
返回