Group of Software Security In Progress

GoSSIP @ LoCCS.Shanghai Jiao Tong University

Things You May Not Know About Android (Un)Packers

作者:Yue Duan,Mu Zhang,Abhishek Vasisht Bhaskar,Heng Yin,Xiaorui Pan,Tongxin Li,Xueqiang Wang,XiaoFeng Wang

单位:University of California, Riverside † Cornell University ,Grammatech. Inc.,Indiana University Bloomington,Peking University

链接:http://wp.internetsociety.org/ndss/wp-content/uploads/sites/25/2018/02/ndss2018_04A-4_Duan_paper.pdf

概述

DROID UNPACK是一个基于 DroidScope模拟器实现的一个脱壳工具。

1

整个Android系统以及被加壳的APP运行在模拟器中,分析与脱壳工作实现在模拟器外。

DROID UNPACK实现了4个工具来分析被加壳的APP:

  • 隐藏代码提取器:用于识别并获取包含DEX、OAT方法的内存区。
  • 多层次脱壳检测器:大多数保护壳并不会一次性把保护程序释放到内存中,而是经过多次操作慢慢释放。多层次脱壳检测器可以记录这种逐步释放保护代码的行为。(The Multi-layer Unpacking Detector discovers iterative unpacking operations that intermittently occur in multiple layers.)
  • 代码自修改检测器:用于检测一种更隐蔽的脱壳操作,即故意删除先前执行过的代码。
  • JNI调用监视器:用于监控来自JNI接口的敏感API调用。

SandScout- Automatic Detection of Flaws in iOS Sandbox Profiles

作者:Luke Deshotels, Ra ̆zvan Deaconescu, Mihai Chiroiu, Lucas Davi, William Enck & Ahmad-Reza Sadeghi
原文:CCS’16
单位:North Carolina State University, University POLITEHNICA of Bucharest & Technische Universität

Contributions

  • 开发了一款工具以自动化分析iOS的沙箱规则并生成人类可理解的SBPL规则。首先,工具会自动化解压、反编译iOS中的沙盒描述文件到SandBox Profile Language(SBPL)
  • 使用Prolog对SBPL规则进行了建模。将SBPL转换为Prolog的facts之后,借助Prolog对沙箱配置进行建模;最后通过他们设计的Prolog查询来对这些沙箱规则进行安全性测试并且用一个iOS APP对检测到的安全问题进行验证。
  • 对iOS的沙箱描述进行了系统化测试。通过他们的工具发现了iOS 9.0.2下的七个漏洞。列举如下
1
2
3
4
5
6
7
1. 绕过iOS隐私设置获取联系人信息
2. 获取用户的位置搜索历史信息
3. 通过获取系统文件的meta信息推测敏感数据
4. 获取用户的姓名和媒体库
5. 破坏磁盘空间且不能通过卸载这个恶意app来恢复
6. 禁止访问系统资源,如地址簿
7. 不使用iOS的进程通信机制完成进程间通信

Rage Against the Virtual Machine: Hindering Dynamic Analysis of Android Malware

Do not go gentle into that good night.

Rage, rage against the dying of the light.

—– From The Poems of Dylan Thomas (1914 – 1953)

论文 Rage Against the Virtual Machine: Hindering Dynamic Analysis of Android Malware 是一篇发表在European Workshop on System Security会议上的很有趣的论文。它提出了两种(一种?)app检测自己是否处于模拟环境的通用方法。

论文下载

文章前半部分都在介绍背景,现状,指出现有的Android Malware 使用一些“启发式的模拟环境检测”来对抗一些自动化的动态分析引擎,采用的启发式特征包括设备序号:

  • IMEI
  • 网络参数:MAC,所处网段
  • 各种传感器:加速度计,陀螺仪,GPS。。
  • 能被检测出来,都是因为模拟环境不够真实。。
  • 这些问题很容易修正,导致方法很快失效

作者的方法1:

  • 首先看作者对模拟环境一系列假设(全系统模拟):
  • 在真实的物理CPU中,PC在每条指令执行之后都会增长
  • 模拟器为了效率引入JIT,VPC只在基本块结束才会更新
  • 多线程会相互打断,上下文的切换则是由操作系统完成的
  • 如果在JIT的基本块被中断,上下文的PC可能是没办法正常存储的
  • 模拟器为此才去的策略让中断只发生在基本块之间的branch语句上
  • 接下来其实我们只需要观测到这种现象就可以说明自己处在模拟环境下

作者的例子:

Thread 1:

1
2
3
4
5
6
while (ture) {
  global = 1
  global += 1
  global += 1
  global += 1
}

Thread 2:

1
2
3
while (true) {
  print(global)
}

Thread2 看到的global的值如果绝大多数都是4的话,就说明自己在模拟器中

作者的方法2:

  • 哈佛结构的ARM(?)并不保证ICache 和 DCache 的一致性
  • 也就是自修改代码再修改完代码段之后,还需要调用cacheflush函数清空ICache并把DCache写回,才能保证CPU读到指令的是修改过的。。。
  • 然而。。。模拟器并不太可能去再实现一个虚拟缓存。。。效率太低了。。。

所以总结来说,作者给出了一个很难被解决的检测模拟环境的方法,针对跨架构的动态分析应该是比较有效。进一步,作者也给出了一些应对的措施,解决方法主要两点:更加精确地二进制翻译(慢),或者使用物理设备辅助模拟执行(比如说Avatar)。。

Security Analysis of Android Factory Resets

论文下载:http://www.cl.cam.ac.uk/~rja14/Papers/fr_most15.pdf

最近传出一则新闻《Android手机恢复出厂设置后,数据依然可恢复》,炒得沸沸扬扬,虽然这则新闻来自于剑桥大学研究人员在2015年MoST安全会议上发表的论文Security Analysis of Android Factory Resets,但是新闻的编辑显然对Android数据擦除和论文所讨论的内容理解上存在一定的偏差,因此我们有必要重新审视这篇论文,并对真实情况下的Android数据擦除进行探究。

1 背景

首先要澄清的一点是,这篇论文的作者并没有像新闻中所说“发现超过五亿Android手机在恢复出厂设置后并没有完全擦除手机数据”,他们只是对26台相关的手机进行了分析,然后推测出类似的手机存在安全问题。但是对于研究人员而言,我们想要深入了解到底这样的安全问题的根源是什么?这也是我们今天论文阅读要深入讨论的主题。

关注数据擦除问题,首先要理解数据擦除的三个层次,在本文中作者也介绍了这三个层次分别是:

  1. analog sanitisation,在模拟电路层面的擦除,真正意义上的“data purging”;
  2. digital sanitisation,在数字电路层面的清除,若在此层面上清除的数据,用任何设备本身提供的接口方式去读取(绕过操作系统访问控制)都无法读取到数据;
  3. ogical sanitisation,通过存储介质提供的操作接口进行的数据删除,通过这种清除方式删除的数据无法通过同一类型的接口进行数据读取。对于移动电话和PDA,NIST 800-88 对于数据删除定义为“clearing” level,建议先删除数据,然后执行Factory Reset.

我们想要在这里一并推荐两篇相关论文:第一篇论文Reliably Erasing Data From Flash-Based Solid State Drives来自FAST 2011,这篇论文主要阐述了三个观点:第一,存储介质的 built-in commands 通常能提供有效的数据擦除功能,但是并不能保证这些命令总是被制造商正确地实现;第二,将SSD的所有数据全部覆盖两次(在大部分情况下)能够有效地清除数据;第三,对于单个文件删除而言,当前的SSD都存在不足之处(这个我们在后面会展开讨论)。第二篇论文User-Level Secure Deletion on Log-structured File Systems来自AsiaCCS 2012,介绍了对于Log-structured File Systems(例如ext4这种)日志化文件系统的用户级别安全删除,当然,这篇文章发表的时间较早,因此只讨论了早期Android版本常用的 YAFFS 文件系统,作者认为只要进行特定的加密或者反复写操作,就能够有效地防止数据恢复攻击。

为了更好理解上述结论,我们简单介绍一下一些数据擦除的相关知识,首先,回到Android设备上,我们知道Android设备通常使用的存储介质是eMMC(Embedded Multi Media Card),主要是针对手机或平板电脑等产品的内嵌式存储器标准规格。这一类存储介质的特点是提供了一系列的操作COMMAND指令,而Android通过kernel的 ioctl system call 来接收上层应用发出的数据删除请求,并相应的执行不同的eMMC COMMAND来进行数据删除。例如 BLKDISCARDBLKSECDISCARD 指令,前者往往会被kernel更换为 DISCARD 或者 TRIM 指令,而后者能够确保数据安全的删除,但是这两种操作都仅仅是logical sanitisation,如果需要执行digital sanitisation.,可以使用 SECURE TRIM , SECURE ERASESANITIZE 指令。

其次,我们需要介绍一下自从 Android 4.3 开始正式支持的TRIM特性,TRIM实际上是一个存储介质引入的ATA指令,由操作系统发送给SSD主控制器,告诉它哪些数据占的地址是需要清理的。传统的情况下,当一个文件被删除的时候,操作系统并不会真正的去删除它。操作系统只是把这个文件的地址标记为空,让它可以被再次使用,这表示这个文件所占的地址已经无效。而由于SSD的特性,这种操作就会出问题:SSD写入数据要以一整页的粒度写入(大小通常为4至8千字节),而写入需要先擦除这整一页才能再次写入数据,为了保证这一页上的有效数据不被擦除,SSD必须先复制这一页到新的空白页上,然后才能擦除这个旧的页。如果没有TRIM,传统的SSD主控制器在对一个页上某些数据进行擦除之后,要等到操作系统要求覆盖数据进去之时,才会执行上述的复制-写操作。这样一来会让写入速度变慢,二来造成了数据残留。TRIM指令的引入,相当于在硬件控制器层面上引入了一个隐式的自动化磁盘碎片整理和数据擦除机制,这样就能从某种程度上减轻数据残留的风险。当然,这个特性必须由硬件本身支持来完成,操作系统仅仅是发送了这样一个信息给硬件控制器,至于硬件什么时候去进行数据的删除和重整理工作,上层是一无所知的。

可以看出,Android上数据残留问题有着非常复杂的现实制约因素(硬件和软件层面),实际上,无论是论文Security Analysis of Android Factory Resets还是这则《Android手机恢复出厂设置后,数据依然可恢复》的新闻,都只关注了不同版本、不同设备上执行 Factory Reset 之后数据的残留问题,但是却没有对这些问题的本质进行总结,我们于是进行了深入的分析和总结,并给出了我们的相关结论:

2 Factory Reset 何时安全(或不安全)?

我们在2013-2014年间同样开展了大量的数据删除和数据恢复实验工作,我们发现,Factory Reset 对数据的有效清理,决定于两个因素:

A. 是否正确地调用了数据清除接口

