乐于分享
好东西不私藏

跨平台系统信息获取库libsigar源码分析系列(六)

跨平台系统信息获取库libsigar源码分析系列(六)

官网:http://securitytech.cc

源码分析mettle后门工具学习 所使用的依赖库

官网:http://securitytech.cc

OpenBSD 分支源码分析

概述

OpenBSD 是一个专注于安全性、代码正确性和可移植性的免费开源 UNIX 操作系统。libsigar 在 OpenBSD 平台的实现与 FreeBSD 有很高的相似性,主要使用 sysctl 系统调用和 kvm 库来获取系统信息,并提供了 /proc 文件系统的可选支持。

核心架构

1. sigar_t 结构体扩展

  1. structsigar_t{
  2.     SIGAR_T_BASE;// 基础结构
  3. int pagesize;// 页面大小
  4. time_t last_getprocs;// 最后一次获取进程信息的时间
  5. sigar_pid_t last_pid;// 最后查询的 PID
  6. bsd_pinfo_t*pinfo;// 进程信息缓存 (kinfo_proc)
  7. int lcpu;// 最后访问的 CPU
  8. size_t argmax;// 参数最大长度
  9. kvm_t*kmem;// kvm 库句柄
  10. unsignedlong koffsets[KOFFSET_MAX];// 内核符号偏移量
  11. int proc_mounted;// /proc 是否挂载
  12. };

2. 内核符号偏移量枚举

  1. enum{
  2.     KOFFSET_CPUINFO,// cp_time
  3.     KOFFSET_VMMETER,// cnt
  4.     KOFFSET_TCPSTAT,// tcpstat
  5.     KOFFSET_TCBTABLE,// tcbtable
  6.     KOFFSET_MAX
  7. };

这些偏移量通过 kvm_nlist 从内核符号表中解析,用于直接读取内核数据。

3. 核心数据类型

  1. typedefstruct kinfo_proc bsd_pinfo_t;

使用标准的 BSD 进程信息结构体。

核心技术

1. sysctl 系统调用

OpenBSD 主要通过 sysctl 获取系统信息:

  1. int mib[2];
  2. size_t len;
  3. // 获取 CPU 数量
  4. mib[0]= CTL_HW;
  5. mib[1]= HW_NCPU;
  6. sysctl(mib, NMIB(mib),&ncpu,&len, NULL,0);
  7. // 获取内存大小
  8. mib[1]= HW_PHYSMEM;
  9. sysctl(mib, NMIB(mib),&mem_total,&len, NULL,0);
  10. // 获取页面大小
  11. mib[1]= HW_PAGESIZE;
  12. sysctl(mib, NMIB(mib),&pagesize,&len, NULL,0);
  13. // 获取启动时间
  14. mib[0]= CTL_KERN;
  15. mib[1]= KERN_BOOTTIME;
  16. sysctl(mib, NMIB(mib),&boottime,&len, NULL,0);

2. kvm 库内核访问

用于访问内核内存和获取进程信息:

  1. kvm_t*kmem = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, NULL);
  2. // 获取所有进程
  3. struct kinfo_proc *proc = kvm_getprocs(kmem, KERN_PROC_ALL,0,
  4. sizeof(*proc),&num);
  5. // 读取内核数据
  6. kvm_read(kmem, offset, data, size);

3. 内核符号解析

  1. staticint get_koffsets(sigar_t*sigar)
  2. {
  3. struct nlist klist[]={
  4. {"_cp_time"},// CPU 时间
  5. {"_cnt"},// 虚拟内存统计
  6. {"_tcpstat"},// TCP 统计
  7. {"_tcbtable"},// TCP 控制块表
  8. { NULL }
  9. };
  10.     kvm_nlist(sigar->kmem, klist);
  11. for(=0; i < KOFFSET_MAX; i++){
  12.         sigar->koffsets[i]= klist[i].n_value;
  13. }
  14. }

4. 内核数据读取

  1. staticint kread(sigar_t*sigar,void*data,int size,long offset)
  2. {
  3. if(!sigar->kmem){
  4. return SIGAR_EPERM_KMEM;
  5. }
  6. if(kvm_read(sigar->kmem, offset, data, size)!= size){
  7. return errno;
  8. }
  9. return SIGAR_OK;
  10. }

