VxWorks 启动流程分析以及溢出测试

在前面的文章成功编译并启动了 VxWorks,这边篇文章将介绍 VxWorks 的启动流程,为了更好的研究启动流程我们使用 gdb 对 vxworks 进行调试。

1. 编译可调式的 vxworks

首先新建 VSB 项目,配置如图 1-1 所示:

图 1-1 新建 BSP

下一步为 BSP 项目添加 DEBUG 配置选项,操作步骤 如下:

  1. 在 Project Explorer 视图中展开 BSP 项目
  2. 右键单击Source Build Configuration ,然后选择 Edit Source Build Configuration
  3. 搜索 debug 关键字,选择 Global Debug Flag 将 value 改为 y

最终效果如图1-2 所示:

image-20240327150316595

图 1-2 配置 VSB debug

最后构建 VSB。构建完 VSB 之后创建 VIP,如图 1-3 所示:

image-20240327164136060

图 1-3 新建 VIP

在 VIP 中需要配置 INCLUDE_DEBUG_AGENTINCLUDE_DEBUG_AGENT_START,可以搜索 DEBUG_AGENT 进行配置,配置完如图 1-4 所示:

image-20240327164814932

图 1-4 配置 Debug agent 图

还需要添加 INCLUDE_SHELL INCLUDE_USB_INIT INCLUDE_USB_XHCI_HCD_INIT INCLUDE_USB_GEN2_STORAGE_INIT,部分配置项如图 1-5 所示:

image-20240327171621740

图 1-5 配置 shell 图

确保以上配置处于加粗的状态,配置完成后构建 VxWorks 镜像。

2. 使用 qemu 启动 VxWorks

本次使用 qemu 6.0.1 进行启动,使用 qemu 源码编译安装,步骤如下:

1
2
3
4
5
6
wget https://download.qemu.org/qemu-6.0.1.tar.xz
tar -xvf qemu-6.0.1.tar.xz
cd qemu-6.0.1/
./configure
make
make install

编译完成之后查看 qemu 版本,如图 2-1 所示:

image-20240327174404505

图 2-1 查看 qemu 版本

进入 VIP/default 目录找到编译好的 VxWorks,如图 2-2 所示:

image-20240327175703237

图 2-2 查找 VxWorks

接着使用 qemu-img 创建模拟存储设备,命令如下:

1
qemu-img create file.img 512M

将 VxWorks 与 file.img 放入同一文件夹内,如图 2-3 所示:

image-20240327180024124

图 2-3 VxWorks 与 file.img 同一文件夹内

使用下面的命令启动 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 所示:

image-20240328095123101

图 2-4 启动 VxWorks

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 所示:

image-20240329103001618

图 3-1 gdb 链接 qemu

首先 gdb 停在了 0x000000000000fff0 出,对应源码的位置在 <vsb_project>/krnl/configlette/dataSegPad.c,dataSegPad 为了将 MMU(Memory Management Unit,内存管理单元) 页大小边界对齐;在链接 VxWorks 时,它被明确列为加载行上的第一个模块,以确保这个数据结构是数据段中的第一个项目,确保数据段不会与由文本段占用的页面重叠。在 MMU 初始化完成后开始进入 VxWorks 的启动流程,首先进入第一个函数 sysInit 函数

image-20240329160314524

图 3-2 sysInit

sysInit 是 VxWorks 的启动入口,它的主要功能是禁用中断、设置堆栈,并调用 usrInit() ,初始堆栈被设置为从 sysInit() 的地址向下增长。这个堆栈仅被 usrInit() 使用,之后不在会用到。随后进入 usrInit 函数,如图 3-3 所示:

image-20240329160628082

图 3-3 usrInit 函数

通过进一步的调试,在 usrInit 函数中一共调用了以下函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
sysStart (startType);               //清除 BSS 并设置向量表基地址。
cacheLibInit (USER_I_CACHE_MODE, USER_D_CACHE_MODE); // 初始化缓存。
gpDtbInit = dt_blob_start; // 初始化DTB
usrFdtInit ((void*)DTB_RELOC_ADDR, (int)DTB_MAX_LEN); // 初始化 flat device tree 库
usrBoardLibInit(); // 初始化板级子系统,提供 BSP 访问 API
usrAimCpuInit (); // 初始化 cpu
excVecInit (); // 初始化exception向量
vxCpuLibInit (); // 初始化 CPU 识别函数
usrCacheEnable (); // 缓存使能
objOwnershipInit (); // 初始化 objOwnerLib 库,其中包含对象所有权函数。
objInfoInit (); // 初始化查找的对象功能
objLibInit ((OBJ_ALLOC_FUNC)FUNCPTR_OBJ_MEMALLOC_RTN, (OBJ_FREE_FUNC)FUNCPTR_OBJ_MEMFREE_RTN, OBJ_MEM_POOL_ID, OBJ_LIBRARY_OPTIONS); // 初始化 objLib 库,该库提供了 VxWorks 用户对象管理工具的接口。
vxMemProbeInit (); // 初始化 vxMemProbe() 异常处理
classListLibInit (); // 初始化对象列表
semLibInit (); // 初始化信号量
condVarLibInit (); // 初始化condition variables库
classLibInit (); // 初始化 class 库
kernelBaseInit (); // 初始化内核对象
taskCreateHookInit (); // 初始化 task hook 相关
sysDebugModeInit (); // 设置 debug flag 让系统处于调试模式
umaskLibInit(UMASK_DEFAULT); // 提供对内核环境中 POSIX 文件模式创建掩码的支持(支持 unmask())
usrKernelInit (VX_GLOBAL_NO_STACK_FILL); // 初始化内核

特别注意 sysInit 函数中的最后一个函数 usrKernelInit ,usrKernelInit 初始化并启动系统的第一个任务,随后进入 usrRoot ,如图 3-4 所示:

image-20240401150013251

图 3-4 usrRoot 函数

