Wasm 软件生态系统安全分析
演讲嘉宾 | 王浩宇
回顾整理 | 廖 涛
排版校对 | 李萍萍
嘉宾简介
王浩宇,华中科技大学教授,博士生导师,华中科技大学OpenHarmony技术俱乐部主任。研究关注于新兴软件系统中的安全、隐私和可靠性问题,近五年发表CCF A类和CSRankings顶会论文近70篇,在软件安全和系统测量领域的顶会论文成果在国内名列前茅。
内容来源
第一届开放原子开源基金会OpenHarmony技术峰会——开发者工具分论坛
视频回顾
视频链接:https://www.bilibili.com/video/BV1UX4y1879e/?spm_id_from=333.999.0.0
正 文 内 容
WebAssembly(Wasm)是W3C标准化组织制定的一种高效、底层、可移植的字节码格式。目前,Wasm越来越多地用于浏览器、无服务器计算、跨平台容器和区块链DApp等场景。Wasm与OpenHarmony生态能够有哪些碰撞呢?华中科技大学网络安全学院王浩宇教授在第一届OpenHarmony技术峰会上,分享了当前在Wasm安全领域所做的探索,并展望了Wasm与OpenHarmony结合的新方向。
01►Wasm软件生态系统介绍
目前,如C、C++、Rust、Go、Java、C#等几乎所有主流高级语言都可以被编译到Wasm,所有的主流浏览器也均支持Wasm。此外业界也实现了很多独立的Wasm虚拟机(运行时),支持解释器、AOT、JIT等模式。
WebAssembly (Wasm) 及其运行环境
Wasm的执行架构与设计特点有:
- 类型安全的栈指令:线性时间类型检查算法,完全确定栈上值的数量和类型;
- 结构化的控制流指令:内部指令仅能根据嵌套结构跳转,简化了编译器实现;
- 可拓展的线性内存:一页为64KB,模块中指定初始内存页和最大内存页数量,运行时可动态增长;函数调用栈,返回地址等重要数据由外部运行时维护,保证了安全性;
- 指令和数据完全分离:函数“地址”通过下标表示,非直接跳转通过跳转表实现。
Wasm的应用前景十分广阔。例如,Wasm能够支持高效的Web计算,因此基于Wasm能够实现大型应用在浏览器中运行;Wasm也支持跨平台容器技术,适用于嵌入式、可信计算以及云计算等场景。此外,Wasm在Web 3.0/区块链中的DApp和智能合约等领域也被应用广泛。
Wasm多语言、跨平台以及高性能的特性,使其非常契合OpenHarmony面向万物互联新场景的开源生态,在移动设备上应用前景广泛。目前,WasmEdge开源项⽬维护者Michael Yuan等人已经发起了OpenHarmony Wasm-SIG提案,致⼒于宣传、实施、推⼴Wasm与OpenHarmony的集成,在OpenHarmony终端设备上可以安全高效的运行第三方开发者用 C、C++、Rust 等语言编写的 Wasm 程序,有利于扩大 OpenHarmony生态的开发者群体。
02►Wasm安全及相关研究
Wasm生态中也有很多安全问题得到了学术界的重视,包括前端编译器安全,代码移植安全,Wasm二进制安全,Wasm相关的恶意应用,Wasm可信执行环境等等。
- 代码内存安全:由于Wasm生态还较不成熟,在传统二进制中已经有成熟防御措施的漏洞仍然能够被利用。例如,由于缺乏stack canary机制,攻击者可以轻松利用栈溢出漏洞;Wasm也缺乏相关的堆保护机制等。
- 程序移植安全:大量的现有程序可以被“直接”编译到Wasm,但可能会引入bug或安全问题;移植会导致代码行为(如指针大小、内存能力、环境变量等)不同;移植时处理不当可能导致不同的堆内存管理实现困难、缺乏安全措施等安全问题。
- 恶意Wasm程序:目前很大一部分Wasm程序被用于恶意挖矿等行为,Wasm也可以作为混淆或者加壳方式被恶意软件利用。
Wasm (安全) 问题及学术界相关研究
然而,目前Wasm安全相关研究还处于初级阶段。一方面,Wasm不断引入的新特性与新场景持续带来新的安全问题和挑战;另一方面,Wasm几乎没有通用的程序分析框架,大部分工具都是针对特定平台的Wasm二进制分析(只支持部分指令集、只对平台相关库函数做建模),无法分析通用 Wasm 二进制;此外,Wasm的二进制反编译器还处于初期阶段,Wasm虚拟机和编译器不够成熟,Wasm代码混淆以及代码保护技术还较为欠缺等。
针对上述安全问题,王浩宇教授所带领的学术团队在Wasm二进制翻译、Wasm程序分析、Wasm运行时/编译器bug检测等方面做了安全增强相关工作。例如,在区块链智能合约场景下,实现了从EVM字节码到eWasm字节码的安全Wasm二进制翻译;提出了Wasm符号执行框架EOSafe、Wasm模糊测试框架WASAI、Wasm通用二进制重写框架BREWasm、Wasm二进制混淆工具Chaos等分析技术。此外,王浩宇教授团队提出了针对Wasm运行时的模糊测试技术,已经在wasmer,wasmtime,WAMR,wasm3,Wasm Edge等运行时中发现了数十个代码缺陷。
王浩宇教授的团队Wasm相关研究工作
03►Wasm二进制重写及其安全应用
在本次峰会的开发者工具分论坛,王浩宇教授介绍了其团队提出的一个通用的Wasm二进制重写工具。Wasm二进制重写具有无需源码、跨平台和跨语言等优点,其应用场景包括Wasm程序修复、测试用例生成、代码插桩、辅助动态分析、Wasm代码漏洞检测、Wasm模糊测试、Wasm二进制保护与混淆等。目前,学术界已有的Wasm二进制重写和插桩的研究大都只局限于简单的指令级别修改,比如在某一指令前后添加一些指令,对控制流的修改也仅局限于某一特定模式的更改等。然而,一个通用的Wasm二进制重写框架是如上众多Wasm研究工作的基础。
实现一个通用的Wasm二进制重写框架存在一些挑战。
(1)Wasm不同段之间的耦合性:Wasm中,一个函数的所有信息,包括函数签名,函数指令等内容分布在不同的Wasm段中,这导致对单独一个段的重写不足以实现Wasm中的一个微小功能。而且开发者需要熟悉多个段的不同的数据结构才能实现对Wasm中某一功能的重写;
(2)结构化控制流与控制流修改:Wasm没有goto-like的跳转指令,且只有将代码块进行嵌套才能添加跳转指令,这对实现灵活的控制流重写带来了较大挑战
(3)Wasm栈平衡校验和修复:一个正确的Wasm二进制需要满足静态校验规则。例如,一个函数的所有信息都靠函数的index索引,且函数的指令需要满足栈平衡。在对Wasm二进制重写后,如果出现了index之间的索引不一致或某一函数指令没有栈平衡等静态校验问题,则会生成错误的Wasm二进制。
王浩宇教授团队针对上述挑战,分别提出了相应的解决方案。针对挑战(1),除了提供细粒度的对每个段中数据结构的重写功能外,还将各个段的结构抽象成一组语义并提供大量针对语义的重写API,使开发者不需要关心底层对各个段的修改逻辑。
针对挑战(2),提出控制流结构原子化的方法,在加载Wasm模块时,对指令进行分割和构建代码块(原子化),利用原子化的控制流结构可以组合构建更复杂的控制流结构,并在修改结束后,将基于原子化代码块的控制流结构转换回Wasm指令。
针对挑战(3),实现两个辅助模块indices-fixer和stack-calculator以修复索引错误和栈平衡。
BREWasm框架
基于上述解决方案,王浩宇教授带领的团队提出了一个通用的Wasm二进制重写框架——BREWasm。该框架主要包括以下5部分功能:
- Wasm Parser:给定一套简易DSL,将Wasm段和数据结构抽象,并解析为一个可操作对象的列表;
- Section Rewriter:基于Wasm段和数据结构抽象,实现细粒度段重写API;
- Semantics Rewriter:将段重写API进行组合,实现一组语义更为丰富的Semantic API;
- Control Flow Reconstructor:实现了一组能够任意灵活修改控制流且无需关注栈平衡的Control Flow API;
- Wasm Encoder:将重写后的可操作对象列表按照段和数据结构抽象重新编码为合法的 Wasm 二进制。
Wasm控制流原子化示意以及BREWasm中提供的部分Control Flow API
BREWasm能够应用在Wasm代码混淆、Wasm程序栈溢出保护、Wasm程序插桩等场景。例如,在Wasm二进制混淆上,BREWasm采取切分原有Wasm代码块来获得控制流重写的基本元素,并将这些元素构成一个switch-case的控制流结构,再将其插入到while控制流中,可以仅用几行代码实现对任意Wasm程序的控制流平坦化混淆;在Wasm程序栈溢出保护上,仅调用BREWasm提供的几个API,即可实现对可能存在栈溢出问题的函数进行hook,在函数被调用前提前在栈上插入canary,在函数执行完成后去检测canary的值是否发生改变,来确定函数执行过程中是否出现了栈溢出问题;在Wasm程序插桩上,BREWasm能够对Wasm二进制插桩,实现动态污点分析,调用图分析,内存访问分析,恶意挖矿检测等功能,也能够给定插桩规则,对每个Wasm指令进行自动化插桩,为Wasm 二进制导入外部实现的分析API。此外,BREWasm还能够非常方便地应用于Wasm代码变换、Wasm程序修复以及Wasm模糊测试等场景。
BREWasm对任意Wasm程序实现控制流平坦化示例
04►总结与展望
跨语言、跨平台、跨场景的开源软件生态是发展趋势,也引入众多新的攻击面。Wasm的特性使其非常契合OpenHarmony面向万物互联新场景的开源生态,而其中的安全问题也不可忽视。期待学术界和工业界一起,为万物互联的开源生态添砖加瓦,持续为新兴软件安全赋能!
上一篇: 微信小程序 app.js 的详细介绍
推荐阅读
-
Wasm 软件生态系统安全分析
-
windows下进程间通信的(13种方法)-摘 要 本文讨论了进程间通信与应用程序间通信的含义及相应的实现技术,并对这些技术的原理、特性等进行了深入的分析和比较。 ---- 关键词 信号 管道 消息队列 共享存储段 信号灯 远程过程调用 Socket套接字 MQSeries 1 引言 ---- 进程间通信的主要目的是实现同一计算机系统内部的相互协作的进程之间的数据共享与信息交换,由于这些进程处于同一软件和硬件环境下,利用操作系统提供的的编程接口,用户可以方便地在程序中实现这种通信;应用程序间通信的主要目的是实现不同计算机系统中的相互协作的应用程序之间的数据共享与信息交换,由于应用程序分别运行在不同计算机系统中,它们之间要通过网络之间的协议才能实现数据共享与信息交换。进程间通信和应用程序间通信及相应的实现技术有许多相同之处,也各有自己的特色。即使是同一类型的通信也有多种的实现方法,以适应不同情况的需要。 ---- 为了充分认识和掌握这两种通信及相应的实现技术,本文将就以下几个方面对这两种通信进行深入的讨论:问题的由来、解决问题的策略和方法、每种方法的工作原理和实现、每种实现方法的特点和适用的范围等。 2 进程间的通信及其实现技术 ---- 用户提交给计算机的任务最终都是通过一个个的进程来完成的。在一组并发进程中的任何两个进程之间,如果都不存在公共变量,则称该组进程为不相交的。在不相交的进程组中,每个进程都独立于其它进程,它的运行环境与顺序程序一样,而且它的运行环境也不为别的进程所改变。运行的结果是确定的,不会发生与时间相关的错误。 ---- 但是,在实际中,并发进程的各个进程之间并不是完全互相独立的,它们之间往往存在着相互制约的关系。进程之间的相互制约关系表现为两种方式: ---- (1) 间接相互制约:共享CPU ---- (2) 直接相互制约:竞争和协作 ---- 竞争——进程对共享资源的竞争。为保证进程互斥地访问共享资源,各进程必须互斥地进入各自的临界段。 ---- 协作——进程之间交换数据。为完成一个共同任务而同时运行的一组进程称为同组进程,它们之间必须交换数据,以达到协作完成任务的目的,交换数据可以通知对方可以做某事或者委托对方做某事。 ---- 共享CPU问题由操作系统的进程调度来实现,进程间的竞争和协作由进程间的通信来完成。进程间的通信一般由操作系统提供编程接口,由程序员在程序中实现。UNIX在这个方面可以说最具特色,它提供了一整套进程间的数据共享与信息交换的处理方法——进程通信机制(IPC)。因此,我们就以UNIX为例来分析进程间通信的各种实现技术。 ---- 在UNIX中,文件(File)、信号(Signal)、无名管道(Unnamed Pipes)、有名管道(FIFOs)是传统IPC功能;新的IPC功能包括消息队列(Message queues)、共享存储段(Shared memory segment)和信号灯(Semapores)。 ---- (1) 信号 ---- 信号机制是UNIX为进程中断处理而设置的。它只是一组预定义的值,因此不能用于信息交换,仅用于进程中断控制。例如在发生浮点错、非法内存访问、执行无效指令、某些按键(如ctrl-c、del等)等都会产生一个信号,操作系统就会调用有关的系统调用或用户定义的处理过程来处理。 ---- 信号处理的系统调用是signal,调用形式是: ---- signal(signalno,action) ---- 其中,signalno是规定信号编号的值,action指明当特定的信号发生时所执行的动作。 ---- (2) 无名管道和有名管道 ---- 无名管道实际上是内存中的一个临时存储区,它由系统安全控制,并且独立于创建它的进程的内存区。管道对数据采用先进先出方式管理,并严格按顺序操作,例如不能对管道进行搜索,管道中的信息只能读一次。 ---- 无名管道只能用于两个相互协作的进程之间的通信,并且访问无名管道的进程必须有共同的祖先。 ---- 系统提供了许多标准管道库函数,如: pipe——打开一个可以读写的管道; close——关闭相应的管道; read——从管道中读取字符; write——向管道中写入字符; ---- 有名管道的操作和无名管道类似,不同的地方在于使用有名管道的进程不需要具有共同的祖先,其它进程,只要知道该管道的名字,就可以访问它。管道非常适合进程之间快速交换信息。 ---- (3) 消息队列(MQ) ---- 消息队列是内存中独立于生成它的进程的一段存储区,一旦创建消息队列,任何进程,只要具有正确的的访问权限,都可以访问消息队列,消息队列非常适合于在进程间交换短信息。 ---- 消息队列的每条消息由类型编号来分类,这样接收进程可以选择读取特定的消息类型——这一点与管道不同。消息队列在创建后将一直存在,直到使用msgctl系统调用或iqcrm -q命令删除它为止。 ---- 系统提供了许多有关创建、使用和管理消息队列的系统调用,如: ---- int msgget(key,flag)——创建一个具有flag权限的MQ及其相应的结构,并返回一个唯一的正整数msqid(MQ的标识符); ---- int msgsnd(msqid,msgp,msgsz,msgtyp,flag)——向队列中发送信息; ---- int msgrcv(msqid,cmd,buf)——从队列中接收信息; ---- int msgctl(msqid,cmd,buf)——对MQ的控制操作; ---- (4) 共享存储段(SM) ---- 共享存储段是主存的一部分,它由一个或多个独立的进程共享。各进程的数据段与共享存储段相关联,对每个进程来说,共享存储段有不同的虚拟地址。系统提供的有关SM的系统调用有: ---- int shmget(key,size,flag)——创建大小为size的SM段,其相应的数据结构名为key,并返回共享内存区的标识符shmid; ---- char shmat(shmid,address,flag)——将当前进程数据段的地址赋给shmget所返回的名为shmid的SM段; ---- int shmdr(address)——从进程地址空间删除SM段; ---- int shmctl (shmid,cmd,buf)——对SM的控制操作; ---- SM的大小只受主存限制,SM段的访问及进程间的信息交换可以通过同步读写来完成。同步通常由信号灯来实现。SM非常适合进程之间大量数据的共享。 ---- (5) 信号灯 ---- 在UNIX中,信号灯是一组进程共享的数据结构,当几个进程竞争同一资源时(文件、共享内存或消息队列等),它们的操作便由信号灯来同步,以防止互相干扰。 ---- 信号灯保证了某一时刻只有一个进程访问某一临界资源,所有请求该资源的其它进程都将被挂起,一旦该资源得到释放,系统才允许其它进程访问该资源。信号灯通常配对使用,以便实现资源的加锁和解锁。 ---- 进程间通信的实现技术的特点是:操作系统提供实现机制和编程接口,由用户在程序中实现,保证进程间可以进行快速的信息交换和大量数据的共享。但是,上述方式主要适合在同一台计算机系统内部的进程之间的通信。 3 应用程序间的通信及其实现技术 ---- 同进程之间的相互制约一样,不同的应用程序之间也存在竞争和协作的关系。UNIX操作系统也提供一些可用于应用程序之间实现数据共享与信息交换的编程接口,程序员可以通过自己编程来实现。如远程过程调用和基于TCP/IP协议的套接字(Socket)编程。但是,相对普通程序员来说,它们涉及的技术比较深,编程也比较复杂,实现起来困难较大。 ---- 于是,一种新的技术应运而生——通过将有关通信的细节完全掩盖在某个独立软件内部,即底层的通讯工作和相应的维护管理工作由该软件内部来实现,用户只需要将通信任务提交给该软件去完成,而不必理会它的具体工作过程——这就是所谓的中间件技术。 ---- 我们在这里分别讨论这三种常用的应用程序间通信的实现技术——远程过程调用、会话编程技术和MQSeries消息队列技术。其中远程过程调用和会话编程属于比较低级的方式,程序员参与的程度较深,而MQSeries消息队列则属于比较高级的方式,即中间件方式,程序员参与的程度较浅。 ---- 4.1 远程过程调用(RPC)