面向Linux系统的嵌入式设备陷门模板化框架
2019-06-17赵利军董莎莎张沙石
赵利军 董莎莎 张沙石
(陆军工程大学军事理论创新与作战实验中心 江苏 徐州 221000)
0 引 言
随着嵌入式设备的广泛使用,如何攻克嵌入式设备成为当前网络空间作战中的关键环节,为了能继续持有攻击主机的访问特权,恶意程序必须较好地隐藏自己不被检测工具发现。作为一种高级的隐藏技术,近几年PC机上的陷门攻击技术——Bootkit技术,得到了广泛使用和发展。Bootkit技术是一种更高级的Rootkit技术,两者的不同之处在于Bootkit将存储位置从文件系统变为硬件存储,并且在操作系统加载之前启动。Rootkit修改操作系统内核,重装系统可将其清除。Bootkit在加载操作系统之前启动,因此独立于任何操作系统。与传统的Rootkit技术主要是在系统启动时提升权限不同,Bootkit通过篡改内核及操作系统引导过程隐藏自身。
2007年研究人员IceLord(网名)发布的公开资料是可以找到的第一个Windows环境下的BIOS Rootkit[1]。由此,针对BIOS的陷门设计从一个技术构想变成了程序实体。同一年,Nitin Kumar和Vipin Kumar在Black Hat大会上呈现了一种名为Vbootkit的Bootkit攻击技术,并成功突破Windows Vista系统。Vbootkit能够篡改引导向量获取执行权限,绕过启动管理器的签名机制。通过Hook Windows Vista系统启动文件入侵操作系统内核[2]。在2008年,Wenbin Zhen等[3]发布了Tophet.a,一款基于篡改OSLoader的Bootkit技术。该技术可使攻击者获取系统控制权并通过篡改boot.ini文件中的语法格式将控制权转换到攻击者的驱动。同年的Black Hat大会上,C.Miller通过嗅探MacBook电池中内建的电量控制器经I2C收到的数据,对电池的嵌入式微控制器固件进行修改,可以使电池过度发热甚至起火,并能够在获得微控制器的控制权后对系统进行配置[4]。Lee等[5]提出了劫持ARM Linux嵌入式设备的方法,该方法借鉴通用计算机下的Linux系统常用劫持方案,将系统调用表或者系统调用函数进行修改,并指向自己的恶意代码,从而实现控制流的转移,达到劫持目的。在文献[6]中阐述了破解Netgear NTV300电视机顶盒的方法。文中作者dump下Netgear NTV300中的固件映像,通过Binwalk解析固件组成、使用的压缩算法等,并使用IDA的静态分析功能,最终实现了对该设备的漏洞注入和远程控制。2013年,Ang Cui等[7]介绍了惠普HP-RFU(远程固件升级)激光打印机固件修改漏洞,通过打印含有特殊命令的文档来隐蔽地修改打印机的固件,进而获得该打印机的控制权,并采用静态分析方法对其中使用的第三方库分析发现超过80.4%的固件程序存在zlib漏洞。文献[8]对惠普RFU漏洞进行了验证,攻击者通过MS Office Word、Adobe PostScript等标准的打印文档向任意打印机中注入恶意代码和命令,使用这种攻击途径可以向打印机发送修改后的固件。2015年,李成林[9]针对目前主流杀毒软件对KiFastCallEntry挂钩提出了躲避方案,提出了改进型SSDT挂钩,该方法不再直接修改SSDT分发表里的函数入口地址,而是在函数体的内部进行修改。2016年,冯培钧等[10]在“一种新型 Linux 内核级 Rootkit 设计与实现”一文中设计并实现了一种新型Linux内核级Rootkit,该Rootkit能够实现后门提权、进程隐藏及文件隐藏等功能,并能绕过当前主流的Rootkit检测工具的检测。2017年,李扬等[11]提出一种基于硬件虚拟化的内核Rootkit技术,该技术利用Intel VT-x硬件虚拟化技术将客户系统(Guest OS)迁移到VMM之上运行实现Rootkit。
但是,对于一个新的嵌入式设备,多数的陷门设计技术都难以在有限的时间内设计出来,设计的陷门通用性也较差,无法部署到不同目标设备上。为此,本文通过研究嵌入式设备的启动过程,建立涵盖不同启动流程异同点的、陷门模板化设计框架,为设计具有一定程度通用性的陷门提供依据。
1 陷门模板化技术
嵌入式设备的软硬件结构特点及启动过程的差异性严重影响陷门的设计效率,人们可以针对不同设备,进行分析和设计符合需求的陷门。但本文尝试引入陷门设计的模板化框架,旨在为提炼出一种具有一定程度通用性的、适用于主流嵌入式设备的陷门设计提供方便。
1.1 嵌入式设备启动流程分析
首先,我们对各种不同的嵌入式设备启动流程进行分析,归纳它们的相同点与差异性,建立统一的启动流程。
存储芯片不同会造成Bootloader引导的差异;Bootloader不同会造成内核启动流程的差异;不同内核加载过程也存在较大差异;不同文件系统挂载的机制也有区别。启动流程分析主要是对这些差异性进行总结和归纳。
Bootloader的启动过程可以是单个阶段的,也可划分为多个阶段,嵌入式设备下一般为二阶段的启动过程,分Stage1和Stage2两部分启动。二阶段的Bootloader启动流程如图1所示。
图1 Bootloader启动流程
导致Bootloader启动方式差异的因素还有内核挂载文件系统机制的差异。以Linux内核为例,Linux内核中包含两种挂载早期根文件系统的机制,即initrd机制和initramfs机制。
initrd是一个功能完备的小型根文件系统,是一种启动早期用户空间处理流程的老式方法,它通常包含一些指令,用于在系统引导完成之前加载一些特定的设备驱动程序。为了使用initrd的功能,大多数架构的引导加载程序会将initrd镜像传递给内核。常见的场景是,引导加载程序先将压缩过的内核镜像加载到内存中,接着将initrd镜像加载到另一段可用内存中。如图2所示。
图2 initrd机制启动流程
在这个过程中,引导加载程序负责在将控制权转交给内核之前,将initrd镜像的地址传递给内核。具体的机制取决于架构、引导加载程序和平台的实现。然而,内核必须知道initrd镜像的位置才能够加载它。当内核引导时,它首先会检测内核命令行中是否包含root=参数并指定一个ramdisk(比如root=/dev/ram0)。然后,将这个压缩的二进制文件从内存中的指定位置复制到一个合适的内核ramdisk中,并挂载它作为根文件系统。与PC机上Linux内核会卸载initrd,尝试挂载另一个文件系统作为其根文件系统不同,这类系统中唯一的根文件系统就是ramdisk,在整个系统初始化完成后,initrd就会成为最终的根文件系统。
与initrd是一种基于RAM的块设备不同,initramfs是一种基于RAM的内存文件系统。initramfs在编译内核的同时被编译,并与内核连接成一个文件。在引导阶段它与内核同时被Bootloader加载到RAM中。而initrd是另外单独编译生成的,是一个独立的文件,它由Bootloader单独加载到RAM中内核空间外的地址。其启动过程如图3所示。
图3 initramfs机制启动流程
不同机制下根文件系统的挂载方式不同,如图4所示。
图4 根文件系统挂载方式示意图
根据上述分析的Bootloader启动流程和内核挂载文件系统机制的差异,对启动流程的共性和特性进行归纳,得到Linux内核嵌入式设备启动流程的一般化描述,如图5所示。
图5 嵌入式Linux启动流程
图中①与①2代表启动过程中不同的执行路径,其中①表示不同设备之间启动过程中的共性阶段,①2表示不同设备之间启动过程的差异性阶段。同理,②与②2、③与③2、④与④2分别代表不同设备系统启动过程中不同的执行路径。
1.2 嵌入式设备启动模型
Linux系统启动流程中,将涉及陷门模块设计的执行阶段划分出来,隔离成黑盒,得到粗粒度的Linux启动流程,如图6所示。
图6 隔离涉及陷门模块设计执行阶段后的启动流程图
图6中,黑盒1和黑盒2属于引导代码级陷门组件模块。黑盒3属于内核级陷门组件模块,黑盒4为文件系统级陷门组件模块。其中,除黑盒1外,其他黑盒内部均存在路径分支,陷门的设计需要依据设备的实际执行路径进行考虑。建立的嵌入式Linux启动模型如图7所示。
图7 嵌入式Linux启动模型
由于Bootloader Stage2、加载initrd、内核自解压与搬移、解压与搬移initrd操作在某些设备启动过程中不存在,因而用虚线框表示。陷门设计时,这些阶段对应的陷门组件需要依据实际设备的启动过程和陷门需求进行组合。
1.3 陷门模板化
高级语言中模板是根据参数类型生成函数和类的机制(有时称为“参数决定类型”)。通过使用模板,可以只设计一个类来处理多种类型的数据,而不必为每一种类型分别创建类。借鉴高级语言中模板函数、模板类等思想,提出陷门模板化技术。
陷门模板可以比作一个生成陷门攻击代码的“模子”,是对适用于某一类型设备(功能相同、结构相似)的攻击方式的抽象化概括,一个陷门模板允许根据具体的设备分析结果填写某些参数,将模板实例化成针对某个特定设备的陷门。陷门模板化设计方法能够实现部分环节的自动化、减少人工工作量,从一定程度上降低了陷门开发的难度,提高了陷门设计的效率。结合启动模型,模板化陷门设计框架如图8所示。
图8 模板化陷门设计框架
整个陷门模板分为BootLoader级组件、内核级组件和文件系统级组件,分别对应引导阶段、内核启动阶段、文件系统挂载阶段的操作。其中文件系统级组件可单独实现陷门攻击,也可与其他两个组件配合使用。陷门模板留有拼接各组件的配置接口,可根据实际对目标设备的分析结果进行扩展。每个组件也以可配置的形式存在,可根据陷门设计需求实例化为某个特定功能的组件。
陷门功能组件中又分别存在不同部件,不同部件功能也相对独立,部件间一般需要互相配合完成特定功能。部件内部也留有“个性化”的配置接口,可根据对实际设备的分析填写相应的参数。特别说明路径选择部件主要负责根据设备启动阶段的判别信息,选择陷门设计所需的部件。
2 陷门模板设计实例
本文提出的模板化陷门设计框架适用于跨平台的陷门设计。根据陷门框架设计的陷门模板如下所示:
# Trapdoor Paramaters Start #
Para0, 0x12345678
Para1, 0x12345678
……
Paran, 0x12345678
# Trapdoor Paramaters End#
# Bootloader Stage Trapdoor Start #
……
# Kernel Stage Trapdoor Start #
……
# Trapdoor Start #
……
# Trapdoor End #
# Kernel Stage Trapdoor End #
# Bootloader Stage Trapdoor End #
陷门模板代码的开头为各模块可配置的参数区。陷门代码的实体部分是一个嵌套的实现过程,与陷门代码逐级感染设备的流程想匹配。陷门最外层为最先执行的陷门代码模块,即引导级陷门代码模块。第二层为内核级陷门代码模块。最里层为陷门实际需要完成的功能模块,如网络劫持、文件下载、破坏设备等,可根据实际设备选择。每一层的执行任务完成后都会由下一层负责清理上一层陷门代码执行留下的痕迹,达到陷门隐蔽的效果。
从陷门设计框架可知,陷门结构也可能只由文件系统级陷门模块和陷门实际功能模块组成。由前面提出的可重构陷门设计方法可知陷门结构中各模块的组合以及各模块内部不同部件的组成,都可根据实际需求进行调整。
陷门功能需要汇编语言实现,因此陷门模板的实现只适用于某个特定架构。以ARM架构为例,阐述陷门模板具体实现。由于完整代码太长,将陷门模板的实现过程进行分块说明。
2.1 可配置参数区实现
陷门模板中的可配置参数保留区分为全局参数区和局部参数区。全局参数区主要是一些执行各模块时必须配置的参数,如Bootloader级模块和内核级模块必须配置内核镜像加载的起始地址及结束地址。局部参数区主要用作陷门各模块内部件执行时的参数配置,如Bootloader级模块陷门寄生加载部件需要配置陷门加载的地址和大小、碎片重组部件需要配置碎片数目和各碎片加载的地址及大小。可配置参数区的实现如下:
# 全局参数区 #
.equ Para0, 0x30008000
//内核加载到内存的起始地址
.equ Para1, 0x30008128
//内核压缩数据起始地址
.equ Para2, 0x12345678
……
.equ Paran, 0x12345678
# Bootloader Stage Trapdoor Start #
# 局部参数区 #
MSAK_Parameter:
.word 0xefefefea
//陷门起始地址
.word 0xefefefeb
//陷门结束地址
……
RecoverPara_MSAK:
//陷门控制流劫持可能覆盖引某条关键指令
.word 0xefefeff1
//将需要恢复的指令保存于此
.word 0x00080000
……
2.2 程序栈实现
“程序栈”是保存和恢复陷门劫持控制流后破坏的设备运行时上下文信息的区域,一般位于每个陷门模块和模块部件的开头和结尾处。具体实现如下:
# 程序栈 #
b SelfDefinationStack
.word 0x11111111
.word 0x00001110
……
.word 0x0000111f
.word 0x00001120
SelfDefinationStack:
str r0, [pc, #-76]
//pc-76为程序栈的第一个数据地址
str r1, [pc, #-76]
……
str r14, [pc, #-76]
mrs r0, cpsr
str r0, [pc, #-80]
mrs r0, spsr
str r0, [pc, #-84]
……
RelocationStack:
ldr r0, RelocationStack
//获取程序栈的位置
sub r0, pc, r0
sub r0, r0, #0xc
ldr r14, SelfDefinationStack
add r14, r14, r0
ldr r0, [r14, #-4]
//恢复保存的寄存器数据
msr spsr, r0
ldr r0, [r14, #-8]
msr cpsr, r0
ldr r14, [r14, #-12
……
ldr r0, [r14, #-68]
ldr r1, [r14, #-64]
2.3 数据池实现
数据池不仅可以用来存储下一个模块的代码和数据,也可作为当前模块需要传递给下一个模块的数据保存区。数据池的实现较为简单,但是陷门模板设计中不可或缺的重要部分,具体形式如下:
B Lable:
Data_Buffer:
.word 0x11111111
.word 0x11111111
.word 0x11111111
……
.ascii
"xffx5fx2dxe9x00x60x0fxe1x40x00x2dxe9x00x60x4fxe1x40......"
Lable: ……
其中第一部分为模块间传递的数据区,.ascii 为某个shellcode形式的陷门模块。
3 结 语
由于嵌入式设备“客制化”的生产模式,大多数情况下难以在给定的时间内设计出针对一个新的嵌入式设备的陷门,设计的陷门通用性也较差,无法部署到不同目标设备上。本文通过研究嵌入式设备的启动过程,建立涵盖不同启动流程异同点的,搭建了陷门模板化设计框架,最后通过势力说明了陷门模板的设计方法。本文提出了框架为设计具有一定程度通用性的陷门提供了依据。