乐于分享
好东西不私藏

跨平台底层网络库libdnet源码分析系列(十一)

跨平台底层网络库libdnet源码分析系列(十一)

网:http://securitytech.cc

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

libdnet 权限模型源码深入分析

目录

  1. 权限模型概述
  2. Linux平台权限实现
  3. BSD/macOS平台权限实现
  4. Windows平台权限实现
  5. 各平台权限对比
  6. 权限错误处理
  7. 安全最佳实践

权限模型概述

libdnet 作为一个底层网络操作库,在多个平台上都需要执行特权操作。这些操作包括:

  • 原始套接字创建(SOCKRAW, PFPACKET)
  • 网络接口配置(SIOCGIFADDR, SIOCSIFADDR等ioctl)
  • 路由表操作(SIOCADDRT, SIOCDELRT)
  • 防火墙规则管理
  • ARP缓存操作

权限需求分类

操作类型
权限要求
风险等级
原始套接字
root/CAPNETRAW
接口配置
root/CAPNETADMIN
路由操作
root/CAPNETADMIN
防火墙规则
root/CAPNETADMIN
ARP操作
root/CAPNETADMIN

Linux平台权限实现

1. 原始套接字权限(ip.c)

Linux平台通过原始套接字实现IP层数据包发送:

  1. // src/ip.c:35
  2. if((i->fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW))<0)
  3. return(ip_close(i));

权限分析

  • socket(AF_INET,SOCK_RAW,IPPROTO_RAW) 需要:
  • CAPNETRAW 能力(Linux能力模型)
  • 或者有效的UID = 0(root用户)

失败场景

  1. // 当权限不足时,socket()调用会失败并设置errno
  2. // EPERM: 操作不允许,缺少CAP_NET_RAW能力
  3. // EACCES: 权限被拒绝

错误处理流程

  1. graph TD
  2.     A[调用socket创建原始套接字]--> B{创建成功?}
  3.     B -->|是| C[设置IP_HDRINCL选项]
  4.     B -->|否| D[返回NULL]
  5.     D --> E[errno设置为EPERM/EACCES]
  6.     E --> F[应用程序检查errno]

2. 以太网套接字权限(eth-linux.c)

Linux使用PF_PACKET套接字访问二层网络:

  1. // src/eth-linux.c:49-50
  2. if((e->fd = socket(PF_PACKET, SOCK_RAW,
  3.      htons(ETH_P_ALL)))<0)
  4. return(eth_close(e));

权限需求

  • CAPNETRAW:捕获和发送原始数据包
  • CAPNETADMIN(部分操作):修改混杂模式

能力检查示例

  1. # 检查进程能力
  2. getpcaps <pid>
  3. # 以特定能力运行程序
  4. setcap cap_net_raw+ep /path/to/program

3. 路由表操作权限(route-linux.c)

Linux提供两种路由操作方式:ioctl和netlink。

方法1:ioctl方式

  1. // src/route-linux.c:91
  2. return(ioctl(r->fd, SIOCADDRT,&rt));
  3. // src/route-linux.c:113
  4. return(ioctl(r->fd, SIOCDELRT,&rt));

权限需求

  • CAPNETADMIN:添加/删除路由表项

方法2:netlink方式

  1. // src/route-linux.c:58-59
  2. if((r->nlfd = socket(AF_NETLINK, SOCK_RAW,
  3.      NETLINK_ROUTE))<0)
  4. return(route_close(r));

权限需求

  • CAPNETADMIN:通过netlink修改路由表

4. 接口配置权限(intf.c)

通过ioctl配置网络接口:

  1. // src/intf.c:340
  2. if(ioctl(intf->fd, SIOCSIFADDR,&ifr)<0&& errno != EEXIST)
  3. return(-1);
  4. // src/intf.c:364
  5. if(ioctl(intf->fd, SIOCSIFHWADDR,&ifr)<0)
  6. return(-1);

权限需求

  • CAPNETADMIN:配置IP地址、子网掩码、MAC地址等

5. ARP操作权限(arp-bsd.c/arp-ioctl.c)

  1. // src/arp-bsd.c:57
  2. if((arp->fd = socket(PF_ROUTE, SOCK_RAW,0))<0)
  3. return(arp_close(arp));

权限需求

  • CAPNETADMIN:修改ARP缓存

6. 防火墙规则权限(fw-ipfw.c, fw-ipchains.c)

  1. // src/fw-ipfw.c:190
  2. if((fw->fd = socket(AF_INET, SOCK_RAW, IPPROTO_IP))<0)
  3. return(fw_close(fw));
  4. // src/fw-ipchains.c:120
  5. if((fw->fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW))<0)
  6. return(fw_close(fw));

权限需求

  • CAPNETADMIN:管理防火墙规则