5. 进程信息缓存

  1. staticint sigar_get_pinfo(sigar_t*sigar,sigar_pid_t pid)
  2. {
  3. int mib[]={ CTL_KERN, KERN_PROC, KERN_PROC_PID,0,
  4. sizeof(*sigar->pinfo),1};
  5. size_t len =sizeof(*sigar->pinfo);
  6. time_t timenow = time(NULL);
  7.     mib[3]= pid;
  8. if(sigar->pinfo == NULL){
  9.         sigar->pinfo = malloc(len);
  10. }
  11. // 60 秒缓存
  12. if(sigar->last_pid == pid){
  13. if((timenow - sigar->last_getprocs)< SIGAR_LAST_PROC_EXPIRE){
  14. return SIGAR_OK;
  15. }
  16. }
  17.     sigar->last_pid = pid;
  18.     sigar->last_getprocs = timenow;
  19. return sysctl(mib, NMIB(mib), sigar->pinfo,&len, NULL,0);
  20. }

6. /proc 可选支持

  1. #define PROCFS_STATUS(status) \
  2. ((((status)!= SIGAR_OK)&&!sigar->proc_mounted)? \
  3.      SIGAR_ENOTIMPL : status)
  4. int sigar_os_open(sigar_t**sigar)
  5. {
  6. struct stat sb;
  7. if(stat("/proc/curproc",&sb)<0){
  8. (*sigar)->proc_mounted =0;
  9. }else{
  10. (*sigar)->proc_mounted =1;
  11. }
  12. }

核心功能模块

1. 内存监控

  1. int sigar_mem_get(sigar_t*sigar,sigar_mem_t*mem)
  2. {
  3. int mib[2];
  4. size_t len;
  5. struct uvmexp vmstat;
  6. sigar_uint64_t kern =0;
  7. // 页面大小
  8.     mib[0]= CTL_HW;
  9.     mib[1]= HW_PAGESIZE;
  10.     sysctl(mib, NMIB(mib),&sigar->pagesize,&len, NULL,0);
  11. // 物理内存总量
  12.     mib[1]= HW_PHYSMEM;
  13.     sysctl(mib, NMIB(mib),&mem_total,&len, NULL,0);
  14.     mem->total = mem_total;
  15. // 虚拟内存统计
  16.     sigar_vmstat(sigar,&vmstat);
  17.     mem->free = vmstat.free;
  18.     kern = vmstat.inactive;
  19.     kern += vmstat.vnodepages + vmstat.vtextpages;
  20.     kern *= sigar->pagesize;
  21.     mem->used = mem->total - mem->free;
  22.     mem->actual_free = mem->free + kern;
  23.     mem->actual_used = mem->used - kern;
  24.     sigar_mem_calc_ram(sigar, mem);
  25. }

2. 交换空间监控

  1. int sigar_swap_get(sigar_t*sigar,sigar_swap_t*swap)
  2. {
  3. struct uvmexp vmstat;
  4.     sigar_vmstat(sigar,&vmstat);
  5.     swap->total = vmstat.swpages * sigar->pagesize;
  6.     swap->used = vmstat.swpginuse * sigar->pagesize;
  7.     swap->free = swap->total - swap->used;
  8.     swap->page_in = vmstat.pageins;
  9.     swap->page_out = vmstat.pdpageouts;
  10. }

3. CPU 监控

总体 CPU 统计

  1. int sigar_cpu_get(sigar_t*sigar,sigar_cpu_t*cpu)
  2. {
  3. cp_time_t cp_time[CPUSTATES];
  4. int mib[]={ CTL_KERN, KERN_CPTIME };
  5. size_t size =sizeof(cp_time);
  6.     sysctl(mib, NMIB(mib),&cp_time,&size, NULL,0);
  7.     cpu->user = SIGAR_TICK2MSEC(cp_time[CP_USER]);
  8.     cpu->nice = SIGAR_TICK2MSEC(cp_time[CP_NICE]);
  9.     cpu->sys  = SIGAR_TICK2MSEC(cp_time[CP_SYS]);
  10.     cpu->idle = SIGAR_TICK2MSEC(cp_time[CP_IDLE]);
  11.     cpu->wait =0;// N/A
  12.     cpu->irq = SIGAR_TICK2MSEC(cp_time[CP_INTR]);
  13.     cpu->soft_irq =0;// N/A
  14.     cpu->stolen =0;// N/A
  15.     cpu->total = cpu->user + cpu->nice + cpu->sys + cpu->idle + cpu->irq;
  16. }

