文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载本文是 CTF Wiki 的 Linux 内核漏洞利用系列中改变自身提权Change Self部分的技术指南聚焦于 Linux 内核如何基于cred结构体判定进程权限以及攻击者如何通过直接修改 cred 内容或修改 cred 指针两条技术路线完成从普通用户到 root 的提权。读完本文你将掌握cred结构体的内存布局、task_struct与comm两种间接定位手法、commit_creds(init_cred)与commit_creds(prepare_kernel_cred(NULL))两类经典调用链以及这些技术在较新内核版本中的失效边界。cred内核进程权限的判定基石Linux 内核通过进程的task_struct结构体中的cred指针来索引cred结构体再根据cred的内容判断一个进程拥有的权限。cred结构体定义于内核源码include/linux/cred.h其核心成员如下struct cred { atomic_t usage; #ifdef CONFIG_DEBUG_CREDENTIALS atomic_t subscribers; /* number of processes subscribed */ void *put_addr; unsigned magic; #define CRED_MAGIC 0x43736564 #define CRED_MAGIC_DEAD 0x44656144 #endif kuid_t uid; /* real UID of the task */ kgid_t gid; /* real GID of the task */ kuid_t suid; /* saved UID of the task */ kgid_t sgid; /* saved GID of the task */ kuid_t euid; /* effective UID of the task */ kgid_t egid; /* effective GID of the task */ kuid_t fsuid; /* UID for VFS ops */ kgid_t fsgid; /* GID for VFS ops */ ... }可以看到cred结构体的最前面记录了一组用户 IDuid真实 UID、suid保存 UID、euid有效 UID、fsuid文件系统 UID以及对应的四个组 IDgid/sgid/egid/fsgid。如果cred结构体成员中的uid-fsgid都为 0那么一般就会认为该进程具有 root 权限。关于这些 ID 的语义例如euid用于特权认证、fsuid用于 VFS 操作标识可参见 Linux 内核基础知识的进程权限管理章节。背景补充cred结构体被设计为客观上下文 主观上下文双重指针模型——task-real_cred指向定义任务真实属性的客观上下文task-cred指向决定任务行为方式的主观上下文两者正常情况下指向同一个 cred。这一设计在include/linux/cred.h中有详细注释。提权思路总览定位与修改既然权限完全由cred决定提权思路就非常直观可以从两个角度入手直接修改 cred 结构体的内容把其中的uid-fsgid全部改成 0修改task_struct结构体中的 cred 指针让它指向一个满足要求的 cred例如 root 权限的 cred。无论采用哪一种方法过程一般都分为两步定位修改——这好比把大象放进冰箱里一样。方法一直接修改 cred 结构体定位具体位置要修改 cred 的内容首先需要获取到 cred 的具体地址。根据是否直接定位可以划分为两种思路。直接定位扫描内存cred结构体的最前面记录了各种 id 信息对于一个普通的进程而言uid-fsgid都是执行进程的用户的身份。因此只要具备扫描内核内存的能力就可以通过特征匹配来定位 credstruct cred { atomic_t usage; #ifdef CONFIG_DEBUG_CREDENTIALS atomic_t subscribers; /* number of processes subscribed */ void *put_addr; unsigned magic; #define CRED_MAGIC 0x43736564 #define CRED_MAGIC_DEAD 0x44656144 #endif kuid_t uid; /* real UID of the task */ kgid_t gid; /* real GID of the task */ kuid_t suid; /* saved UID of the task */ kgid_t sgid; /* saved GID of the task */ kuid_t euid; /* effective UID of the task */ kgid_t egid; /* effective GID of the task */ kuid_t fsuid; /* UID for VFS ops */ kgid_t fsgid; /* GID for VFS ops */ ... }在实际定位的过程中可能会发现很多满足要求的 cred这主要是因为 cred 结构体可能会被拷贝、释放。一个很直观的想法是在定位过程中利用usage不为 0 来筛掉一些 cred但仍然会发现一些usage为 0 的 cred。这是因为 cred 从usage归零到真正释放存在一定的时间差而且 cred 是使用 RCU 机制延迟释放的因此无法靠usage完全过滤。间接定位通过 task_struct进程的task_struct结构体中存放着指向 cred 的指针因此可以通过如下三步完成定位定位当前进程task_struct结构体的地址根据 cred 指针相对于task_struct结构体的偏移计算得出 cred 指针存储的地址读取该地址获取 cred 的具体地址。通过 commcomm用来标记可执行文件的名字同样位于进程的task_struct结构体中。可以发现comm其实就在 cred 指针的正下方所以也可以先定位comm再向上推导出 cred 的地址/* Process credentials: */ /* Tracers credentials at attach: */ const struct cred __rcu *ptracer_cred; /* Objective and real subjective task credentials (COW): */ const struct cred __rcu *real_cred; /* Effective (overridable) subjective task credentials (COW): */ const struct cred __rcu *cred; #ifdef CONFIG_KEYS /* Cached requested key. */ struct key *cached_requested_key; #endif /* * executable name, excluding path. * * - normally initialized setup_new_exec() * - access it with [gs]et_task_comm() * - lock it with task_lock() */ char comm[TASK_COMM_LEN];然而在进程名字并不特殊的情况下内核中可能存在多个相同的字符串这会严重影响搜索的正确性与效率。因此建议在利用开始前使用prctl系统调用把当前进程的comm设置为一个特殊的字符串再开始定位 comm从而显著提高特征匹配的命中率。修改拿到 cred 的具体地址后可以直接把 cred 中的uid-fsgid都修改为 0。修改方式取决于已获得的漏洞原语在具有任意地址读写能力后可以直接修改 cred在可以ROP 执行内核代码后可以利用 ROP gadget 修改 cred。方法二已过时UAF 使用同样的堆块虽然目标是修改 cred 的内容但不一定非得知道 cred 的具体位置只要能修改 cred 即可。一个经典的思路是利用 UAFUse-After-Free复用堆块申请一块与 cred 结构体大小一样的堆块释放该堆块fork()出新进程恰好使用刚刚释放的堆块作为其 cred此时修改 cred 结构体的特定内存从而提权。但是此方法在较新版本内核中已不再可行我们已无法直接分配到cred_jar中的 object。这是因为cred_jar在创建时设置了SLAB_ACCOUNT标记在CONFIG_MEMCG_KMEMy时默认开启cred_jar 不会再与相同大小的 kmalloc-192 进行合并从而无法通过通用堆风水占位void __init cred_init(void) { /* allocate a slab in which we can store credentials */ cred_jar kmem_cache_create(cred_jar, sizeof(struct cred), 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_ACCOUNT, NULL); }方法三修改 cred 指针定位具体位置在这种方式下需要知道 cred 指针而不是 cred 内容的具体地址。直接定位显然cred 指针本身并没有什么非常特殊的地方所以很难通过直接扫描内存的方式定位到 cred 指针。间接定位通过 task_struct与直接修改 cred一节相同进程的task_struct结构体中存放着指向 cred 的指针可以定位当前进程task_struct结构体的地址根据 cred 指针相对于task_struct结构体的偏移计算得出 cred 指针存储的地址。通过 commcomm位于task_struct中且在 cred 指针的正下方所以同样可以先定位comm再定位 cred 指针的地址/* Process credentials: */ /* Tracers credentials at attach: */ const struct cred __rcu *ptracer_cred; /* Objective and real subjective task credentials (COW): */ const struct cred __rcu *real_cred; /* Effective (overridable) subjective task credentials (COW): */ const struct cred __rcu *cred; #ifdef CONFIG_KEYS /* Cached requested key. */ struct key *cached_requested_key; #endif /* * executable name, excluding path. * * - normally initialized setup_new_exec() * - access it with [gs]et_task_comm() * - lock it with task_lock() */ char comm[TASK_COMM_LEN];同样地在进程名字并不特殊的情况下内核中可能出现多个相同字符串影响搜索的正确性与效率因此建议先用prctl设置一个特殊的 comm 字符串再开始定位。修改拿到 cred 指针的地址后具体修改可以使用如下两种方式修改 cred 指针为内核镜像中已有的init_cred的地址。这种方法适用于能够直接修改 cred 指针、并且知道init_cred地址的情况伪造一个 cred然后修改 cred 指针指向该伪造地址。这种方式比较麻烦一般并不使用。方法四commit_creds(init_cred)commit_creds()函数被用来将一个新的 cred 设为当前进程task_struct的real_cred与cred字段。因此如果能够劫持内核执行流调用该函数并传入一个具有 root 权限的 cred就能直接完成对当前进程的提权int commit_creds(struct cred *new) { struct task_struct *task current;//内核宏用以从 percpu 段获取当前进程的 PCB const struct cred *old task-real_cred; //... rcu_assign_pointer(task-real_cred, new); rcu_assign_pointer(task-cred, new);在内核初始化过程中会以 root 权限启动init进程其 cred 结构体为静态定义的init_cred。由此不难想到可以通过commit_creds(init_cred)来完成提权/* * The initial credentials for the initial task */ struct cred init_cred { .usage ATOMIC_INIT(4), #ifdef CONFIG_DEBUG_CREDENTIALS .subscribers ATOMIC_INIT(2), .magic CRED_MAGIC, #endif .uid GLOBAL_ROOT_UID, .gid GLOBAL_ROOT_GID, .suid GLOBAL_ROOT_UID, .sgid GLOBAL_ROOT_GID, .euid GLOBAL_ROOT_UID, .egid GLOBAL_ROOT_GID, .fsuid GLOBAL_ROOT_UID, .fsgid GLOBAL_ROOT_GID, .securebits SECUREBITS_DEFAULT, .cap_inheritable CAP_EMPTY_SET, .cap_permitted CAP_FULL_SET, .cap_effective CAP_FULL_SET, .cap_bset CAP_FULL_SET, .user INIT_USER, .user_ns init_user_ns, .group_info init_groups, .ucounts init_ucounts, };注意init_cred不仅将uid-fsgid全部初始化为GLOBAL_ROOT_UID/GID即 0还将cap_permitted、cap_effective、cap_bset全部设置为CAP_FULL_SET因此直接复用该静态对象即可获得完整的 root 权限与全量 capability。方法五已过时commit_creds(prepare_kernel_cred(NULL))内核提供了prepare_kernel_cred()函数用以拷贝指定进程的 cred 结构体。当传入参数为 NULL 时该函数会拷贝init_cred并返回一个具有 root 权限的 credstruct cred *prepare_kernel_cred(struct task_struct *daemon) { const struct cred *old; struct cred *new; new kmem_cache_alloc(cred_jar, GFP_KERNEL); if (!new) return NULL; kdebug(prepare_kernel_cred() alloc %p, new); if (daemon) old get_task_cred(daemon); else old get_cred(init_cred);由此可以想到如果能够在内核空间中调用commit_creds(prepare_kernel_cred(NULL))就能直接完成提权。在经典的内核 ROP 利用中该调用链通常构造为如下栈布局该图展示了 64 位内核提权 ROP 的标准构造即用pop %rdi; ret传入 NULL 参数、调用prepare_kernel_cred()后再用mov %rax, %rdi; ret把返回的 cred 指针作为commit_creds()的参数不过自从内核版本 6.2 起prepare_kernel_cred(NULL)将不再拷贝 init_cred而是将其视为一个运行时错误并返回 NULL这使得这种提权方法无法再应用于 6.2 及更高版本的内核struct cred *prepare_kernel_cred(struct task_struct *daemon) { const struct cred *old; struct cred *new; if (WARN_ON_ONCE(!daemon)) return NULL; new kmem_cache_alloc(cred_jar, GFP_KERNEL); if (!new) return NULL;也就是说在 6.2 及更新内核上必须显式传入一个合法的task_struct指针如通过泄露当前进程task_struct地址而不能简单地传 NULL。总结与延伸改变自身提权Change Self是内核提权中最直接的一类思路其核心可以归纳为以cred为靶心以定位 修改为基本动作。本文覆盖的各类手法按适用性与时效性可总结如下手法核心思路现状直接修改 cred 内容扫描内存定位后改uid-fsgid为 0依赖任意地址读写/ROP 原语UAF 复用同尺寸堆块fork 复用 freed cred 堆块后篡改已过时SLAB_ACCOUNT阻止与 kmalloc-192 合并修改 cred 指针指向init_cred或伪造 cred依赖任意地址读写commit_creds(init_cred)劫持控制流传静态 root cred可用需知道符号地址commit_creds(prepare_kernel_cred(NULL))ROP 调用动态构造 root cred已过时内核 6.2 起传入 NULL 直接返回错误该文档位于 change-self.md同目录下的 change-others.md 介绍了改变别人修改特权进程的数据与代码如modprobe_path、vDSO的提权路线两者共同构成 内核提权引言 中改变自身 / 改变别人的完整视角。若希望系统掌握内核 pwn 基础建议先阅读 Linux 内核基础知识其中对task_struct、cred、commit_creds/prepare_kernel_cred的语义以及 KASLR、SMEP/SMAP、KPTI 等缓解机制有更完整的阐述。赞分享文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载相关推荐CTF-Wiki 内核 Pwn 基础Linux Kernel 特权模型、cred 提权与防护机制全解CTF Wiki 内核 Pwn 基础Linux Kernel 特权模型、cred 提权与防护机制全解 本文是 CTF Wiki 开源项目 docs/zh t文档网络安全教程突破网盘下载技术壁垒LinkSwift直链解析引擎深度解析突破网盘下载技术壁垒LinkSwift直链解析引擎深度解析 你是否曾在深夜等待一个重要的项目文件下载完成却只能眼睁睁看着进度条缓慢爬行是否因为网盘限速而错文档网络安全教程PowerSploit 之 Set-ServiceBinaryPath通过服务二进制路径修改实现 Windows 权限提升PowerSploit 之 Set ServiceBinaryPath通过服务二进制路径修改实现 Windows 权限提升 导读 Set ServiceBin渗透测试网络安全上一篇Avail节点容器编排文档操作手册下一篇百度ERNIE 4.5-VL-28B-A3B开源280亿参数多模态模型如何重塑AI应用生态创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
智能垃圾分类毕设资源拆解:PyQt5界面与深度学习模型实战 简介:这是一份面向计算机、人工智能相关专业学生及课程学习者的智能垃圾分类系统项目资料,可作为毕业设计或课程作业的完整参考。项目围绕计算机视觉、机器学习与大数据分析展开,涵盖图像预处理、特征提取、CNN分类模型训练及数据平台构建等环… · 2026/9/25 3:06:08
rpcx 代码简化实战:上帝对象拆分与方法白名单注册规则的收敛 后端RPC框架微服务 【免费下载链接】rpcx Best microservices framework in Go, like alibaba Dubbo, but with more features, Scale easily. Try it. Test it. If you feel its better, use it! 𝐉𝐚𝐯𝐚有𝐝&#x… · 2026/9/25 3:06:08
CTF-Wiki Linux 用戶態 Pwn 條件競爭(Race Condition)漏洞攻防全解析 文档网络安全教程 【免费下载链接】ctf-wiki Come and join us, we need you! 项目地址: https://gitcode.com/gh_mirrors/ct/ctf-wiki 点击查看 免费下载 導讀:條件競爭(Race Condition)是 Linux 用戶態 Pwn 中一類利用「程序執… · 2026/9/25 3:06:07
Hypothesis 发布说明写作指南:从 RELEASE.rst 模板到自动化发布管线 测试开发工具 【免费下载链接】hypothesis The property-based testing library for Python 项目地址: https://gitcode.com/gh_mirrors/hy/hypothesis 点击查看 免费下载 导读
Hypothesis 是一个基于属性的 Python 测试库,其持续交付依赖一套严格的&q… · 2026/9/25 4:24:11
学生选课管理信息系统课设:SCDB表设计与SQL事务实现要点 简介:面向学生选课管理的信息系统课程设计报告,模拟了选课业务中的主要管理环节:学生入校注册后统一记录基本信息,课程库维护每门课程的开设信息,教师最多可主讲三门课程,学生选课后将选课记录写入数据库&a… · 2026/9/25 4:24:05
从零实现AES加密引擎:zip4cj的S盒、T表与AES-CTR模式深度剖析 从零实现AES加密引擎:zip4cj的S盒、T表与AES-CTR模式深度剖析 【免费下载链接】zip4cj 一个用于创建和解压ZIP压缩格式的库 项目地址: https://gitcode.com/Cangjie-TPC/zip4cj
🔐 zip4cj 是一个基于仓颉语言(Cangjie)实现… · 2026/9/25 4:24:05
数据库课程设计:进销存系统中的事务、范式与并发控制实战 简介:本资源是一份面向高校计算机与信息管理专业学生的数据库课程设计实战材料,聚焦商店进销存管理系统的完整开发实践,助力初学者掌握数据库建模、SQL编程与系统分析全流程。压缩包共3个文件(704KB),含SQL… · 2026/9/25 4:23:59
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37