BSD/macOS平台权限实现

1. 原始套接字权限(ip.c)

BSD平台的实现与Linux类似:

  1. // src/ip.c:35
  2. if((i->fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW))<0)
  3. return(ip_close(i));

权限需求

  • root用户(UID=0)
  • BSD系统不使用Linux能力模型,必须以root运行

2. 以太网原始访问(eth-bsd.c)

BSD使用BPF(Berkeley Packet Filter)或PF_RAW:

  1. // src/eth-snoop.c:40(Solaris风格,但BSD类似)
  2. if((e->fd = socket(PF_RAW, SOCK_RAW, RAWPROTO_SNOOP))<0)
  3. return(eth_close(e));

BSD特有方式 – BPF

  1. // BSD典型实现(不在libdnet中,但常见)
  2. int fd = open("/dev/bpf0", O_RDWR);
  3. ioctl(fd, BIOCSETIF,&ifr);

权限需求

  • root访问 /dev/bpf* 设备文件
  • 设备文件权限通常为 crw-------1root wheel

3. 路由表操作(route-bsd.c)

  1. // src/route-bsd.c:205
  2. if((r->fd = socket(PF_ROUTE, SOCK_RAW, AF_INET))<0)
  3. return(route_close(r));

权限需求

  • root用户:通过PF_ROUTE套接字操作路由表

4. 接口配置(intf.c)

  1. // src/intf.c:398
  2. if(ioctl(intf->fd, SIOCGIFFLAGS,&ifr)<0)
  3. return(-1);
  4. // src/intf.c:402
  5. if(ioctl(intf->fd, SIOCSIFFLAGS,&ifr)<0)
  6. return(-1);

权限需求

  • 读取操作(SIOCG*):通常普通用户可执行
  • 写入操作(SIOCS*):需要root权限

5. ARP操作(arp-bsd.c)

  1. // src/arp-bsd.c:57
  2. if((arp->fd = socket(PF_ROUTE, SOCK_RAW,0))<0)
  3. return(arp_close(arp));

权限需求

  • root用户:通过PF_ROUTE套接字操作ARP表

Windows平台权限实现

Windows的权限模型与Unix系统完全不同,使用以下机制:

  • 用户账户控制(UAC)
  • 安全描述符(Security Descriptors)
  • 特权令牌(Privilege Tokens)

1. IP层原始套接字(ip-win32.c)

  1. // src/ip-win32.c:35-43
  2. if((ip->fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW))==
  3.     INVALID_SOCKET)
  4. return(ip_close(ip));
  5. on = TRUE;
  6. if(setsockopt(ip->fd, IPPROTO_IP, IP_HDRINCL,
  7. (constchar*)&on,sizeof(on))== SOCKET_ERROR){
  8. SetLastError(ERROR_NETWORK_ACCESS_DENIED);
  9. return(ip_close(ip));
  10. }

权限分析

  • Windows上 SOCK_RAW + IPPROTO_RAW 需要:
  • 管理员权限(运行在提升的命令提示符)
  • Windows 7+:需要以管理员身份运行程序

权限检查

  1. // Windows API权限检查示例
  2. HANDLE hToken;
  3. OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES,&hToken);
  4. LUID_AND_ATTRIBUTES privileges;
  5. LookupPrivilegeValue(NULL, SE_LOAD_DRIVER_NAME,&privileges.Luid);

2. 路由操作(route-win32.c)

Windows使用IP Helper API:

  1. // src/route-win32.c:72-73
  2. if(CreateIpForwardEntry(&ipfrow)!= NO_ERROR)
  3. return(-1);
  4. // src/route-win32.c:97-98
  5. if(DeleteIpForwardEntry(&ipfrow)!= NO_ERROR)
  6. return(-1);

权限需求

  • 管理员权限:调用 CreateIpForwardEntry 和 DeleteIpForwardEntry

3. ARP操作(arp-win32.c)

  1. // src/arp-win32.c:46-47
  2. if(CreateIpNetEntry(&iprow)!= NO_ERROR)
  3. return(-1);
  4. // src/arp-win32.c:66-68
  5. if(DeleteIpNetEntry(&iprow)!= NO_ERROR){
  6.     errno = ENXIO;
  7. return(-1);
  8. }

权限需求

  • 管理员权限:调用 CreateIpNetEntry 和 DeleteIpNetEntry

4. 网络接口操作(intf-win32.c)

Windows通过COM接口和IP Helper API:

  1. // 典型的Windows接口操作
  2. GetAdaptersInfo();
  3. GetIfEntry();
  4. SetIfEntry();

权限需求

  • 读取操作(Get*):通常普通用户可执行
  • 写入操作(Set*):需要管理员权限

5. Windows权限提升示例