CPU 列表

  1. int sigar_cpu_list_get(sigar_t*sigar,sigar_cpu_list_t*cpulist)
  2. {
  3.     sigar_cpu_list_create(cpulist);
  4. #ifdef HAVE_KERN_CP_TIMES
  5. // 支持每个 CPU 的统计
  6. if(sigar_cp_times_get(sigar, cpulist)== SIGAR_OK){
  7. return SIGAR_OK;
  8. }
  9. #endif
  10. // 旧版本: 所有指标在第 1 个 CPU,其余为 0
  11.     sigar_cpu_get(sigar,&cpulist->data[cpulist->number++]);
  12. for(=1; i < sigar->ncpu; i++){
  13.         SIGAR_CPU_LIST_GROW(cpulist);
  14.         cpu =&cpulist->data[cpulist->number++];
  15.         SIGAR_ZERO(cpu);
  16. }
  17. }

4. 进程监控

进程列表

  1. int sigar_os_proc_list_get(sigar_t*sigar,sigar_proc_list_t*proclist)
  2. {
  3. struct kinfo_proc *proc;
  4. int num;
  5. if(!sigar->kmem){
  6. return SIGAR_EPERM_KMEM;
  7. }
  8.     proc = kvm_getprocs(sigar->kmem, KERN_PROC_ALL,0,
  9. sizeof(*proc),&num);
  10. for(=0; i < num; i++){
  11. // 跳过系统进程和 PID 0
  12. if(proc[i].KI_FLAG & P_SYSTEM){
  13. continue;
  14. }
  15. if(proc[i].KI_PID ==0){
  16. continue;
  17. }
  18.         SIGAR_PROC_LIST_GROW(proclist);
  19.         proclist->data[proclist->number++]= proc[i].KI_PID;
  20. }
  21. }

进程内存

  1. int sigar_proc_mem_get(sigar_t*sigar,sigar_pid_t pid,sigar_proc_mem_t*procmem)
  2. {
  3. bsd_pinfo_t*pinfo = sigar->pinfo;
  4.     sigar_get_pinfo(sigar, pid);
  5. // 虚拟内存大小 = text + data + stack
  6.     procmem->size =
  7. (pinfo->p_vm_tsize + pinfo->p_vm_dsize + pinfo->p_vm_ssize)*
  8.         sigar->pagesize;
  9.     procmem->resident = pinfo->p_vm_rssize * sigar->pagesize;
  10.     procmem->share = SIGAR_FIELD_NOTIMPL;
  11.     procmem->minor_faults = pinfo->p_uru_minflt;
  12.     procmem->major_faults = pinfo->p_uru_majflt;
  13.     procmem->page_faults = procmem->minor_faults + procmem->major_faults;
  14. }

进程凭据

  1. int sigar_proc_cred_get(sigar_t*sigar,sigar_pid_t pid,sigar_proc_cred_t*proccred)
  2. {
  3. bsd_pinfo_t*pinfo = sigar->pinfo;
  4.     sigar_get_pinfo(sigar, pid);
  5.     proccred->uid  = pinfo->p_ruid;
  6.     proccred->gid  = pinfo->p_rgid;
  7.     proccred->euid = pinfo->p_uid;
  8.     proccred->egid = pinfo->p_gid;
  9. }

进程时间

  1. #define tv2msec(tv) \
  2. (((sigar_uint64_t)tv.tv_sec * SIGAR_MSEC)+(((sigar_uint64_t)tv.tv_usec)/1000))
  3. int sigar_proc_time_get(sigar_t*sigar,sigar_pid_t pid,sigar_proc_time_t*proctime)
  4. {
  5. bsd_pinfo_t*pinfo = sigar->pinfo;
  6.     sigar_get_pinfo(sigar, pid);
  7.     proctime->user  = tv2msec(pinfo->p_uutime);
  8.     proctime->sys   = tv2msec(pinfo->p_stime);
  9.     proctime->total = proctime->user + proctime->sys;
  10.     proctime->start_time = tv2msec(pinfo->p_ustart);
  11. }

