Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【Rust调用Windows API】获取正在运行的全部进程信息

【Rust调用Windows API】获取正在运行的全部进程信息

原创
作者头像
beifengtz
发布于 2024-11-18 02:31:56
发布于 2024-11-18 02:31:56
3180
举报

前言

WIndows API官方文档 提供了C++的调用示例,最近想尝试用Rust去实现,本系列博客记录一下实现过程。

依赖

Rust调用Windows API需要引入依赖winapi,在Cargo.toml中添加依赖

代码语言:shell
AI代码解释
复制
winapi = "0.3.9"

调用不同的API集就需要使用相应的功能features,很好的一个判断方式是你在微软官方文档中看到的是在哪个头文件内,就添加哪个feature,例如本篇文章需要使用 tlhelp32.hprocessthreadsapi.h 那么就将这俩feature添加进去

代码语言:shell
AI代码解释
复制
winapi = { version = "0.3.9", features = ["tlhelp32", "processthreadsapi"] }

实现

大致步骤

  1. 创建进程快照,拿到快照句柄
  2. 遍历快照中的进程(以迭代器的方式实现),得到每个进程的数据
  3. 释放快照句柄

创建快照句柄

创建进程快照需要用到 CreateToolhelp32Snapshot 方法,它在 tlhelp32.h 头文件中定义。

代码语言:rust
AI代码解释
复制
use winapi::um::tlhelp32::{CreateToolhelp32Snapshot, TH32CS_SNAPPROCESS};
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
use winapi::um::errhandlingapi::GetLastError;

/// 保存进程快照并返回进程信息迭代器 `ProcessInformationIterator`
pub fn list() -> Result<ProcessInformationIterator, String> {
    let process_snapshot: HANDLE = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
    if process_snapshot == INVALID_HANDLE_VALUE || process_snapshot.is_null() {
        unsafe {
            return Err(format!("Cannot list processes, code: {}", GetLastError()));
        }
    }
    Ok(ProcessInformationIterator::new(process_snapshot))
}

代码中的 ProcessInfomationIterator 是自定义的,为了结构更清晰,这里使用迭代器模式来读取。

如果保存进程快照失败,返回的句柄会是一个无效的值(这里用了两个条件或的关系去判断是否无效,其实任用其一都可以,他们都表示一个“空”内存或“空”指针),使用 GetLastError 方法可以获取错误代码,错误代码对应含义见系统错误代码说明,也可以通过API解析成可读文本,这个后面的文章再介绍,这里先用code简单表示一下。

实现迭代器

Rust中的迭代器模式实现方法这里就不多赘述,你只需要知道实现一个迭代器至少需要 一个迭代元素Item一个实现了Iterator特征的迭代器 就可以了。

迭代元素Item

代码语言:rust
AI代码解释
复制
let vec = vec![1, 2]
for item in vec {
	...
}

上面代码的item就是迭代器中具体的元素,因为进程信息有很多,这里就使用一个结构体来存

代码语言:rust
AI代码解释
复制
use winapi::um::tlhelp32::PROCESSENTRY32;

pub struct ProcessInformation {
    inner: PROCESSENTRY32,
}

这里并没有直接将进程的数据解析之后再存入结构体,而是直接将 PROCESSENTRY32 结构体做一个包装,这里是为了节省不必要的计算,从句柄中直接读取出来的 PROCESSENTRY32 并不是所有信息都是Rust直接可读的,在需要时才解析,并且通过getter方法读取数据更方便以后拓展,下面是ProcessInformation的具体方法实现。

代码语言:rust
AI代码解释
复制
use winapi::um::processthreadsapi::{GetPriorityClass, OpenProcess};
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::tlhelp32::PROCESSENTRY32;
use winapi::um::winnt::{HANDLE, PROCESS_ALL_ACCESS};

pub(crate) fn char_arr_to_string(chars: &[i8]) -> String {
    chars.into_iter().map(|&c| c as u8 as char).collect()
}

