Kernel环境搭建
内核下载与编译
Linux-5.4.98下载
curl -O -L https://mirrors.tuna.tsinghua.edu.cn/kernel/v5.x/linux-5.4.98.tar.xz
unxz linux-5.4.98.tar.xz
验证内核签名
防止内核被恶意修改
curl -O -L https://mirrors.tuna.tsinghua.edu.cn/kernel/v5.x/linux-5.4.98.tar.sign
gpg --verify linux-5.4.98.tar.sign
使用相应的公钥验证签名
下载对应内核个版本发布者的公钥
gpg --locate-keys torvalds@kernel.org gregkh@kernel.org
导入torvalds的公钥
gpg --verify linux-5.4.98.tar.sign
消除警告:选择使用TOFU信任对应的密钥
gpg --tofu-policy good 647F28654894E3BD457199BE38DBBDC86092693E
gpg --trust-model tofu --verify linux-5.4.98.tar.sign
解压压缩包得到内核源码
tar -xf linux-5.4.98.tar
Linux-5.4.98编译
编译选项配置
make menuconfig
选择:Kernel hacking==》Compile-time checks and compiler options==》
勾选Compile the kernel with debug info用于调试(默认已勾选)
编译内核
sudo -s
make bzImage -j4
出现报错
解决
sudo vim .config
\\修改为
CONFIG_SYSTEM_TRUSTED_KEYS=""
CONFIG_SYSTEM_REVOCATION_KEYS=""
编译成功:
内核常见文件:
bzImage:目前主流的kernel镜像格式==》big zImage==》适用于大于512kb的kernel
该镜像会被加载至内存的高地址,高于1MB
bzImage使用gzip压缩,文件的开头部分有gzip解压缩的代码==》不能使用gunzip解压缩
文件目录:arch/x86/boot/bzImage
zImage:比较老的kernel镜像格式,适用于不大于512kb的kernel
这个镜像会被加载至内存的低地,内存的前640kb
不能使用gunzip解压缩
vmlinuz:包含压缩后的vmlinux,还包含gzip解压缩的代码(zImage或者bzImage文件)
该文件是bootable==》
bootable:它能够把内核加载至内存中,对于Linux来说,该文件位于/boot目录下
该目录包含了启动系统时所需要的文件
vmlinux:静态链接的Linux kernel,以可执行文件的形式存在,未经过压缩
该文件为再生成vmlinuz的过程中产生==》适合于调试,不是bootable的
vmlinux.bin:静态链接的Linux kernel,只是一个可启动的(bootable)二进制文件
所有的符号信息和重定位信息都被删除
生成命令:
objcopy -O binary vmlinux vmlinux.bin
uImage:是U-boot专用的镜像文件,它是在zImage之前加上一个长度为0x40的tag构成
tag==》说明这个镜像文件的类型,加载的位置,生成时间,大小等信息
下载编译busybox
busybox下载
wget https://busybox.net/downloads/busybox-1.32.1.tar.bz2
tar -jxf busybox-1.32.1.tar.bz2
busybox编译
配置编译参数
make menuconfig
Setttings==》选中Build static binary (no shared libs) busybox编译为静态文件
==》Linux System Utilities==》取消Support mounting NFS file systems on Linux < 2.6.23(NEW)
==》Networking Utilities==》取消inetd
编译
make -j4
配置文件系统
使用busybox创建_install目录
make install
编译完成后,进入_install目录,创建文件夹
cd _install
mkdir -pv {bin,sbin,etc,proc,sys,home,lib64,lib/x86_64-linux-gnu,usr/{bin,sbin}}
touch etc/inittab
mkdir etc/init.d
touch etc/init.d/rcS
chmod +x ./etc/init.d/rcS
配置初始化脚本
首先配置etc/inttab,指定系统初始化脚本
::sysinit:/etc/init.d/rcS
::askfirst:/bin/ash
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init
配置etc/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
也可以在根目录下创建init
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
setsid cttyhack setuidgid 1000 sh
umount /proc
umount /sys
poweroff -d 0 -f
chmod +x ./init
配置用户组,创建两个用户组root和ctf,两个用户root和ctf
echo "root:x:0:0:root:/root:/bin/sh" > etc/passwd
echo "ctf:x:1000:1000:ctf:/home/ctf:/bin/sh" >> etc/passwd
echo "root:x:0:" > etc/group
echo "ctf:x:1000:" >> etc/group
echo "none /dev/pts devpts gid=5,mode=620 0 0" > etc/fstab
打包文件系统为镜像文件
find . | cpio -o --format=newc > ../../rootfs.cpio
\\或者
find . | cpio -o -H newc > ../core.cpio
若后续需要向文件系统中添加其他文件,可以选择在_install文件夹中添加(不推荐)
也可以解压文件系统镜像后添加文件再重新打包
\\解压磁盘镜像
cpio -idv < ./rootfs.cpio
\\打包文件镜像
find . | cpio -o --format=newc > ../new_rootfs.cpio
使用qemu运行内核
1、将bzImage和rootfs.cpio复制到同一个目录下
2、编写启动脚本
vim boot.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 nokaslr" \
-cpu kvm64,+smep \
-smp cores=2,threads=1 \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
-s
参数说明:
-m:虚拟机内存大小
-kernel:内存镜像路径
-initrd:磁盘镜像路径
-append:附加参数
nokalsr:关闭内核地址随机化,方便调试
rdinit:指定初始启动进程,/sbin/init进程默认以/etc/init.d/rcS作为启动脚本
loglevel=3&quiet:不输出log
consolve=tty50:指定终端为/dev/tty50,启动就能进入终端界面
-monitor:将监视器重定向到主机设备/dev/null==>防止CTF被人偷了qemu拿flag
-cpu:设置CPU安全选项,在这里开启了smep保护
-s:相当于-gdb tcp::1234简写,可以通过gdb连接本地端口进行调试
运行boot.sh,启动
编译内核模块
在载入/卸载时通过printk()在内核缓冲区进行输出
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int __init kernel_module_init(void)
{
printk("<1>Hello the Linux kernel world!\n");
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");
头文件介绍
linux/module.h:对于LKM是一个必须的头文件
linux/kernel,h:载入内核相关信息
linux/init.h:包含一些有用的宏
入口点/出口点:
内核模块的入口在module_init(),出口在module_exit()
在内核载入/卸载内核模块时调用这两个函数
编译内核模块
Makefile
本地:
obj-m += clo.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
//模拟kernel
obj-m += clo.o
KDIR =/home/closure/kernel/linux-5.4.98
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
rm -rf *.o *.ko *.mod.* *.symvers *.order
Makefile简介:
obj-m:指定编译的结果为.ko文件==》可装载内核模块
CURRENT_PATH & LINUX_KERNEL & LINUX_KERNEL_PATH:
三个自定义变量==》
通过shell命令获得当前路径,内核版本,内核源码路径
KDIR:标识内核源码目录,提供驱动编译所需要的环境
all:编译指令
clean:清理指令
编译:
make
编译成功:
测试内核模块:
sudo insmod clo.ko
sudo rmmod clo
dmesg
本地实现:
模拟实现: