基础知识
kernel 介绍
kernel 是现代操作系统最基本的部分,主要功能有两点:
- 控制I/O并与硬件进行交互
- 提供 application 能运行的环境
包括 I/O,权限控制,系统调用,进程管理,内存管理等多项功能都可以归结到上边两点中。
intel CPU 将 CPU 的特权级别分为 4 个级别:Ring 0, Ring 1, Ring 2, Ring 3。
Ring0 只给 OS 使用,Ring 3 所有程序都可以使用,内层 Ring 可以随便使用外层 Ring 的资源。
大多数的现代操作系统只使用了 Ring 0 和 Ring 3。
Loadable Kernel Modules(LKMs)
可加载核心模块 (或直接称为内核模块) 就像运行在内核空间的可执行程序,包括:
- 驱动程序
- 设备驱动
- 文件系统驱动
- …
- 内核扩展模块
LKMs 的文件格式和用户态的可执行程序相同,可以用IDA等反编译工具分析内核模块。
ioctl 是一个系统调用,用于与设备通信,与驱动程序进行交互
int ioctl(int fd, unsigned long request, …) 的第一个参数为打开设备 (open) 返回的 文件描述符,第二个参数为用户程序对设备的控制命令,再后边的参数则是一些补充参数,与设备有关。
常用命令:
- lsmod: 列出已经加载的模块
- rmmod: 从内核中卸载指定模块
- insmod: 讲指定模块加载到内核中
- cat /proc/cpuinfo:查看所开保护
- cat /proc/slabinfo: 查看内核堆块
- grep prepare_kernel_cred /proc/kallsyms:查看prepare_kernel_cred地址
- grep commit_creds /proc/kallsyms:查看commit_creds地址
内核态函数:
- copy_from_user() 实现了将用户空间的数据传送到内核空间
- copy_to_user() 实现了将内核空间的数据传送到用户空间
kernel 中有两个可以方便的改变权限的函数:
- int commit_creds(struct cred *new)
- struct cred* prepare_kernel_cred(struct task_struct* daemon)
执行 commit_creds(prepare_kernel_cred(0)) 即可获得 root 权限(root 的 uid,gid 均为 0)
内核态和用户态切换
- 用户空间进入内核空间的过程:
- 通过 swapgs 切换 GS 段寄存器,将 GS 寄存器值和一个特定位置的值进行交换,目的是保存 GS 值,同时将该位置的值作为内核执行时的 GS 值使用。
- 将当前栈顶(用户空间栈顶)记录在 CPU 独占变量区域里,将 CPU 独占区域里记录的内核栈顶放入 rsp/esp。
- 通过 push 保存各寄存器值
- 通过系统调用号,跳到全局变量 sys_call_table 相应位置继续执行系统调用。
- 内核空间返回用户空间
- 通过 swapgs 恢复 GS 值
- 通过 sysretq 或者 iretq 恢复到用户控件继续执行。如果使用 iretq 还需要给出用户空间的一些信息(CS, eflags/rflags, esp/rsp 等)
常见保护:
- KPT(Kernel PageTable lsolation, 内核页表隔离)
- KASLR:内核地址空间布局随机化
- SMAP/SMEP:SMAP(Supervisor Mode Access Prevention,管理模式访问保护)和SMEP(Supervisor Mode Execution Prevention, 管理模式执行保护)的作用分别是禁止内核访问用户空间的数据和禁止内核执行用户空间的代码。
Linux kernel pwn相关
一般来说,题目会给出一下四个文件:
- baby.ko: 一般为漏洞程序,可以用ida 打开
- bzImage:打包的内核代码,可以用来寻找gadget
- Initramfs.cpio: 文件系统映像
- startvm.sh: 一个用于启动 kernel 的 shell 的脚本
startvm.sh:
#!/bin/bash
stty intr ^]
cd `dirname $0`
timeout --foreground 600 qemu-system-x86_64 \
-m 64M \
-nographic \
-kernel bzImage \
-append 'console=ttyS0 loglevel=3 oops=panic panic=1 nokaslr' \
-monitor /dev/null \
-initrd initramfs.cpio \
-smp cores=1,threads=1 \
-cpu qemu64 2>/dev/null
-gdb tcp::1234
- -m 64M:设置虚拟 RAM 为 64M,默认为 128M
- -initrd initramfs.cpio,使用 rootfs.cpio 作为内核启动的文件系统
- -kernel bzImage:使用 bzImage 作为 kernel 映像
题目解析:栈溢出
1. 使用ida分析程序:baby.ko
查看信息:
mira@ubuntu:~/test/kernel/$ checksec ./baby.ko
[*] '/home/mira/test/kernel/level1/baby.ko'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x0)
没有开 PIE,无 canary 保护,没有去除符号表。
使用ida打开分析程序如下:
__int64 __usercall sub_0@<rax>(__int64 a1@<rbp>, __int64 a2@<rsi>, __int64 a3@<rdi>)
{
__int64 src; // rdx
__int64 dst; // [rsp-88h] [rbp-88h]
__int64 v6; // [rsp-8h] [rbp-8h]
_fentry__(a3, a2);
if ( (_DWORD)a2 != 0x6001