Kernel Rop
进程描述符(process descriptor)
在内核中使用结构体 task_struct
表示一个进程,结构体定义于内核源码include/linux/sched.h
task_struct结构体中的权限管理部分
/* Process credentials: */ |
Process credentials 是 kernel 用以判断一个进程权限的凭证,在 kernel 中使用 cred
结构体进行标识,对于一个进程而言应当有三个 cred:
- ptracer_cred:使用
ptrace
系统调用跟踪该进程的上级进程的cred(gdb调试便是使用了这个系统调用,常见的反调试机制的原理便是提前占用了这个位置) - real_cred:即客体凭证(objective cred),通常是一个进程最初启动时所具有的权限
- cred:即主体凭证(subjective cred),该进程的有效cred,kernel以此作为进程权限的凭证
cred结构体在include/linux/cred.h
/* |
可以直接把uid–>fsgid 都修改为 0获得root权限
或者了利用rop执行commit_creds(prepare_kernel_cred(NULL))
获得root权限
自从内核版本 6.2 起,prepare_kernel_cred(NULL)
将不再拷贝 init_cred,而是将其视为一个运行时错误并返回 NULL,只能commit_creds(&init_cred)
init_cred
是init
进程的cred
,因此其权限为root ,泄露出内核基址之后,如果保护开的不牛x,我们也便能够获得init_cred
的地址
struct cred* prepare_kernel_cred(struct task_struct* daemon) |
ROP
主要是在内核栈栈溢出,rop执行特权函数提权后,返回用户态,再起一个shell就可以继承root权限,所以我们返回前要把栈伪造成正常进入内核提权时的栈
保存好用户栈的板子
//-masm=intel |
内核时返回用户态
swapgs
指令恢复用户态 GS 寄存器sysretq
或者iretq
恢复到用户空间
返回用户态前,内核栈板子
↓ 提权操作的rop |
强网杯 2018 - core
链接:https://pan.baidu.com/s/13_X5ZqLYyym2OPithQMeCQ
提取码:w9xj
grxer@Ubuntu22 ~/k/Q/give_to_player> checksec core.ko |
文件系统
看下init脚本
cat /proc/kallsyms >/tmp/kallsyms #可以泄露commit_creds,prepare_kernel_cred 的函数的地址 |
启动脚本
只开启了kaslr
core.ko
创建了个虚拟文件节点来和内核通信
struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops);
函数可以快速创建一个文件节点
name
:文件名mode
:文件读写执行权限parent
:该文件挂载的procfs节点,若为NULL则自动挂载到/proc
目录下proc_ops
:该文件的proc_ops结构体
remove_proc_entry(const char *, struct proc_dir_entry *)
用以注销此前创建的文件
- 第一个参数为文件名
- 第二个参数为其挂载的节点,若为NULL则默认为
/proc
目录
这里的结构体甚至函数,不同内核版本可能会不一样,最好还是去当前内核版本源码里看一下 https://elixir.bootlin.com/linux/v4.15.8/source/include/linux/fs.h#L1692
struct file_operations { |
题目里重构了下write,unlocked_ioctl,release分别对用用户态的write,ioctl,close函数
ioctl函数里的case 0x6677889A,存在溢出问题
__int64 __fastcall core_copy_func(__int64 a1) |
判断a1 > 63时用的是__int64,qmemcpy(v2, &name, (unsigned __int16)a1);时用的是unsigned __int16,用一个低16bit较大的负数,如(0xffffffffffff0000|(0x100))可以实现内核栈溢出
这里name可以通过write控制
__int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 a3) |
现在rop只需要canary和commit_creds(prepare_kernel_cred(NULL))函数地址
core_read中的off可以通过case 0x6677889C控制,可以把canary读到我们用户态得变量里,泄露canary,函数地址可以通过/tmp/kallsyms来泄露
unsigned __int64 __fastcall core_read(__int64 a1) |
由于只开了kaslr,这道题方法还是蛮多的
从这道题来看,感觉内核态canary感觉按照rsp算比较准,rsp+0x40的位置
调试
关掉kaslr调试
!/bin/sh |
commit_creds(&init_cred)
|
commit_creds(prepare_kernel_cred(NULL))
|