0%

内核pwn的环境搭建和初认识

内核pwn

今天就先不刷pwnabe.tw上的题了,开整内核方面的东西。

0x1 环境搭建

搭建1起来没有lot环境搭建困难,通过搭建环境也了解了很多东西,我第一次知道文件系统居然是独立的,以前以为一直和内核紧密相连,也越来越理解一切皆文件这句话了,不管是设备还是什么,都可以当做文件操作,真的很神奇。

我是借鉴(照抄)这个博客搭建环境的https://www.anquanke.com/post/id/258874,写的很细很棒,不仅讲了一些原理,还很细致的把环境搭建讲明白了,在搭建过程中,我主要卡在两个地方了,一个是我不了解Makefile,把M小写了,导致编译模块的时候一直不成功,当时都快自闭了,另一个是整文件系统的时候,在etc的initab编写的时候有条指令不能照搬,原博客指令如下

1
2
3
4
5
6
::sysinit:/etc/init.d/rcS
::askfirst:/bin/ash
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init

正确指令

1
2
3
4
5
6
::sysinit:/etc/init.d/rcS
ttyS0::askfirst:/bin/ash
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init

至于原因,我没看懂😃,剩下的照抄就行了,然后kernel就能通过qemu正确运行了。

1
2
3
4
5
6
7
8
9
10
rootzhang@rootzhang-virtual-machine:~/pwn$ ./boot.sh
warning: TCG doesn't support requested feature: CPUID.01H:EDX.vme [bit 1]
warning: TCG doesn't support requested feature: CPUID.01H:EDX.vme [bit 1]
mount: mounting none on /sys failed: No such device

Please press Enter to activate this console.
/ # ls
bin etc init lib64 proc sbin usr
dev home lib linuxrc root sys

0x2 内核初理解

内核是什么东西

内核并不是什么神奇的东西,他也是一个程序,一切的行为都有对应代码支撑,他的定位就是中间层,对上提供用户态程序的运行和对用户态提供抽象的操作硬件的api,对下直接和硬件相互交互的程序。

内核和用户态的切换

当发生系统调用,产生异常,外设产生中断等事件的时候,会发生用户态到内核太的切换,一般的函数调用思路就是保存当前栈帧的状态,然后跳到新地址,内核态和用户态的切换也是类似,其中用户态到内核态的切换具体过程如下(照搬的)

  1. 通过 swapgs 切换 GS 段寄存器(代码段寄存器),将 GS 寄存器值和一个特定位置的值进行交换,目的是保存 GS 值,同时将该位置的值作为内核执行时的 GS 值使用。

  2. 将当前栈顶(用户空间栈顶)记录在 CPU 独占变量区域里,将 CPU 独占区域里记录的内核栈顶放入RSP/ESP

  3. 通过 push 保存各寄存器值,具体的

    代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    ENTRY(entry_SYSCALL_64)
    /* SWAPGS_UNSAFE_STACK是一个宏,x86直接定义为swapgs指令 */
    SWAPGS_UNSAFE_STACK

    /* 保存栈值,并设置内核栈 */
    movq %rsp, PER_CPU_VAR(rsp_scratch)
    movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp

    /* 通过push保存寄存器值,形成一个pt_regs结构 */
    /* Construct struct pt_regs on stack */
    pushq $__USER_DS /* pt_regs->ss */
    pushq PER_CPU_VAR(rsp_scratch) /* pt_regs->sp */
    pushq %r11 /* pt_regs->flags */
    pushq $__USER_CS /* pt_regs->cs */
    pushq %rcx /* pt_regs->ip */
    pushq %rax /* pt_regs->orig_ax */
    pushq %rdi /* pt_regs->di */
    pushq %rsi /* pt_regs->si */
    pushq %rdx /* pt_regs->dx */
    pushq %rcx tuichu /* pt_regs->cx */
    pushq $-ENOSYS /* pt_regs->ax */
    pushq %r8 /* pt_regs->r8 */
    pushq %r9 /* pt_regs->r9 */
    pushq %r10 /* pt_regs->r10 */
    pushq %r11 /* pt_regs->r11 */
    sub $(6*8), %rsp /* pt_regs->bp, bx, r12-15 not saved */