impl ProcessInformation {
    pub(crate) fn new(entry: PROCESSENTRY32) -> ProcessInformation {
        ProcessInformation {
            inner: entry
        }
    }

    /// 获取进程ID
    pub fn get_pid(&self) -> u32 {
        self.inner.th32ProcessID as u32
    }

    /// 获取进程名
    pub fn get_name(&self) -> String {
        char_arr_to_string(&self.inner.szExeFile)
    }

    /// 获取父进程ID
    pub fn get_parent_pid(&self) -> u32 {
        self.inner.th32ParentProcessID as u32
    }

    /// 获取线程数量
    pub fn get_thread_count(&self) -> usize {
        self.inner.cntThreads as usize
    }

    /// 获取基础优先权值
    pub fn get_priority_base(&self) -> i64 {
        self.inner.pcPriClassBase as i64
    }

    /// 获取优先权类别,如果调用进程没有指定权限可能会获取失败,失败时返回 `None`
    pub fn get_priority_class(&self) -> Option<i32> {
        let mut priority_class = None;
        unsafe {
            let handle = OpenProcess(PROCESS_ALL_ACCESS, 0, self.inner.th32ProcessID);
            if !handle.is_null() {
                let class = GetPriorityClass(handle);
                CloseHandle(handle);
                priority_class = Some(class as i32);
            }
        }
        priority_class
    }
}

迭代器实现

迭代器中需要保存一些迭代遍历的状态,因此除了前面保存的快照句柄之外还要存储迭代的索引以及释放句柄的状态,迭代器是不可逆的

代码语言:rust
AI代码解释
复制
use winapi::um::winnt::HANDLE;

pub struct ProcessInformationIterator {
    process_snapshot: HANDLE,
    index: usize,
    finished: bool,
}

impl ProcessInformationIterator {
    pub(crate) fn new(process_snapshot: HANDLE) -> ProcessInformationIterator {
        ProcessInformationIterator {
            process_snapshot,
            index: 0,
            finished: false,
        }
    }
}

然后就是迭代器的具体实现

代码语言:rust
AI代码解释
复制
use winapi::um::winnt::HANDLE;
use winapi::um::tlhelp32::{Process32First, Process32Next, PROCESSENTRY32};
use winapi::um::handleapi::CloseHandle;

impl Iterator for ProcessInformationIterator {
    type Item = ProcessInformation;

    fn next(&mut self) -> Option<Self::Item> {
        if self.finished {
            return None;
        }
        self.index += 1;

        let mut entry: PROCESSENTRY32 = unsafe { std::mem::zeroed() };

        entry.dwSize = size_of::<PROCESSENTRY32>() as u32;
        //  读取快照中的第一个进程
        let res = unsafe {
            if self.index == 1 {
                Process32First(self.process_snapshot, &mut entry)
            } else {
                Process32Next(self.process_snapshot, &mut entry)
            }
        };
        if res == 0 {
            unsafe {
                CloseHandle(self.process_snapshot);
            }
            self.finished = true;
            return None;
        }

        Some(ProcessInformation::new(entry))
    }
}

上面的代码有几点需要说明一下:

  1. entry在初始化时需要先到一个全0值的内存空间,并不是分配为一个Rust引用空间,这里用 unsafe 方法 std::mem::zeroed()
  2. 在读取进程Entry之前需要先指定内存长度,这里用 size_of::<PROCESSENTRY32>() 来获取并赋值给 entry.dwSize
  3. 遍历时第一个元素需要调用 Process32First读取,后续的使用 Process32Next 读取
  4. 遍历完时记得关闭快照剧本 使用 CloseHandle 接口

特殊情况处理:如果用户并没有迭代完,上面的代码实现可能会出现快照句柄未释放的情况,所以还需要给迭代器实现一个Drop特征,在释放迭代器时释放快照句柄

