阅读RAZOR

原文链接

意义

商用软件 大量功能 for 用户流行度,但个体用户仅需小部分功能

bloated code 缺点:阻碍最优执行;更多的薄弱点,更大的攻击面;浪费内存

RAZOR基于users‘ specification,采用几个control-flow heuristics去推断支持用户期望的功能所需的related-code,以此给deployed binaries进行code reduction

实现

software debloating最初总依赖 程序源代码可用(不可行:用户一般无权访问源码;rebuild软件很难;每个用户想去除的功能不一),故在软件在终端部署完成后对binary进行debloating最好,即post-deployment。

给定一个bloated binary和一组test casesRAZOR移除unnecessary code,产生一个debloated binary,它用minimal code size支持所有被要求的功能

大致实现流程:
在这里插入图片描述

  1. post-deployment approach的挑战:
    1)如何让没有普通end-user表达自己想要哪部分功能。
    终端用户提供一组sample input

    2)如何修改software binary只留下需要功能

    换个说法 1)如何表达unnecessary功能。2)如何映射高级功能到low-level code。(功能 to 代码) 3)怎么从有限的test cases中发现更多related-code

  2. related-code:对支持用户要求功能是必要的,但在处理sample inputs时未执行过的代码叫相关代码。

    但是用户很难提供一个齐全的input,能把实现某个功能的所需代码都用到。就算用户能提供得很全面,也很难识别这些input的reachable code。所以需要使用best-effort heuristics去尽可能的识别related-code。使用heuristics去推断related-code基于假设:具有较大差异的代码路径展示较少的相关功能(不同指令、调用新函数、额外库函数、新的高级功能)

  3. 识别出related-code后,开发一个binary rewriting platform移除不必要代码,产生一个debloated program。只保留与sample input相关的功能,需要反汇编跟control-flow graph(CFG),通过观察程序执行得到。

因此,RAZOR有三个组成部分:Tracer监控并记录执行给定sample inputs时的executed code,还可获得disassembling和CFG;PathFinder使用heuristics根据executed code识别出related code;Generator基于Tracer跟PathFinder的输出产生new binary。

具体设计
Execution Trace Collection

Tracer用test cases执行bloated program并记录control-flow information,主要是三类信息:1)executed instructions,包括指令的内存地址跟原始字节(raw bytes);2)条件分支是否taken(使用?);3)间接jumps和calls的明确目标以及跳转频数。

Tracer只记录每个executed basic block的地址,当发现动态代码行为才切换到instruction-level recording(平时不用因为指令级效率低且大多现实程序只有静态代码)

需要多个工具一同收集execution trace。只用软件仪器可能需要significant overhead,只用硬件可能要特殊硬件而且可能不全面。此外不同的跟踪环境下可能paths不同。

作者用software-based instrumentation(Dynamoria和Pin)收集简单程序完整traces:首先在静态块开始处记录其起始地址,在条件跳转的指令和目标间插入两个代码片收集是否taken,记录每个间接调用和跳转的确切目标。runtime时,静态块执行一次后马上移除,条件分支被taken后的instrumentation(测量代码?)马上移除避免开销,间接调用不移除;
hardware-based(Intel Processor Trace,Intel PT)跟踪大程序:TNT包记录条件分支的taken,TIP包记录间接调用的目标,Intel PT直接把trace写入物理内存(不经过页表和cache),非常高效的获取控制流信息。

构建CFG图:RAZOR根据collected execution traces反汇编bloated binary并构建CFG图。

Heuristic-based Path Inference

Zero-code heuristic(zCode):如果no-taken分支可通过其他块达到,就直接把该分支加到CFG图中
Zero-call heuristic(zCall):如果 non-taken分支不包含任何call指令,就加到CFG中(如进到if的某个不包含函数调用的分支里是可以的)
Zero-libcall heuristic(zLib):分支里的call指令调用的是相同binary下的函数?或者是已经被执行过的外部函数,该分支也可以添加进去
Zero-functionality heuristic(zFunc):如果分支调用了未被执行过的外部函数,但该外部函数不触发新的高级功能,那就也添加进去