我们发现,Android平台上全磁盘数据清除一般发生在两种情况下:第一,当用户手工触发了操作系统或Recovery提供的Factory Reset,注意到不管是操作系统执行Factory Reset还是Recovery执行Factory Reset,实际上都是需要重启进入Recovery来完成这个工作的,Android本身并不负责进行这个工作(绝大多数情况下,手机的官方Recovery都不提供用户操作的界面,当然也不排除少数手机自带可操作的Recovery)。如前所述,SSD控制器通常会提供一些特殊的COMMAND来帮助数据清除,例如 BLKSECDISCARD,按照正常的情况,Recovery会去调用这个指令来安全的擦除磁盘,我们的测试表明在大部分手机上这个功能是安全可靠的(相关分区所有可读数据块完全被置为0,凭借普通的恢复技术得不到任何有用数据);第二,很多手机提供了所谓“Bootloader解锁”功能,在解锁的同时就会强制触发一个全磁盘清理(通常这个清理会处理/data和/sdcard两个分区),根据我们的测试这也是一个比较安全的数据清除过程。

但是,这个过程存在一些变数,有一个非常著名的例子即三星字库门事件 ( http://wiki.cyanogenmod.org/w/EMMC_Bugs ),就是因为eMMC芯片上对erase命令支持有问题,一旦执行了相关操作有很高概率导致设备变砖,因此后续的补丁释出时就在kernel上patch了MMC_CAP_ERASE flag 来强行屏蔽各类erase命令。在这种情况下,Factory Reset自然就会变成不安全的。

事实上,论文Security Analysis of Android Factory Resets对于上层数据删除对应的底层命令给出了一个非常好的总结表格:

Fig

B. 是否为了兼容性使用了不安全的清除方式

Android设备上有一个比较著名的“刷机”特性。当用户为了绕过官方的签名限制刷入新的rom包时,往往会给系统先刷入一个第三方的Recovery,比较著名的第三方Recovery包括CWM和TWRP。我们发现,在这种情况下,第三方Recovery为了兼容性往往会引入不安全的Factory Reset,这个兼容性问题来自于现在诸多Android设备都将SD卡内置入手机中(用户使用的外置SD卡的品质无法保证,干脆全部屏蔽了),实际上就是将/data分区下的一个目录虚拟为/sdcard,这样导致了一个问题,如果清理/data分区就会把/sdcard分区全部清理掉,实际上Android默认是不希望清理掉SD Card上的数据的,于是第三方Recovery就自作聪明的去用rm -rf命令来逐一清理/data下的各个目录(除了/sdcard),这种方法根本无法保证数据的清除,会导致极为严重的数据残留。实际上,我们认为这是Android Factory Reset导致数据残留里面,最根本性的问题。

3 还有哪些导致数据残留的问题?

我们进一步发现,不仅仅是“恢复出厂设置”这个机制背后隐藏了大量的数据残留问题,在Android平台上同样存在很多平时不为人注意的操作会导致数据残留问题。

3.1 APP的 “清理数据” 功能

在Andorid系统中,有一个针对各个应用的“清理数据”功能,能够把每个应用所在/data/分区下私有的文件进行清理,这个过程实际上触发了:

initiateClearUserData()->
... ->
    clearUserData()->
        do_rm_user_data()->
            delete_user_data()->
                delete_dir_contents()->
                    unlinkat()

最后的unlinkat,在SSD上去触发文件删除功能。由于SSD是一个Block Device,而文件的大小是不确定的,在SSD上对文件的删除无法使用 BLKDISCARDBLKSECDISCARD 来处理,因此这里必然带来了数据残留的风险。

3.2 APP 卸载过程

同“清理数据”功能一样,APP的卸载功能,过程为

deletePackage()->
    deletePackageAsUser()->
        deletePackageX()->
            deletePackageLI()->
                removePackageDataLI()->
                    removeDataDirsLI()->
                        remove()->
                            uninstall()->
                                _delete_dir_contents()

最后还是落到文件删除过程上去,也存在着数据残留的风险。

由于这两个过程本身是由Android系统来控制的(用户本身无法随意访问到/data/分区下的文件),因此这两个过程不仅造成了数据残留,同时还屏蔽了用户可能的后续操作(例如覆写或者加密相关文件)。会带来很多隐患。例如root过的手机可以读取/data分区恢复某个app删除前的数据,如果这个app是诸如支付类或者IM类,可能会导致登陆凭证或隐私信息的泄漏。

4 总结

今天介绍的这篇Security Analysis of Android Factory Resets论文还是很好地揭示了Android上数据残留的问题,当然,我们进一步的分析表明,大部分手机(特别是自Android 4.3之后)的Factory Reset都使用了 BLKSECDISCARD 指令,应该是比较安全的。需要提醒的是那些使用第三方Recovery的用户。此外,如果担心app保存的数据无法彻底清理的问题,一方面用户需要开启全磁盘加密并使用复杂密码,另一方面,定期的执行安全的Factory Reset也不失为一种办法吧。

Cross-Architecture Bug Search in Binary Executables

今天要介绍的一篇论文 Cross-Architecture Bug Search in Binary Executables 来自SP’15,这篇论文主要讨论了现有软件在包含漏洞且被编译到三个热门的架构(x86/64,ARM和MIPS)时,如何利用某个架构的分析技术来帮助分析另外架构上代码实现的漏洞检测问题。

  • 出处:SP’15
  • 作者:Jannik Pewny, Behrad Garmany, Robert Gawlik, Christian Rossow, Thorsten Holz
  • 单位:波鸿鲁尔大学 & 萨尔大学

1 Abstract && Introduction

  • 现在越来越多的闭源软件会在不同架构上实现,这对漏洞查找带来了挑战
  • 过去的方法的问题:需要源码;单架构(x86);依赖动态分析(嵌入式设备难以应用)
  • 解决的问题:假设已知一个架构的漏洞,在其他架构上寻找相同的漏洞(IR相似性检测)
  • 假设没有混淆,但可以应对不同优化选项造成的差别
  • 贡献:
    • 使用IR抽取不同架构实现的语法信息(x86, ARM and MIPS)
    • 使用Sampling和MinHashing等高效方法实现(Testing and Jaccard)
    • 提出结构匹配标准,在CFG子图当中匹配
    • 在现实软件中实验

2 Approach

  • 目的是用相似代码标准找bug
  • 假设相似的代码只存在细小的差别,并有相同的bug

Workflow

  1. 产生 bug signature
  2. 转换成IR
  3. 产生 Semantic Hashes
  4. 穷举搜索,结合CFG局部调优,匈牙利算法
  5. 每条指令都表示成符号表达(symbolic expression),说明输出是如何被输入影响的

Bug Signatures

  • 签名长度难以限定,过长容易绝对化、过短容易误报

Unifying Cross-Architecture Instruction Sets

  • 先反汇编出代码结构(IDA)
  • 然后把指令转换成类似RISC的指令

Extracting Semantics via Sampling

  • 每个基本块产生(一系列)公式,并用prover简化,结果将输出表示成输入的函数
  • 产生公式后,随机产生输入,形成(输入, 输出)对(prover的效率较低,如果是要收集I/O对的话,可以用模拟执行的方法)

Similarity Metric via Semantic Hashes

  • I/O对数量太大,不利于大规模比较计算相似度
  • 使用 MinHash 算法,一种快速估算Jaccard系数的方法(相关数学证明可参考wiki)

Comparing Larger Binary Structures

  • 通过CFG把之前基本块的相似组合成更大规模的相似
  • 提出 Best Hit Broadening 算法(本质上是匈牙利算法)

3 Implementation

Common Ground: The IR Stage

  • 三个架构:x86, ARM, MIPS
  • 反汇编:IDA Pro
  • 中间语言:VEX-IR from Valgrind toolkit
  • 自动化:pyvex, a python framework bindings to the VEX-IR

Extracting Semantics: Sampling

从[-1000, 1000]区间产生输入向量,足够避免 collisions

Semantic Hash

  • 给每个基本块计算多个hash值;一个基本块有多个公式,按照输入数量分组
  • 然后按照相同输入数量分组比较,最后合成整体相似度

Bug Signature Matching

确定不动点,按照CFG上下匹配基本块,用匈牙利算法得到相似度最大的匹配组合(没有讨论优化、混淆造成的基本块合并、拆分的CFG改变的情况)

4 Evaluation

  • 三个架构都是32位
  • 编译器:gcc v4.6.2/v4.8.1 and clang v3.0
  • 函数级别的比较

False/True Positives Across Architectures

  • BusyBox 1.20.0 vs 1.21.1第一准确率有90.4%
  • 不同架构的结果就差多了,第一准确率30%左右
    • 作者的方法对CFG是敏感的
    • 混淆、优化会导致CFG改变
    • (不同架构的比较准确率比相同架构差太多,如果IR是语义保持的,那么作者的方法还需进一步改进)
    • (如果是因为转换成IR而导致信息损耗,那么就需要更好的IR了)

False/True Positives Across Compilers/Code Optimization

  • True Positive

  • False Positive
    • dir 和 ls 功能相近,误报反而说明方法是有效的

Bug Search in Closed-Source Software

  • 在现实程序中找 Heartblead
  • 目标是openssl库中的tlsl_process_heartbeat(TLS)和dtlsl_process_heartbeat(DTLS)两个函数

5 Discussion

Vulnerability Verification

无法自动化证明找到的代码片段是有问题的

False Negatives

这里的漏报是因为CFG改变、内联、优化造成的

6 Related Work

Code Similarity

  • 文章的亮点有二:跨平台、相似性比较找漏洞
  • 核心:代码相似性检测
  • 对Blanket Execution评价:
    • 只能检测函数级别的相似
    • 只支持 x64 ,而且因为PIN,其他架构也难以迁移

Efficient Dynamic Tracking Technique for Detecting Integer-Overflow-to-Buffer-Overflow Vulnerability

今天要介绍的一篇论文Efficient Dynamic Tracking Technique for Detecting Integer-Overflow-to-Buffer-Overflow Vulnerability 来自AsiaCCS’15,基于动态信息流技术来检测软件漏洞是安全研究的热点,这篇论文的主要作者是国内南京大学的研究人员,讨论了一个称为Integer-Overflow-to-Buffer-Overflow的漏洞检测。


Introduction

Integer Overflow to Buffer Overflow (IO2BO)

整数溢出导致分配出预期外的内存大小,进而发生缓冲区溢出

常用方法:污点分析,插入overflow checks

会产生大量的误报

IntPatch 现实生活中,当程序被patch后,仍会报错

本文提出 a novel runtime IO2BO detector, IntTracker

和 IntPatch 相似,IntTracker 会根据 critical path 选择所有的整数运算,并通过固定模式的规则排除一些不会发生溢出的运算。IntTracker 发现可能发生整数溢出并不马上报告,而是跟踪数据流,当污染的数据可以到达内存操作,这时才发出溢出警告。

有很低的误报和漏报率

在 IntTracker 中采用了a specific range of very large and rarely used integer values (we call it Dirty-zone) 代替溢出值。

两种 sanitization routines: Santz-postcond: 固定模式,静态检测溢出;Santz-spec:动态

as an exten- sion of the GCC compiler


sanitization routines

postcondition test

Santz-spec

Santz-spec is usually application specific and designed based on developers’ preference, Santz- spec is usually of various patterns and hard to be detect- ed statically.


design of IntTracker

  1. static selection analysis: 根据污点分析方法选择出在critical paths(从污点源到 Mem 运算)上的所有整数运算,并排除被 Santz-postcond 保护的运算

  2. code instrumentation analysis: 在每个算术运算后插入 postcondition test; 在每个 Mem运算前插入dirty value guarding code


Dynamic Protection by IntTracker

保证四个条件

  • PR1

使用 Dirty value 代替溢出值,不会改变程序的语义

溢出很少被程序员故意使用,对于溢出,溢出值本身就是有害的,而溢出值本身对于程序语义并不重要。

  • PR2

当 Dirty value 经过算术运算后,仍在 dirty zone中,保持 dirty value 的性质

根据观察得出 Observation 1. Overflow paths for harmful IO2BO vul- nerabilities are short in general.

assume that normal integer values produced by Arith op are always in a small range (Assumption 1). 根据 Table 3

Here we use [0xA0000000, 0xE0000000) as the Dirty-zone

our designated initial dirty value (i.e. 0xC0000000)

算术运算:Result = Dirty op Delta

根据数据, Delta 在 small range 的概率为0.999997756

证明:

  1. case Result = Dirty * Delta:

    Delta equals to 1 : Dirty Value 不变

    Delta is a value greater than 1 :检测出可能overflow,将溢出值用 Dirty Value 代替

    概率为 1

  2. case Result = Dirty ≪ Delta

    跟例子1相似

  3. case Result = Delta – Dirty

    Delta is a value from Small-zone,检测出可能overflow,将溢出值用 Dirty Value 代替

    If Delta is a value out of Small-zone but also smaller than Dirty Value,检测出可能overflow,将溢出值用 Dirty Value 代替

    If Delta is a value bigger than Dirty,结果几乎不可能在Dirty Zone中

    概率 0.999997756

  4. case Result = Dirty ± Delta

    概率 0.999997756

Therefore after N Arith op’s, we can get P N (I Result ) ≈ (0.999997756)N

  • PR3

Dirty values 能够被 Santz-spec 检测出, 减少误报

Santz-spec 一般用来检测 溢出值和 normal values 的区别

Assumption 1 is well supported by real world scenarios.

因为正常值是一个小的范围,所以通常开发者设定的 thresholds 比起我们很大的 Dirty Value 来说很小,所以Santz-spec 一般能检测出。

  • PR4

当 Dirty value 抵达 Memory 操作,可以被看做溢出的标志

(见Table 4)

Implementation

GCC-4.5.0

The selection analysis works on the GIMPLE IR

∼ 4K lines of C code

能识别的内存操作:

1
2
3
4
5
6
7
8
9
memory allocation routines (such as malloc, al-loc, and realloc)

memory or string copy routines (such as memcpy, strncpy)

memory manipulation routines (such as memset and memmove)

C++ operators such as new[ ]. 

additional memory allocations or block copy routines (for exam-ple, jas_realloc in jasper).

Evaluation


Limitation

  1. 依旧有不能检测出的反例存在

  2. 除法

    In our study on CVE bugs, only 4 out of the 183 cases have divisions appearing along overflow paths and the Delta in all 4 cases are small values 。

  3. test cases of high code coverage and overflow-inducing inputs

    测试中

Deobfuscation of Virtualization-Obfuscated Software: A Semantics-Based Approach

今天要介绍的一篇论文Deobfuscation of Virtualization-Obfuscated Software: A Semantics-Based Approach 来自CCS’11,这篇论文讨论了一类高级混淆技术——Virtualization-Obfuscation的反混淆对抗。

Abstract

虚拟机反混淆很难,因为混淆过的程序本身很难理解和逆向,所以这种混淆可有效地对抗静态和动态分析技术。

以往的反混淆往往是建立在已经逆向好字节码解释器的基础上,并由此来推导出字节码程序的逻辑。这种outside-in的方式在字节码解释器的原理结构已知的情况下,能够有不错的结果,但无法运用于其他场景。

本文从可观测到的代码行为着手,识别出所有与这些行为相关的指令。这种inside-out的方法只需要更少的假设,弥补了以往反混淆的不足。

Introduction

以前的工作,只要知道了解释器的结构,就能很好地恢复出字节码程序,但是一旦解释器不再符合这些工作中假设的模型(如线程或多层VM混淆)。

在现代OS中,程序往往需要使用一些预定义的接口与系统进行交互,这也就是很多程序的语义。所以只要识别出所有与这些预定义接口相关的指令,就能将其他语义不相关的指令剔除,本文不试图恢复原程序的指令,仅为了提取出代码的相关行为。由于不用预先定义好混淆的模型,所以相对而言更为通用,可对抗多种混淆技术。

Deobfuscation

静态分析往往只能分析出虚拟机解释器的结构,动态trace则会把解释器指令和原程序指令混合起来,但很难把它们区分开来。

本文方法在不需要假设任何解释器或dispathcher行为模型的基础上,试图识别出原程序的指令,剔除剩余的其他指令

方法概览

  1. 动态trace
  2. 从trace中识别出系统调用及其参数,可自定义需要监控的系统调用
  3. 在指令trace上进行分析,提取出相关指令
  4. 重建相关指令的subtrace,这条subtrace就是原程序代码的一个近似

Value-based Dependence Analysis

找出所有直接或间接影响到系统调用参数的值的指令,并将这些指令定义成语义相关的。

这里做依赖性分析的目的是反向识别出所有直接或间接影响系统调用参数值的指令。但是这里没用用动态切片(Dynamic program slicing),slicing算法会找出代码中所有的控制和数据依赖关系,不过这种虚拟机程序中,所有的字节码操作都控制依赖于字节码解释器的dispatch code. 这种嵌套的结果会把解释器的代码也加进来,造成的反混淆结果的不精确。

这里使用传统的use-definition chain,这个不会引入控制依赖性,避免了切片带来的不精确。

use-def会计算I1用到的ecx和edx,以及这个地址上的值,但是这里我们并不关心ecx和edx是怎么来的,而是只关心什么时候往ecx和edx相加后的地址上写入了这个值,所以这里又是和传统的use-def不同的一点。

这里对use的定义如下

做法:以系统调用为出发点,反向遍历trace,如果有使用内存地址,则反向找出往这个地址上写入内容的指令,而不是如何计算出这个地址的指令。

但是如果在系统调用的参数里涉及到结构体指针,且在系统调用里用到了这个结构体的某个成员,仅仅使用上述方法,是无法反向找出def的位置的。

这里采用的解决办法是,先正向遍历syscall的trace,找出其中所有可能用到结构体成员内容的指令,并将此时对应的地址记下来,也将其视为syscall的参数,这样再反向从syscall调用点之前开始找def的位置,就能够避免上述不精确问题的出现了。

Relevant Conditional Control Flow

虚拟机可能有内部的标志寄存器,无法直接通过x86的eflags寄存器看出来,但是可以通过从目标地址的计算对eflags的依赖关系来看。

这里用到了作者之前工作中开发一个equational reasoning系统,这个系统会把指令翻译成等价的易于理解的表达式。

这样就能通过看目标地址的计算表达式中是否包含Flag计算,来判断这个跳转的目标地址是否可能是条件跳转了。

下面看一个没有使用标准条件分支指令的情况

再看个VMProtect中更复杂的例子:

这里的条件分支被间接地隐藏起来了,所以在计算目标地址的表达式时,只用到了直接的依赖关系。

对于每个这样的间接地址访问,都记录下地址是如何计算出来的,也生成一个相应地表达式(也即上图中得LOC),最后,只要这个表达式中没有Flag操作,就能把这个表达式删掉,剩下的,就可能也是条件分支目标地址计算过程中会用到的值。

Relevant Call-Return Control Flow

针对上述这些不同的call/return混淆,归纳出如下定义:

调用者终归要在内存中放入一个返回地址,返回时终归会从内存中取出一个地址,然后做控制流转移。

所以这里返回地址就是call和return之间的纽带。

但是由于跳转表或者函数指针等情况,也会导致误报,所以这里添加一个限制条件。

在前述过程中已经提取出来与syscall相关的指令,所以只需从这些相关指令中识别出call/return,剔除剩余的call/return。

尽管这里仍然存在一些争议,但作者认为这样的定义足以得到想要的call/return对。

Relevant Dynamic Trace

最后就是把前述3步中的结果组合起来,建立相关subtrace。

Experimental Evaluation

Experimental Methodology

同一条指令可能有多种混淆方式(比如乘法展开成循环加法),同一段代码片段也可能在不同的位置会有所不同(内联,循环展开等)。

文中采用了一种不完美的解决办法:将trace和提取出来的subtrace都是为sequence,然后用已有的序列匹配算法来进行比较,然后对比较结果打分,一个序列的分数比其他序列高就表明这个更匹配。

  1. 识别出subtrace中得条件分支和call指令,并对他们进行替换。在匹配的时候也建立等价类,保证功能相同但opcode不同的指令也能够匹配上
  2. 匹配的时候只用了opcode,没有把operand带进来,因为对于同一条指令而言,在结果中和原程序中的operand可能不同。
  3. 剔除原程序trace和subtrace的所有库函数调用、os初始化的指令
  4. 算法将trace切成一个个的segment,每个segment起始于一个syscall之后,结束于下一个syscall或trace结尾。最后对trace中所有匹配的指令数量打个分

    Relevance score: relevant trace中原始trace的指令所占的百分比

    Obfuscation score: 混淆工具添加的,但是在反混淆过程中被成功剔除的指令所占的百分比

最终实验方法:

  1. 测试程序的源码编译成可执行程序
  2. 给定输入集,跑原程序的动态trace
  3. 从第二步里的trace中提取出可执行程序模块部分的subtrace
  4. 用虚拟机混淆技术对程序进行混淆
  5. 跑混淆后程序的动态trace
  6. 使用前述反混淆分析方法进行分析,生成relevant subtrace
  7. 将混淆后的subtrace和原程序subtrace进行比较,并打分
  8. 将relevant subtrace和原程序subtrace进行比较,并打分
  9. 计算relevant score和obfuscation score
  10. 组合所有的输入集合和混淆技术,重复前述过程

实验结果

实验中用了3个作者自己的toy,分别是一个阶乘,矩阵乘法和fibonacci序列的程序,以及两个恶意软件,和一个计算md5的benchmark。

上面这些程序分别直接编译,和用VMProtect和CodeVirtualizer进行混淆,结果如下。

Deep Packer Inspection: A Longitudinal Study of the Complexity of Run-Time Packers

今天要介绍的一篇论文SoK: Deep Packer Inspection: A Longitudinal Study of the Complexity of Run-Time Packers 来自SP’15,这篇论文是一篇总结性质的论文 (SoK:State of Knowledge),对当前计算机程序的加壳保护技术 (Packer) 进行了系统和深入的分析讨论。

作者单位:DeustoTech, University of Deusto, Spain; Eurecom Sophia Antipolis, France

论文下载:http://www.s3.eurecom.fr/docs/oakland15_packing.pdf

1 简介

本文深入探讨了当前在PC平台上广为使用的加壳技术,其贡献在于:a) 提出了一种Packer Taxonomy,从壳的结构和复杂性上评估不同壳的强度; b) 广泛研究了大量的壳,包括一些地下流传的加壳技术。 作者指出对抗当前加壳技术的一些朴素的认识包括:

  1. 在某个时刻,壳会把整个原始代码段都完整释放到内存中
  2. 对于使用多层加壳技术的样本,每一层都会逐级解码下一级的代码,直至最后一层壳将原始代码释放出
  3. 壳的代码和原始代码的执行泾渭分明(存在某一个时刻,壳代码会将控制权转移给原始代码)
  4. 壳代码与原始代码总是运行在同一个进程间,不存在IPC通讯过程