内核态到用户态的切换也是如此

内核的分级保护域

简称rings,感觉就是权限的意思,大部分现代操作系统只采用ring0权限和ring3权限,ring0权限就是root,kernel就是ring0,ring3权限较低,一般用户态程序就是ring3,以我目前的粗略理解,kernelpwn就是通过内核模块的漏洞对用户态程序进行提权到root然后getshell。

进程管理

kernel调度着所有系统资源,进程也是资源,也归kernel管,管理的策略就是使用结构体 task_struct 记录进程信息,每个task_struct都记录着一个进程的信息,其中又有专门的结构体cred记录程序的权限,每个程序都有一个cred结构,如果能修改对应进程的cred,那也就能修改这个进程的权限了,结构体源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
/* keys to */
struct key __rcu *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void *security; /* subjective LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
struct rcu_head rcu; /* RCU deletion hook */
} __randomize_layout;

进程权限改变

进程提权主要依靠两个协力完成。

  • struct cred* prepare_kernel_cred(struct task_struct* daemon):该函数用以拷贝一个进程的cred结构体,并返回一个新的cred结构体,需要注意的是daemon参数应为有效的进程描述符地址或NULL,假如传入的是null,那函数就会返回一个root权限的cred.
  • int commit_creds(struct cred *new):该函数用以将一个新的cred结构体应用到进程

通过commit_creds(prepare_kernel_cred(NULL))这套组合拳就能把用户态进程的cred更新为一个root的cred.

(也有可能直接溢出改,我猜的,毕竟还没做过题😃)

内核保护机制

KASLR:内核地址随机化,和aslr类似,知道了一个地址再加上一个偏移就能知道基地址了,在没开启KASLR的时候,内核的基地址是0xffffffff81000000

STACK PROTECTOR:和canary类似,用以检测是否发生了内核堆栈溢出,如果发生内核堆栈溢出则会产生kernel panic.

SMAP/SMEP:smap的作用时阻止内核空间直接访问用户空间的数据,smep用以阻止内核空间执行用户空间的数据。目的是让内核空间和用户空间完全隔开。

一共有两种绕过(照搬)

  • 在设计中,为了使隔离的数据进行交换时具有更高的性能,隐性地址共享始终存在(VDSO & VSYSCALL),用户态进程与内核共享同一块物理内存,因此通过隐性内存共享可以完整的绕过软件和硬件的隔离保护,这种攻击方式被称之为ret2dir(return-to-direct-mapped memory )
  • Intel下系统根据CR4控制寄存器的第20位标识是否开启SMEP保护(1为开启,0为关闭),若是能够通过kernel ROP改变CR4寄存器的值便能够关闭SMEP保护,完成SMEP-bypass,接下来就能够重新进行ret2usr

KPTI:KPTI即内核页表隔离(Kernel page-table isolation),内核空间与用户空间分别使用两组不同的页表集,这对于内核的内存管理产生了根本性的变化(完全没理解)

LKMs

可装载内核模块,简称LKMs,顾名思义就是内核中可装载可拆卸的程序,可以提供内核原本不具备的服务又不至于重新更新整个系统的东西,一般的kernelpwn题的漏洞都是出在出题人自己写的lkms上面。

  • lsmod:列出现有的LKMs
  • insmod:装载新的LKM(需要root)
  • rmmod:从内核中移除LKM(需要root)

内核态函数调用(照搬)

  1. printf()变更为**printk()**,但需要注意的是printk()不一定会把内容显示到终端上,但一定在内核缓冲区里,可以通过 dmesg 查看效果。
  2. memcpy()变更为copy_from_user()/copy_to_user()
    • copy_from_user() 实现了将用户空间的数据传送到内核空间
    • copy_to_user() 实现了将内核空间的数据传送到用户空间
  3. malloc()变更为**kmalloc()**,内核态的内存分配函数,和malloc()相似,但使用的是 slab/slub 分配器
  4. free()变更为**kfree()**,同 kmalloc()

对内核的搭建和认识就到这了,下面进入实战阶段