usrRoot 函数是系统第一个任务的入口地址,主要是负责 post-kernel 的初始化,该函数存在大量的初始化函数,具体函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
usrKernelCoreInit ();               // 初始化 Event 信号,消息队列,看门狗,hook dbg
poolLibInit (); // 初始化内存池,池中的块大小在池创建时指定的并每块大小一致
memInit (pMemPoolStart, memPoolSize, MEM_PART_DEFAULT_OPTIONS); // 初始化 memLib 库,该库主要是用于提供 RTP 堆分配内存块的 API
memPartLibInit (pMemPoolStart, memPoolSize); // 初始化 core 内存块
kProxHeapInit (pMemPoolStart, memPoolSize); // 初始化 kernel proximity heap,主要是核心附件的堆分配
pgPoolLibInit(); // 初始化 Page Pool
pgPoolVirtLibInit(); // 初始化 Page Pool 虚拟空间
pgPoolPhysLibInit(); // 初始化 Page Pool 物理空间
usrMmuInit (); // 根据 BSP 的 sysPhysMemDesc 表初始化全局的 MMU 映射
pmapInit(); // 提供物理地址映射到 kernel/RTP 的功能
kCommonHeapInit (KERNEL_COMMON_HEAP_INIT_SIZE, KERNEL_COMMON_HEAP_INCR_SIZE); // 初始化内核堆,用于内核和内核应用程序的动态内存分配,使用 ANSI 标准的 malloc 、free 进行管理
usrKernelCreateInit (); // 初始化 Object creation,比如:消息队列,看门狗,信号
usrNetApplUtilInit (); // 初始化 application/stack logging
envLibInit (ENV_VAR_USE_HOOKS); // 初始化 envLib,为了兼容 UNIX 环境变量,可以用过 putenv 创建修改环境变量
edrStubInit (); // 在 BOOT记录中记录 ED&R
usrSecHashInit (); // 初始化 secHash,如:MD5,SHA1,SHA256
usrDebugAgentBannerInit (); // debug agnet banner
usrShellBannerInit (); // shell baner
vxbDmaLibInit(); // 初始化 VxBus DMA 子系统
vxbIsrHandlerInit (VXB_MAX_INTR_VEC, VXB_MAX_INTR_CHAIN); // 初始化 VxBus ISR handler 方法
vxbIntLibInit (VXB_MAX_INTR_DEFER); // 初始化 VxBus 中断
vxDyncIntLibInit(); // 初始化支持消息中断的 VxBus 动态中断控制器
vxIpiLibInit (); // 初始化对称多处理 (SMP) 和非对称多处理 (AMP) 中断。
miiBusFdtLibInit(); // 初始化 MII bus FDT 子系统
miiBusLibInit(); // 初始化 MII bus 系统
vxbPciInit (); // 初始化 VxBus PCI 子系统库,该子系统库提供 PCI 主机控制器驱动程序
vxbPciMsiInit (); // 处理 PCI设备的 MSI 和 MSI-X 中断
vxbParamLibInit (); // 初始化driver parameter机制,driver parameter 的默认值可以被 BSP(DST) 覆盖
usrIaPciUtilsInit(); // 初始化 intel PCI
sysHwInit1 (); // 附加系统的初始化,如 PIC, IPI 向量
boardInit(); // 板级初始化
kernelIdleTaskActivate(); // 添加对 Idle Tasks 的支持 (SMP Only)
usrIosCoreInit (); // 内核 I/O
usrNetworkInit0 (); // 初始化网络
vxbLibInit (); // 初始化VxBus子系统
intStartupUnlock (); // 打开中断
sysIntEnableFlagSet(); // 标记中断使能
usrSerialInit (); // 设置标准输入、输出设备
usrClkInit (); // 初始化时钟
cpcInit (); // CPUs Cross-Processor Call (SMP Only)
vxdbgCpuLibInit (); // 初始化 VxDBG 对 CPU 的控制
pgMgrBaseLibInit(); // 初始化 Basic Page Manager
usrKernelExtraInit (); // 初始化内核其它机制,如:Signal、POSIX
usrIosExtraInit (); // 初始化IO系统其它机制,如:系统日志,标准 IO 库
usrHostnameSetup (TARGET_HOSTNAME_DEFAULT); // 设置 hostname 为 TARGET_HOSTNAME_DEFAULT,一般情况下为 target
sockLibInit (); // Socket 接口
selTaskDeleteHookAdd (); // select机制的初始化
cpuPwrLightMgrInit ();cpuPwrMgrEnable (TRUE); // 空闲时 CPU 电源管理
cplusCtorsLink (); // 确保在内核启动时调用编译器生成的初始化函数,包括 C++ 静态对象的初始化函数。
usrSmpInit (); // 多处理器支持
miiBusMonitorTaskInit(); // MII 总线监控任务。
usrNetworkInit (); // 完成网络系统初始化
usrBanner (); // 启动时显示 Wind River banner
usrToolsInit (); // 软件开发工具,例如 target loader、符号表、debug库、kernel shell等
usrAppInit (); // 系统启动后调用项目文件中应用程序的初始化函数 usrAppInit(),用户程序入口

在 usrAppInit 函数中主要是用户自定义的程序,不做深入讨论。最后用图总结一下 VxWorks 的启动流程,如图 3-5 所示:

图 3-5 VxWorks 启动流程图

4. 内核应用程序

usrAppInit 函数会 VxWorks 启动后启动内核应用程序,那么怎么把程序添加到自启动函数中去呢?在此之前初步认识一下内核应用程序

在 VxWorks 中内核应用程序在内核空间执行,这一点与 Unix Linux 是不一样的,内核应用程序可以是:
• 由 object module loader 下载并动态链接到操作系统。
• 静态链接到操作系统,使其成为 kernel image 的一部分。

首先找到 usrAppInit.c 文件,在 c 文件中到 usrAppInit 函数,其函数内容如图 4-1 所示:

图 4-1 usrAppInit 函数

编写一个函数并使用 taskSpawn 启动,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <taskLib.h>
#include <stdio.h>
#include <string.h>

void helloWorld () {
printf("hello vxworks!");
}

