基础

内核

内核本质和用户进程一样,但是内核拥有完全的硬件访问能力 , 用户态代码只有部分硬件访问能力

分级保护环 Rings 将计算机资源划分成不同权限的模型

从最高级0级到最低级

Intelcpu权限分四个等级 现代操作系统中只会使用ring0和ring3
用户态:ring3+用户进程运行环境
内核态:ring0+内核代码运行环境

状态切换

不同级切换途径:

  • 中断(interrupt) 和 异常(exception) 收到中断/异常时切换至ring0
  • 特权级指令 例如iret 或者 sysenter

现代操作系统用syscall

系统调用

系统调用。异常,外设中断等事件发生时进行切换

系统调用指令执行后 在内核态完成以下操作

  1. 通过swapgs切换GS段寄存器 将GS寄存器的值和一个特定位置的值交换,保存GS值,该位置的值作为内核运行GS值使用
  2. 当前栈顶记录在CPU独占变量区域,将独占区域记录的内核栈顶放入rsp/rsp
  3. push保存各寄存器的值
  4. 判断是否为x32_abi
  5. 跳转到sys_call_table,执行系统调用
 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 */

退出时

  1. 通过swapgs恢复GS值
  2. 通过sysretq或者iretq恢复到用户控件继续执行 ,如果用ireq还需要给出用户空间一些信息.

虚拟内存空间

虚拟内存地址空间分为两块: user space 和 kernel space .
linux通常较高的分配给内核

进程权限管理

用户应用权限时kernel管理的

进程描述符

内核中用 task_struct表示进程。定义于内核源码include/linux/sched.h中
task_struct.png

进程权限凭证 credential

结构体cred用于管理一个进程的权限

/*
* The security context of a task
*
* The parts of the context break down into two categories:
*
* (1) The objective context of a task. These parts are used when some other
* task is attempting to affect this one.
*
* (2) The subjective context. These details are used when the task is acting
* upon another object, be that a file, a task, a key or whatever.
*
* Note that some members of this structure belong to both categories - the
* LSM security pointer for instance.
*
* A task has two security pointers. task->real_cred points to the objective
* context that defines that task's actual details. The objective part of this
* context is used whenever that task is acted upon.
*
* task->cred points to the subjective context that defines the details of how
* that task is going to act upon another object. This may be overridden
* temporarily to point to another security context, but normally points to the
* same context as task->real_cred.
*/
struct cred {
atomic_long_t usage;
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 *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; /* 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 ucounts *ucounts;
struct group_info *group_info; /* supplementary groups for euid/fsgid */
/* RCU deletion */
union {
int non_rcu; /* Can we skip RCU deletion? */
struct rcu_head rcu; /* RCU deletion hook */
};
} __randomize_layout;

一个cred结构体记载进程中四种不同的用户ID
real UID :进程启动时用户ID
saved UID :进程最初的有效用户ID
effective UID:进程正在运行时所属的用户ID
UID for VFS ops : 创建文件时标识的用户ID

权限改变

改变cred结构体就能改变其执行权限

struct cred* prepare_kernel_cred(struct task_struct* daemon)
拷贝一个进程的cred 返回一个新的cred
int commit_creds(struct cred *new) 将新的cred应用到当前程序

Loadable Kernel Modules LKMs

linux采用宏内核架构 一切系统服务都需要由内核提供。缺乏可扩展性和可维护性。内核装载很多可能用到的服务 占据大量内存空间
内核空间的LKMs可以提供新的系统调用和其他服务 ,可以像积木一样被装载入内核/从内核中卸载。

常见LKMs:

  • 启动程序
    • 设备驱动
    • 文件系统驱动
  • 内核扩展模块

LKMs文件格式和用户态可执行程序相同 可以用IDA分析
模块可以单独被编译 但是需要在运行时链接到内核作为内核的一部分

insmod: 将指定模块加载到内核
rmmodL: 卸载指定模块
lsmod: 列出已经加载的模块
modprobe: 添加或删除模块

大多数kernel漏洞出现在LKM中

内核交互

ioctl

linux 定义了系统调用ioctl供进程和设备之间进行通信
第一个参数是打开设备返回的文件描述符,第二个参数是用户程序对设备的控制命令,后面是补充参数
ioctl 可以和设备驱动沟通 。

常用内核态函数

printf() -> printk() printk不一定会把内容显示到终端上,但一定在内核缓冲区。
memcpy() -> copy_from_user()/copy_to_user()
malloc() -> kmalloc() 使用的是slab分配器
free -> kfree()
kernel 记录了进程的权限 。
commit_creds(prepare_kernel_cred(&init_task)) 可以设置root权限 这是最常用的提权手段
这些变量的地址可以在/proc/kallsyms 中查看 较老的内核版本是/proc/ksyms

Mitigation

Kernel保护机制
canary dep PIE RELRO

KASLR 内核的aslr

*FGKASLR 以函数粒度重新排布内核代码

STACK PROTECTOR stack cookie 检测是否发生内核堆栈溢出
通常取自gs段寄存器某个固定偏移处的值

SMAP 管理模式访问保护
SMEP 管理模式执行保护 通常同时开启,组织内核空间直接访问/执行用户空间的数据,防范ret2user攻击