libdnet本身不包含权限提升代码,但以下是应用程序可能使用的模式:

  1. // 检查是否以管理员身份运行
  2. BOOL IsElevated(){
  3.     HANDLE hToken;
  4.     TOKEN_ELEVATION elevation;
  5.     DWORD size;
  6. if(!OpenProcessToken(GetCurrentProcess(),
  7.         TOKEN_QUERY,&hToken))
  8. return FALSE;
  9. if(!GetTokenInformation(hToken,TokenElevation,
  10. &elevation,sizeof(elevation),&size)){
  11. CloseHandle(hToken);
  12. return FALSE;
  13. }
  14. CloseHandle(hToken);
  15. return elevation.TokenIsElevated!=0;
  16. }

各平台权限对比

权限模型对比表

平台
权限模型
原始套接字
接口配置
路由操作
防火墙
Linux
POSIX + Capabilities
CAPNETRAW
CAPNETADMIN
CAPNETADMIN
CAPNETADMIN
BSD/macOS
POSIX(仅root)
root
root
root
root
Windows
UAC + 令牌
管理员
管理员
管理员
管理员

权限检查方式对比

操作
Linux
BSD/macOS
Windows
检查当前用户
getuid() == 0
getuid() == 0
IsElevated()
设置能力
setcap
N/A
重新以管理员运行
临时提权
N/A
N/A
UAC提示

错误码对比

场景
Linux
BSD/macOS
Windows
缺少原始套接字权限
EPERM
EPERM
ERRORNETWORKACCESS_DENIED
缺少管理员权限
EPERM
EPERM
ERRORACCESSDENIED
设备不存在
ENOENT
ENOENT
ERRORFILENOT_FOUND

权限错误处理

1. Unix系统错误处理模式

libdnet使用标准的Unix错误处理模式:

  1. // src/ip.c:35-36
  2. if((i->fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW))<0)
  3. return(ip_close(i));

调用者检查方式

  1. ip_t*ip = ip_open();
  2. if(ip == NULL){
  3. if(errno == EPERM){
  4.         fprintf(stderr,"需要CAP_NET_RAW权限或root权限\n");
  5. }else{
  6.         perror("ip_open失败");
  7. }
  8. }

2. Windows错误处理模式

  1. // src/ip-win32.c:42-43
  2. if(setsockopt(ip->fd, IPPROTO_IP, IP_HDRINCL,
  3. (constchar*)&on,sizeof(on))== SOCKET_ERROR){
  4. SetLastError(ERROR_NETWORK_ACCESS_DENIED);
  5. return(ip_close(ip));
  6. }

调用者检查方式

  1. ip_t*ip = ip_open();
  2. if(ip == NULL){
  3.     DWORD error =GetLastError();
  4. if(error == ERROR_NETWORK_ACCESS_DENIED){
  5.         fprintf(stderr,"需要管理员权限\n");
  6. }else{
  7.         fprintf(stderr,"错误: %d\n", error);
  8. }
  9. }

3. 常见权限错误及解决方案

错误
场景
Linux解决方案
Windows解决方案
EPERM
缺少原始套接字权限
sudo./program

 或 setcap cap_net_raw+ep
以管理员身份运行
EACCES
设备权限不足
chmod666/dev/bpf*
检查UAC设置
EPERM
无法添加路由
sudo

 或添加CAPNETADMIN
以管理员身份运行

安全最佳实践

1. 最小权限原则

正确做法

  1. # 只授予必要的权限
  2. setcap cap_net_raw+ep /usr/local/bin/sniffer

错误做法

  1. # 授予过多权限
  2. setcap cap_net_admin,cap_net_raw+ep /usr/local/bin/sniffer

2. 运行时权限检查

  1. #include<unistd.h>
  2. #include<sys/capability.h>
  3. // 检查是否有必要的Linux能力
  4. int check_capabilities(){
  5. cap_t caps = cap_get_proc();
  6. if(!caps)return-1;
  7. cap_flag_value_t value;
  8.     cap_get_flag(caps, CAP_NET_RAW, CAP_EFFECTIVE,&value);
  9.     cap_free(caps);
  10. return(value == CAP_SET)?0:-1;
  11. }

3. 权限降级

在获得必要的特权后,应该尽快降级权限:

  1. // 示例:获取必要的权限后降级为非特权用户
  2. void privilege_drop(){
  3. if(setuid(getuid())<0){
  4.         perror("setuid failed");
  5. }
  6. if(setgid(getgid())<0){
  7.         perror("setgid failed");
  8. }
  9. }

