- 内核调试、rootkit分析
- 监控windows系统交互
10.1 驱动与内核代码
- 驱动程序分析的困难性
- 常驻内存,负责响应用户态请求
- 应用程序不直接与驱动程序通信,而是直接访问设备对象,向具体的物理设备发送请求
- 驱动程序必须加载到内核空间
- 驱动首次被加载:DriverEntry函数被调用
- 驱动:通过注册回调函数提供功能
- 区别于DLL:通过导出表
- 驱动对象&设备对象:windows为每个驱动创建一个驱动对象,以参数的形式传递给DriverEntry函数,DriverEntry函数用会调函数填充这个驱动对象,再创建一个可以被用户态应用程序访问的设备对象,应用程序与驱动的交互请求都通过这个设备对象进行
- ReadFile:用户态应用程序获得硬件设备的文件句柄——>调用ReadFile——>内核处理——>驱动程序回调函数响应对I/O设备的读请求
- DeviceIoControl:请求内核恶意组件最常见函数
- 作用:用户态应用程序传递一个任意长度的缓冲区数据作为输入,接收一个任意长度的缓冲区数据作为输出
- 内核处理用户态请求
- 一些内核恶意代码没有明显的用户态组件,也没有创建内核对象,仅仅运行在驱动程序中
- 恶意驱动与内核组件交互
- ntoskrnl.exe:操作系统核心功能
- hal.dll:与主要硬件设备交互
10.2 安装内核调试
- 内核调试
- 设置虚拟机操作系统,开启内核调试
- C:\boot.ini (/debugport=COM1【使用哪个端口连接调试系统与被调试系统】 /baudrate=115200【指定串口数据传输速率】)
- 配置vmware使虚拟机与宿主系统之间有一条虚拟化的串口
- 宿主操作系统配置windbg
- 设置虚拟机操作系统,开启内核调试
10.3 使用windbg
- 从内存中读取(dx addressToRead)
- da:以ASCII文本显示
- du:以Unicode文本显示
- dd:以32位双字节显示
- 改变内存的值
- ex addressToWrite dataToWrite
- 算数操作符
- +、-、*、/
- dwo:解引用32位指针,查看该指针代表地址的值(dwo (esp+4))
- du dwo (esp+4)
- 以宽字节字符串显示函数的第一个参数
- 设置断点
- bp 设置基本断点
- 当断点命中时,可以指定将被执行的操作
- g 完成操作后继续执行
- bp GetProcAddress “da dwo(esp+8); g”
- 当GetProcAddress被调用时中断,打印函数第二个参数,然后继续执行
- 函数的第二个参数是函数名
- bp 设置基本断点
- 列举模块
- lm
- 列举加载到进程的所有模块
- 用户模式的EXE和DLL
- 内核模式的内核驱动
- 如同OllyDbg中的内存映射
- 列举加载到进程的所有模块
- lm
10.4 微软符号表
- 定义:包含某些函数和变量的名字
- 作用:
- 告诉我们某个函数创建了一个进程的地址空间
- 可以使用函数名称查找内存中的函数和数据
- 搜索符号
- moduleName!symbolName
- moduleName
- 表示.EXE、.DLL或者.SYS 文件名(不含扩展名)
- ntoskrnl.exe是个特例,它被命名为 nt
- symbolName
- 与这个地址相关联的函数名
- 例如: u nt!NtCreateProcess
- 反汇编该函数
- x:允许使用通配符来搜索函数或符号
- x nt!CreateProcess:显示导出函数和内部函数
- ln address:列出最接近给定内存地址的符号
- 延时断点
- bu:用符号在没有加载的代码中设置一个延迟断点
- $iment:确定一个给定模块的入口点
- bu $iment(driverName):在驱动代码执行前,驱动入口点处中断
- 查看结构信息
- 包括未公开的(恶意代码常用)
- dt moduleName!symbolN:现实结构
- dt moduleName!symbolN address:从地址address显示结构中的数据
- 初始化函数:DriverInit,唯一一个在驱动每次被加载时都调用的函数,有时恶意代码会将全部恶意载荷放到该函数中
- 配置windows符号表
10.5 内核调试实践(在内核中读写文件的驱动)
- 从内核空间写文件的好处:更加难以被察觉(CreateFile 和 WriteFile——>NtCreateFile 和 NtWriteFile,导入函数中看不到)
- 用户空间的代码
- CreateService:创建驱动,参数dwServiceType 是 0x01(内核驱动)
- CreateFileA:创建一个文件来获得内核设备句柄(.\FileWriter\Device,7.1.4特殊文件)
- DeviceIoControl:恶意代码调用该函数向内核驱动发送数据(不唯一,CreateFile、ReadFile、WriteFile都可)
- CreateService:创建驱动,参数dwServiceType 是 0x01(内核驱动)
- 内核模式代码
- 运行windbg如果开启了详细信息输出功能,将会在每次内核模块载入时得到通知
- 调试恶意代码时,如果有内核模块被加载,该模块值得怀疑(排除KMixer.sys)
- !drvobj 驱动名字:找到对应名字的驱动对象,可以获得驱动对象的地址
- !object \Driver:浏览驱动对象,列出\Driver(根命令空间)中的所有对象
- dt 驱动名字 address:查看驱动对象数据结构
- MajorFunction:指向主函数表首项的一个指针,主函数表包含从用户空间调用恶意驱动时执行了什么操作
- windows内核使用UNICODE_STRING结构
- RtlInitUnicodeString:用来创建内核态字符串
- ZwCreateFile:在内核态创建文件,对象名用来表示所涉及的主设备(大多数前缀是\DosDevices)
- 查找驱动对象
- 查找加载到内核空间的驱动程序
- 应用程序直接与设备进行交互,而不是驱动,用户态识别设备对象再去查找驱动对象
- 使用 !devobj 找到更多关于驱动对象的信息,能得到设备对象句柄
- 设备对象提供一个指向驱动对象的指针,通过这个对象指针可以获得驱动程序的分发函数表
- 使用 !devhandles 获得拥有相应设备句柄的所有用户态应用程序列表(使用该驱动的程序)
- 知道了具体是哪个应用程序被感染了
10.6 Rootkit
- 定义:通过修改操作系统内部函数来隐藏自己存在的痕迹
- 系统服务描述表(SSDT)挂钩技术
- SSDT:微软使用它来查找进入内核的系统调用,通常不被第三方应用程序或驱动程序访问。
- 内核态代码只能被用户态的SYSCALL、SYSENTER或INT 0x2E指令访问
- 举例:
- EAX=0x25,是NtCreateFile函数的序号,代码进入内核后系统使用这个值作为SSDT的索引
- 挂钩后,rootkit会改变ssdt中某项的值,内核态系统调用时就变成运行rootkit的代码(指针)
- rootkit分析实战
- 分析已感染系统是否安装有恶意驱动
- 检查SSDT,观察其函数指针是否在NT模块的地址范围内,识别挂钩函数
- lm:列举打开模块,找出是哪个模块包含了挂钩函数地址,定位驱动程序
- 查找挂钩代码。开始分析驱动。
- 安装挂钩的代码段
- 实现了挂钩NtCreateFile函数的功能,1、2处创建了两个字符串,Rootkit用它们调用MmGetSystemRoutineAddress查找NtCreateFile函数和KeServiceDescriptorTable的导出地址(ntoskrnl.exe模块导出)
- 有了SSDT和NtCreateFile的地址,接下来4~5之间遍历SSDT,直到找到匹配NtCreateFile地址的值,使用挂钩函数的地址覆盖即可(6)。
- 实现了挂钩NtCreateFile函数的功能,1、2处创建了两个字符串,Rootkit用它们调用MmGetSystemRoutineAddress查找NtCreateFile函数和KeServiceDescriptorTable的导出地址(ntoskrnl.exe模块导出)
- 执行挂钩的函数
- 安装挂钩的代码段
- 中断
- 有时rootkit使用中断来干扰系统事件,执行代码
- 中断允许硬件触发软件事件
- IoConnectInterrupt: 驱动调用为中断注册处理程序,然后为这个中断指定中断服务例程 (ISR),当触发中断时,该例程会被调用
- IDT:中断描述符表
- 存储ISR信息
- !idt:查看idt
- 正常IDT:所有中断都在微软签名的驱动中
10.7 加载驱动
- 假设要分析的恶意代码都有一个加载它的用户态应用程序。
- OSR Driver Loader工具
10.8 Windows Vista、Windows 7和 x64 版本的内核问题
- BCDEdit程序替换boot.ini决定引导哪个系统
- PatchGuard:添加内核保护补丁机制,阻止第三方程序修改内核。能够感染调试过程,因为调试器在插入断点时 会修改代码
- 从64位版的Vista开始强制执行驱动签名
- 只加载有数字签名的驱动
- 有效防护!
- 可以通过在BCDEdit中指定nointegritychecks关闭驱动签名功能