Linux Kernel ROP

这个题目是内核空间的栈溢出,关于内核模块的漏洞网上有很多分析,在这里我打算只说下自己通过这个题目获得的知识,在此记录一下

Load CTF Files to QEMU

CTF files包含了4个文件

1
2
3
4
├── bzImage
├── core.cpio
├── start.sh
└── vmlinux

start.sh是系统的启动脚本,可以看到开启了kaslr

1
2
3
4
5
6
7
8
qemu-system-x86_64 \
-m 128M \
-kernel ./bzImage \
-initrd ./core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \
-s \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
  • qemu-system-x86_64:模拟x86_64架构
  • -m:指定RAM大小
  • -kernel:加载的内核镜像
  • -initrd:指定内核启动的文件系统
  • -s:相当于 -gdb tcp::1234,绑定端口,使用gdb调试

还提供了vmlinux,这里说下vmlinux和bzImage的关系refference

vmlinux是未压缩的、静态链接的、可执行的、但是不能bootable的内核文件
vmlinuz是由vmlinux经过压缩后,可以bootable的内核文件,它实际上就是zImage或bzImage
bzImage是vmlinuz经过gzip压缩后的文件,适用于大内核
zImage是vmlinuz经过gzip压缩后的文件,适用于640k内存的小内核

但是题目提供的vmlinux和内核使用的不一样,所以需要从文件系统中提取文件

提取cpio文件系统中的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mv core.cpio core.cpio.gz
gunzip core.cpio.gz
cpio -idmv < core.cpio

├── bin
├── core.ko
├── etc
├── exploit
├── gen_cpio.sh
├── init
├── lib
├── lib64
├── linuxrc -> bin/busybox
├── proc
├── root
├── sbin
├── sys
├── tmp
├── usr
└── vmlinux

vmlinux是未压缩的内核文件,可以从这里找Gadgets
core.ko就是存在漏洞的内核模块
init是启动的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 up
udhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
insmod /core.ko

#poweroff -d 120 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys

poweroff -d 0 -f

poweroff -d 120 -f & 设置了定时关机,影响调试,需要注释掉
echo 1 > /proc/sys/kernel/kptr_restrict 限制不能从 /pro/kallsyms 获取内核符号表信息

/proc/kallsyms包含了kernel image和所有动态加载模块的符号表。如果一个函数被编译器内联(inline)或者优化掉了,则它在/proc/kallsyms有可能找不到。此外,如果不是root用户,则显示/proc/kallsyms中的地址都是0:

但是cat /proc/kallsyms > /tmp/kallsyms 这句,将kallsyms拷贝了一份在/tmp目录下,可以在这里查找commit_creds和prepare_kernel_cred的地址

Privilege Transfer

Since userspace and kernel space are in different memory segment with different permission, we need some instructions to switch the spaces.

To switch from userspace to kernel space, we need to use syscall. To switch back,we need to use two instructions as blew.

1
2
swapgs
iretq

The typical use of SWAPGS is to keep the ‘user’ GS (which likely has a base address of 0) in the GSBase MSR, and the address of the kernel’s per-CPU structure in KernelGSBase. Upon entry to Ring 0 (through a system call, software interrupt, or some other method), the kernel will use SWAPGS so accessing [GS:0] will get the pointer to the kernel data. Upon leaving Ring 0, SWAPGS will be called again, to switch GS back to the ‘user’ GS. refference

Iretq instruction recovery the context of userspace, thus we come back from kernelspace to userspace. Before use this instruction,some value should be stored in the stack, and the stack construction as blew.

当使用IRET指令返回到相同保护级别的任务时,IRET会从栈将返回指令指针返回代码段选择器以及EFLAGS映像分别弹入RIP、CS以及RFLAGS寄存器,然后恢复执行中断的程序或过程。
当使用IRET指令返回到不同保护级别时,IRET不仅会从栈弹出以上内容,还会弹出RSP以及SS

栈上保存了trap frame,返回到用户模式的时候,恢复信息从以下结构中读取:

1
2
3
4
5
6
7
struct trap_frame {
void *rip; // instruction pointer +0
uint64_t cs; // code segment +4
uint64_t rflags; // CPU flags +8
void *rsp; // stack pointer +12
uint64_t ss; // stack segment +16
} __attribute__((packed));
1
2
3
4
5
6
7
8
9
10
11
12
13
14

|---------|
|iretq;ret| -> the addr of the gadget High
|---------|
| RIP | -> instruction address
|---------|
| CS | -> code segments
|---------|
| EFLAGS | -> flags for system status
|---------|
| RSP | -> user stack pointer
|---------|
| SS | -> user stack segment Low
|---------|

privilege escalation

getting privilege escalation via commit_creds(prepare_kernel_cred(0)
The above privilege escalation payload allocates a new credential struct (with uid = 0, gid = 0, etc.) and applies it to the calling process.

debugging

target remote:1234通过端口调试,gdb ./vmlinux启动,只加载了 kernel 的符号表,模块加载进内核后,并没有作为vmliunx的一部分传给gdb,因为需要加载内核模块的符号表,由于模块也是一个ELF文件,需要知道模块的.text的节区地址。这个信息分别保存/sys/module/stack_smashing/sections/.text中

1
2
3
gdb ./vmlinux
target remote:1234
add-symbol-file ./core.ko textaddr
1
2
3
4
5
6
7
8
9
10
11
/ $ id
uid=1000(chal) gid=1000(chal) groups=1000(chal)
/ $ /tmp/exploit
[*]status has been saved.
vmlinux_base 0xffffffff92800000
vmlinux_base 0xffffffff92800000
[*] set off
[*] core_read
[+]canary: 0x808226e1f3461f00
/ $ id
uid=0(root) gid=0(root)