进程状态

  1. int sigar_proc_state_get(sigar_t*sigar,sigar_pid_t pid,sigar_proc_state_t*procstate)
  2. {
  3. bsd_pinfo_t*pinfo = sigar->pinfo;
  4.     sigar_get_pinfo(sigar, pid);
  5.     SIGAR_SSTRCPY(procstate->name, pinfo->p_comm);
  6.     procstate->ppid = pinfo->p_ppid;
  7.     procstate->priority = pinfo->p_priority;
  8.     procstate->nice = pinfo->p_nice;
  9.     procstate->processor = pinfo->p_cpuid;
  10. // 状态映射
  11. switch(pinfo->p_stat){
  12. case SIDL:    procstate->state ='D';break;
  13. case SRUN:    procstate->state ='R';break;
  14. case SSLEEP:  procstate->state ='S';break;
  15. case SSTOP:   procstate->state ='T';break;
  16. case SZOMB:   procstate->state ='Z';break;
  17. case SDEAD:   procstate->state ='Z';break;
  18. }
  19. }

进程参数

OpenBSD 使用自定义的 sigar_proc_args_get 实现来获取进程参数。

技术亮点

1. 统一的 sysctl 接口

  • 所有系统信息通过 sysctl 获取
  • 结构化的 mib 数组
  • 高效的二进制接口

2. kvm 库内核访问

  • 直接访问内核内存
  • 获取完整的进程信息
  • 内核符号解析支持

3. /proc 可选支持

  • 检测 /proc 是否挂载
  • 在失败时优雅降级
  • 适应不同系统配置

4. 进程信息缓存

  • 60 秒有效期
  • 减少 sysctl 调用
  • 提高性能

5. 版本兼容性

  • KERN_CPTIME vs KERN_CP_TIME
  • HAVE_KERN_CP_TIMES 检测
  • 新旧 API 兼容

6. 与 FreeBSD 共享头文件

  1. typedefstruct kinfo_proc bsd_pinfo_t;

大量复用 FreeBSD 的代码和结构定义。

错误处理

自定义错误码

  1. #define SIGAR_EPERM_KMEM (SIGAR_OS_START_ERROR+EACCES)
  2. #define SIGAR_EPROC_NOENT (SIGAR_OS_START_ERROR+2)

错误字符串

  1. char*sigar_os_error_string(sigar_t*sigar,int err)
  2. {
  3. switch(err){
  4. case SIGAR_EPERM_KMEM:
  5. return"Failed to open /dev/kmem for reading";
  6. case SIGAR_EPROC_NOENT:
  7. return"/proc filesystem is not mounted";
  8. default:
  9. return NULL;
  10. }
  11. }

未实现功能

以下功能在 OpenBSD 上未实现:

  • sigar_sys_info_get_uuid – 系统 UUID
  • sigar_system_stats_get – 系统统计
  • sigar_proc_cumulative_disk_io_get – 进程磁盘 I/O
  • sigar_proc_env_get – 进程环境变量
  • sigar_proc_fd_get – 进程文件描述符
  • sigar_proc_exe_get – 进程可执行文件

与 FreeBSD 的区别

特性
OpenBSD
FreeBSD
进程结构
kinfo_proc
kinfo_proc
内核访问
kvm
kvm
/proc 支持
可选
完整支持
sysctl 扩展
KERN_CPTIME
KERN_CPTIME
进程过滤
P_SYSTEM 标志
无特定过滤
共享内存
自定义实现
更完整

总结

OpenBSD 分支的实现特点:

  1. 安全导向 – 严格的访问控制和错误处理
  2. sysctl 为主 – 主要通过 sysctl 获取信息
  3. kvm 辅助 – 使用 kvm 获取进程信息
  4. 兼容性强 – 与 FreeBSD 共享大量代码
  5. /proc 可选 – 不依赖 /proc 文件系统
  6. 简洁设计 – 代码清晰,易于维护
  7. 性能优化 – 进程信息缓存减少系统调用

OpenBSD 的实现体现了该系统对安全性和代码正确性的重视,同时也保持了与其他 BSD 系统的高度兼容性。

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 跨平台系统信息获取库libsigar源码分析系列(六)

猜你喜欢

  • 暂无文章