一、内核前置基础知识:
1.physmap:
physmap是内核管理的一块非常大的连续的虚拟内存空间,为了提高效率,该空间地址和RAM地址直接映射。RAM相对physmap要小得多,导致了任何一个RAM地址都可以在physmap中找到其对应的虚拟内存地址,而用户空间的虚拟内存也会映射到RAM。
所以可能会形成如下关系:
usr_data—>RAM—->physmap
那么physmap中就有可能会保存usr_data,那么就为提权代码放到内核空间提供了前置条件,同时有mmap就可能会导致条件竞争。
2.ioctl:系统调用,用于与设备通信
由于内核和用户空间隔离开,所以就需要一个接口来使得用户可以在一定情况下访问内核空间:
int ioctl(int fd, unsigned long request, …)
第一个参数为打开设备 (open) 返回的文件描述符,第二个参数为用户程序对设备的控制命令,再后边的参数则是一些补充参数,与设备有关。
3.状态转换相关操作:
当发生系统调用,产生异常,外设产生中断等事件时,会发生用户态到内核态的切换
ENTRY(entry_SYSCALL_64)
(1).用户态至内核态:
①swapgs指令触发,切换到kernel GS:
SWAPGS_UNSAFE_STACK
②保存栈值,设置内核栈:
movq %rsp, PER_CPU_VAR(rsp_scratch)
movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp
③压栈保存寄存器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #注释头
/* 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 */
|
④判断类型并跳转:
1 2 3 4 5
| #注释头
movq PER_CPU_VAR(current_task), %r11 testl $_TIF_WORK_SYSCALL_ENTRY|_TIF_ALLWORK_MASK, TASK_TI_flags(%r11) jnz entry_SYSCALL64_slow_path
|
▲
1 2 3 4 5 6 7
| #注释头
entry_SYSCALL64_slow_path: /* IRQs are off. */ SAVE_EXTRA_REGS movq %rsp, %rdi call do_syscall_64 /* returns with IRQs disabled */
|
(2)内核态至用户态:
①swapgs恢复GS
②iretq(加上寄存器信息)或sysretq,如果使用 iretq 还需要给出用户空间的一些信息(CS, eflags/rflags, esp/rsp 等)
kernel 的 crash 通常会引起重启
4.相关保护技术Mitigation:
(1)SMAP和SMEP保护技术:
(arm里面叫PXN(Privilege Execute Never)和PAN(Privileged Access Never))
①SMAP:禁止内核访问用户空间的数据(Supervisor Mode Access Prevention)
②SMEP:禁止内核执行用户空间的代码(Supervisor Mode Execution Prevention)
内核命令行中添加nosmap和nosmep禁用
(2)kernel canary:
编译内核时设置CONFIG_CC_STACKPROTECTOR,可以起到类似于stack canary的技术。
(3)KALSR:内核地址随机化
5.提权代码与函数结构体:
cred结构体:kernel用cred结构体记录进程权限(每个结构都有一个cred结构),保存了进程的相关信息,如果利用这个cred就可以提权。一般调用commit_creds(prepare_kernel_cred(0))完成提权然后用户态“着陆”起shell。
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 40 41
| #注释头
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;
|
不同内核版本的cred结构体可能不太一样。
二、内核态函数及相关变化:
1.printf() -> printk(),但需要注意的是 printk() 不一定会把内容显示到终端上,但一定在内核缓冲区里,可以通过 dmesg 查看效果
2.malloc() -> kmalloc(),内核态的内存分配函数,和malloc()相似,但使用的是slab/slub分配器。
3.free() -> kfree(),同 kmalloc()
4.memcpy() -> copy_from_user()/copy_to_user()
5.copy_from_user() 实现了将用户空间的数据传送到内核空间
6.copy_to_user() 实现了将内核空间的数据传送到用户空间
7.提权相关函数:
1 2 3 4
| #注释头
int commit_creds(struct cred *new) struct cred* prepare_kernel_cred(struct task_struct* daemon)
|
执行commit_creds(prepare_kernel_cred(0))即可获得 root 权限
函数地址可在/proc/kallsyms,老版本/proc/ksyms中查看(cat|grep),权限一般需要root
三、保护绕过技术:
1.ret2usr:
在没有SMAP/SMEP的情况下把内核指针重定向到用户空间的漏洞利用方式被称为ret2usr
2.ret2dir:
如果用户空间用mmap()把提权代码映射到内存RAM,那么就可以在physmap里找到其对应的副本,就可以修改EIP跳到副本执行,这种利用方式被称为ret2dir。
3.kernel canary:
绕过方法同用户空间的canary绕过大致相同,编译内核时设置CONFIG_CC_STACKPROTECTOR,可以起到类似于stack canary的技术。