代码语言:rust
AI代码解释
复制
impl Drop for ProcessInformationIterator {
    fn drop(&mut self) {
        if self.finished {
            return;
        }
        // 释放快照句柄。
        unsafe {
            CloseHandle(self.process_snapshot);
        }
        self.finished = true;
    }
}

代码汇总

我在写的时候放在了自定义的utils::process::win包下面,具体引用路径根据自己的情况调整

mod.rs文件

代码语言:rust
AI代码解释
复制
use crate::utils::process::win::process_information::ProcessInformationIterator;
use winapi::um::tlhelp32::{CreateToolhelp32Snapshot, TH32CS_SNAPPROCESS};
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
use winapi::um::winnt::HANDLE;

pub mod process_information;

pub fn list() -> Result<ProcessInformationIterator, String> {
    let process_snapshot: HANDLE = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
    if process_snapshot == INVALID_HANDLE_VALUE || process_snapshot.is_null() {
        unsafe {
            return Err(format!("Cannot list processes, code: {}", GetLastError()));
        }
    }
    Ok(ProcessInformationIterator::new(process_snapshot))
}

pub(crate) fn char_arr_to_string(chars: &[i8]) -> String {
    chars.into_iter().map(|&c| c as u8 as char).collect()
}

process_information.rs文件

代码语言:rust
AI代码解释
复制
use crate::utils::process::win::char_arr_to_string;
use crate::utils::process::win::process_module::ProcessModuleIterator;
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::handleapi::CloseHandle;
use winapi::um::processthreadsapi::{GetPriorityClass, OpenProcess};
use winapi::um::tlhelp32::{Process32First, Process32Next, PROCESSENTRY32};
use winapi::um::winnt::{HANDLE, PROCESS_ALL_ACCESS};

/// [PROCESSENTRY32](https://learn.microsoft.com/zh-cn/windows/win32/api/tlhelp32/ns-tlhelp32-processentry32) 的Rust包装实现
pub struct ProcessInformation {
    inner: PROCESSENTRY32,
}

impl ProcessInformation {
    pub(crate) fn new(entry: PROCESSENTRY32) -> ProcessInformation {
        ProcessInformation {
            inner: entry
        }
    }

    /// 获取进程ID
    pub fn get_pid(&self) -> u32 {
        self.inner.th32ProcessID as u32
    }

    /// 获取进程名
    pub fn get_name(&self) -> String {
        char_arr_to_string(&self.inner.szExeFile)
    }

    /// 获取父进程ID
    pub fn get_parent_pid(&self) -> u32 {
        self.inner.th32ParentProcessID as u32
    }

    /// 获取线程数量
    pub fn get_thread_count(&self) -> usize {
        self.inner.cntThreads as usize
    }

    /// 获取基础优先权值
    pub fn get_priority_base(&self) -> i64 {
        self.inner.pcPriClassBase as i64
    }

    /// 获取优先权类别,如果调用进程没有指定权限可能会获取失败,失败时返回 `None`
    pub fn get_priority_class(&self) -> Option<i32> {
        let mut priority_class = None;
        unsafe {
            let handle = OpenProcess(PROCESS_ALL_ACCESS, 0, self.inner.th32ProcessID);
            if !handle.is_null() {
                let class = GetPriorityClass(handle);
                CloseHandle(handle);
                priority_class = Some(class as i32);
            }
        }
        priority_class
    }
}

pub struct ProcessInformationIterator {
    process_snapshot: HANDLE,
    index: usize,
    finished: bool,
}

impl ProcessInformationIterator {
    pub(crate) fn new(process_snapshot: HANDLE) -> ProcessInformationIterator {
        ProcessInformationIterator {
            process_snapshot,
            index: 0,
            finished: false,
        }
    }
}

impl Drop for ProcessInformationIterator {
    fn drop(&mut self) {
        if self.finished {
            return;
        }
        // 释放快照句柄。
        unsafe {
            CloseHandle(self.process_snapshot);
        }
        self.finished = true;
    }
}

impl Iterator for ProcessInformationIterator {
    type Item = ProcessInformation;

