VxWorks 启动流程 & 溢出分析
VxWorks 启动流程分析以及溢出测试
在前面的文章成功编译并启动了 VxWorks,这边篇文章将介绍 VxWorks 的启动流程,为了更好的研究启动流程我们使用 gdb 对 vxworks 进行调试。
1. 编译可调式的 vxworks
首先新建 VSB 项目,配置如图 1-1 所示:
下一步为 BSP 项目添加 DEBUG 配置选项,操作步骤 如下:
- 在 Project Explorer 视图中展开 BSP 项目
- 右键单击Source Build Configuration ,然后选择 Edit Source Build Configuration
- 搜索 debug 关键字,选择 Global Debug Flag 将 value 改为 y
最终效果如图1-2 所示:
最后构建 VSB。构建完 VSB 之后创建 VIP,如图 1-3 所示:
在 VIP 中需要配置 INCLUDE_DEBUG_AGENT 和 INCLUDE_DEBUG_AGENT_START,可以搜索 DEBUG_AGENT
进行配置,配置完如图 1-4 所示:
还需要添加 INCLUDE_SHELL INCLUDE_USB_INIT INCLUDE_USB_XHCI_HCD_INIT INCLUDE_USB_GEN2_STORAGE_INIT,部分配置项如图 1-5 所示:
确保以上配置处于加粗的状态,配置完成后构建 VxWorks 镜像。
2. 使用 qemu 启动 VxWorks
本次使用 qemu 6.0.1 进行启动,使用 qemu 源码编译安装,步骤如下:
1 | wget https://download.qemu.org/qemu-6.0.1.tar.xz |
编译完成之后查看 qemu 版本,如图 2-1 所示:
进入 VIP/default 目录找到编译好的 VxWorks,如图 2-2 所示:
接着使用 qemu-img 创建模拟存储设备,命令如下:
1 | qemu-img create file.img 512M |
将 VxWorks 与 file.img 放入同一文件夹内,如图 2-3 所示:
使用下面的命令启动 VxWorks
1 | qemu-system-x86_64 -machine q35 -m 2048 -smp 8 -serial stdio -kernel vxWorks -nographic -monitor none -device nec-usb-xhci,id=usb0,msi=off,msix=off -drive if=none,id=stick,file=file.img -device usb-storage,bus=usb0.0,drive=stick |
启动成功如图 2-4 所示:
3. 调试 VxWorks
接着使用 qemu 对 VxWorks 进行调试,启动命令如下:
1 | qemu-system-x86_64 -machine q35 -m 2048 -smp 8 -serial stdio -kernel vxWorks -nographic -s -S -monitor none -device nec-usb-xhci,id=usb0,msi=off,msix=off -drive if=none,id=stick,file=file.img -device usb-storage,bus=usb0.0,drive=stick |
使用 gdb 对 qemu 进行链接,如图 2-5 所示:
首先 gdb 停在了 0x000000000000fff0 出,对应源码的位置在 <vsb_project>/krnl/configlette/dataSegPad.c,dataSegPad 为了将 MMU(Memory Management Unit,内存管理单元) 页大小边界对齐;在链接 VxWorks 时,它被明确列为加载行上的第一个模块,以确保这个数据结构是数据段中的第一个项目,确保数据段不会与由文本段占用的页面重叠。在 MMU 初始化完成后开始进入 VxWorks 的启动流程,首先进入第一个函数 sysInit 函数
sysInit 是 VxWorks 的启动入口,它的主要功能是禁用中断、设置堆栈,并调用 usrInit() ,初始堆栈被设置为从 sysInit() 的地址向下增长。这个堆栈仅被 usrInit() 使用,之后不在会用到。随后进入 usrInit 函数,如图 3-3 所示:
通过进一步的调试,在 usrInit 函数中一共调用了以下函数:
1 | sysStart (startType); //清除 BSS 并设置向量表基地址。 |
特别注意 sysInit 函数中的最后一个函数 usrKernelInit ,usrKernelInit 初始化并启动系统的第一个任务,随后进入 usrRoot ,如图 3-4 所示:
usrRoot 函数是系统第一个任务的入口地址,主要是负责 post-kernel 的初始化,该函数存在大量的初始化函数,具体函数如下:
1 | usrKernelCoreInit (); // 初始化 Event 信号,消息队列,看门狗,hook dbg |
在 usrAppInit 函数中主要是用户自定义的程序,不做深入讨论。最后用图总结一下 VxWorks 的启动流程,如图 3-5 所示:
4. 内核应用程序
usrAppInit 函数会 VxWorks 启动后启动内核应用程序,那么怎么把程序添加到自启动函数中去呢?在此之前初步认识一下内核应用程序
在 VxWorks 中内核应用程序在内核空间执行,这一点与 Unix Linux 是不一样的,内核应用程序可以是:
• 由 object module loader 下载并动态链接到操作系统。
• 静态链接到操作系统,使其成为 kernel image 的一部分。
首先找到 usrAppInit.c 文件,在 c 文件中到 usrAppInit 函数,其函数内容如图 4-1 所示:
编写一个函数并使用 taskSpawn 启动,代码如下:
1 | #include <taskLib.h> |
编译镜像并启动 VxWorks,如图 4-2 所示:
接着修改代码,代码如下所示:
1 | void test () { |
这是一个经典的栈溢出,通过 GDB 来看一下 VxWorks 中各个寄存器的情况,同样编译后启动 VxWorks。在 test 函数下断点,首先进行测试,如图 4-3 所示:
首先是溢出情况,如图 4-4 所示:
在这里可以看到返回地址已经被指向了未知的地址。再查看栈中的情况,如图 4-5 所示:
而在未溢出的情况下,会跳转到 shellInternalFunctionCall 函数,如图 4-6 所示:
未溢出时栈中数据,如图 4-7 所示:
再来看看 VxWorks 的保护机制,如图 4-8 所示:
VxWorks 并没有什么保护机制,因此在利用漏洞比较方便,可以直接执行 shellcode,同时由于 VxWorks 的特性在程序崩溃时就会重启,因此在利用时需要保证程序不会崩溃退出。
5. 与 Linux 内存布局进行对比
在 Linux 中操作系统将不同进程的虚拟地址和不同内存的物理地址映射起来,进程持有的虚拟地址会通过 CPU 芯片中的内存管理单元(MMU)的映射关系,来转换变成物理地址,然后再通过物理地址访问内存。如图 5-1 所示:
虚拟地址与物理地址的映射有分段、分页以及结合使用三种方式,在 Linux 中内存分页把虚拟空间和物理空间分成大小固定的页。
虚拟内存分为内核空间和用户空间,根据位数的不同,地址空间的范围也不同,32 位和 64 位范围如图 5-2 所示:
在 VxWorks 中同样存在虚拟内存,同样使用 MMU 进行管理,但是在 VxWorks 中存在多个分区,可以使用 adrSpaceShow 命令展示当前内存使用情况,如图 5-3 所示:
对于32位与64位CPU,VxWorks7 所提供的内存管理机制是相同的,虚拟内存被分区管理,每个分区具有专门的用处和相应的分配机制。
VxWorks 虚拟内存大致结构如下,如图5-4 所示:
- Shared User Virtual Memory:共享用户虚拟内存区用于为共享映射分配虚拟内存,如共享数据区、共享库、使用mmap()的MAP_SHARED选项进行内存映射
- RTP Private Virtual Memory RTP私有虚拟内存区用于创建RTP的私有映射:代码与数据段、RTP堆空间以及使用mmap()的MAP_PRIVATE选项进行内存映射,在系统中的所有RTP都可以访问整个RTP私有内存区。所以,RTP使用重叠地址空间管理。
- Kernel System Virtual Memory 内核系统虚拟内存区包含了内核系统内存。从中可以定位到内核镜像(text、data、bss)、内核临接堆(kernel proximity heap)
- Kernel Virtual Memory Pool 内核虚拟内存池用于在内核中实现内存的动态管理。该区域用于按需分配虚拟内存,如创建和扩展内核应用程序、内存映射设备、DMA内存、用户保留内存和一致性内存等需求。
在此基础上还有一个 Global RAM Pool 用于动态分配RAM空间的内部分配,该内存池用于创建或扩充:内核通用堆(kernel common heap)、RTP私有内存与共享内存。全局RAM内存池也为如下对象提供内存:VxWorks内核镜像、用户保留内存、持久内存、DMA32堆空间等。
需要注意的是:VxWorks 字节顺序为 little-endian ,在网络程序中必须使用 htons() 将端口转换为网络字节顺序。
在 VxWorks 中可以针对处理器的MMU配置架构独立的接口,以提供虚拟内存支持。在 BSP 中搜索 MMU 相关的内容,如图 5-5 所示:
可以通过 VM_PAGE_SIZE 虚拟内存默认分页大小,默认值是 0x1000 即 4KB,如图 5-6 所示:
VxWorks 中可以通过 vmContextShow() 和 rtpMemShow() 函数排查,对应的需要在 BSP 中添加
- vmContextShow 需要添加 INCLUDE_VM_SHOW 和INCLUDE_VM_SHOW_SHELL_CMD
- rtpMemShow 需要添加 INCLUDE_MEM_EDR_RTP_SHOW 和 INCLUDE_MEM_EDR_RTP_SHOW_SHELL_CMD
6. 总结
本次主要是通过调试的方式来熟悉了一下 VxWorks 的启动流程,之所以这么做是为了加深映象,在 VIP 项目启动流程以源码的形式给出了,主要文件在
在编译中的过程并不需要开启调试,VxWorks 的调试模式主要还是针对 WorkBench,本次实验用的版本为 2018 版的 VxWorks,对应的 WorkBranch 对 GDB 调试的支持并不好。
VxWorks 作为业界领先的实时操作系统,还有许多内容值得我们学习。
另外需要注意的是:WorkBench 在新版中对于 GDB 的支持更完善了,并不需要使用这种方式进行调试。
如有错误,欢迎指正
7. 参考链接
https://www.vxworks.net/app/907-vxworks-7-programmer-guide-memory-management