作者指出,上述这些认识事实上低估了壳的复杂性,使得基于这些认识的一些方法在处理复杂壳时无能为力。作者提出了如下三个问题:

  1. 有多少已知的壳是符合上述朴素认识的(即,可以被一些已有的unpacking技术方法所处理)?
  2. 目前观察到的最复杂的壳是什么情况?
  3. 有多少Malware families使用了此类(最复杂)的packing技术

为了回答上述问题,作者开发了一系列动态分析工具来研究加壳技术的各类动态运行时信息,进而揭示了许多更为本质的壳的特征。更为重要的是,这些研究又引出了新的一类问题:壳的发展极为迅速,早年的一些经典强壳(Armadilllo、Asprotect、Yoda等)实际上虽然是地下流传,但也广为人知且被深入剖析,然而实际上更多私有的加壳保护技术仍然尚未被深入研究。作者试图去了解:

  1. 有多少并未广泛流行的壳被使用
  2. 这些地下流行的壳和主流公开发售的加壳保护技术相比复杂度如何
  3. (终极问题)在过去这些年,packing界的landscape到底发生了什么样的变化,壳演化得更为复杂了么?是否有更为多样化的packer出现?

2 A Packer Taxonomy

作者提出了对壳的分类学,具体地,从如下几个方面来进行衡量:

A 加壳层数

传统的对于壳的研究将加壳后的程序执行过程分为不同的阶段,一般认为,壳本身作为一个routine,会先于原始代码执行,然后通过一个所谓的“tail jump”转移指令回到真实的OEP。这种分析方法甚至还被一些论文正规化定义,提出了execution phases和code wave等概念,然而,类似VMP和Themida这一类vm based code protection技术的出现改变了对这种分层的认识。因此本文作者提出了一种保守的定义:在程序最初执行前,所有内存中代码属于第L_0层,当一块内存被修改并执行,该内存所属的层数,要由所有修改它的代码中,最大的L_i决定(即该内存属于L_{i+1})层。

B 并行Packers

很多壳会启动多个processes来脱壳,典型的例子包括由一个process释放出一个executable然后执行。衡量此类壳的复杂度的一个关键点在于这些进程之间是否存在通讯,通讯的情况如何

C 转移模型

在壳的分层模式下,不同层之间的转移模型也是衡量壳复杂度的一个关键点,这里作者定义了前向(从高层layer往低层layer)和后向(从低层layer往高层layer)的转移,通常情况下多见的是简单的一次转移(壳往真实OEP转移)。而对于有N层layer的程序,大家一般认为运行时只会线性地发生N-1次转移,这是不对的,壳往往会在多层之间跳转来跳转去