    fn next(&mut self) -> Option<Self::Item> {
        if self.finished {
            return None;
        }
        self.index += 1;

        let mut entry: PROCESSENTRY32 = unsafe { std::mem::zeroed() };

        entry.dwSize = size_of::<PROCESSENTRY32>() as u32;
        //  读取快照中的第一个进程
        let res = unsafe {
            if self.index == 1 {
                Process32First(self.process_snapshot, &mut entry)
            } else {
                Process32Next(self.process_snapshot, &mut entry)
            }
        };
        if res == 0 {
            unsafe {
                CloseHandle(self.process_snapshot);
            }
            self.finished = true;
            return None;
        }

        Some(ProcessInformation::new(entry))
    }
}

测试

测试代码

代码语言:rust
AI代码解释
复制
#[test]
pub fn test_list() {
    println!("PID\tName\tParent PID\tThreads\tPriority Base\tPriority Class");
    let iter = list().unwrap();
    for process in iter {
        let pid = process.get_pid();
        let name = process.get_name();
        let parent_pid = process.get_parent_pid();
        let thread_count = process.get_thread_count();
        let priority_base = process.get_priority_base();
        let priority_class = process.get_priority_class();

        println!("{}\t{}\t{}\t{}\t{}\t{:?}", pid, name, parent_pid, thread_count, priority_base, priority_class)
    }
}

结果

代码语言:shell
AI代码解释
复制
PID     Name    Parent PID      Threads Priority Base   Priority Class
0       [System Process]        0       6       0       None
4       System  0       236     8       None
64      Secure System   4       0       8       None
132     Registry        4       4       8       None
504     smss.exe        4       2       11      None
728     csrss.exe       712     11      13      None
824     wininit.exe     712     1       13      None
832     csrss.exe       816     15      13      None

...

