通过busy box搭建文件系统
wget https://busybox.net/downloads/busybox-1.33.0.tar.bz2
tar -jxvf busybox-1.33.0.tar.bz2 #-j:解压bz2 -x:解压 -v:显示文件 -f:使用本地归档文件解压
cd busybox-1.33.0
make menuconfig
###勾选setting 中的Build static binary file (no shared lib) 按y选中,保存退出,即可使生成的文件系统不依赖动态glibc库
make install
cd _install #这个是刚才生成的文件系统目录
生成的文件系统如图,但是还不能将它与内核进行使用,还需要配置一些文件系统必备文件
/sbin和/bin
在系统启动后挂载到根文件系统中的,所以/sbin,/bin目录必须和根文件系统在同一分区
-
/sbin : 基本的系统命令,如shutdown,reboot,用于启动系统,修复系统,管理员才可以运行
-
/bin : 普通的基本命令,如ls,chmod,用户都可使用
/usr/sbin和/usr/bin
用户目录下的命令管理
- /usr/sbin : 用户安装的系统管理的必备程式例如:dhcpd、httpd、imap、in.*d、inetd、lpd、named、netconfig、nmbd、samba、sendmail、squid、swap、tcpd、tcpdump等
- /usr/bin : 用户安装的软件的运行脚本例如:gcc、vim、diff、make、man等
其他
/boot:和系统启动相关的文件,像grub相关文件都放在这里
/etc : 系统和网络的配置文件目录如:
文件 | 作用 |
---|---|
/etc/resolv.conf | DNS服务器配置 |
/etc/hosts | 本地域名解析文件 |
/etc/init.d | 这个目录来存放系统启动脚本 |
/etc/passwd | 用户数据库 |
/proc:一个虚拟的目录。它是系统内存的映射,可以通过直接访问这个目录来获取系统信息。也就是说,这个目录的内容不在硬盘上而是在内存里
/sys : 类似于/proc目录,但它更强
/home : 用户家目录,存放用户个人文件和应用程序,(linuxbrew目录是你安装brew后出现的包管理器目录类似于apt)
/lib64 和 /lib : 系统最基本的动态链接共享库分为64bit 、32bit
/run : 系统产生的启动以来的信息文件
/media 和 /mnt : 多媒体挂载点和临时挂载点
/lost+found:系统发生错误时(比如非法关机),可以在这里找回一些丢失文件
/var : 存放着那些不断在扩充着的东西,为了保持/usr的相对稳定,那些经常被修改的目录可以放在这个目录下(如系统的日志文件就在/var/log目录中)
文件类型
来到/dev
目录下l命令
可以看到如下:
lrwxrwxrwx 1 root root 25 4月 7 13:40 initctl -> /run/systemd/initctl/fifo
#l 表示符号链接
crw-rw---- 1 root kvm 10, 232 4月 7 16:59 kvm
#c 表示字符设备
drwxr-xr-x 2 root root 60 4月 7 13:40 lightnvm
#d 表示目录
brw-rw---- 1 root disk 7, 0 4月 7 13:40 loop0
#b 表示块设备
-rwxrwxr-x 1 hnhuangjingyu hnhuangjingyu 34 4月 7 09:39 p.py
#- 表示常规文件
在_install
目录下新建基本目录:
mkdir -pv {bin,sbin,usr/{bin,sbin},etc,proc,sys,home,lib64,lib/x86_64-linux-gnu}
创建文件:
touch etc/inittab#ubuntu 中就是init.d目录里面的文件
Linux 启动过程: 根据bois设置启动—找到设备MBR中的bootloader引导启动系统—启动kernel—启动init进程
而init进程就是根据 /etc/inittab文件启动相应级别的进程和操作
格式为: id: runlevels: action :process
,id为登机项的标识符,run为系统的运行级别,proces为在哪个级别下运行,action:在登机项的process一定条件下执行的动作
mkdir etc/init.d
init.d目录包含许多系统各种服务的启动和停止脚本,比如常用的/etc/init.d/networking restart
touch etc/init.d/rcS
chmod +x ./etc/init.d/rcS
rcS是文件系统的初始化脚本,在inittab文件中本解析调用
配置初始化脚本
- 配置inittab
➜ etc
echo "::sysinit:/etc/init.d/rcS
::askfirst:/bin/ash
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init" > inittab
- 配置init.d/rcS ,内容如下
#!/bin/sh
mount -t proc none /proc
mount -t sys none /sys
/bin/mount -n -t sysfs none /sys
/bin/mount -t ramfs none /dev
/sbin/mdev -s
配置用户、用户组
echo "root:x:0:0:root:/root:/bin/sh" > etc/passwd
echo "pwner:x:1000:1000:pwner:/home/pwner:/bin/sh" >> etc/passwd
echo "root:x:0:" > etc/group
echo "pwner:x:1000:" >> etc/group
echo "none /dev/pts devpts gid=5,mode=620 0 0" > etc/fstab
配置glibc
。。。。
也可写入完整命令脚本一键搭建完成:
#在_install 目录下运行
#!/bin/sh
touch etc/inittab
mkdir etc/init.d
touch etc/init.d/rcS
chmod +x ./etc/init.d/rcS
cd etc
echo "::sysinit:/etc/init.d/rcS
::askfirst:/bin/ash
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init" > inittab
echo "#!/bin/sh
mount -t proc none /proc
mount -t sys none /sys
/bin/mount -n -t sysfs none /sys
/bin/mount -t ramfs none /dev
/sbin/mdev -s" > init.d/rcS
cd ..
echo "root:x:0:0:root:/root:/bin/sh" > etc/passwd
echo "pwner:x:1000:1000:pwner:/home/pwner:/bin/sh" >> etc/passwd
echo "root:x:0:" > etc/group
echo "pwner:x:1000:" >> etc/group
echo "none /dev/pts devpts gid=5,mode=620 0 0" > etc/fstab
打包文件系统为镜像
➜ _install
find . | cpio -o --format=newc > ~/rootfs.cpio
附解压镜像
!!!请不要用ubuntu20搭建kernel环境,报错太多了,我已经投靠ubuntu18(ubuntu16也可以)的部下了!!!!
下载内核4.4.72
wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.4.72.tar.gz
tar -xvf linux-4.4.72.tar.gz
内核编译、生成
sudo apt-get update
sudo apt-get install git fakeroot build-essential ncurses-dev xz-utils qemu flex libncurses5-dev fakeroot build-essential ncurses-dev xz-utils libssl-dev bc bison libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev libelf-dev #下载编译内核的依赖
cd linux-4.4.72
make menuconfig
选择保存、退出(默认就行)
make bzImage -j4 #制作内核镜像
#最终linux-4.4.72/arch/x86_64/boot/bzImage就是我们要使用的内核压缩镜像
qemu启动
因为qemu启动需要配置一些参数就干脆写到sh脚本里面
#!/bin/sh
qemu-system-x86_64 \
-m 128M \
-kernel ./bzImage \
-initrd ./rootfs.cpio \
-monitor /dev/null \
-append "root=/dev/ram rdinit=/sbin/init console=ttyS0 oops=panic panic=1 loglevel=3 quiet nokalsr" \
-cpu kvm64,+smep \
-smp cores=2,threads=1 \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
-s
-m
:虚拟机内存大小-kernel
:内存镜像路径-initrd
:磁盘镜像路径append:附加参数选项
nokaslr
:关闭内核地址随机化,方便我们进行调试rdinit
:指定初始启动进程,/sbin/init
进程会默认以/etc/init.d/rcS
作为启动脚本loglevel=3
&quiet
:不输出logconsole=ttyS0
:指定终端为/dev/ttyS0
,这样一启动就能进入终端界面
-monitor
:将监视器重定向到主机设备/dev/null
,这里重定向至null主要是防止CTF中被人给偷了qemu拿flag-cpu
:设置CPU安全选项,在这里开启了smep保护-s
:相当于-gdb tcp::1234
的简写
qemu内核运行
最后将文件系统压缩文件、内核压缩文件、qemu启动脚本放在同一目录下,即可启动我们的内核
warning: TCG doesn't support requested feature: CPUID.01H:EDX.vme [bit 1]
这个不用管,只是警告
编译内核模块
编写hellokernel.c
文件
#include <linux/module.h> //LKM必备的头文件
#include <linux/kernel.h> //载入内核
#include <linux/init.h> //需引用的宏
static int __init kernel_module_init(void)
{
printk("<1>Hello the Linux kernel world!\n"); //内核控制台打印函数 <1>标志信息级别(共8个:0~7 定义在linux/kernel中)
return 0;
}
static void __exit kernel_module_exit(void)
{
printk("<1>Good bye the Linux kernel world! See you again!\n");
}
/* 内核载入内核模块时会缺省调用*/
module_init(kernel_module_init);
/*内核卸载内核模块时会缺省调用*/
module_exit(kernel_module_exit);
MODULE_LICENSE("GPL"); //许可证信息
MODULE_AUTHOR("arttnba3"); //作者信息
编写Makefile
文件 -------> 具体Makefile文件编写可以在我博客里面找下,也可自行问浏览器
obj-m += hellokernel.o #把文件test.o作为"模块"进行编译。obj-y则是直接将test.o编译进内核
CURRENT_PATH := $(shell pwd) #变量 := 从shell解析器获得pwd命令的值
LINUX_KERNEL := $(shell uname -r)
LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL) #内核头文件目录
all: #隐式编译该文件下的文件 例如Makefile同目录下有个test.c文件则会被编译为test.o文件
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean: #是一条伪指令。仅当执行make clean时才会执行下面的命令
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
# -C :在LINUX_KERNEL_PATH的值目录下执行make命令
# M : 返回CURRENT_PATH的值目录下执行make
make
命令前
hellokernel.c Makefile
make
命令后
hellokernel.c hellokernel.mod.c hellokernel.o modules.order
hellokernel.ko hellokernel.mod.o Makefile Module.symvers
运行到内核
sudo insmod hellokernel.ko #将hellokernel.ko文件 通过模块方式载入内核
lsmod #显示以安装模块
dmesg | grep 'world' #显示开机以来产生的信息并过滤,我们内核程序的打印信息
sudo rmmod hellokernel #删除模块
dmesg | grep 'world'
内核I/O接口通信编程
运行起来的内核现在就只能输入输出,那么就需要新增一些交互功能
在需要对内核模块进行交互时就需要通过文件进行注册一个虚拟设备节点
随后即可在用户态使用系统调用read,write,ioctl
进入内核态
方式一:在内核态通过虚拟设备节点方式实现用户态通信(设备)
linux的I/O设备:
- 字符设备:在I/O传输中以字符为单位进行传输的设备(键盘、串口、控制台),字符设备必须是有序的被访问
- 块设备:即数据被存储在固定小的的块中,通过地址方式进行访问(文件系统、硬盘、SD卡),块设备可随机访问
- 网络设备(network device)例如网卡
file_operations结构体:
在下载的内核目录include/linux/fs.h文件中,file_operations
来完成对设备的一些相关定义,我们注册设备时会用到该结构体
主设备号、次设备号:
在Linux内核中,使用类型dev_t
(unsigned long)来标识一个设备的设备号
一个字符设备号由主设备号
与次设备号
组成,高字节存储主设备号,低字节存储次设备号:
主设备号
:标识设备类型,使用宏MAJOR(dev_t dev)
可以获取主设备号次设备号
:用以区分同类型设备,使用宏MINOR(dev_t dev)
可以获取次设备号
- Linux还提供了一个宏
MKDEV(int major, int minor);
用以通过主次设备号生成对应的设备号 - 在
/deb
目录下一个文件就是一个设备节点,而对应的设备信息使用结构体device(linux/device.h中)进行保存 - 高层次抽象的设备类用结构体class(linux/device/class.h中)进行保存
- 每一个设备节点实例都会包含一个指向相应设备类实例的指针
设备注册和注销:
因为编写的内核模块运行在内核空间创建的设备节点只有root用户才有权限进行读写,在内核中采用inode结构体
(linux/fs.h中)表示一个文件,i_mode成员
表示文件的权限
那么在内核中修改设备节点可供普通用户访问的流程则是:(file_open定义在linux/fs.h中)
根据上面所述的实现流程,即可开始编写实现功能的内核模块了,新建devicekernel.c
文件代码并修改Makefile文件的目标文件。如下:
//devicekernel.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#define DEVICE_NAME "a3device"
#define DEVICE_PATH "/dev/a3device"
#define CLASS_NAME "a3module"
static int major_num;
static struct class * module_class = NULL;
static struct device * module_device = NULL;
static struct file * __file = NULL;
struct inode * __inode = NULL;
static struct file_operations a3_module_fo =
{
.owner = THIS_MODULE
};
static int __init kernel_module_init(void)
{
printk(KERN_INFO "[arttnba3_TestModule:] Module loaded. Start to register device...\n");
major_num = register_chrdev(0, DEVICE_NAME, &a3_module_fo); //注册设备
if (major_num < 0) //注册失败
{
printk(KERN_INFO "[arttnba3_TestModule:] Failed to register a major number.\n");
return major_num;
}
printk(KERN_INFO "[arttnba3_TestModule:] Register complete, major number: %d\n", major_num);
module_class = class_create(THIS_MODULE, CLASS_NAME); //创建设备类
if (IS_ERR(module_class)) //通过IS_REE()函数判断内核节点操作是否报错
{
unregister_chrdev(major_num, DEVICE_NAME);
printk(KERN_INFO "[arttnba3_TestModule:] Failed to register class device!\n");
return PTR_ERR(module_class);
}
printk(KERN_INFO "[arttnba3_TestModule:] Class device register complete.\n");
module_device = device_create(module_class, NULL, MKDEV(major_num, 0), NULL, DEVICE_NAME);//设备节点创建,最终在/dev目录下生成一个设备节点文件
if (IS_ERR(module_device))
{
class_destroy(module_class);
unregister_chrdev(major_num, DEVICE_NAME);
printk(KERN_INFO "[arttnba3_TestModule:] Failed to create the device!\n");
return PTR_ERR(module_device);
}
printk(KERN_INFO "[arttnba3_TestModule:] Module register complete.\n");
/*完成上面的设备创建后,即开始修改设备文件权限,linux一切皆文件修改文件即修改设备*/
__file = filp_open(DEVICE_PATH, O_RDONLY, 0); //得到file结构体
if (IS_ERR(__file))
{
device_destroy(module_class, MKDEV(major_num, 0));
class_destroy(module_class);
unregister_chrdev(major_num, DEVICE_NAME);
printk(KERN_INFO "[arttnba3_TestModule:] Unable to change module privilege!\n");
return PTR_ERR(__file);
}
__inode = file_inode(__file); //得到inode指针
__inode->i_mode |= 0666; //修改权限
filp_close(__file, NULL);
printk(KERN_INFO "[arttnba3_TestModule:] Module privilege change complete.\n");
return 0;
}
static void __exit kernel_module_exit(void) //释放资源
{
printk(KERN_INFO "[arttnba3_TestModule:] Start to clean up the module.\n");
device_destroy(module_class, MKDEV(major_num, 0));
class_destroy(module_class);
unregister_chrdev(major_num, DEVICE_NAME);
printk(KERN_INFO "[arttnba3_TestModule:] Module clean up complete. See you next time.\n");
}
module_init(kernel_module_init);
module_exit(kernel_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("arttnba3");
#Makefile
#obj-m += hellokernel.o
obj-m += devicekernel.o
CURRENT_PATH := $(shell pwd)
LINUX_KERNEL := $(shell uname -r)
LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL)
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
和上面demo的流程一样,将ko模块写入内核,操作如图:且在本系统的/dev目录下找到设备节点
编写设备节点内核模块
syscallkernel.h
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#define DEVICE_NAME "a3device"
#define CLASS_NAME "a3module"
#define NOT_INIT 0xffffffff
#define READ_ONLY 0x1000
#define ALLOW_WRITE 0x1001
#define BUFFER_RESET 0x1002
static int major_num;
static int a3_module_mode = READ_ONLY;
static struct class * module_class = NULL;
static struct device * module_device = NULL;
static void * buffer = NULL;
static spinlock_t spin;
static int __init kernel_module_init(void); //注册设备
static void __exit kernel_module_exit(void); //注销设备
static int a3_module_open(struct inode *, struct file *); //kmalloc
static ssize_t a3_module_read(struct file *, char __user *, size_t, loff_t *); //copy_to_user
static ssize_t a3_module_write(struct file *, const char __user *, size_t, loff_t *);//copy_from_user
static int a3_module_release(struct inode *, struct file *);//kfree
static long a3_module_ioctl(struct file *, unsigned int, unsigned long);//__internal_a3_module_ioctl
static long __internal_a3_module_ioctl(struct file * __file, unsigned int cmd, unsigned long param);//swich option
static struct file_operations a3_module_fo =
{
.owner = THIS_MODULE,
.unlocked_ioctl = a3_module_ioctl,
.open = a3_module_open,
.read = a3_module_read,
.write = a3_module_write,
.release = a3_module_release,
};
syscallkernel.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include "syscallkernel.h"
module_init(kernel_module_init);
module_exit(kernel_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("arttnba3");
static int __init kernel_module_init(void)
{
spin_lock_init(&spin);
printk(KERN_INFO "[arttnba3_TestModule:] Module loaded. Start to register device...\n");
major_num = register_chrdev(0, DEVICE_NAME, &a3_module_fo);
if(major_num < 0)
{
printk(KERN_INFO "[arttnba3_TestModule:] Failed to register a major number.\n");
return major_num;
}
printk(KERN_INFO "[arttnba3_TestModule:] Register complete, major number: %d\n", major_num);
module_class = class_create(THIS_MODULE, CLASS_NAME);
if(IS_ERR(module_class))
{
unregister_chrdev(major_num, DEVICE_NAME);
printk(KERN_INFO "[arttnba3_TestModule:] Failed to register class device!\n");
return PTR_ERR(module_class);
}
printk(KERN_INFO "[arttnba3_TestModule:] Class device register complete.\n");
module_device = device_create(module_class, NULL, MKDEV(major_num, 0), NULL, DEVICE_NAME);
if(IS_ERR(module_device))
{
class_destroy(module_class);
unregister_chrdev(major_num, DEVICE_NAME);
printk(KERN_INFO "[arttnba3_TestModule:] Failed to create the device!\n");
return PTR_ERR(module_device);
}
printk(KERN_INFO "[arttnba3_TestModule:] Module register complete.\n");
return 0;
}
static void __exit kernel_module_exit(void)
{
printk(KERN_INFO "[arttnba3_TestModule:] Start to clean up the module.\n");
device_destroy(module_class, MKDEV(major_num, 0));
class_destroy(module_class);
unregister_chrdev(major_num, DEVICE_NAME);
printk(KERN_INFO "[arttnba3_TestModule:] Module clean up complete. See you next time.\n");
}
static long a3_module_ioctl(struct file * __file, unsigned int cmd, unsigned long param)
{
long ret;
spin_lock(&spin);
ret = __internal_a3_module_ioctl(__file, cmd, param);
spin_unlock(&spin);
return ret;
}
static long __internal_a3_module_ioctl(struct file * __file, unsigned int cmd, unsigned long param)
{
printk(KERN_INFO "[arttnba3_TestModule:] Received operation code: %d\n", cmd);
switch(cmd)
{
case READ_ONLY:
if (!buffer)
{
printk(KERN_INFO "[arttnba3_TestModule:] Please reset the buffer at first!\n");
return -1;
}
printk(KERN_INFO "[arttnba3_TestModule:] Module operation mode reset to READ_ONLY.\n");
a3_module_mode = READ_ONLY;
break;
case ALLOW_WRITE:
if (!buffer)
{
printk(KERN_INFO "[arttnba3_TestModule:] Please reset the buffer at first!\n");
return -1;
}
printk(KERN_INFO "[arttnba3_TestModule:] Module operation mode reset to ALLOW_WRITE.\n");
a3_module_mode = ALLOW_WRITE;
break;
case BUFFER_RESET:
if (!buffer)
{
buffer = kmalloc(0x500, GFP_ATOMIC);
if (buffer == NULL)
{
printk(KERN_INFO "[arttnba3_TestModule:] Unable to initialize the buffer. Kernel malloc error.\n");
a3_module_mode = NOT_INIT;
return -1;
}
}
printk(KERN_INFO "[arttnba3_TestModule:] Buffer reset. Module operation mode reset to READ_ONLY.\n");
memset(buffer, 0, 0x500);
a3_module_mode = READ_ONLY;
break;
case NOT_INIT:
printk(KERN_INFO "[arttnba3_TestModule:] Module operation mode reset to NOT_INIT.\n");
a3_module_mode = NOT_INIT;
kfree(buffer);
buffer = NULL;
return 0;
default:
printk(KERN_INFO "[arttnba3_TestModule:] Invalid operation code.\n");
return -1;
}
return 0;
}
static int a3_module_open(struct inode * __inode, struct file * __file)
{
spin_lock(&spin);
if (buffer == NULL)
{
buffer = kmalloc(0x500, GFP_ATOMIC);
if (buffer == NULL)
{
printk(KERN_INFO "[arttnba3_TestModule:] Unable to initialize the buffer. Kernel malloc error.\n");
a3_module_mode = NOT_INIT;
return -1;
}
memset(buffer, 0, 0x500);
a3_module_mode = READ_ONLY;
printk(KERN_INFO "[arttnba3_TestModule:] Device open, buffer initialized successfully.\n");
}
else
{
printk(KERN_INFO "[arttnba3_TestModule:]Warning: reopen the device may cause unexpected error in kernel.\n");
}
spin_unlock(&spin);
return 0;
}
static int a3_module_release(struct inode * __inode, struct file * __file)
{
spin_lock(&spin);
if (buffer)
{
kfree(buffer);
buffer = NULL; //一般uaf的题这里会去掉
}
printk(KERN_INFO "[arttnba3_TestModule:] Device closed.\n");
spin_unlock(&spin);
return 0;
}
static ssize_t a3_module_read(struct file * __file, char __user * user_buf, size_t size, loff_t * __loff)
{
const char * const buf = (char*)buffer;
int count;
spin_lock(&spin);
if (a3_module_mode == NOT_INIT)
{
printk(KERN_INFO "[arttnba3_TestModule:] Buffer not initialized yet.\n");
return -1;
}
count = copy_to_user(user_buf, buf, size > 0x500 ? 0x500 : size);
spin_unlock(&spin);
return count;
}
static ssize_t a3_module_write(struct file * __file, const char __user * user_buf, size_t size, loff_t * __loff)
{
char * const buf = (char*)buffer;
int count;
spin_lock(&spin);
if (a3_module_mode == NOT_INIT)
{
printk(KERN_INFO "[arttnba3_TestModule:] Buffer not initialized yet.\n");
count = -1;
}
else if(a3_module_mode == READ_ONLY)
{
printk(KERN_INFO "[arttnba3_TestModule:] Unable to write under mode READ_ONLY.\n");
count = -1;
}
else
count = copy_from_user(buf, user_buf, size > 0x500 ? 0x500 : size);
spin_unlock(&spin);
return count;
}
相对应的更新下Makefile文件的目标为syscallkernel.ko,执行make,写入内核
在内核增加了系统调用接口函数后就可以在内核进行编程调用了
写入测试模块程序: 在本机环境gcc -o main main.c
//main.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
char * buf = "test for read and write.\n";
int main(void)
{
char ch[0x100];
int fd = open("/dev/a3device", 2);
int len = strlen(buf);
ioctl(fd, 0x1000, NULL);
write(fd, buf, len);
ioctl(fd, 0x1001, NULL);
write(fd, buf, len);
read(fd, ch, len);
write(0, ch, len);
ioctl(fd, 0x1002, NULL);
read(fd, ch, len);
write(0, ch, len);
close(fd);
return 0;
}
因为/dev需要root身份访问,所以需要用sudo运行测试程序
方式二:在内核态通过虚拟文件节点方式实现用户态通信(文件节点)
进程文件系统:
procfs即进程文件系统
( Process file system ),其中包含一个伪文件系统,在系统启动时动态生成文件,不会占用真正的储存空间,而是占用一定的内存。通过上面了解到/proc目录
是一个虚拟的目录。它是挂载的节点就是procfs,用以内核访问进程信息
proc_ops结构体:
即用于存储procfs的结构体,定义于linux/proc_fs.h中
linux内核5.6以上才有这个结构体
编写虚拟文件节点内核模块
在上面syscallkernel.h上添加如下代码
#define PROC_NAME = "a3proc"
static struct proc_dir_entry * a3_module_proc = NULL;
static struct proc_ops a3_module_fo =
{
.proc_ioctl = a3_module_ioctl,
.proc_open = a3_module_open,
.proc_read = a3_module_read,
.proc_write = a3_module_write,
.proc_release = a3_module_release,
};
在上面syscallkernel.c上修改如下代码
static int __init kernel_module_init(void)
{
spin_lock_init(&spin);
a3_module_proc = proc_create(PROC_NAME, 0666, NULL, &a3_module_fo);
return 0;
}
static void __exit kernel_module_exit(void)
{
printk(KERN_INFO "[arttnba3_TestModule:] Start to clean up the module.\n");
remove_proc_entry(PROC_NAME, NULL);
printk(KERN_INFO "[arttnba3_TestModule:] Module clean up complete. See you next time.\n");
}
编写prockernel_test.c用于测试prockernel.ko模块
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
char * buf = "test for read and write.\n";
int main(void)
{
char ch[0x100];
int fd = open("/proc/a3proc", 2);
int len = strlen(buf);
ioctl(fd, 0x1000, NULL);
write(fd, buf, len);
ioctl(fd, 0x1001, NULL);
write(fd, buf, len);
read(fd, ch, len);
write(0, ch, len);
ioctl(fd, 0x1002, NULL);
read(fd, ch, len);
write(0, ch, len);
close(fd);
return 0;
}
gdb + qemu调试内核
将编译后产生的内核文件vmlinux
放入上面同一目录下,再新建gdb.sh
脚本,内容如下:
#!/bin/sh
sudo gdb vmlinux -ex 'set architecture i386:x86-64' -ex 'target remote localhost:1234'
目录如下:
通过boot.sh
启动内核,再另开一个终端运行gdb.sh
进行调试内核
获取gadget
通过 ROPgadget --binary ./vmlinux > gadget.txt
获取gadget到文本文件,过程会稍慢最终的文件有几十MB
CTF赛题
下载的bzImage
镜像可通过vmlinux-to-elf
进行提取,也可如下脚本进行解压从而得到vmlinux
使用./extract-vmlinux ./bzImage > vmlinux
进行解压
#!/bin/sh
check_vmlinux()
{
#使用 readelf 检查它是否是一个有效的 ELF
readelf -h $1 > /dev/null 2>&1 || return 1
cat $1
exit 0
}
try_decompress()
{
# “tr”过滤器的晦涩使用是为了解决旧版本的
# "grep" 报告行的字节偏移而不是模式。
# 尝试找到头文件($1)并从这里解压
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 调用:
me=${0##*/}
img=$1
if [ $# -ne 1 -o ! -s "$img" ]
then
echo "Usage: $me <kernel-image>" >&2
exit 2
fi
# 准备临时文件:
tmp=$(mktemp /tmp/vmlinux-XXX)
trap "rm -f $tmp" 0
# 解压后重试
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
# 最后检查未压缩的图像或对象
check_vmlinux $img
# Bail out:
echo "$me: Cannot find vmlinux." >&2
一键脚本start.sh
#!/bin/bash
# 静态编译 exp
gcc exp2.c -g -w -static -o rootfs/exp
# rootfs 打包
pushd rootfs
find . | cpio -o --format=newc > ../rootfs.cpio
popd
# 启动 gdb
#gnome-terminal -e 'gdb -x gdb2.sh'
# 启动 qemu
sudo qemu-system-x86_64 \
-initrd rootfs.cpio \
-kernel bzImage \
-append 'console=ttyS0 nokaslr root=/dev/ram oops=panic panic=1' \
-enable-kvm \
-monitor /dev/null \
-m 64M \
--nographic \
-smp cores=1,threads=1 \
-cpu kvm64,+smep \
-s
gdb.sh
脚本
#!/bin/bash
gdb -ex "file vmlinux"\
-ex "add-symbol-file babydriver.ko 0xffffffffc0000000"\
-ex "gef-remote --qemu-mode localhost:1234"\
-ex "b babywrite" \
-ex "c"
#-ex "b babyopen" \
#-ex "b babyioctl" \
#-ex "b babyread" \
#-ex "b babyrelease" \