一种趣的软件隔离方式

最近读到一篇16年NDSS的文章,感觉很有趣,同时也给自己的研究方向带来了一定的帮助,给大家分享一下,希望能给大家带来帮助。废话不多说,直接开始。

加载屏蔽

1.软件实现XoM

所有的RISC指令集包括咱们最常用的ARM的所有内存读取操作必然是用一个加载指令,比如:ldr。但是这样对于手机这样的小型移动设备很不友好,所以需要MMU的XoM提出。大概方法是这样:

  1. 将内存中代码和数据分区域放置
  2. 屏蔽每一个可能被攻击者使用加载指令的控制地址,防止读取代码页

我们可以把地址空间分为两部分来简化这个屏蔽的复杂度;数据防止在低地址空间代码就放置在高地址空间。
一种趣的软件隔离方式_第1张图片
图上有一个Guard Region,它是由两个标记为不可访问的内存页构成的。这个区域在基址上加了一个小的常量偏移优化了加载过程。用了这样的分割就可以在运行时候检测最重要的一位来判断这个地址是指向数据还是代码。这样所有有效的数据地址包括所有被保护的内存地址这一位上都是一个0。
因为我们做的是一个内存访问策略不是程序完整性所以对于ARM指令集我们可以用一个更加有效率的地址检查方式。因为在bic指令还有tst指令后面会有加载指令跟在后面。这样我们就可以直接检查加载地址那个重要的一位。tst指令屏蔽通过地址的测试来预测加载指令,从而避免了屏蔽指令和负载之间的数据依赖。同时还可以在地址查找失败之前插装,直接跳转到地址冲突处理机制。而tst是不能应付已经预先设置号的场景,而且bic的效率是它的2倍多。我想了一下原因可能会根据分支预测执行,在错误的情况下导致流水线某一部分丢失。同时,bic无序执行给它的屏蔽带来了很多好处。

-----------------------------------------------------------------------------------------------
这里就得插一个分支预测和无序执行
现代的计算机对于执行速度的要求越来越高这样就出现了流水线,而且流水线执行越来越重要,这就需要知道当前执行的下一步需要执行什么。分支预测的基本思想始于Tom McWilliams和Curt Widdoes引入两位预测器。两位预测使用0到3之间的简单计数器来预测是否采用分支指令。如果计数器为0或1,则预测器预测没有执行分支指令。如果是2或3,它预测指令将被执行,下一条指令应该来自所采取的路径。如果预测错误,则获取正确的指令。并根据分支是否被采用而决定将计数器是否被更改为1。
IBM创造了IBM 7030,也被称为延伸。扩展包括许多不同的组织技术,包括预编码、内存操作数预取、无序预测、分支错误预测恢复和精确中断。STRETCH预先执行了所有使用索引寄存器的分支,这导致了大量分支的错误预测。这个分支的错误预测导致拉伸性能很差。Burroughs B4900使用分支预测历史状态,在程序执行期间存储在内存中。该方案通过使用分支操作码表示分支操作符类型来实现4种状态的分支预测。这个特定的方案预测分支的准确率为93%。

分支类型 有条件分支 无条件分支
直接 If-then-else; for loop Precedural(jal); goto(j)
间接 Return(jr); function pointers(jalr)

在英特尔奔腾3和奔腾4有一个分支预测单元。Pentium III使用本地分支预测,它保留一个历史缓冲区来预测分支,准确率约为90%。奔腾4只是使用了一个更有效的分支预测算法,准确率约为94%。
今天,Intel的Intel Core i7在程序运行时使用一个分支数组来存储分支的结果,并使用一个算法来尝试确定下一个分支。新算法使用两层预测器,第一级存储最近的分支历史,第二级存储较慢的访问,但存储更多的分支历史。这对于在大量代码(如数据库)中进行分支预测非常有用。

--------------------------------------------------------------------------------------------

优化加载屏蔽
利用SFI的优化:
受到Wahbe的SFI的影响,通过设置敏感数据存储的基地址的掩码,同时允许加上较小的常量寻址,这就避免了额外的地址计算加法指令,而且基地址的掩码让堆栈指针始终指向数据部分中有效地址,这就可以进行常量偏移的堆栈访问,但是非常数偏移是不能执行的。另外我们不能限制程序计数器也是常量偏移的加载。ARM不允许使用32位的立即数指令操作数,所以较大的常量存储在每个函数之后分配的常量池中。这些常量池在代码段是可读数据,但对常量池的访问是受到严格限制。所有常量池中的加载使用的常量偏移都来自当前程序计数器,所以不用担心 攻击者会从这里来攻击。这个方法不需要在每个load指令前去处理,只需要在生成load地址指令之后进行处理就可以了。

向前调用指针隐藏

这个方法没什么好讲的,很简单就是将指向函数和虚方法的前向指针替换为指向跳转数组的指针,即,直接跳转到原始的指针地址,存储在XoM中。

返回地址隐藏

运用XoM来保证存储返回地址不被读取,而且对于返回地址采用XoR进行加密。

你可能感兴趣的:(代码重用防御)