Debloated Binary Synthesization(合成)

现在有了:original bloated binary 跟 expanded CFG
1.Generator对原始二进制按照CFG反汇编,生成一个包含所有必要指令的伪汇编文件(pseudo-assembly)。
2.将伪汇编文件修改成有效汇编文件,修改包括符号化基础块、具体化间接调用/跳转、插入错误处理代码。
3.编译汇编文件成为包含必要指令的机器代码的目标文件(object file)
4.复制机器代码到原始二进制中的new code section
5.修改新代码段来修复对原始代码和数据的所有引用?
6.设置原始代码段不可被执行(为什么还要把源代码保存在debloated program中?为了可能性读取?)

给每个basic block设置唯一标签(如L1,L2),然后把直接jump跟call的目标地址用标签替代,这样不管我们如何操作汇编文件都能产生正确的机器代码?(只更新显示的直接调用地址)

indirect call/jump具体化。确保所有possible targets都指向new code section。Generator处理间接调用:同一模块下的local targets,用许多compare-and-call指令替换,compare成功后执行。不同模块下的global targets,先通过全局转换函数找到正确模块,每个模块有一张存着被其他模块调用过的targets的translation table,然后从表中找到正确target。

错误处理:对于条件跳转到non-taken target或者间接调用指令target不被允许就直接转到fault handler,将退出执行并dumps the call stack。(这样真的好么?)。生成ELF binary后还得修改.gcc_except_table指向这个错误处理程序。

结果

在 常用benchmarks跟Firefox、FoxitReader上都成功从bloated binaries中减少大量代码并且没有引入任何安全问题,heuristics也只使code size少量增加,因此对于debloating现实app也是实用的

RAZOR能有效减少70%~80%的original code,只给新binary引入了1.7%的overhead,未引入任何安全问题。

RAZOR采用激进些的启发法zLib、zFunc等,虽然代码减少的稍微少一点,但是可以fail cases会减少很多。例举了一些不同启发法fail的实例:不用zCode就会一直进到if的正确分支;zCall训练集中if总不进,测试集里却可能进,zCall可以把进if的跳转指令加上;zLib可以进一些执行过的函数;zFunc,strcmp,strncmp

RAZOR更保守的移除bloated code,虽然移除的bugs可能少些,但不会引进新的bug

code reduction:因为RAZOR工作在binaries上面,所以不能测量源代码行的减少,所以测量的是可执行内存区域的大小。

RAZOR debloat CHISEL基准点基本少于一分钟,但是CHISEL要1到11小时。

针对Firefox跟FoxitReader,代码减少率达到60%跟80%以上,并且zLib跟zFunc基本可以做到无crash。对于一些银行网站、电力公司网址等,只支持clients所需的功能可以使攻击面降到最低,较安全。

缺点:假如程序包含大量间接调用,RAZOR的performance overhead会比较大,因为要用大量比较跟直接调用替换,引入大量if-else。解决:通过frequency-based optimization可以减少开销。
在一些包含大量内存跟string操作的库上debloating就不好用。因为这些库基于参数值、进程状态执行。解决:1.用功能级debloating代替basic block级 2.探索已有的库debloating方法。

CHISEL只支持given inputs,跟把输入输出提前对应起来没差。并且多次重复相同输入也会导致不同execution path。too naive。CHISEL实现smaller code size,但是有fails,还引入了可被利用的vulnerabilities(如buffer边界检查被移除),即CHISEL有很多鲁棒性问题跟安全问题,如unexpected operation、infinite loop(remove loop condition)、crashes(remove the check)、missed output(stdout\stderr)

dead code elimination方法和delta debugging方法只关注移除静态代码或者根据一些特定input阻止程序行为,不能识别related-code。

问题:

sample input的格式是什么?什么时候用户给出这么一个sample input?流式计算是直接默认用户的常用功能,其他功能按需加载还是也会有这么一个sample input?

代码切片跟debloating的关联在哪?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值