可以用以下两种方式绕过:

  • 内核线性映射区对物理地址空间的完整映射,找到用户空间对应页框的内核空间地址 ret2dir
  • intel下系统根据CR4寄存器的第20位标识是否开启SMEP保护 若能够通过 kernelROP g改变CR4寄存器的值便能关闭SMEP 但是开启了KPTI的内核 用户地址空间无执行权限

KPTI 内核页表隔离 内核空间和用户空间使用两组不同的页表集,对内核的内存管理产生了根本性的变化
这两张表上都有对用户内存空间的完整映射 但是用户页表中只映射了少量内核代码
主要用于修复Meltdown漏洞

内核“堆”保护机制

Hardened Usercopy

主要检查拷贝过程中对内核空间中数据的读写是否会越界:
读取的数据长度是否超出源object范围
写入的数据长度是否超出目的object范围

用于copy_to_user() 与 copy_from_user() 等数据交换API。不适用于内核空间内的数据拷贝。

Hardened freelist

开启保护之前 slub中的free object 的next指针直接存放next free object 地址 ,可以通过读取freelist泄露出内核线性映射区的地址
开启之后next存放当前free object 下一个free object 随机值 三者异或

至少要获取一和三才能篡改

Random freelist

开启这个保护后 ,object之间的连接顺序是随机的,让攻击者无法预测下一个分配的object的地址

发生在slub allocator刚从buddy system 拿到新slub的时候 ,运行时freelist仍然遵循LIFO

进行分配时 会把内存上内容清零

CONFIG_SLAB_FREELIST_RANDOMIZATION

CTF

一般有三个文件 :

  1. boot.sh: 用于启动kernel的shell脚本。多用qemu
  2. bzImage: compressed kernel binary
  3. rootfs.cpio: 系统映像

how to switch between rings normally in linux amd64?

kernel 在启动时 ,会把MSR_LSTAR定义到kernel内部一个入口点,执行syscall之类的切换指令,就会把pc设置到这个入口点。返回地址会保存到rcx! 返回时 设置到Ring3 然后跳转到rcx!

/proc/kallsyms 可以看到内核的调试符号

ioctl

int fd = open(“/dev/pwn-college”,0)

ioctl(fd,COMMAND_CODE,&custom_data_structrue)

data access

copy_to_user(userspace_address , kernel_address ,length)

copy_from_user( kernel_address ,userspace_address,length)

escape seccomp

内核通常会保持将gs寄存器指向 current_task_struct

memory management

process memory

  • binary

  • libraries

  • heap

  • stack

  • mapped memory

  • helper regions

  • kernel code

一个 virtual memory 对应一个进程

physical memory 在整个操作系统共享

page_table.png

memory_locate.png

cr3存储pml4 的位置 ,每个进程有独立的cr3


cheatsheet

writing userspace shellcode

关键: 使用kernel API

需要在kernel内部 call 这些kernel API , 使用call指令。

commit_creds 可以接受prepare_kernel_cred ,也可以接受init_cred的地址 ,甚至可以接受一个伪造的cred。

run_cmd 不会有回显,因为是在内核模块内部运行,通常用/bin/chmod a+rwx /flag

用pid查找未被kill的进程的某个虚拟地址对应的真实地址。

流程

struct kpid = find_get_pid(pid);
task = get_pid_task (kpid,PIDTYPE_PID);

unsigned long v_address = 0x404040;
pgd_t * pgde;
p4d_t * p4de;
pud_t * pude;
pmd_t * pmde;
pte_t * pte;

struct mm_struct *mm = task->mm;
pgde = pgd_offset(mm,v_address);
p4de = p4d_offset(pgde,v_address);
pude = pud_offset(p4de,v_address);
pmde = pmd_offset(pude,v_address);
pte = pte_offset_kernel(pmde,v_address);
unsigned long page_addr = pte_val(*pte) & PAGE_MASK;
unsigned long page_offset = v_address & ~PAGE_MASK;
unsigned long phy_addr = page_addr | page_offset;
unsigned long PT_addr = pte_page(*pte);
unsigned long vptr = kmap(PT_addr);
char *flag = (char *)(vptr + page_offset);
printk(KERN_INFO "flag : %s\n",flag);
kunmap(page_addr);

跟踪子进程的话 可以用current -> pid 获取当前进程pid 然后 pid+1 跟踪子进程。

编写shellcode的话,比较好的方法是搞成内核模块,然后用gdb获取各种地址。

查看slab状态方法

linux功能

cat /proc/slabinfo

debugger

slab list

slab info <cache_name>

slab contains <addr_1> <addr_2>

sysfs

/sys/kernel/slab/<cache_name>/cpu_partial

/sys/kernel/slab/<cache_name>/objs_per_slab

/sys/kernel/slab/<cache_name>/min_partial

useful gadgets

work_for_cpu_fn

push rbx
mov rbx,rdi
mov rdi,QWORD PTR [rdi+0x28]
mov rax,QWORD PTR [rbx+0x20]
call ???????<__x86_indirect_thunk_array>
mov QWROD PTR [rbx+0x30],rax
pop rbx
ret

可能用法 : rax搞成commit_creds rdi搞成&init_cred 然后一把梭。