👋 奇安信天工实验室,聚焦真实世界漏洞分析。漏洞研究成果曾发表于DEFCON、BlackHat、GeekCon、HITB、ACM CCS、Usenix、EuroS&P、RAID等国际重量级会议。
- 团队研发的破壳平台,提供基于查询、面向团队协作的漏洞辅助分析能力。
👋 奇安信天工实验室,聚焦真实世界漏洞分析。漏洞研究成果曾发表于DEFCON、BlackHat、GeekCon、HITB、ACM CCS、Usenix、EuroS&P、RAID等国际重量级会议。
一、前言 大语言模型(LLM)近期成为安全研究和竞赛中的热门话题,涵盖了大模型在安全领域的应用以及自身的安全性等方向,这一趋势为许多非大模型安全研究领域的研究者提供了了解和探索的机会。 得益于强大的上下文理解与模式识别能力,大模型被认为具备从已知漏洞中学习特征并检测、修复未知漏洞的潜力。目前,大多数基于大模型的漏洞检测和修复方案集中在函数级别,只处理小范围的代码片段,不过也有少量研究开始探索代码库级别的问题。 本次 DataCon 聚焦于真实漏洞环境,提供了和漏洞相关的完整程序代码,要求参赛者"结合大模型技术自动化识别出漏洞样例中存在的安全隐患"。为此,本文将从函数级别和代码库级别两个方面简要分析和梳理当前研究进展,并结合代码库级别漏洞检测的需求,探讨未来的研究方向。 二、函数级别漏洞检测 本节主要参考 Large Language Model for Vulnerability Detection and Repair: Literature Review and the Road Ahead,未注明来源的研究方法,可在该综述中查阅 从提升大模型漏洞挖掘能力的角度,主要可以将当前研究分为三种方法:微调、提示工程和检索增强生成(RAG)。微调通过在漏洞数据上的定向训练来调整模型的参数;提示工程则以黑盒的方式,通过精心设计的 prompt 来优化模型在具体应用场景中的输出效果;RAG 从数据库中检索相关知识并整合到 LLM 的上下文中,同样不需要修改 LLM 的参数。 2.1 微调 一般的微调训练包含以下几个步骤:数据准备、模型设计、模型训练和模型优化。根据不同研究方法主要针对的训练阶段,可以划分为以下几个类别: 以数据为中心的创新(数据准备) 漏洞数据集通常存在标签不平衡和标签不正确的问题。一些研究通过数据采样、伪标签生成以及反事实数据生成等方法,尝试针对这些问题提出解决方案。 结合程序分析方法(数据准备) 一些研究将程序分析工具的结果引入模型的预训练或微调阶段,以帮助 LLM 更好地理解代码逻辑和数据依赖关系。例如: 可以提取抽象语法树(AST)和程序依赖图(PDG)来进行语句级控制依赖和数据依赖的预训练。 利用程序切片提取控制和数据依赖信息,将控制流图(CFG)分解为执行路径,为模型提供漏洞检测的支持。 结合其他深度学习模型(模型设计) 通过 Bi-LSTM (双向长短期记忆网络)处理分段后的代码输入克服了 LLM 的长度限制;亦或是通过 GNN (图神经网络)提取代码的图结构特征来补充 LLM 对代码结构的理解。 针对特定领域进行训练(模型训练) 通过在特定编程语言和特定漏洞类型的数据上进行预训练,使用如掩码语言模型、对比学习、程序依赖预测和漏洞语句标注等不同的预训练目标,可以增强模型对特定编程语言、特定漏洞模式以及复杂代码依赖关系的理解能力。 因果学习(训练优化) 通过识别和剔除那些与漏洞标签存在虚假相关的非稳健特征,进而使用因果推理算法来提升模型的因果推理能力。 其中,相比于其他方法更像是在解决一些大模型的通病,“结合程序分析方法"以及"针对特定领域进行训练"两类方法,则更依赖研究者对程序分析和漏洞挖掘领域的深入理解。限于时间原因笔者没能深入了解相关方法的研究细节,感兴趣的读者建议阅读相关论文。 2.2 微调:如何训练自己的模型 那如果我们想要微调自己的大模型,有哪些需要关注的问题呢?首先是选择使用的研究方法:根据数据规模、数据是否标记、模型选择和预算,可以划分为如图所示的模型训练方法。**CPT(持续预训练)**使用大量未标记的特定领域数据对已经预训练的大模型进行进一步训练;**SFT (有监督微调)**更专注于增强特定任务的性能,使用有标签的数据进行训练。**FULL(全参数微调)**细化所有参数,需要大量的计算能力和时间;相比之下,**PEFT(参数高效微调)**通过仅微调少量模型参数大大降低了计算成本,常见的 LoRA (Low Rank Approximation)就属于 PEFT 方法。在针对于漏洞检测的领域,许多研究采用了 SFT 和 PEFT。 此外,在一个不考虑特殊优化方法的微调过程中,还要涉及模型选择和数据集选择:...
一、前言 权限认证是一种用于控制系统资源访问的安全机制,如果权限认证存在缺陷,将对Web应用的保密性、完整性、可用性造成严重影响。 此次分享的内容是之前挖掘国内某OA漏洞时,受tomcat下路由分发特性导致认证绕过思路影响去分析的。旨在讨论Resin URI规范化特性导致的权限认证绕过问题。 二、Servlet URL匹配模式 在 java 应用中通常通过xml声明Servlet <Servlet> <!-- 唯一标识这个 Servlet 的名称 --> <Servlet-name>helloServlet</Servlet-name> <!-- Servlet 类的全限定名 --> <Servlet-class>com.Mmuz.helloServlet</Servlet-class> <!-- 初始化参数(可选) --> <init-param> <param-name>xxx</param-name> <param-value>Hello, World!</param-value> </init-param> <!-- Servlet 加载顺序(可选) --> <load-on-startup>1</load-on-startup> </Servlet> <!-- URL 映射配置 --> <Servlet-mapping> <!-- 引用上面定义的 Servlet-name --> <Servlet-name>helloServlet</Servlet-name> <!-- 定义 URL 映射路径--> <url-pattern>/hello/1.Servlet</url-pattern> </Servlet-mapping> 也可以在类定义出上使用Servlet规范中定义的WebServlet注解去声明。 Servlet声明时可以不同类型的urlpattern,当路由请求时,Servlet 会根据不同的URL模式进行匹配。匹配方式主要有4种,优先级是精确匹配 > 路径匹配 > 后缀匹配 > 缺省匹配 精确匹配 uri必须和url-pattern完全一致,如 /hello/1.Servlet 最长路径匹配 也叫路径匹配,指以*结尾的url-pattern,示匹配以该路径开头的所有请求,越长的路径匹配优先级越高。 /hello/* * 由于Resin中会将*转换为 /*,所以*是路径匹配 /* /hello/a* 匹配/hello/a/x 后缀匹配...
Joern是源码级的静态分析工具,支持多种语言(二进制有ghidra支持),接下来将从Joern的基本概念,Joern的基本使用,到使用Joern来进行PHP漏洞复现,在到实际使用过程中遇到的问题和解决方案(降低误报率) 来进行介绍。 一、Joern的基本概念 在使用Joern对代码进行分析时,其主要步骤是将项目代码转换为代码属性图(CPG),再提供查询接口来供用户基于CPG进行漏洞挖掘。 Robust parsing ; Code Property Graphs ; Taint Analysis ; Search Queries ; Extendable via CPG passes Joern的官方文档中介绍其核心特点有: 强大的解析能力,指的是Joern内含多种解析器,支持将多种语言转换为代码属性图(CPG) Joern中的代码属性图是其进行静态分析的根基,该数据结构包含程序语法、控制流和数据流的相关信息 污点分析,Joern提供污点分析引擎,用户可以通过定制化的方式来对攻击者可控数据进行分析此外灵活搜索查询和CPG可扩展也是Joern的优点。对于图CPG节点和CPG图本身,Joern支持用户对其灵活的访问和修改 上图展示了用户使用Joern来进行漏洞挖掘的完整逻辑: 首先项目导入,Joern会根据用户提供的项目路径和路径下的文件名后缀来对项目进行解析生成代码属性图,比如项目中如果主要是cpp文件,Joern就会将项目识别为c++项目 生成的代码属性图将加载到Joern的shell中,shell会提供访问代码属性图的接口 用户依据自己积累的漏洞模式,通过查询的方式,对目标项目进行漏洞挖掘。比如通过设置source点和sink点,来进行污点传播分析(在第二节中介绍) Joern输出具有漏洞模式的代码传播路径,快速的帮助用户定位可能存在漏洞的地方,此时需要用户通过审计以及编写poc的方式确认漏洞是否存在 二、Joern的基本使用 使用静态分析工具常见的要求是分析程序从source点到sink点的传播路径,也就是污点传播分析。 这里的source点从宏观上讲,指的是程序中攻击者的可控输入点,具体来说如在php中的$_GET $_POST $_SERVER, 设备固件中HttpGetEnv中的返回值,main函数中的argv等。 sink点指的是程序中的污点,当攻击者输入可以进入到污点将发生安全风险,如危险函数system popen mysql_queryi的输入参数,不局限于危险函数,有特征的字符串拼接也可以设置为sink点,如"SELECT UPDATE INSERT",当程序中有sql query语句拼接特征的代表有注入风险可能。 接着将从Joern官网的例子来演示用Joern进行污点传播分析的基本使用 https://docs.Joern.io/cpgql/data-flow-steps/ //目录 :c/X42.c #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { char buffer[10]; if (argc > 1 && strcpy(buffer, argv[1]) == 0) { fprintf(stderr, "It depends!...
一、Rust 简介 Rust语言自其发布以来就备受人们关注,作为一门现代的系统级编程语言,Rust在安全性方面引起了人们的极大兴趣。它与其他语言相比,引入了一系列创新的安全特性,旨在帮助开发者编写更可靠、更安全的软件。在这个基础上,许多大厂开始纷纷在自己的项目中引入Rust,比如Cloudflare的pingora,Rust版的git – gitxoide,连微软都提到要将自家的win32k模块用rust重写,足以见得其火爆程度。 之所以人们对Rust那么充满兴趣,除了其强大的语法规则之外,Rust提供了一系列的安全保障机制也让人非常感兴趣,其主要集中在以下几个方面: 内存安全:Rust通过使用所有权系统和检查器等机制,解决了内存安全问题。它在编译时进行严格的借用规则检查,确保不会出现数据竞争、空指针解引用和缓冲区溢出等常见的内存错误。 线程安全:Rust的并发模型使得编写线程安全的代码变得更加容易。它通过所有权和借用的机制,确保在编译时避免了数据竞争和并发问题,从而减少了运行时错误的潜在风险。 抽象层安全检测:Rust提供了强大的抽象能力,使得开发者能够编写更加安全和可维护的代码。通过诸如模式匹配、类型系统、trait和泛型等特性,Rust鼓励使用安全抽象来减少错误和提高代码的可读性。 Rust强大的编译器管会接管很多工作,从而尽可能的减少各种内存错误的诞生。 二、Rust 不会出现漏洞吗? 在 Rust 的各类机制下,开发人员在编译阶段被迫做了相当多的检查工作。同时在 Rust 的抽象机制下,整体的开发流程得到了规范,理论上应该是很难再出现漏洞了。然而,安全本质其实是人,错误本质上是由人们的错误认知引发的。即便是在 Rust 的保护之下,人们也是有可能犯错,从而导致新的问题的出现。对于这种场景,我们可以用一种宏观的方法论来概括,那就是认知偏差。这里可以用一个图来大致描述一下这个认知偏差: 换句话说,在使用Rust开发中,人们认为Rust能够提供的防护和Rust实际上提供的防护,这两者存在一定的差异。具体来说,可以有一下几种场景: Rust 检查时,能否防护过较为底层的操作状态? Rust 自身特性是否会引入问题? Rust 能否检查出作为mod 或者 API被其他人调用时,也能完全保护调用安全吗? 为了能够更好的了解认知差异,接下来我们就介绍几种比较典型的 Rust 下容易出现的漏洞。 三、漏洞案例一:对操作系统行为错误的认知 再进行开发过程中,Rust 通常会需要与操作系统底层进行交互。然而在这些操作过程中,本质上是对底层的API 或者对底层操作系统的操作,此时考察的是开发者对于操作系统的理解。而Rust编译器的防护机制并无法直接作用于这些底层的操作系统对象,从而会导致错误的发生。 一种常见的认知偏差就是默认操作系统提供的特性,比如说接下来要提到的特殊字符过滤规则。 3.1 BatBadBut(CVE-2024-24576) 在2024年4月,安全研究员RyotaK公开了一种他发现现有大部分高级语言中常见的漏洞类型,取名为BatBadBut,其含义为batch文件虽然糟糕,但不是最糟糕的。 batch files and bad, but not the worst 在Windows下,想要执行bat文件就必须要启动一个cmd.exe,所以执行的时候通常会变成cmd.exe /c test.bat。 每个高级语言在Windows平台下需要创建新的进程的时候,最终都会调用Windows的APICreateProcess。为了防止命令注入,它们大多数会对参数进行一定的限制,然而Windows平台下的CreateProcess存在一定的特殊行为,使得一些常见的过滤手段依然能够被绕过。作者给了一个nodejs的例子,在nodejs中,当进行进程创建的时候,通常是这样做的 const { spawn } = require('child_process'); const child = spawn('echo', ['hello', 'world']); 这种做法通常是没问题的,此时由CreateProcess创建的进程为echo,参数为后续的两个参数。同时,这个调用过程中伴随的如下的过滤函数,会将"过滤成\" /* * Quotes command line arguments * Returns a pointer to the end (next char to be written) of the buffer */ WCHAR* quote_cmd_arg(const WCHAR *source, WCHAR *target) { /* * Expected input/output: * input : hello"world * output: "hello\"world" * output: "hello world\\" */ } 此时,上述的指令会形成如下的指令:...
一、前言 静态分析技术因其在学术研究和工业应用中的广泛用途而备受关注。尽管静态分析技术已经取得了长足的进步,但现有的静态分析框架仍然存在易用性方面的挑战。本文主要介绍了 Clang Static Analyzer(CSA)的使用方法,对部分检查器的源代码进行了浅析,并探讨了其设计思想。希望通过本文,能够帮助那些希望深入了解静态分析技术的同学更好地掌握相关知识。 二、CSA 工作原理 CSA 分析器的设计灵感来自几篇基础性的研究论文 (Precise interprocedural dataflow analysis via graph reachability, T Reps, S Horwitz, and M Sagiv, POPL ‘95, A memory model for static analysis of C programs, Z Xu, T Kremenek, and J Zhang)。 分析器基本上是一个源代码模拟器,它能跟踪可能的执行路径。程序的状态由状态(ProgramStateRef)进行封装。在 CSA 术语中对应 ExplodedNode。 分析器通过对分支进行推理,找出多条路径,然后对状态进行分叉。在真分支上,假设该分支的条件为真,而在假分支上,假设该分支的条件为假。这种"假设"会对程序的值产生约束,这些约束被记录在程序状态(ProgramState)中并由约束管理器管理。如果假设分支的条件会导致约束条件无法满足,那么该分支将被视为不可行,该路径将被删除,这就是路径敏感性。CSA 通过缓存节点来减少指数级爆炸,如果新节点的状态和程序点与现有节点相同,路径就会被"缓存",我们只需重新使用现有节点即可。 更详细的工作原理可以参考 CSA README.md。 总而言之,Clang Static Analyzer(CSA) 是基于 Clang AST(编译器前端) 实现的源代码静态符号执行工具。支持分析 C,C++,和 Objective-C 。基于静态符号执行技术实现了路径敏感技术,也支持过程间分析。工具被集成进 llvm-project,有长期的维护和发展。如果你对符号执行比较熟悉,会注意到其与 KLEE,Angr 的诸多相似之处。 三、CSA 安装指南 clang is all you need。...
一、前言 Avalanche Web 应用程序无法独立执行任务,但是它可以自由地使用不同的服务来执行任务。信息路由(也称为 InfoRail 服务)位于两者之间,负责在服务之间分发消息。Web创建一条消息并将其发送到 InfoRail 服务,后者将其转发到适当的目标服务。目标服务处理该消息并再次通过 InfoRail 服务将响应返回给 WebWLAvanacheServer是其中的移动设备服务,默认开启在1777端口上。WLAvanacheServer这个组件首次漏洞公开时间是2023年,成为了Avalanche的新攻击面,本文主要对WLAvanacheServer这个组件进行漏洞分析。 二、消息结构 这里是在6.4.0这个版本上分析消息结构,整个消息结构由三个主要部分组成: preamble header payload 下面是一个详细的消息内存结构: 0:039> dc 0242e8c0 0242e8c0 60000000 21000000 21000000 00000000 ...`...!...!.... 0242e8d0 03000000 05000000 10000000 656e7770 ............pwne 0242e8e0 34313464 34313431 34313431 34313431 d414141414141414 0242e8f0 00000031 00000003 00000005 6e777010 1............pwn 0242e900 32346465 32343234 32343234 32343234 ed42424242424242 0242e910 00003234 00000000 00000000 00000000 42.............. 0242e920 f57527e1 08202df9 0056a528 007713b0 .'u..- .(.V...w. 2.1 preamble 它的长度为16字节,主要由以下部分组成: MsgSize:整个消息的长度 HdrSize:消息头的长度 PayloadSize:消息的payload长度 unk:这里目前不知道是做什么的,应该是某个标志,不影响分析 2....
一、What is Sandbox 沙箱机制(Sandboxing)是一种安全技术,用来隔离运行中的应用程序或代码,使其在受限的环境中执行。这种机制的目的是限制应用程序或代码与系统资源(如文件系统、网络、硬件)的直接交互,从而防止恶意软件或不受信任的代码造成安全威胁或数据泄露。 当提到chrome沙箱时通常会想到的是用于限制应用程序或代码与系统资源的直接交互的沙箱,而在实际的漏洞利用层面内存安全仍然是一个重要问题,在众多chrome可利用的漏洞中,v8漏洞可以说占到大多数,而V8漏洞很少是"经典"的内存损坏错误(释放后使用、越界访问等),而是微妙的逻辑问题,反过来再利用这些问题来损坏内存。因此,现有的内存安全解决方案在很大程度上并不适用于V8,于是在此背景下衍生出了v8 Sandbox。本文主要对v8 sandbox的一些绕过方法进行汇总分析。 二、V8 Sandbox V8沙箱是一个基于软件的沙箱,其背后的基本思想是隔离 V8堆内存,使任何内存损坏都不会"扩散"到进程内存的其他部分,从而增加v8漏洞的利用难度。具体实现方式有两种: 第一种,如果buffer位于沙盒内,就将40位的地址偏移左移24位后得到的64位结果写入相应字段地址中: disable sandbox: enable sandbox: 通过对%DebugPrint具体的实现代码下断,可以找到具体的decode过程,首先从指定字段地址出得到64位值(sandboxed_pointer),再将sandboxed_pointer右移24位(kSandboxedPointerShift)得到偏移(offset),最后将offset与基址(cage_base)相加得到真实的地址指针: 第二种,如果buffer位于沙盒外,则会将指定字段地址内的值作为索引,通过指针表间接的引用buffer: 例如blink对象,在v8中所有的blink对象都分配在v8堆外,并以api对象的形式在v8中表示: V8 api对象实际上是blink对象的包装器,其中embedder fields字段存储内容用实际为一个表索引,此索引位置保存着对应的blink对象的实际地址及其类型: 同样也可以通过对%DebugPrint实现下断找到具体的decode过程: 三、V8 Sandbox Breaking 3.1 signature confusion breaking sandbox 在V8 webassembly中,wasm模块导出函数主要由函数签名(signature)和函数实现(call_target)组成,假设有以下代码,此代码可以导出read_0与read_1两个函数: (module (memory 1) (func $read_0 (export "read_0") (param $offset i32) (result i64) (i64.load (local.get $offset) ) ) (func $read_1 (export "read_1") (param $var1 i64) (result i64) i64.const 0 ) ) 在js代码中使用read_0(0x41)触发对read_0函数的调用,然后对Builtins_JSToWasmWrapper下断可以得到signature与call_target的获取过程: 先通过函数对象获取shared_info字段,其中r14寄存器存的一直都是基地址,而rdi则是函数对象地址: 通过shared_info字段得到function_data: 通过function_data获取signature,signature对象不在沙盒中,所以是通过外部表的形式间接引用的,所以此处得到的是一个表索引: 通过function_data获取func_ref: 通过func_ref可以得到internal,internal是一个外部对象,而call_target就在internal中并且也是一个外部对象,所以都只能得到一个表索引: 最后Builtins_JSToWasmWrapperAsm函数会通过call rdx进入call_target指向的地址,在通过几个Jmp后会进入真实的jited代码,rax为传入的地址偏移:...
一、前言 当探讨计算机科学中的模式匹配技术时,正则表达式(Regular Expressions,通常简称regexp)无疑是一项强大的工具。它广泛应用于文本处理的各个方面,如搜索、编辑或者操控字符串。正则表达式允许用户通过定义特定的字符组合来查找、替换以及操作文本,其应用范围从简单的数据校验到复杂的系统日志分析等不一而足。正则表达式使用广泛,如果使用不当,会带来一些安全问题。 本文主要讨论正则表达式引起的ReDoS拒绝服务,侧信道泄露,权限绕过,数据校验,回溯限制以及正则执行问题。 二、ReDoS 拒绝服务 正则表达式底层通常会使用NFA非确定性有限自动机来实现,将正则匹配转换为路径匹配。举几个例子: 表达式一a:消耗1个字符a,可以从起始0到达终态1实现match 表达式二a*:消耗0个或多个字符a,可以从起始0到达终态1实现match,图中ε表示空串 空字符串可以直接从0->1找到一条访问路径, aa形式的字符串可以0->2->3->4->2->3->4->1的方式找到一条访问路径实现match 到此时似乎一切都没什么问题?看接下来的一个表达式 表达式三(a*)*b:其NFA图如下: 可以看到现在的6-..>7之间的所有路径本质和表达式二一致,如果把它当作一个整体并从更高层次看0->4之间的所有路径像是一个更大的表达式二,11->12路径形式则与表达式一等同。即表达式三由两个表达式二的图以"父图和子图"的方式嵌套并添加一个表达式一构成。 现在考虑如下输入及测试: # 后续文中使用的search都是该函数 # 主要用于打印正则匹配消耗的时间,单位s def search(r,s): a=time.time() re.match(r,s) b=time.time() c=b-a print("use:"+str(c)) >>> search("(a*)*b","a"*22) use:0.24314594268798828 >>> search("(a*)*b","a"*23) use:0.4825863838195801 >>> search("(a*)*b","a"*24) use:1.0666351318359375 >>> search("(a*)*b","a"*25) use:2.1269423961639404 可以看到输入每增加一个a字符,那么正则表达式消耗时间约增加一倍。 回溯问题:正则表达式匹配的时候,先按照贪婪匹配所有的a,之后尝试匹配b的时候发现匹配失败,说明当前路径不匹配,那么会进行路径回退,然后尝试另一条路径,由于"表达式二"的重复嵌套,会造成回退的路径非常多,计算量非常大,最终造成了ReDoS拒绝服务。 对于满足如下几条性质的正则表达式,会存在指数回溯问题 【时间负载度为O(2^n),n是字符的个数】 ReDos是一种算法复杂度攻击,通过提供最坏情况输入来利用算法的攻击。 将 +/* 应用到一个子正则表达式A 子正则表达式A可以多种方式重复匹配相同的输入,比如 a|a / a+ / a|aa 等就能重复匹配输入 在重复的表达式之后要存在一个无法与输入匹配的表达式B 【以便让匹配失败,从而路径回退】 因此之前的测试search("(a*)*b","a"*25)就满足上面的3个性质,下面看一些其它例子: 2.1 情形一:邮件格式匹配 正则表达式: ^([a-zA-Z0-9])(([\-.]|[_]+)?([a-zA-Z0-9]+))*(@) {1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))$ 分析:表达式中片段(([\-.]|[_]+)?([a-zA-Z0-9]+))* , 其中的+满足条件1,*满足条件2,条件3只要构造特定输入数据就能满足。 测试: >>> search("^([a-zA-Z0-9])(([\-.]|[_]+)?([a-zA-Z0-9]+))* (@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))$", "a"*25) use:2.2077696323394775 2....
本文是天工实验室在DEFCON上发表的议题《Dragon Slaying Guide: Bug Hunting in VMware Device Virtualization》的第三部分内容。在该议题的前两篇文章中,分别介绍了VMware虚拟化实现和虚拟USB设备漏洞挖掘。 本周将继续讲解议题最后一个部分:SCSI Device Virtualization。主要介绍虚拟磁盘系统SCSI相关设备模拟在VMware Workstation和ESXI下的异同,以及我们在VMKernel中发现的有关磁盘设备模拟的设计缺陷。 一、前言 磁盘设备的代码架构和USB设备类似,首先他有许多作为前端的主机控制器设备模拟代码,比如 LsiLogic,NVME,PVSCSI,AHCI,有趣的地方在与作为连同主机控制器与磁盘设备的中间层代码实际上只处理SCSI协议指令,VMware为每种主机控制器都设计了用于将主机控制器请求翻译成SCSI请求的代码,所有不同磁盘设备的主机控制器请求在最终都会被统一成SCSI指令,后端设备模拟代码同样使用了类似面向对象的设计,从基本的SCSI设备对象可以派生出各种不同类型的设备,CDROM,Disk 等等。 无论是Esxi还是Workstation的 vmx 都有几乎一样的处理代码,但真正引起我们注意的是 Esxi 在默认情况下并没有使用 vmx 中对磁盘请求处理的代码,只有在启用了 HostEmulated 配置时,才会由vmx负责处理。 通过前文对 vmm 部分的分析,我们发现Esxi默认情况下正是通过 vmm 中对 VMKernel 的调用使用 VMKernel 中对应的模拟代码进行处理,这很快就引起了我们的兴趣,在 VMKernel 的范围下,哪怕是出现断言错误都可能带来整个 Hypervisor 的瘫痪,从而引起单个Esxi上所有客户机拒绝服务。 二、漏洞一:CVE-2024-22273:Out-of-bounds Read/Write 关于磁盘部分的第一个漏洞,如果按照控制器-后端设备模型来看,它处于后端设备模拟部分。 VMware会将所有请求在模拟阶段统一转化为SCSI指令,接着解析你访问的磁盘ID,根据磁盘ID来将SCSI指令转发到对应的设备上。转发这一过程,在VMware中有一个专门的函数进行处理,我们将其称为SCSIDevice_Dispatch。在SCSIDevice_Dispatch函数中,首先会解析你SCSI指令中的CDB部分,判断CDB请求是否符合要求。处理完CDB请求后,会来到我们将其称为HBAHosted_PostIo的函数中。 该函数中有一段特别的代码逻辑,这段代码逻辑可以理解为磁盘验证器功能,磁盘验证器负责检测磁盘是否存在坏扇区。验证原理如下:硬盘以块为单位(扇区)写入数据,硬盘每次更新扇区时,也会更新校验和(紧接在扇区数据之后存储)。当从硬盘驱动器读取扇区时,预计扇区校验和将与扇区数据匹配,如果不匹配,则表示磁盘在写入操作期间出现了问题,存在坏扇区。 VMware的磁盘验证器代码中,申请了一块等价磁盘大小的堆作为校验和的存储内容,对磁盘进行访问操作时(Read/Write),磁盘验证器并没有对命令中访问扇区的边界做任何检测与限制,直接计算用户访问的目标扇区的校验和并将其写入对应内存,这就导致了用户访问的目标扇区可能超出磁盘的扇区上限,引发严重的堆越界访问问题。 又因为CDB命令中,读写指令的最大范围为Write16/Read16。 即允许访问的扇区数最大数据类型为uint64,从而形成了骇人听闻的任意写漏洞。 该漏洞同时存在于Workstation/Esxi中,但对应的触发路径不尽相同。在Workstation中,可以从CD/ROM或SCSI磁盘两个方面分别去触发该漏洞;但在Esxi中,只能从CD/ROM去触发漏洞,无法从SCSI磁盘角度去触发。 因为在Esxi中,VMM会判断主机是否处于hostedEmulation模式,如果处于,就将请求通过UserRPC发送到VMX中处理,如果不处于,就将请求通过vmkcall发送到VMKernel,这就导致了在Esxi的磁盘设备上无法触发该漏洞。 当然,正因为Esxi会将磁盘请求发送到VMKernel中去处理,这也引出了后续关于VMKernel中磁盘的漏洞。 三、漏洞二:CVE-2024-37086:Out-of-bounds Read 我们发现,VMware在VMKernel中对发送来的磁盘请求处理已经尽可能的小心翼翼,禁用了许多不必要的SCSI命令,只启用小部分必要的SCSI命令。但即使是在如此谨慎的情况下,被启用的SCSI命令中依然存在严重的设计问题。 SCSI命令中的UNMAP命令允许一个或多个LBA(Logical Block Address)被取消映射,该命令常用在精简配置技术中,以提高存储利用率、灵活的容量规划和不间断存储配置服务。 在SPC-6(SCSI Primary Commands - 6)中对于UNMAP命令的设计如下图所示: 先来解释一下图里重要的数据结构: Table 204 UNMAP command中最重要的字段为PARAMETER LIST LENGTH,表示应用客户端发送到设备服务器的 UNMAP 参数数据的长度...
本文是天工实验室在DEFCON上发表的议题《Dragon Slaying Guide: Bug Hunting in VMware Device Virtualization》的第二部分内容。在该议题的第一篇文章中,介绍了VMware虚拟化实现的内容。 本周将继续讲解议题第二个重要部分:USB Device Virtualization,通过讲解我们挖掘的漏洞,介绍主机控制器、VUsb及模拟USB设备,即整个USB系统视角下各处可能出现的安全问题。 一、前言 我们对vmm和vmx以及是其他位于Host上的服务上进行逆向分析,并且尽可能全面的分析整个 USB 设备的模拟过程,从USB主机控制器的实现(UHCI, EHCI, XHCI)到具体后端USB设备的模拟,并在其中发现了 多个错误,其中一些甚至与天府杯2023的所使用的漏洞达成了一致,接下来我们会介绍VMware对USB系统模拟的实现,以及其中一些典型错误产生的原因,以及所能它们带给我们的启示。 VMware 实现 USB 模拟的代码,大体上可以结构为三个部分,第一个部分为 USB 主机控制器模拟,这部分代码负责模拟 USB 主机控制器设备,包括 UHCI,EHCI,XHCI 三种,这部分代码负责处理整个USB传输中与主机控制器相关的数据传输部分,三种控制器的mmio处理基本都是现在 vmm 中,vmm 负责按照手册标准处理内存寄存器访问的各种实现,并通过UserRpc调用vmx配合实现具体主机控制器的数据传输过程。整个模拟最重要的部分为将Guest传输的数据从主机控制器表示形式转换成保存在URB对象的USB Request形式,并将URB对象通过VUsb中间层传输给后端USB设备。 第二个部分我称为 VUsb 中间层,这层代码负责对接主机控制器和后端USB设备,他在所有后端USB设备上形成了一层抽象,USB主机控制器代码大多负责将Guest的输入重新封装成 URB 对象,VUsb 这层代码则负责管理URB相关的对象,他会更具后端目标设备类型的不同选择不同URB对象分配和释放方式,不同的后端USB设备还会在VUsb层形成一个统一表示后端设备的 VUsbDeviceObj,在传输URB时VUsb还会将Urb交给VUsbDeviceObj中目标Endpoint对应VUsbPipe对象管理。 第三个部分即VUsb 后端设备,VMware实现了大量不同类型USB设备的模拟,包括 HID,Bluetooth,CCID,他们大多采用一个回调函数处理URB对象,并根据URB对象的保存的数据进行行为模拟。其中比较特殊的时VUsbGeneric这类设备,这类设备在Guest Machine表示通用USB设备,一般用来直通Host上的物理USB设备,VMware通过Host上的本地 usbarbitrator 服务转发vmx中的请求数据。 二、漏洞一:CVE-2024-22255: Uninitialized Memory 我们分享的第一个漏洞来自于UHCI控制器模拟部分。在USB设备的控制传输中,USB设备使用有效载荷之一是Standard Device Request,他以 Setup Packet 格式开始。 Setup Packet Format Offest Field Size 0 bmRequestType 1 1 bRequest 1 2 wValue 2 4 wIndex 2 6 wLength 2 其中最有趣的字段在于 wLength,他将提醒 Usb 设备请求跟随的后续数据的长度。Standard Device request 是Usb设备的有效载荷,对于USB主机控制器来说它并不以此为单位传输数据,对于UHCI,他以 Transfer Descriptor 为单位传输数据,并在Guest内存中以Queue Head (QH) 类似链表的形式链接起来。VMware的UHCI控制器在处理控制传输时,需要以单次Standard Device request为单位分配URB对象,他将合并位于Queue Head上所有与请求相关的Transfer Descriptor (TD)中的待传输数据。...