12624   chrome.exe      12148   19      8       Some(32)
16352   chrome.exe      12148   18      4       Some(64)
14904   chrome.exe      12148   17      4       Some(64)
14672   wslinstaller.exe        892     2       8       None
11160   chrome.exe      12148   20      4       Some(64)
18048   chrome.exe      12148   19      4       Some(64)
5452    chrome.exe      12148   14      4       Some(64)
14468   svchost.exe     892     3       8       None
18060   chrome.exe      12148   14      4       Some(64)
17748   dllhost.exe     688     8       8       Some(32)
16084   vctip.exe       16648   27      8       Some(32)
9008    OpenConsole.exe 10644   6       8       Some(32)
15516   cargo.exe       10644   4       8       Some(32)
11312   cargo.exe       15516   4       8       Some(32)

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
3.1 DLL注入:常规远程线程注入
动态链接库注入技术是一种特殊的技术,它允许在运行的进程中注入DLL动态链接库,从而改变目标进程的行为。DLL注入的实现方式有许多,典型的实现方式为远程线程注入,该注入方式的注入原理是利用了Windows系统中提供的CreateRemoteThread()这个API函数,该函数第四个参数是准备运行的线程,我们将LoadLibrary()函数填入其中,这样就可以执行远程进程中的LoadLibrary()函数,进而将我们自己准备的DLL加载到远程进程空间中执行,DLL在被装载后则会自动执行初始化部分。
王瑞MVP
2023/10/11
3480
19.12 Boost Asio 获取远程进程
远程进程遍历功能实现原理与远程目录传输完全一致,唯一的区别在于远程进程枚举中使用EnumProcess函数枚举当前系统下所有活动进程,枚举结束后函数返回一个PROCESSENTRY32类型的容器,其中的每一个成员都是一个进程信息,只需要对该容器进行动态遍历即可得到所有的远程主机列表。
王瑞MVP
2023/11/09
2220
19.12 Boost Asio 获取远程进程
Windows编程之进程遍历(C++实现)
       Windows编程之进程遍历 PS: 主要扣代码使用,直接滑动到最下面使用. 遍历进程需要几个API,和一个结构体   1.创建进程快照   2.遍历首次进程   3.继续下次遍历   4.进程信息结构体 API 分别是: 1.创建进程快照 HANDLE WINAPI CreateToolhelp32Snapshot(      进程快照API DWORD dwFlags,                 遍历的标志,表示你要遍历什么(进程,模块,堆...) DWORD t
IBinary
2022/05/10
9430
Windows编程之进程遍历(C++实现)
C/C++ 进程/线程/模块遍历
遍历进程 #include <windows.h> #include <tlhelp32.h> //进程快照函数头文件 #include <stdio.h> int main() { int countProcess=0; //当前进程数量计数变量 PROCESSENTRY32 currentProcess; //存放快照进程信息的一个结构体 currentPr
王瑞MVP
2022/12/28
3980
C/C++ 获取进程某模块入口地址
实现获取指定进程中特定模块的枚举以及得到该模块入口地址等信息。 实现代码: HMODULE GetProcessModuleHandle(DWORD pid, CONST TCHAR* moduleName){ // 根据 PID 、模块名(需要写后缀,如:".dll"),获取模块入口地址。 MODULEENTRY32 moduleEntry; HANDLE handle = NULL; handle = ::CreateToolhelp32Snapshot(
王瑞MVP
2022/12/28
7140
C/C++ 获取进程某模块入口地址
7.3 通过API枚举进程
首先实现枚举当前系统中所有进程信息,枚举该进程的核心点在于使用CreateToolhelp32Snapshot()函数,该函数用于创建系统进程和线程快照,它可以捕获当前系统中进程和线程相关的信息(如PID、线程数量、线程ID等),在对这些信息进行处理后,可以获得很多有用的数据,如当前系统中所有正在执行的进程的信息列表,以及每个进程各自的详细信息(如CPU、内存占用量等)。
王瑞MVP
2023/09/23
2170
红队技巧:绕过ESET_NOD32抓取密码
聊一聊绕过ESET_NOD32抓取密码的方法,这里的ESET_NOD32指的是ESET_NOD32 File Security For Microsoft windows server,测试版本如下:
鸿鹄实验室
2021/04/01
9930
红队技巧:绕过ESET_NOD32抓取密码
Windows编程之模块遍历(C++实现)
  1.获取你想要遍历的进程ID (可以通过遍历进程,也可以通过通过句柄获得进程ID)
IBinary
2022/05/10
6940
RedTeamTips--PEB隐藏
文章前先给各位师傅拜个早年啦,要过年了,公众号也会停更一段时间,年后回复啦。这篇文章中,我们将介绍如何来隐藏你程序的PEB信息。首先先来了解一下什么是PEB,其全程为Process Envirorment Block ,直译过来就是进程环境信息块,存放进程信息,每个进程都有自己的PEB信息。位于用户地址空间。其结构如下:
鸿鹄实验室
2021/04/01
6420
RedTeamTips--PEB隐藏
通过ReadProcessMemory读取进程内存「建议收藏」
修改一个程序的过程如下:1、获得进程的句柄 2、以一定的权限打开进程 3、调用ReadProcessMemory读取内存,WriteProcessMemory修改内存,这也是内存补丁的实现过程。下面贴出的是调用ReadProcessMemory的例程
全栈程序员站长
2022/11/09
1.5K0
红队 | Windows注入的一些方式
在渗透过程中有时候为了权限维持或者其他等一些操作,比如以前的搜狗输入法可以替换dll文件当用户切换输入法就会去加载我们替换的dll文件,dll文件可以自己编写一些net user或者其他的一些方法,也可以通过msf等来生成dll文件进行替换。
HACK学习
2021/08/13
1.1K0
3.2 DLL注入:远程APC异步注入
APC(Asynchronous Procedure Call)异步过程调用是一种Windows操作系统的核心机制,它允许在进程上下文中执行用户定义的函数,而无需创建线程或等待OS执行完成。该机制适用于一些频繁的、短暂的或非常细微的操作,例如改变线程优先级或通知线程处理任务。在APC机制中,当某些事件发生时(例如文件IO,网络IO或定时器触发),这些事件将被操作系统添加到一个APC队列中,该队列绑定到执行线程。在下一次发生ALERTABLE的事件时(例如调用SleepEx或SignalObjectAndWait时),OS将弹出APC函数并在执行线程上下文中调用该函数,并在执行完毕后恢复线程执行。
王瑞MVP
2023/09/13
4580
7.5 通过API判断进程状态
进程状态的判断包括验证进程是否存在,实现方法是通过枚举系统内的所有进程信息,并将该进程名通过CharLowerBuff转换为小写,当转换为小写模式后则就可以通过使用strcmp函数对比,如果发现继承存在则返回该进程的PID信息,否则返回-1。
王瑞MVP
2023/09/24
2910
C/C++ 进程线程操作技术
手动创建多线程: 多线程的创建需要使用CreateThread()其内部应该传递进去ThreadProc()线程执行函数,运行结束后恢复.
王瑞MVP
2023/02/25
7400
DLL注入explorer.exe进程[通俗易懂]
  最近一直在学习dll注入远程进程的相关知识,于是有了这篇文章。通过注入的方式会运行程序,在资源管理器中是看不到,相关的进程的,这为程序的隐藏提供了极大的便利。
全栈程序员站长
2022/08/29
2.3K1
干货|Windows下进程操作的一些C++代码
0x01 进程遍历 因为进程是在随时进行变动的所以我们需要获取一张快照 1.1 CreateToolhelp32Snapshot HANDLE CreateToolhelp32Snapshot( DWORD dwFlags, DWORD th32ProcessID); 因为要获取进程第一个参数选择TH32CS_SNAPPROCESS来获取系统中所有的进程,具体可以参考[CreateToolhelp32Snapshot]:https://docs.microsoft.com/zh-cn/windows/w
HACK学习
2021/08/13
1.5K0
DLL远程线程注入
CreateToolhelp32Snapshot函数 https://learn.microsoft.com/zh-cn/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot 获取指定进程的快照,以及这些进程使用的堆、模块和线程。(也就是说,我们可以利用这个函数来获取进程的PID)
YanXia
2023/04/07
7550
DLL远程线程注入
6.1 KMP算法搜索机器码
KMP算法是一种高效的字符串匹配算法,它的核心思想是利用已经匹配成功的子串前缀的信息,避免重复匹配,从而达到提高匹配效率的目的。KMP算法的核心是构建模式串的前缀数组Next,Next数组的意义是:当模式串中的某个字符与主串中的某个字符失配时,Next数组记录了模式串中应该回退到哪个位置,以便继续匹配。Next数组的计算方法是找出模式串每一个前缀中最长的相等前缀和后缀,并记录下来它们的长度,作为Next数组中的对应值。
王瑞MVP
2023/09/20
2660
6.1 KMP算法搜索机器码
7.6 实现进程挂起与恢复
挂起与恢复进程是指暂停或恢复进程的工作状态,以达到一定的控制和管理效果。在 Windows 操作系统中,可以使用系统提供的函数实现进程的挂起和恢复,以达到对进程的控制和调度。需要注意,过度使用进程挂起/恢复操作可能会造成系统性能的降低,导致死锁等问题,因此在使用时应该谨慎而慎重。同时,通过和其他进程之间协同工作,也可以通过更加灵活的方式,实现进程的协调、交互等相应的功能,从而实现更加高效和可靠的进程管理。
王瑞MVP
2023/09/24
4410
7.6 实现进程挂起与恢复
绕过360实现lsass转储
AvDump.exe是Avast杀毒软件中自带的一个程序,可用于转储指定进程(lsass.exe)内存数据,它带有Avast杀软数字签名。
红队蓝军
2022/04/18
1.2K0
绕过360实现lsass转储
相关推荐
3.1 DLL注入:常规远程线程注入
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档