void usrAppInit (void)
{
#ifdef USER_APPL_INIT
USER_APPL_INIT; /* for backwards compatibility */
#endif
/* TODO: add application specific code here */
taskSpawn("hello", 100, 0, 8192, (FUNCPTR)helloWorld, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}

编译镜像并启动 VxWorks,如图 4-2 所示:

image-20240403154009817

图 4-2 helloWorld 函数运行成功

接着修改代码,代码如下所示:

1
2
3
4
void test () {
char buf[8];
gets(buf);
}

这是一个经典的栈溢出,通过 GDB 来看一下 VxWorks 中各个寄存器的情况,同样编译后启动 VxWorks。在 test 函数下断点,首先进行测试,如图 4-3 所示:

image-20240403160338072

图 4-3 溢出测试

首先是溢出情况,如图 4-4 所示:

image-20240403160816990

图 4-4 GDB 查看溢出情况

在这里可以看到返回地址已经被指向了未知的地址。再查看栈中的情况,如图 4-5 所示:

image-20240403165938023

图 4-5 溢出时栈中数据

而在未溢出的情况下,会跳转到 shellInternalFunctionCall 函数,如图 4-6 所示:

image-20240403164409623

图 4-6 未溢出的情况

未溢出时栈中数据,如图 4-7 所示:

image-20240403170318940

图 4-7 未溢出时栈中数据

再来看看 VxWorks 的保护机制,如图 4-8 所示:

image-20240403172053348

图 4-8 VxWorks 保护机制

VxWorks 并没有什么保护机制,因此在利用漏洞比较方便,可以直接执行 shellcode,同时由于 VxWorks 的特性在程序崩溃时就会重启,因此在利用时需要保证程序不会崩溃退出。

5. 与 Linux 内存布局进行对比

在 Linux 中操作系统将不同进程的虚拟地址和不同内存的物理地址映射起来,进程持有的虚拟地址会通过 CPU 芯片中的内存管理单元(MMU)的映射关系,来转换变成物理地址,然后再通过物理地址访问内存。如图 5-1 所示:

图 5-1 内存映射关系

虚拟地址与物理地址的映射有分段、分页以及结合使用三种方式,在 Linux 中内存分页把虚拟空间和物理空间分成大小固定的页。

虚拟内存分为内核空间和用户空间,根据位数的不同,地址空间的范围也不同,32 位和 64 位范围如图 5-2 所示:

图 5-2 Linux 虚拟内存布局

在 VxWorks 中同样存在虚拟内存,同样使用 MMU 进行管理,但是在 VxWorks 中存在多个分区,可以使用 adrSpaceShow 命令展示当前内存使用情况,如图 5-3 所示:

image-20240407113032446

图 5-3 adrSpaceShow 命令使用

对于32位与64位CPU,VxWorks7 所提供的内存管理机制是相同的,虚拟内存被分区管理,每个分区具有专门的用处和相应的分配机制。

VxWorks 虚拟内存大致结构如下,如图5-4 所示:

图 5-4 VxWorks 虚拟内存布局
  • 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 所示:

image-20240409100157877

图 5-5 配置 MMU

可以通过 VM_PAGE_SIZE 虚拟内存默认分页大小,默认值是 0x1000 即 4KB,如图 5-6 所示:

image-20240409100637994

图 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 项目启动流程以源码的形式给出了,主要文件在 /orhConfig.c,sysLib.c,sysAlib.s 文件中。

在编译中的过程并不需要开启调试,VxWorks 的调试模式主要还是针对 WorkBench,本次实验用的版本为 2018 版的 VxWorks,对应的 WorkBranch 对 GDB 调试的支持并不好。

VxWorks 作为业界领先的实时操作系统,还有许多内容值得我们学习。

另外需要注意的是:WorkBench 在新版中对于 GDB 的支持更完善了,并不需要使用这种方式进行调试。

如有错误,欢迎指正

7. 参考链接

https://www.vxworks.net/app/907-vxworks-7-programmer-guide-memory-management

https://mp.weixin.qq.com/s/SUhkdP9i7ie-ZESsCVRWmA