首先,还是从我们的老朋友 BIOS 进行加电自检(POST,Power-On Self-Test)后,开始读取 MBR 记录。MBR 为 512 字节大小的扇区,其中前 446 字节硬编码着 bootloader 程序。BIOS 从 MBR 记录读取 bootloader 程序,将其加载到内存,并运行该程序。
对于不同的操作系统,有不同的 bootloader 程序,一般在安装操作系统时,安装程序会向 MBR 写入 bootloader 程序。如 Windows 操作系统,使用自带的 bootloader 程序。对于 Linux 发行版,一般使用新版的 GRUB2 来生成并写入 bootloader。
bootloader 的工作便是载入内核文件,使操作系统正常运行。
在 Linux 中,由 bootloader 载入的内核文件位于 /boot 节点下:
[root@centos boot]# ls --format=single-column /boot config-2.6.32-696.el6.x86_64 # 内核编译时启用的配置信息与模块设定 efi # EFI 引导程序 grub # GRUB 引导程序支持 initramfs-2.6.32-696.el6.x86_64.img # 虚拟文件系统,主角 ;-) symvers-2.6.32-696.el6.x86_64.gz # 驱动模块符号表 System.map-2.6.32-696.el6.x86_64 # 内核符号表 vmlinuz-2.6.32-696.el6.x86_64 # 内核文件
如上,vmlinuz 便是 bootloader 载入的 Linux 内核文件,内核文件主要负责检测基础的硬件设施,并载入相关驱动,这些驱动一般位于 /lib/modules/
那么 initramfs 是干嘛的?当内核启动时,需要往 /lib/modudles 下读取驱动文件,以支持 SATA、USB 等接口的设备,但是明显 /lib/modules 是存储于 SATA 磁盘中,没有 SATA 驱动的情况下也无法读取挂载,这里就出现了先有鸡还是先有蛋的问题。
而 initramfs 便是来解决这个问题,initramfs 事实上是一个具有根目录结构的文件,其中包含基础的应用程序,以及核心的内核模块,例如 SCSI、SATA、USB 等,用于支持最基础的外部设备。
新建目录,将 initramfs 拷贝到目标目录下:
[root@centos ~]# mkdir /tmp/initramfs [root@centos ~]# cp /boot/initramfs-2.6.32-696.el6.x86_64.img /tmp/initramfs/initramfs.img
initramfs 使用 gzip 进行压缩,将其解压:
[root@centos ~]# cd /tmp/initramfs/ [root@centos initramfs]# file initramfs.img initramfs.img: gzip compressed data, from Unix, last modified: Wed Oct 4 17:17:17 2017, max compression [root@centos initramfs]# mv initramfs.img initramfs.gz [root@centos initramfs]# gzip -d initramfs.gz
在解压完成后,实际上文件使用 cpio 进行归档,最后我们使用 cpio 将其解压出来:
[root@centos initramfs]# file initramfs initramfs: ASCII cpio archive (SVR4 with no CRC) # -i 解压文件 # -d 必要时创建目录 # -H newc 指定类型为 SVR4 的归档文件 # --no-absolute-filenames 不要将文件解压覆盖到绝对路径 [root@centos initramfs]# cpio -i -d -H newc --no-absolute-filenames < initramfs 108638 blocks
从最后的目录结构可以看到 initramfs 的真实内容:
[root@centos initramfs]# ll total 54432 drwxr-xr-x. 2 root root 4096 Oct 5 22:50 bin drwxr-xr-x. 2 root root 4096 Oct 5 22:50 cmdline drwxr-xr-x. 3 root root 4096 Oct 5 22:50 dev -rw-r--r--. 1 root root 23 Oct 5 22:50 dracut-004-409.el6_8.2 drwxr-xr-x. 2 root root 4096 Oct 5 22:50 emergency drwxr-xr-x. 7 root root 4096 Oct 5 22:50 etc -rwxr-xr-x. 1 root root 8989 Oct 5 22:50 init drwxr-xr-x. 2 root root 4096 Oct 5 22:50 initqueue drwxr-xr-x. 2 root root 4096 Oct 5 22:50 initqueue-finished drwxr-xr-x. 2 root root 4096 Oct 5 22:50 initqueue-settled drwxr-xr-x. 2 root root 4096 Oct 5 22:50 initqueue-timeout drwxr-xr-x. 7 root root 4096 Oct 5 22:50 lib drwxr-xr-x. 3 root root 4096 Oct 5 22:50 lib64 drwxr-xr-x. 2 root root 4096 Oct 5 22:50 mount drwxr-xr-x. 2 root root 4096 Oct 5 22:50 netroot drwxr-xr-x. 2 root root 4096 Oct 5 22:50 pre-mount drwxr-xr-x. 2 root root 4096 Oct 5 22:50 pre-Pivot drwxr-xr-x. 2 root root 4096 Oct 5 22:50 pre-trigger drwxr-xr-x. 2 root root 4096 Oct 5 22:50 pre-udev drwxr-xr-x. 2 root root 4096 Oct 5 22:50 proc drwxr-xr-x. 2 root root 4096 Oct 5 22:50 sbin drwxr-xr-x. 2 root root 4096 Oct 5 22:50 sys drwxr-xr-x. 2 root root 4096 Oct 5 22:50 sysroot drwxrwxrwt. 2 root root 4096 Oct 5 22:50 tmp drwxr-xr-x. 7 root root 4096 Oct 5 22:50 usr drwxr-xr-x. 4 root root 4096 Oct 5 22:50 var
列一下 lib/modules/
[root@centos initramfs]# ls lib/modules/2.6.32-696.el6.x86_64/kernel/drivers/ acpi ata atm auxdisplay bcma block bluetooth cdrom
initramfs 中包含最核心的驱动程序,bootloader 在启动时,将 initramfs 临时挂载到根目录,在内核文件正常加载驱动后,对其进行卸载,并挂载真正的根目录节点。
最后,内核程序启动 init / systemd 对系统服务进行加载。
在 Linux 发行版下,bootloader 通常由 GRUB 程序进行管理,而 bootloader 又负责加载内核文件,较新的发行版一般使用新的 GRUB2 进行引导。
由于笔者使用的是 CentOS 6.X,所以此时默认还是旧版的 GRUB 引导管理程序,但不同版本配置上大同小异,所以以旧版为例。
默认情况下,/boot/grub 下(新版为/boot/grub2)存放 GRUB 的配置文件:
[root@centos ~]# ls /boot/grub grub.conf # GRUB 的引导菜单配置文件 stage1 stage2
可以注意到目录下有 stage1 和 stage2 文件,这个其实便是 bootloader 程序。由于 MBR 区域只有可怜的 512 字节,而 bootloader 程序相对都会比较大,所以 GRUB 将启动分为两个阶段,真正存在于 MBR 处的代码便是 stage1 程序,再由此加载真正的 bootloader 程序 stage2。
再看看主配置文件 /boot/grub/grub.conf 的内容:
# grub.conf generated by anaconda default=0 timeout=5 title CentOS 6 (2.6.32-696.el6.x86_64) root (hd0,0) kernel /vmlinuz-2.6.32-696.el6.x86_64 ro root=/dev/mapper/vg_centos-lv_root nomodeset rd_NO_LUKS LANG=en_US.UTF-8 rd_LVM_LV=vg_centos/lv_swap rd_NO_MD SYSFONT=latarcyrheb-sun16 crashkernel=auto rd_LVM_LV=vg_centos/lv_root KEYBOARDTYPE=pc KEYTABLE=us rd_NO_DM rhgb quiet initrd /initramfs-2.6.32-696.el6.x86_64.img
通过配置项名称应该都可以猜到相关的功能,例如 default 设置默认的启动菜单为 Centos 6 项,默认超时为 5 秒钟。
在 title 中,则定义了菜单项:
root (hd0,0) kernel /vmlinuz-2.6.32-696.el6.x86_64 ro root=/dev/mapper/vg_centos-lv_root nomodeset rd_NO_LUKS LANG=en_US.UTF-8 rd_LVM_LV=vg_centos/lv_swap rd_NO_MD SYSFONT=latarcyrheb-sun16 crashkernel=auto rd_LVM_LV=vg_centos/lv_root KEYBOARDTYPE=pc KEYTABLE=us rd_NO_DM rhgb quiet initrd /initramfs-2.6.32-696.el6.x86_64.img
root 指定 /boot 节点的位置,如果是多个磁盘,则从 hd0、hd1 等依次排列。后面的 0 代表所在的分区位置。
GRUB 从 0 开始索引分区,新版 GRUB2 则从 1 开始索引分区,磁盘索引一致,都是从 0 开始。
kernel 指令负责载入内核文件,包括载入的根目录位置、语言、键盘等参数。
initrd 负责挂载 initramfs 虚拟文件系统。
Linux 中的内核模块通常与内核进行分离,作为独立的模块进行加载,当然也可以将其编译到内核中。独立的内核模块存放于 /lib/modules/
该目录下的文件 modules.dep,记录了内核模块位置、依赖等信息。对于现在各种各样的设备驱动,手工配置依赖信息非常繁琐,所以当加入驱动后,可以通过 depmod 自动生成依赖信息:
depmod -a
依赖信息会被写入到 modules.dep 文件中,内核通过该文件对驱动进行正确加载。
从引导过程中可以发现,vmlinuz 内核主要依赖 /lib/modules 下的内核驱动来对外部设备提供支持。
而在这起事件中,攻击者恶意删除了/lib/modules 下的所有文件。
于是在后续安装驱动中,/lib/modules 的内容被同步到 initramfs 中,但此时目录下已然没有完整的驱动信息。所以内核在挂载 initramfs 后,无法正常加载驱动程序,提示无法找到 modules.dep 文件,此时并非读取真正根目录下的 /lib/modules :-)。
对于 /lib/modules 下的内核模块,正常通过编译 Linux 内核来生成,当然也可以用过 yum / apt 等在线包管理工具来重新安装 Linux 内核。
在机器与网络隔离的情况下,无法通过网络下载编译完的二进制文件,也许只能通过自行编译内核来修复。
但编译内核也需要对应的编译工具、环境、源码包等。另一种更简单的方式,可以下载对应的发行版的镜像,来进行修复。
首先通过镜像引导,进入 rescue 救援模式终端,由于发行版的版本、内核型号都相同,同时镜像也是跑在相同的机器上,所以载入的驱动也是一致。
通常进入救援模式时,都会自动帮你挂载原机的系统分区,例如 CentOS 将其挂在到 /mnt/sysimage 下。
1)先将所有驱动模块拷贝到原本的系统节点:
cp -R /lib/modules/2.6.32-696.el6.x86_64 /mnt/sysimage/lib/modules/
2)切换 /mnt/sysimage 为根目录:
chroot /mnt/sysimage
此时访问 / 下的任何内容,实际上为 /mnt/sysimage 。
3)进入 /boot 节点,重构 initramfs 镜像,记得备份:
cd /boot cp initramfs-2.6.32-696.el6.x86_64.img initramfs-2.6.32-696.el6.x86_64.org # -f 覆盖原始 initramfs 镜像 # -v 显示详细信息 dracut -f -v initramfs-2.6.32-696.el6.x86_64.img 2.6.32-696.el6.x86_64
dracut 用于重构 initramfs 镜像,最后的内核版本号要对应 /lib/modules 下的版本。
4)如果过程中修改了其他文件(例如修改了 shadow 等文件),由于 SELinux 的缘故,需要加入 SELinux 重建标识:
touch /.autorelabel
重启,首次进入时,等待 SELinux 重新建立策略:
再次重启后,正常进入系统,基本模块修复:
内核通过 initramfs 载入核心驱动为基础的外部设备提供支持 内核模块位于 /lib/modules/