D 壳的隔离

一些简单的壳代码和原始代码之间“井水不犯河水”,程序运行时首先进入壳的routine,然后通过一条tail transition instruction进入到原始代码中来。这种简单的模式现在被很多复杂的壳所抛弃(在unpack.cn上,大家把现有的方式叫做“壳中有肉,肉中有壳”)

E 代码的Frame

通常情况下,我们观察到的壳的行为是写一段内存,然后执行该段内存。作者把这种写入然后执行的内存块称为一个Frame;在作者的分类学中,如果一个layer只被完整写入一次,然后后续执行,这种layer可被称为single-frame layer,然而现实中很多layer往往被多个不同的routine写入然后才能完整执行

F 代码可见性

对于分析者而言,dump 内存获取原始代码是一种常见的分析手法,因此越来越多的壳会留意如何防止内存dump攻击,简单的壳往往存在某个时刻所有的原始代码都存在于内存之中,而更为复杂老练的壳则会逐渐地将要执行到的代码释放出来,因此只有在特定的时刻dump才能获取最多的代码,更有甚者会在运行后将释放出来执行过的原始代码再次加壳写回,杜绝了任何时刻出现全部完整代码的可能。

G 代码转移技巧

通过异常触发控制流转移,通过hooking进行控制权转移以及通过代码插桩内联的方式来进行转移,这是常见的一些高级控制转移技术。