4. 安全的权限请求提示

  1. // 在应用程序中提供清晰的错误信息
  2. void report_permission_error(constchar*operation){
  3.     fprintf(stderr,"错误: 执行 '%s' 需要额外权限\n", operation);
  4.     fprintf(stderr,"\n");
  5.     fprintf(stderr,"Linux解决方案:\n");
  6.     fprintf(stderr,"  sudo %s\n", program_name);
  7.     fprintf(stderr,"  或: setcap cap_net_raw+ep %s\n", program_name);
  8.     fprintf(stderr,"\n");
  9.     fprintf(stderr,"Windows解决方案:\n");
  10.     fprintf(stderr,"  以管理员身份运行此程序\n");
  11. }

5. 使用capabilities的完整示例

  1. # 1. 编译程序
  2. gcc -o mytool mytool.-ldnet
  3. # 2. 设置capabilities
  4. sudo setcap cap_net_raw+ep ./mytool
  5. # 3. 验证capabilities
  6. getcap ./mytool
  7. # 输出: ./mytool = cap_net_raw+ep
  8. # 4. 普通用户运行
  9. ./mytool
  10. # 不需要sudo即可运行
  11. # 5. 移除capabilities
  12. sudo setcap -./mytool

6. Windows安全注意事项

  • 避免在程序中硬编码凭据
  • 使用Windows服务而非交互式程序进行持续操作
  • 考虑使用PowerShell脚本进行权限提升: powershellStart-Process-FilePath"mytool.exe"-VerbRunAs

附录:完整的权限检查示例程序

Linux权限检查程序

  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<sys/capability.h>
  4. #include<errno.h>
  5. int check_root(){
  6. return(getuid()==0);
  7. }
  8. int check_cap_net_raw(){
  9. cap_t caps = cap_get_proc();
  10. if(!caps)return0;
  11. cap_flag_value_t value;
  12. int ret = cap_get_flag(caps, CAP_NET_RAW, CAP_EFFECTIVE,&value);
  13.     cap_free(caps);
  14. return(ret ==0&& value == CAP_SET);
  15. }
  16. int main(){
  17.     printf("libdnet权限检查工具\n");
  18.     printf("===================\n\n");
  19.     printf("当前UID: %d\n", getuid());
  20.     printf("当前EUID: %d\n", geteuid());
  21. if(check_root()){
  22.         printf("状态: 以root用户运行\n");
  23. }else{
  24.         printf("状态: 普通用户\n");
  25. }
  26. if(check_cap_net_raw()){
  27.         printf("CAP_NET_RAW: ✓ 拥有\n");
  28. }else{
  29.         printf("CAP_NET_RAW: ✗ 缺少\n");
  30.         printf("解决方案: sudo setcap cap_net_raw+ep ./mytool\n");
  31. }
  32. return0;
  33. }

Windows权限检查程序

  1. #include<windows.h>
  2. #include<stdio.h>
  3. BOOL IsElevated(){
  4.     HANDLE hToken;
  5.     TOKEN_ELEVATION elevation;
  6.     DWORD size;
  7. if(!OpenProcessToken(GetCurrentProcess(),
  8.         TOKEN_QUERY,&hToken))
  9. return FALSE;
  10. if(!GetTokenInformation(hToken,TokenElevation,
  11. &elevation,sizeof(elevation),&size)){
  12. CloseHandle(hToken);
  13. return FALSE;
  14. }
  15. CloseHandle(hToken);
  16. return elevation.TokenIsElevated!=0;
  17. }
  18. int main(){
  19.     printf("libdnet Windows权限检查工具\n");
  20.     printf("===========================\n\n");
  21. if(IsElevated()){
  22.         printf("状态: ✓ 以管理员身份运行\n");
  23.         printf("可以执行所有libdnet操作\n");
  24. }else{
  25.         printf("状态: ✗ 普通用户权限\n");
  26.         printf("解决方案: 右键点击程序 -> 以管理员身份运行\n");
  27.         printf("\n受限操作:\n");
  28.         printf("  - 原始套接字创建\n");
  29.         printf("  - 路由表修改\n");
  30.         printf("  - 防火墙规则管理\n");
  31. }
  32. return0;
  33. }

总结

libdnet的权限模型跨平台设计确保了在不同操作系统上的一致性,同时尊重各平台的安全模型:

  1. Linux:采用capabilities模型,支持细粒度的权限控制
  2. BSD/macOS:使用传统的root权限模型
  3. Windows:基于UAC和令牌的管理员权限

开发使用libdnet的应用程序时,应该:

  • 在代码中检查权限并给出清晰的错误提示
  • 遵循最小权限原则
  • 提供详细的权限配置说明
  • 考虑使用capabilities而不是直接要求root权限(Linux)

通过合理处理权限问题,libdnet应用程序可以在保证安全性的前提下,充分利用其强大的底层网络操作能力。

  • 公众号:安全狗的自我修养

  • vx:2207344074

  • http://gitee.com/haidragon

  • http://github.com/haidragon

  • bilibili:haidragonx