H 壳的粒度

壳会按照不同的粒度释放原始代码,包括:以内存页为单位,以一个函数为单位,以基本块为单位,以指令为单位

3 壳的复杂度分类

Type I

UPX为代表,只有一个简单的unpacking routine,一开始就会执行,然后释放全部原始代码

Type II

壳包含多层,每一层顺序释放下一层代码,直至最后一层去释放原始代码

Type III

类似Type II,只是不按层的顺序来依次释放,执行过程包含更为复杂的回路,例如先执行层2,然后执行层4,层4释放出层5的部分代码,然后回到层2,再进行一些层5的修复和反调试,最后进入层5

Type IV

壳可能是单层也可能是多层,但壳的代码有一些会和原始的代码混在一起,这些混在原始代码中的壳代码负责进行一些完整性校验和反调试工作。然而,这种壳依然会把原有的代码全部释放到内存中去

Type V

壳的代码和原始代码交织在一起,原始代码被分为多个frames,每一帧被执行时壳才会去释放它,因此只有整个程序被完整执行过,该程序的snapshot才是比较完整的(动态执行很难覆盖全部代码,因此脱壳很难做到完备)

Type VI

最复杂的壳:每次只释放出原始程序的一个片段进行执行(vm based protection)!

4 分析技术

A Execution Tracing

基于 TEMU 的 framework,适用于监控多进程类型的 packer,同时方便监控各类 API 调用(特别是 memory un-mapping 和 memory deallocation 相关)

B 系统事件收集

关注堆、栈、内存 module 等的情况,关注 IAT——特别是被抹去 IAT 的情况,通过对 trace 的分析来跟踪

C 后期处理分析

为内存页面做标记,根据前述的状态转移和页面被写后执行等情况来判定某一内存页面的属性。

D 图形化

虽然是Coarse-grained 图形化,举个例子:下面是一个非常酷炫的为Obsidium 1.2, a Type-IV packer生成的图

5 案例分析

UPolyX 0.4: A Type-III packer

基于 UPX 改造的壳,原始代码变成了两层,在运行时会在两层之间切换。 然而存在明显的 tail jump;unpacking routine 没有修改原始代码的逻辑;寻找 OEP 十分容易; 因此作者认为这是一个 Type III 的 packer

ACProtect 1.09: A Type-IV packer

作者发现这个 packer 有 216 层(??!!),但是原始代码在第二层就释放了。 使用了 IAT hooking;

作者认为这是个标准的 Type-IV packer,protected code 和 packer code 交织执行。其特点包括:原始代码不在最后一层释放,程序最后执行的代码也不是原始代码;入口点混淆技术

Armadillo 8.0: A Type-VI packer

号称是最强类型的 packer,双进程保护,父进程调试子进程,子进程产生一个异常,由父进 程捕获,父进程为子进程写入代码。Armadillo 每次只写入一部分可执行代码。

6 LONGITUDINAL STUDY OF THE COMPLEXITY OF RUN-TIME PACKERS

论文最后放了很多图,讨论了整个加壳界的一些统计情况~大家可以去看看论文

Automated Identification of Cryptographic Primitives in Binary Code With Data Flow Graph Isomorphism

今天要介绍的一篇论文Automated Identification of Cryptographic Primitives in Binary Code with Data Flow Graph Isomorphism 来自AsiaCCS’15,这篇论文讨论的主题和我们小组的研究方向类似,讨论了二进制代码中密码算法的识别工作。

摘要

作者提出了一种自动化检测二进制文件中对称密钥算法和它们参数的方法,核心思想基于数据流图的同构(Data Flow Graph Isomorphism)

该工作可以适应编译器优化和源代码细微变化(但是回避了代码混淆问题)

Related Work

作者首先回顾了已有的一些二进制代码中密码算法识别方法

基于数据常量的检测方法

检测对称密钥算法经常包含特殊的常数

检测常数的工具: Findcrypt2 (IDA plugin) [14], KANAL (PEiD plugin), H&C Detector, 这些工具基于静态分析,而有些时候常数会动态生成,导致检测失败。

常数检测可能是检测过程中有效的第一步,但这并不能被当做一种检测密钥算法的独立手段

基于 input / output 关系的识别方法

这个方法是目前精度较高的,基本没有false positive,这里作者引用了我们在ISC‘11会议上发表的论文Detection and Analysis of Cryptographic Data Inside Software

Solution Overview

论文的方法包括三个步骤:

  1. 根据所给的汇编代码建立相应的DFG
  2. 根据重写规则规范化DFG
  3. 根据加密算法的graph signature 寻找同构子图

Data flow graph construction

常数

寄存器

内存: load store

Normalization

三种类型的重写规则:normalization rules, memory simplification rules, general simplification rules.

Normalization Rules

Memory Access Simplification Rules

3条规则:

  1. store after store:前一条没有作用的store可以被移除
  2. load after store:load的输出与store的输入相等,那么load可以被移除
  3. load after load:两个操作的输出相同,那么它们可以被merge

aliasing issue

General Simplification Rules

Common Subexpression Elimination

如果两个表达式拥有一样的输入输出,那么可以去掉其中的一个

Constant Simplification Rules

适用于以下情况:

  1. 如果算术逻辑运算的操作数都是常数,可以用计算结果代替
  2. 如果算术或逻辑运算的一个操作数等于该操作的identify element或absorbing element

Signature生成

为密码算法生成相应的签名。目前还没有自动方法,只能手工生成签名

macro signature: 当匹配成功时,增加一个节点继续匹配

Subgraph isomorphism

使用乌尔曼子图同构算法

Experimental results

作者主要测试了三个算法XTEA MD5 AES 测试了Crypto++, LibTomCrypt 和 Botan三个密码学算法库

存在的问题

只能检测比较简单的算法如XTEA, MD5 和 AES. 公钥算法无法检测

有一些密码学库使用了MMX指令集,这个也不支持