Group of Software Security In Progress

GoSSIP @ LoCCS.Shanghai Jiao Tong University

2015移动安全挑战赛(阿里&看雪主办)全程回顾(5)

pic4

题目下载

2015年移动安全挑战赛的最后一道题目,在规定的比赛时间内,仅有来自我们GoSSIP的wbyang一名选手解决了这道问题,今天我们就来揭开这一道最高难度题目的神秘面纱。

先把名为AliCrackme_5.apk的文件丢到JEB里看一看:

pic1

dex文件并没有进行加壳和混淆,看上去是一个非常简单的程序,Java代码部分使用函数Register("Bomb_Atlantis", input)对输入进行判断。所以需要分析的逻辑应该都在libcrackme.so里的Register函数中。

接下来我们用IDA打开这个libcrackme.so,不出所料的发现IDA完全没法处理,应该是进行了强烈的混淆和加壳处理:

pic2

使用和解决前面题目相同的技巧,我们继续使用dd的方法来去处一部分的混淆和加壳。运行一次程序后,从/proc/self/maps里找到libcrackme.so在内存中的位置,使用dd命令从/proc/self/mem中提取出内存中的libcrackme.so,接着使用在解决第四题时使用过的技巧,将libcrackme.solibc.so一起加载到IDA里。

用IDA打开dump出的代码后,我们发现仍然有大部分的代码无法被IDA识别,需要手动定位到需要分析的代码然后手工定义(IDA快捷键C)代码,同时由于代码会在THUMB指令集和ARM指令集之间切换,有时候需要用快捷键ALT+G来将T寄存器设置为不同的值,设置正确后才能正确翻译出代码。这里我们首先遇到的问题是无法定位Register函数,同样使用第四题中的技巧,用InDroid监控到Register函数的真实地址,就可以在该地址上开始分析。

libcrackme.so这个动态库里使用的一些混淆方法,对于处理了前面一些类似混淆后现在的我们来说已经不是问题(^_^)。通过分析代码,我们定位了几个函数,这些函数的偏移在不同的设备上应该是不同的。整体的逻辑其实并不复杂,首先会有一个固定的字符串“Bomb_Atlantis”和一个固定的salt去进行一次md5运算,salt是动态生成的,不过由于dump内存的时候这些动态的值已经生成好了,所以能够直接发现这个salt(出于一些版权原因我们不便公布本题目的一些内部细节,因此该salt值请大家自己分析)

之后程序会将这个md5值和我们的输入进行一些异或和计算的操作,经过几步比较简单、可逆的变换之后,进入一个比较复杂的函数,经过这个函数处理后直接和一个内存中的值进行比较,返回比较结果。

pic3

这里说一个我们在做第五题时用到的分析方法——动态hook。由于libcrackme.so中并没有对调用自身的上层应用进行验证,这就导致了我们可以自己写一个程序去加载这个so,调用其中的方法。这也导致了我们在加载libcrackme.so后,可以加载另一个用于hook的so,这样我们可以hook libcrackme.so中的任意函数,从而知道任意函数的参数和返回值,这对于我们理解程序有着非常大的帮助。这里我们使用的hook框架是著名Android安全研究人员Collin Mulliner开发的Android平台上的一个二进制注入框架adbi。当然这道题目并不能够通过注入的方法将我们的so注入进去,因为源程序禁掉了ptrace这样的系统调用。我们对adbi稍作修改,使之成为一个可以手动加载的动态hook框架。同时由于我们没法通过符号表来定位函数的地址,所有的hook地址都需要硬编码,并且要和运行这道题目程序的Android设备内存映射严格对应。

需要指出的是adbi中存在一个小bug,hook.c这个文件的118行应该是

h->jumpt[0]=0x60

而不是0x30,对应的thumb汇编应该是

push{r5,r6}

而不是

push{r4,r5},

这个小bug在解题过程中会造成一些影响。使用adbi来hook这道题目的函数还需要注意一点,这题的代码中有一些函数使用的THUMB指令集,hook这些函数时,不要忘记人工的对hook地址+1。

通过hook的方法,我们已经能够动态的分析libcrackme.so,首先我们验证了我们对之前几步变换的分析结果。之后就是分析最后一个复杂的处理函数,通过静态分析+动态调试,我们发现这是一个类似于白盒密码学的加密函数。我们的输入进入函数后,首先经过几步类似DES的预处理,之后会进行若干轮的查表,通过查询一个巨大的表将我们的输入进行某种加密,生成一段密文,再经过几次简单的处理后和最后内存中的一段常量(出于一些版权原因我们不便公布本题目的一些内部细节,因此该常量请大家自己分析)进行比较。

通过动态调试,我们能够计算出加密算法最后应该输出的值,但是由于这个加密算法的密钥融入了整个置换表中,要找出一个逆置换表显然不太可能。我们简单过滤了libcrackme.so的其他函数,也没有发现用于解密的函数,想要正常解密密文是不太现实了。不过根据对加密算法的分析,我们发现这若干轮的置换是相互独立的,并且每一轮的复杂度并不高,这就意味这我们可以在可以接受的时间内对算法进行爆破。我们一开始的想法是code reuse,直接在Android设备上爆破,但是发现速度太慢,最后只能用笨办法,通过hook从内存中dump出来置换表,用C代码重写了这个算法,有惊无险地在比赛结束前半小时搜索出结果。根据逆推算法推出正确输入是:

3EFoAdTxepVcVtGgdVDB6AA=   

好了,我们的2015移动安全挑战赛全系列回顾就到此为止了!希望大家能和我们多多交流讨论,欢迎大家关注我们的微博GoSSIP_SJTU,基本上每天都会有精彩的内容发布哦。

安全论文每日读 2015.03.26

今天介绍的论文When Firmware Modifications Attack: A Case Study of Embedded Exploitation是一篇不折不扣的逆向工程炫技论文,作者展示了通过一个完整的Firmware逆向工程来实施对打印机这种看似非常安全的设备的攻击!

论文下载

Background

  • 本文作者在2011年的欧洲黑客会议Chaos Communication Congress上做过一个题为”Print Me If You Dare: Firmware Modification Attacks and the Rise of Printer Malware”的报告。在报告上他通过对固件中远程升级(Remote Firmware Update)分析和漏洞利用的过程,完全控制了一台HP打印机。
  • 本文发表于2014年的NDSS会议,主要介绍了了3年前的报告中未介绍的一些细节,并从该例子展开,讨论了与嵌入式设备相关的安全问题

Firmware Modification Attack

  • 这一节首先介绍对嵌入式设备进行Firmware Modification Attack的可能性
    • Feasibility:
      1. 大部分的Firmware都有远程更新的功能(Remote Firmware Update, RFU)
      2. 之前的一些研究也发现网络上存在大量使用弱口令的嵌入式设备
      3. 一些设备使用网路启动,而且使用不安全的连接方式(TFTP),因此存在在2层使用ARP攻击的可能性、
    • Fail-Safe:
      1. 大部分设备都会检查收到的固件是否与自身匹配,来避免用户使用错误固件进行升级导致的设备故障。这降低了攻击者发起错误的攻击的代价(reduces the penalty of a misdirected attack.),而且令大规模的攻击成为可能。
      2. 为了避免不必要的错误,厂商提供的固件的时候还可以选择只更新固件的一部分(bootloader,OS)。因此攻击者也可以利用这一特性只对bootloader进行修改。因此,一次攻击需要的流量可以从几M降低降低到几百K。(例如,之前攻击者扫描到了一个有弱口令的路由器,但是只能识别出路由器的厂商,那么几百个型号全部尝试一遍,流量就从几百M降低到几十M)
    • Platform Independence:
      1. 虽然不同设备的架构,硬件配置都不同,但是一个厂商的一个系列的设备的固件的源代码大部分都是相同的,这就意味着,一个漏洞(或者后门)会“平台无关”的出现在一个系列甚至跨系列的设备中,
    • 对固件校验签名显然并不能完全杜绝被攻击的可能

Case Study: HP LASERJET Exploitation

  • 在接下来的这个部分,作者将讲述了他从几乎为0开始对HP的P2055DN进行逆向工程,并讲述了自己发现这个HP-RFU漏洞的过程
  • 在开始分析之前,作者知道的只有(从HP官方网站):
    1. HP的RFU依托打印机的标准协议的PJL(printer job language)实现
    2. 标准的PJL头后面跟一个没有文档的ACL格式数据
  • Discover Process:
    • 首先,对RFU binary的每个字节进行统计,分布情况表明数据被压缩但是没有被加密。
    • 没有文档,怎么办,只能猜,作者看了下头部的数据,然后,猜出了头部的一点数据格式: 固件各个分块压缩后的长度和压缩前的长度
    • 但是连压缩算法是什么都不不知道。。于是作者决定。。买台机子拆了。。。
    • 然后从主板上:
      1. 作者看到了SoC上型号,于是知道了是Marvell的一个ARM架构的芯片
      2. 看到了flash的规格:8M
    • 然后,把flash焊下来,接在一个Arduino上,把整个flash的内容读了出来
    • 好的。。可以上IDA了。。从flash第一个字节开始逆。。。。作者把bootload逆掉了,然后,checksum和压缩的算法都知道了(结果是个zlib…),作者还指出bootloader使用的zlib版本似乎有已知漏洞。。。
      • PS:整个分析过程完成于2011年前,那是时候应该还没有binwalk之类的工具
  • Proof of Concept Printer Malware
    • 作者首先写了一个HPacker提供解包和重打包功能,
    • 然后,作者确认了是个vxworks(ELF image)
      • 开始识别里面的各种函数,以编写malware..
        • 作者的目标是识别出socketlib, print job processing, raw network I/O interface..
        • 然后,首先用IDA 手工识别socketlib,
        • 然后patch了内核,把所有打印到UART的调试信息重定向到TCP
        • 然后。。。借助这些信息,找到了vxworks文档中的更多的函数。。
        • 然后。。写了个rootkit,放到了固件里。。
    • 最后,作者利用这些信息,用2800行汇编代码,完成了可以反向链接的C&C rootkit,并成功植入了固件中

Figure 1

Figure 2

Threat Model And Assessment

  • 在这一节,作者以HP RFU的这个漏洞,介绍了读者可能没意识到的嵌入式系统与PC环境的差异
  • Threat Model Characterization & Threat Assessment
    1. 攻击者当然可以直接连接到打印机上进行攻击
    2. 但是另外一种攻击方式是,通过XSS脚本,让内网的用户帮助你发送恶意的RFU请求到打印机上,然后打印机就会变成攻击者渗透到内网的入口
    3. 或者诱使用户打印恶意的文档,完成攻击

Figure 3

  • Compounding Factors: 针对HP RFU还有更多的问题加剧了危害
    1. RFU更新功能根本不需要认证….就是说一个XSS就跪了….
    2. RFU更新默认打开…
    3. RFU作为advanced feature并不能在HTTP或者TELENT上进行配置,用户需要装一个叫WJA的300M+的软件并且装了SQL才能进行配置。。。
    4. 而且,这个漏洞报告给HP的时候,HP发布了第一版的公告上,有很多型号都没有列上去。。。因为,当时WJA也没有提供关闭那些型号的RFU的功能。。。然后直到WJA更新,HP才将第一版做了修改加上了其他的型号
    5. 最后,这款打印机的flash具有One-Time-Programmable特性,也就是说,攻击者愿意的话,他把rootkit放进去,你就只能把flash悍下来了。。。

Vulnerable Device Population Analysis

  • 作者做了全网扫描,74770个识别出的HP打印机中中只有808个补了漏洞,211个不需要补。。&这一千多台没漏洞的里面还有253台有弱口令。。。。
  • We also identified the following populations of vulnerable printers within two notable organizations:
    • United States Department of Defense: 201 printers
    • HP: 6 printers
  • 后面作者还顺便做了还做了一些目前在使用的HP打印机的年龄,以及其中使用的第三方库版本的分析,简而言之,大量已知的安全问题还遗留在这些设备中

安全论文每日读 2015.03.23

今天介绍的论文PowerSpy: Location Tracking using Mobile Device Power Analysis是一篇通过功耗旁路分析获取移动智能设备GPS隐私信息的攻击论文

下载

简介

对于移动用户来说,位置信息是敏感信息,是需要保护的信息。例如,应用程序需要用户的允许才能读取GPS数据,或者基于蜂窝网或者Wi-Fi连接的模糊位置数据。

本文的工作发现应用程序可以绕过所有的限制,直接获得手机的位置。这是因为他们观察到手机的位置明显地受到手机的蜂窝无线电功耗影响。 该功耗主要受到手机与基站的距离,以及他们中间是否有障碍物的影响。如果手机距离基站越近,他们之间的障碍物越少,手机消耗的能量越少。

我们工作的主要障碍是,除了蜂窝无线电,手机里很多组件和应用同时会消耗能量。同时,手机当前的状态也会影响功耗(打电话,数据传输,或者空闲)。然而,通过机器学习的方法,在一段时间内测量手机的总能量消耗能够发现用户的位置。

贡献

基于手机之前的功耗数据,我们使用机器学习的方法可以区分手机用户的路径。我们仅仅使用的是总功耗, 实现:

  • 区分路径
  • 实时追踪
  • 推测新路径

模型与假设

  • 假设恶意的程序已经安全在手机上,用来跟踪用户。这个恶意程序不能获得GPS或者蜂窝网数据。
  • 假设程序可以获取网络连接信息和功率数据:功耗具有足够的变量来表现唯一的特性
  • 假设敌手预先知道用户将要走的区域或者路径的知识
  • 提出的问题:当开车的时候接收到到电话,功率图上会出现peak, 作者提出可以采用一些技术处理这个问题

原理

位置信息影响信号强度和功率消耗;功率消耗可以揭露位置

Fig 1

研究内容

  • 区分路径:根据已有的数据样本,将新的数据样本进行比对和打分

  • 实时追踪:目标是根据已有的训练集合和实际数据,定位设备并且实时追踪它。 1 首先,和区分路径一样,将测量的数据与已有的路径profile进行比较。不同之处在于,我们使用更小的路径profile。 2 其次,前面的方法可能会有错误,我们使用简单的运动模型算法来改进追踪。

  • 推测新路径:假设移动设备拥有者知道区域,然而区域的路径数目非常大,实际上不能记录某一条。 1 利用隐藏马尔可夫链(HMM)建模,再根据粒子滤波(Particle Filter)方法估计每一个状态,输出N条不同长度的路径(Alg. 2); 2 在这N条不同的路径中,再选一条最优路径, 包含出现次数最多的road segment(Alg. 3).

实验

实验设备:Nexus 4,使用PowerSpy Android应用来测量信号强度,电压,电流, GPS坐标,温度,放电状态,蜂窝网ID.

  • 区分路径: 对4条19km的不同路径和43条曲线的数据库,成功的概率是93%. 对7条不同路径和51条曲线的数据库,成功的概率是90.2%

Fig 2

  • 实时追踪:使用10条训练曲线和一条测试曲线,分析收敛时间和距离误差. 收敛时间大概在2分钟,80%误差少于1km。 Fig 3

  • 推测新路径:预先用手机记录了42个道路段的数据, 再使用两部手机进行实验,两部手机在后台运行不同数目的应用程序。第一部手机运行适中数目的应用程序,email,gmail等。第二部手机运行大量的应用程序,facebook, skype等等。测试距离误差和精确位置的百分比。 Fig 4

未来的研究方向

  1. 推测功耗的方法: 新的手机内部包括电流表,可直接读取功耗数据。其他的手机提供电压数据,目前有研究通过读取电压变化得到手机的功耗。
  2. 基于电量区分路径的方案:电量是显示功耗非常粗糙的方式。虽然在我们的研究中,它不太精确。但是,如果从它提取更好的特征,或者使用更好的方法,可以实现基于电量来区分路径/推测位置的方案。在给定HTML 5 Battery API(用JavaScript在网页上获取电池状态),测试这种方案将会更加有趣。研究发现,将来增加电量的采样速率可能会使这个API非常危险,甚至允许恶意的网络攻击。
  3. 从3G扩展到LTE
  4. 对数据样本进行优化
  5. 收集大量的数据样本

防御

不可防御: 也许通过增加噪声,限制采样频率可以保护位置隐私。然而,该方法不依赖于高采样频率。

功率数据和网络连接的危险组合

安全硬件设计:关于功耗的安全问题在于它泄漏了收发器电路和与信号强度相关的通信任务的功耗。因此,仅仅提供处理器的功耗(去除接收发送模块的功耗)是一个平衡功能和隐私的方法

root权限管理: 简单有效的方法是需要root用户权限才可以获取手机的功耗数据

功耗作为一项粗糙的位置标识: 类似于蜂窝网ID,需要合适的权限获取功耗

安全论文每日读 2015.03.19

今天要介绍的论文OmniUnpack: Fast,Generic,and Safe Unpacking of Malware发表于ACSAC 2007安全学术会议,是一篇讨论如何针对加壳后的恶意程序进行恶意代码检测的论文。

摘要

恶意程序会使用加壳技术来轻易逃避基于签名的杀毒软件检测。因此最新的杀毒软件通过动静态结合的分析技术来恢复加壳软件的内容,但目前来看这样的技术并不很有效,因此作者提出了一个新技术叫OmniUnpack,当恶意程序脱壳后,实时监控程序执行,他能够直接将脱壳后执行的程序送给杀毒软件检测。并能够处理已知和未知的加壳算法并仅仅引入很小的性能损耗(至多11%)。

背景

加壳程序通常是通过解压或解密流程,将真正的恶意payload释放到内存中执行。通常这个unpack的步骤可以是一步,或多步来完成,即每次unpack出一部分代码。对于杀毒软件来说,必须等到程序开始执行以后,也就是恶意程序释放了真正的恶意payload后才能开始扫描代码的工作。 作者罗列了之前的工作,包括静态解密加壳算法(受限于已知壳),基于模拟器执行的通用脱壳(受限于模拟器环境与真机差异),基于单步调试(时间损耗)。而OmniUnpack的思想是监控程序执行,跟踪被写以及被写后执行的内存页面,一旦程序调用了潜在的危险系统调用时,OmniUnpack就调用杀毒软件来扫描被写的内存页面,如果不报毒,则继续执行。OmniUnpack修改少量的操作系统而不使用调试、模拟器等来防止程序自我保护技巧,OmniUnpack实施在内存页面级别的跟踪而不是指令级别,而且只扫描新生成的代码,因此性能损耗也非常低。

系统综述

因为内存页面级别的跟踪会导致大量的误报,也就是说内存页面被写后执行不代表真的脱壳代码已经生成,例如分步脱壳的中间过程,以及同一个页面被多次写和执行,这些都并不意味着真正脱壳的完成,因此OmniUnpack是等到内存页面被写和执行,并且调用了危险的系统调用时,才认为是脱壳步骤完成,才调用杀毒软件来扫描代码。危险的系统调用:例如Windows上的创建和写注册表等这类能够修改OS状态的都算。另外整个扫描过程是随着程序的执行而持续进行的,原先的单次扫描可能会因为多步脱壳解密而漏掉真正的恶意代码,持续的扫描让恶意软件延迟释放恶意负载也毫无作用,而这样做的前提是OmniUnpack只有很小的时间性能损耗。

OmniUnpack算法

记录被写的内存页面 W,和被写后执行的内存页面 WX(显然 WXW 的子集),直到有危险系统调用在 WX 中被执行,则去扫描之前所有的 W,检测出恶意就返回了,没有就继续,并且将 WXW 置空。

实现

在Intel IA-32处理器的WinXP中将OmniUnpack作为内存驱动。借鉴了OllyBone这个OD插件,这个插件采用类似PaX PAGEEXEC的方法来允许页面级别的BreakOnExecute,用户空间的杀毒软件是基于开源的ClamAV。 内存页面监控是利用了硬件提供的内存保护机制,被写后执行的行为可以通过拦截违反W⊕X策略的异常处理来检测。当然这个W⊕X策略只是为了拦截异常而设置,原始的策略被保留和恢复当需要保证被监控的程序继续正常执行下去。许多硬件架构(IA-64,Sun Sparc,Alpha等)支持内存页面的RWX策略设置,但像(IA-32)这种并不支持,不过可以用PaX PAGEEXE项目提到的方式,来软件模拟NX策略的内存页面管理。有危险系统调用就进行代码扫描,用户空间的杀毒软件通过共享内存方式得到加壳程序内存,而杀毒软件的签名必须是满足两个条件:

  • 基于原始恶意程序而非脱壳步骤和加壳后的
  • 基于调用危险系统调用前的恶意代码片段特征
  • 基于恶意代码从unpack自身到调用危险系统调用之间,并没有二次修改或擦除之前已经unpack的代码。

实验

实验环境是个QEMU的虚拟机,里面是XP SP2。 对比PolyUnpack(作者自己实现的,因为原版有版权问题)和ClamAV unpacker,速度上秒杀PolyUnpack,5秒对比100多秒,准确率上秒杀ClamAV,80%对于15%。 通过执行恶意程序到最终的unpack步骤完成,OmniUnpack大概要比PolyUnpack快上20多倍,而只比ClamAV慢五倍,这里是只算上第一次到达unpack步骤的时间。 网上找了20个不同的加壳程序去加密一个样本,ClamAV脱了其中五个壳并检测出3个,可能因为脱壳程序有bug的原因,而OmniUnpack检测出其中的16个,没有检测出的Armadillo和CExe的原因是这两个壳解密了一个可执行文件并执行,OmniUnpack没有去跟踪新创建的进程。nPack解密可执行文件并执行后就crash了,teLock貌似是因为有QEMU不支持的指令,作者认为这个问题可以在真机上被克服。 作者最后用普通压缩程序加壳做性能检测发现只会增加11%以下的性能损耗。

相关工作

值得一提的是PolyUnpack,貌似是使用调试器执行加壳程序,一旦发现有没有出现在原始静态反汇编中的指令序列时,就认为有壳,但他受限于静态反汇编自身的短板,而且调试器引入的性能损耗也很大。

2015移动安全挑战赛(阿里&看雪主办)全程回顾(4)

APK界面

题目下载

本次移动安全挑战赛的第四题和第三题一样,是一个包含了加壳dex的APK文件,我们使用同解决上一题一样的方法,用InDroid得到原始dex文件的dexdump结果:

使用InDroid进行脱壳的演示视频:

Dex整体处理过程和上一题也类似,使用handleMessage处理最后的判断输入成功与否,只有sendEmptyMessage(0)后,触发除以0的异常才能成功。不过这一题将用户输入转成byte后,传给一个native的方法:ali$aM$j方法,另外参数还包括一个常数48和Handler。看样子逆向native库势在必行了。这一题的lib文件夹下文件和上一题是一样的,有三个文件,其中libmobisecy.so其实是个zip文件,解压后是个classes.dex,直接反汇编后,类和方法的名字都在,只是里面的代码都是

1
throw new RuntimeException();

libmobisecz.so直接就是一堆binary数据,猜测应该是运行时会被解密,通过某种方式映射到为真正的代码执行。因此我们的目标就是libmobisec.so这个ELF文件。 直接用IDA打开libmobisec.so,发现IDA会崩溃。用readelf发现正常的节区头数据都被破坏了,因此应该这个so本身也被加过壳了,很多数据只有在动态运行时才会解开,所以直接使用动态的方法,先运行这个程序后,直接在内存中把这个so dump出来。 首先需要在输入框中随便输入些数据后,点击确定,保证用户输入数据执行到native方法里后再做dump。我们使用的方法是查看maps后,使用dd命令把整个so都dump出来。 输入命令:

1
2
3
4
5
6
7
8
9
10
11
12
root@maguro:/ # ps | grep crackme.a4
u0_a73    1935  126   512204 48276 ffffffff 400dc408 S crackme.a4
root@maguro:/ # cat /proc/1935/maps
5e0f2000-5e283000 r-xp 00000000 103:04 741132    /data/app-lib/crackme.a4-1/libmobisec.so
5e283000-5e466000 r-xp 00000000 00:00 0 
5e466000-5e467000 rwxp 00000000 00:00 0 
5e467000-5e479000 rw-p 00000000 00:00 0
5e479000-5e490000 r-xp 00191000 103:04 741132    /data/app-lib/crackme.a4-1/libmobisec.so
5e490000-5e491000 rwxp 001a8000 103:04 741132    /data/app-lib/crackme.a4-1/libmobisec.so
5e491000-5e492000 rw-p 001a9000 103:04 741132    /data/app-lib/crackme.a4-1/libmobisec.so
5e492000-5e493000 rwxp 001aa000 103:04 741132    /data/app-lib/crackme.a4-1/libmobisec.so
5e493000-5e4c1000 rw-p 001ab000 103:04 741132    /data/app-lib/crackme.a4-1/libmobisec.so

使用dd命令将libmobisec.so的内存dump出来

1
root@maguro:/ # dd if=/proc/1935/mem of=/sdcard/alimsc4 skip=1578049536 ibs=1 count=3993600

dd命令使用的数字都是十进制的,skip就是libmobisec.so的起始地址,count是总长度。 为了让IDA还能够识别libmobisec里的libc函数,我们还需要把libc也载入到IDA中,libc就直接从system/lib里拖出来就行了。

1
adb pull /system/lib/libc.so ./

用IDA先打开libc,调整好是在内存中的偏移即rebase program,再在load additional binary里载入dd出来的libmobisec.so,通过maps里的偏移后载入。接下来的任务就是在其中找到M\$j这个函数的地址。 一开始尝试直接在dd出来的ELF文件中找这个M\$j这个函数名,类似的名字会被处理成

1
Java_ali_00024a_M_00024j

类似下图:

图1

不过我没找到这个M\$j这个名字,逆过JNI库的都知道,如果符号表里找不到这个函数名,说明在JNI_Onload的时候,使用RegisterNatives函数重新将一个JNI函数映射为Native函数了。

正当一筹莫展的时候,我再次想起了InDroid系统。在Dalvik中,每个方法都是一个Method的结构体,其中当这个方法是native的时候,Method的insns这个指针会指向native方法的起始地址。因此我们修改了下InDroid,让Dalvik在执行M\$j这个方法前,去打印了M\$j方法的insns指针。这时我们得到了一个指向另一片内存区域的值,既不在libdvm中,也不在libmobisec中,并且这片内存页被映射成了rwx,由此推断里面也极有可能是代码,我们继而又dd出了这块内存,用IDA打开,使用ARM平台反汇编,发现该处就一条指令,是LOAD PC到另一个地址,而这个地址恰好在libmobisec中。于是我们直接到IDA中跳到这个地址,发现正好是个压栈指令,印证了我们的想法,此处就是M$j函数,于是在在IDA里该地址指令处,右击选择create function,让IDA识别这一段汇编指令为函数指令后,就可以通过F5查看看反编译的C代码了。

这个函数本身做了一些控制流混淆,同时还有很多字符串加解密的功能函数,一些简单的如异或操作,也被展开成与和或的组合等更长更复杂的表达式形式。另外还看到一些变形过的RC4,等等。不过因为我们已经是dump出来执行过的数据,所以必要的数据都已经解密了。如下图:

图2

通过查看反编译的C代码,我发现程序中是直接通过JNI方法调用了Java中的bh类的方法a(在图2常量中也可以看到)。 再次回到dex层查看a方法,该方法是不断的将输入传递给不同的函数进行处理,先是cda方法,cCa方法,pa方法,xa方法,ali$aM$d方法(native),aSa方法,xa方法,ali$aM&z方法(native),cda方法,cCa方法,每一个方法都是些简单的数学运算,编码,以及密码学处理等可逆的操作,结合逆向和Indroid对输入输出的监控,都可以轻松确定每个Java函数的作用,具体过程如下代码显示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
invoke-static {}, LbKn;.a:()Z // method@08a1
move-result v3
invoke-static {v3}, LbKn;.b:(I)V // method@08a2
add-int/lit8 v0, v5, #int 1 // #01
invoke-static {v4, v5}, Lcd;.a:([BI)[B // method@0b23
move-result-object v1
add-int/lit8 v2, v0, #int 1 // #01
invoke-static {v1, v0}, LcC;.a:([BI)[B // method@0a30
move-result-object v0
add-int/lit8 v1, v2, #int -1 // #ff
invoke-static {v0, v2}, Lp;.a:([BI)[B // method@0e8d
move-result-object v0
invoke-static {v0, v1}, Lx;.a:([BI)[B // method@0ede
move-result-object v0
add-int/lit8 v2, v1, #int -1 // #ff
invoke-static {v0, v1}, Lali$a;.M$d:([BI)[B // method@03d3
move-result-object v0
add-int/lit8 v1, v2, #int 1 // #01
invoke-static {v0, v2}, LaS;.a:([BI)[B // method@022e
move-result-object v0
invoke-static {v0, v1}, Lx;.a:([BI)[B // method@0ede
move-result-object v0
add-int/lit8 v2, v1, #int 1 // #01
invoke-static {v0, v1}, Lali$a;.M$z:([BI)[B // method@0440
move-result-object v0
add-int/lit8 v1, v2, #int 1 // #01
invoke-static {v0, v2}, Lcd;.a:([BI)[B // method@0b23
move-result-object v0
add-int/lit8 v2, v1, #int 1 // #01
invoke-static {v0, v1}, LcC;.a:([BI)[B // method@0a30
move-result-object v0
return-object v0

值得注意的是,其中有两个native的方法,因为InDroid还可以监控调用native方法的参数以及返回值,我们发现这几个native都没有对输入做复杂的处理,只有M\$d对输入的第四个字节做了减8的处理。

做了这些逆变换以后我们其实并没有找到最终比较的处理,不过在解密过的数据中(图2),不仅有之前需要调用的各种方法和类,还可以发现有个十分可疑的Base64的字符串。

1
aJTCZnf6NyBPYJfbrBuLu0wOhRFbPtvqpYjiby5J81M=

并且在native的M\$z方法的反汇编代码中,可以看到有对这个Base64字符串的长度比较,由于我们并没有找到真正的比较函数,因此得到这个字符串后,我们直接从M\$z开始向上逆推之前的变换就得到了的答案。

具体解密代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
 #!/usr/bin/env python
# encoding: utf-8

from Crypto.Cipher import AES

def Lcda(s):
    return ''.join(map(lambda x: chr((ord(x) + 3) & 0xff), s))
def de_Lcda(s):
    return ''.join(map(lambda x: chr((ord(x) - 3) & 0xff), s))

def LcCa(s, a):
    return ''.join([chr(((ord(s[i]) ^ a) + i) & 0xff) for i in xrange(len(s))])
def de_LcCa(s, a):
    return ''.join([chr(((ord(s[i]) - i) & 0xff) ^ a) for i in xrange(len(s))])

def Lpa(s):
    return s[1:] + s[0]
def de_Lpa(s):
    return s[-1] + s[:-1]

def Lxa(s):
    return s.encode("base64")[:-1]
def de_Lxa(s):
    return s.decode("base64")

def LaliaMd(s):
    return s[:3] + chr((ord(s[3]) - 8) & 0xff) + s[4:]
def de_LaliaMd(s):
    return s[:3] + chr((ord(s[3]) + 8) & 0xff) + s[4:]

def LaSa(s):
    BS = 16
    pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
    cc = AES.new("qqVJwt11yyLm7hVK1iI2aw==".decode("base64"), AES.MODE_ECB)
    cipher = cc.encrypt(pad(s))
    return cipher
def de_LaSa(s):
    cc = AES.new("qqVJwt11yyLm7hVK1iI2aw==".decode("base64"), AES.MODE_ECB)
    cipher = cc.decrypt(s)
    return cipher

res = "aJTCZnf6NyBPYJfbrBuLu0wOhRFbPtvqpYjiby5J81M="

flag = de_Lcda(de_LcCa(de_Lpa(de_Lxa(de_LaliaMd(de_LaSa(de_Lxa(res))))), 49))
print flag

结果为:

1
alilaba2345ba

这里还需要提一下如何寻找M\$dM\$z两个函数在so库中的地址的方法,不过这个方法是一些经验的总结,原因是整个native ELF文件的节区结构是被修改过的。这两个方法和M\$j不太一样,因为在dump出的libmobisec里可以找到M\$z的函数名,证明这个方法没有使用RegiterNatives来做变换,因此我们可以通过符号表来找这个函数与文件头部的偏移。方法是找M\$z和字符串表的偏移,如0x03FE,然后穷搜整个文件:

图3

因为符号表应该会把字符串表偏移作为一项,这块区域的结构体,我们对照ELF结构发现并不是标准的符号表,但还是可以大概看出结构体的内容,包括索引,字符串表偏移,以及ELF特殊的标志数,因此推测0x57BE4偏移是M\$z函数。该地址也正好是个压栈的指令,证明了我们的猜想。

安全论文每日读– 2015.03.16

今天我们送上一篇关于程序分析工具BAP的论文,BAP: A Binary Analysis Platform 这篇论文 发表于CAV 2011 (the Proceedings of 2011 Conference on Computer Aided Verification),主要介绍了鼎鼎大名的CMU Cylab实验室(PPP的大本营)所开发的一款二进制程序分析引擎。BAP现已发布到0.9.5版本,而论文发布于2011,所涉及的是0.3.0版本。

BAP是为了用于程序验证(program verification)和二进制程序分析而开发的一款基础平台工具,支持x86和ARM架构。BAP将整个工具简单地分为两部分:前端和后端两部分,通过中间语言表式的代码进行联系:

前端程序 包括Trace Interface、Instruction Lifting、Binary Format Interface,用于将所得到的一段二进制代码(可以是静态获得的代码段,也可以是动态得到的trace)反汇编成汇编指令,再转换为中间语言语句。

后端程序 对前端所生成的中间语言语句进行分析操作,文中列出了包括Program Analysis、Program Verification、Graphs、Optimizations、Code Generation等一系列可以提供的分析功能,同时用户也可以进行自定义的分析测试。

考虑到汇编指令执行过程中带来的副作用(对标志寄存器的影响),作者在BAP中引入了自己设计中间语言——BIL(中间语言语法可参考 http://bap.ece.cmu.edu./doc/bap.pdf ,但是在0.9.5的版本中所使用的BIL语法与手册中提到的语法有出入)。BIL会将汇编指令所涉及的对标志寄存器的操作分离出来,形成单独的语句,文中认为这些得到的中间语言语句是side-effect free的。其目的就是能明确地分析这些隐藏的对控制流产生影响的操作。相较于以往的二进制反汇编分析工具,这是BAP最突出的优点。同时BIL也可以转换为其他有效的表示方式,例如static single assignment(SSA) form,便于后端程序的分析操作.

BAP在CyLab之后的一些研究中被大量使用,例如:1)Q, the ROP compiler;2)Mayhem, a binary symbolic executor;3)Tie, a type recovery system等等,具体可参见 http://bap.ece.cmu.edu/

BAP 0.8的安装

BAP 0.9.5相较于0.8版本做了很大的修改,原有的handbook不再适用,有兴趣的话可以去git上下载,安装较为方便,以下主要介绍一下ubuntu上安装0.8版本的BAP

系统要求:

Ubuntu 11.04/11.10/12.04。不建议使用14.04等高于12.04的版本,由于BAP依赖于Ocaml,建议安装的OCaml版本号3.12 <= v < 4.01.00,否则在编译过程中极易出现一些程序语法上的错误。Ubuntu 12.04可提供的Ocaml安装包最高版本为3.12.1,可以直接下载获得。

前期准备:

安装所依赖的一些工具的软件包

# __BEGIN_REQUIRED__
sudo apt-get -y install ocaml-nox ocaml-native-compilers ocaml-findlib \
camlidl binutils-dev automake libcamomile-ocaml-dev otags libpcre3-dev \
camlp4-extra bison flex zlib1g-dev libgmp3-dev g++ libtool make
# __END_REQUIRED__

如果系统是64位系统,还需安装一下软件包

$ sudo apt-get install gcc-multilib g++-multilib lib32z1-dev

由于BAP可以将BIL程序转换成LLVM的中间语言,还需要LLVM code generation。 、

$ sudo apt-get install libllvm-3.1-ocaml-dev

原作认为3.1至3.3版本都可以使用,但都没有测试过。(在我自己的编译过程中使用了3.1版本)

编译BAP:

$ ./autogen.sh
$ ./configure
$ make
$ make test      #test意义不大

在执行配置文件configure时可能会出现以下提醒:

configure: WARNING: Pin not found. BAP pintraces will be unavailable.
configure: WARNING: ETAGS not found. BAP will not build tags.
configure: WARNING: lablgnomecanvas not found: the graph editor and view_graph will not be compiled

其中Pin的安装可以独立出来,稍后进行,后两个只要安装etags工具以及liblablgtk2-gnome-ocaml-devliblablgtk2-ocaml-dev软件包,对BAP并无很大影响。

Pin的安装:

由于BAP能够对程序动态运行后的trace进行分析,使用了pin这个动态插桩工具。pin的安装较为简单,直接在`$BAPDIR/pintraces`目录下的运行getpin.sh,即可自动下载`pin-2.13-61206-gcc.4.4.7-linux.tar.gz`并解压安装。并在`$BAPDIR/pintraces`目录下编译所需的pintools,会生成`$BAPDIR/pintraces/obj-ia32/gentrace.so`

BAP toolkit:

编译完成后会生成数个工具,包括:
toil: Lifting Binary Code to BIL
iltrans: Programs Transformations on BIL Code 
topredicate: Predicate-based Program Verification 
ileval: Concrete evaluation 
codegen: LLVM-based code generation

以上工具在handbook里都给出了简单的使用例子,而每个工具的详细使用方法都可以通过-help选项进行查看

安全论文每日读– 2015.03.12

今天介绍一篇2015年NDSS会议上的论文:No More Gotos: Decompilation Using Pattern-Independent Control-Flow Structuring and Semantics-Preserving Transformations,文章主要讨论了现有反编译工具的不足,并提出了相关的改进。

  • 作者:Khaled Yakdan, Sebastian Eschweiler, Elmar Gerhards-Padilla, Matthew Smith
  • 单位:德国波恩大学

Overview

  • 当前的问题:反编译对于安全应用的分析有着举足轻重的作用,然而当前的反编译器都依靠结构分析(structure analysis)这种模式匹配的方法,使得恢复出的源码中存在大量goto,严重影响可读性。
  • 问题的意义:优质的反编译可以有效取代低效的人工软件逆向,反馈出源码后,就可以借助已有的源码分析工具进行进一步的分析,包括漏洞分析、污点分析等
  • 文章的方法:提出与模式无关的控制流结构化算法,使得反编译得到的源码中不存在goto等影响阅读的元素

Toy Example

Toy_1

Toy_2

  • 上述Figure 5是对Figure 3中控制流图回复的源码比较,其中Figure 5左边的是文章提出的方法,而右边的是Hex-Rays恢复的源码

Architecture of the Approach

Alt text

  • 如图, 因为第一部分是文章的重点,以下将会着重介绍控制流结构化的步骤:模式无关的控制流结构化算法

Pattern-Independent Structuring

  • 输入:划分好区域、定义好起始结束点的控制流图(CFG)
  • 输出:抽象语法树(AST)

一些需要预先解释的

  • 这里介绍的算法处理的是只有一个入口、一个出口的有向无环图
  • 对于循环,先无视backward edge,当做无环处理
  • 起始点、结束点:即Dominator,如例子中的$b_1, n_7, c_1, n_9, d_1, n_8$
  • 区域:Dominator以及其间点和边的集合,如例子中的$R_1, R_2, R_3$

Reaching Condition

  • 即程序要到达每个程序点需要满足的条件,比如例子中,$n_5$的Reaching Condition是 !b_1 || b_1 && !b_2
  • 在确定起始、结束点的情况下,只需要简单的深搜(DFS)就可以得到每个点的可达条件

Structuring Acyclic Regions

Alt text

  • 如图,道理上,直接将最左处理成连续 if(condition){body} 也是正确的
  • 但为了提高可读性,会做一些合并处理,如 $n_5, n_6$ 的条件是相反的,可以合并成 if(condition){n_5}else{n_6} 形式

Structuring Cyclic Regions

  • latching node:循环中的最后一个点,即将起始点作为直接后继的点。如例子中的 $c_3$
  • loop node:起始点和latching node间路径上的点
  • successor node: 离开循环后执行的第一个点

由上,可以把循环中的点分类,其中直接前驱是loop node的点也被认为是loop node, 和无环情况类似,以上已经可以得到正确的AST了,但有很大提升可读性的空间,这就是需要长期积淀的工作了

Alt text

上图的简化过程都是平时经验的总结(具体规则见附录)

Semantics-Preserving Control-Flow Transformations

以上都是针对单入口、单出口的CFG的处理,但实际中的循环有很多多入口、多出口的情况,如例子中的$R_1$就是多出口,所以这里目的把多入口、出口情况转化成单入口、出口情况,就可以统一处理了。

多入口:原本while(c){...}的循环检测就变成while(c || i){...},循环体会重置标识位$i$的值

Alt text

多出口:想法就是把原来循环体中的条件放到循环体外检测

Alt text

Evaluation

Metrics

  1. Correctness:语义等价
  2. Structuredness:反编译出的代码中的goto数量衡量
  3. Compactness:反编译器输出的代码的总行数

Experiment && Results

  • 正确性的测试将GNU Coreutils作为实验对象,用准备好的testcase分别测试已有的二进制文件和反编译出来的源码编译成的二进制,比较二者的结果。

Alt text

  • 结果总共1738个函数,编译成功的1530个函数全部通过了测试
  • 因为文章的方法的目的是没有goto,所以Structuredness的统计显然是0;而没有了goto和label了之后,这里恢复出的代码的规模(代码行数)也显然少于其他反编译器(如IDA)

Appendix

Alt text

安全论文每日读– 2015.03.11

今天介绍一篇2013年Securiy&Privacy会议上的论文:PRIVEXEC: Private Execution as an Operation System Service,文章主要讨论了一种“隐私执行”的安全加固服务。

引言

隐私浏览坏处: – 当下主流浏览器中,尽管使用隐私浏览模式,还是会在磁盘上留下痕迹 – 一旦存下来,很难保证这些数据不会被取证工具恢复出来

阻止泄露的一个办法是磁盘加密,但是无法保证磁盘加密的密钥不会泄露出去。

所以Privexec最好具有以下特点: 1. 由OS提供,这样能够为任何应用程序提供这个服务,并且便于分析。 2. 能够解决标准的加密技术(如磁盘加密)无法解决的问题。

本文亮点主要在于: 1. 提出了一个新的OS隐私执行的服务。无需修改应用程序,即可提供隐私保证服务 2. 基于这个模型实现了一个原型系统 3. 原型系统的功能和性能测试效果很不错。

威胁模型

分为两个场景 1. 会话进行中:用户能远程访问目标系统,在正常进程隔离的保护下,无法访问物理、内核和其他进程的内存,但是攻击者能控制网络流量。这种情况下,依赖如SSL/TLS这样的通用保护机制和用户知道该如何阻止数据泄露 2. 隐私会话结束后:攻击者能物理接触到目标系统,访问任何本地存储设备,因此能通过取证工具从FS、交换设备的进程内存页中提取出没有安全删除的数据。 3. 应用程序或用户恶意泄露隐私数据的,不在考虑范围内。PRIVEXEC针对的是避免由于粗心而导致泄露的隐私数据。

设计与实现

Alt text

仔细看完这张图,结合图下面的文字说明,几乎可以概括完这篇文章的整体idea。

安全属性

将所有进程分为公共进程(public process)和私有进程(private process),私有进程即为隐私的进程,执行过程会受到OS的限制。

安全属性如下: 1. 在没有进程私有知识的情况下,是无法将写入存储器里的数据恢复出来的 2. 在没有进程私有知识的情况下,是无法将交换到磁盘里的进程内存恢复出来的 3. 其他进程无法通过IPC channel获得隐私进程生成的数据 4. 应用程序私有数据不是永远不变的,并且不能放到受保护的易失存储器以外 5. 私有进程终止时,必须安全抹掉其所有数据

系统设计目标

  • 通用: 无需修改应用程序或库的源码、重新编译,即可让这个服务适用于任何类型程序、任何fs和I/O设备
  • 灵活:用户自己选择是否使用隐私模式来启动进程
  • 自动:一旦启动,无需人为干预
  • 低开销

文件系统

每一组私有进程关联一个secure storage container和一个private execution key(PEK, os保护,不会泄露给用户或其他进程)

Alt text

实现

Alt text

do_fork保证父进程的flag能通过继承传给子进程

添加的两个flag说明如下: – PF_PRIVEXEC:表示这个进程是个隐私进程,进程对应内核中task_struct结构的flags置为PF_PRIVEXEC。 – CLONE_PRIVEXEC:clone的调用到do_fork时,若检查到这个标志,则设置进程对应的flags为PF_PRIVEXEC。

在privexec的wrapper中,通过clone,并将clone的flag设置为CLONE_PRIVEXEC,就先创建了一个这样的一个带PF_PRIVEXEC的子进程,接下来这个子进程就是以隐私的方式来运行了。

隐私磁盘I/O

在VFS和具体fs之间加了中间层,组合了两个已有的stackable fs。

eCryptfs

原来的eCryptfs里,文件是独立加密的,加密元信息存在文件中。好处是不需要一个完整的设备或分配一个分区来支持,随用随分配,故可以创建任意多个

eCryptfs在这里有两个地方是与隐私执行的目标相违背的: 1. 一个加密的目录解密后,所有具有足够权限的用户和程序都能访问到 2. eCryptfs希望在kernel keyring里找到密钥,这样同一个用户的其他进程也可能dump到key。

所以这里对eCryptfs做如下修改

Alt text

scatterlist使用privexec_token验证进程是否可访问

inode无视unix的权限控制,验证访问的来源,保证只能由eCryptfs的上层目录来访问。

修改后保证一个eCryptfs只能由一个单一的私有进程组的进程访问,插入的代码同样保证不会影响正常进程使用eCryptfs.

Overlayfs

Alt text

私有进程可以看到完整的fs,可是写文件的话,就会写到mount上来的底层的那个eCryptfs上,这样只要保证进程执行结束后,这个eCryptfs是不可恢复的即可。

相当于把I/O重定向到这个mount上的eCryptfs上去了。

swap space

用PEK来加密交换到磁盘上的内存页面

Alt text

私有IPC

如前面图中的IPC交流所示。允许其他进程通过IPC往这个进程写数据,但不允许这个进程往其他外部进程(非该进程所在的私有进程组的进程)写数据。

修改了kernel中相应IPC函数,做token检查。

内存独立

关掉一些系统特性,如调试工具,禁止未授权访问会暴露kerbel虚拟或物理内存的设备。

启动私有进程

PRIVEXEC-aware的应用程序能直接启用,通过在clone设置CLONE_PRIVEXEC的标志位。

其他已有程序,则通过一个PRIVEXEC wrapper来运行。具体启动过程前面已经介绍过。

clone出一个具有PRIVEXEC的进程,创建一个空的secure storage container,挂载到随机生成的一个位置去,fs具体特征如前所述,最后将目标程序的可执行文件装载到chroot的系统中来。这时应用程序集成了wrapper的PEK,开始了其隐私执行。

应用程序终止时,PRIVEXEC wrapper清空了挂载了fs,并退出。

即使应用程序crash或被kill掉,container和overlay都在挂载着,但此时已经无法再解密这个fs了,因为对应的PEK已经被销毁。

局限性

  • 将物理内存的内容写到磁盘上,但是如果此时有隐私进程正在执行,则会是以明文的形式写到磁盘上,作者说可用swap类似的方式解决,随后release。
  • 攻击者可能通过提权、加载一个自己的内核模块等方式直接读到kernel里的PEK,这样就失去了隐私的意义,不过这个不在攻击模型的考虑范围内。
  • X应用程序在unix domain socket上存在一定的问题,需要放松策略,禁止MIT-SHM才能工作。攻击者可以通过UI截图的方式的进行攻击,泄露隐私。

安全论文每日读– 2015.03.10

今天介绍一篇今年NDSS上关于智能家居设备Firmware安全分析的文章Firmalice – Automatic Detection of Authentication Bypass Vulnerabilities in Binary Firmware

物联网(Internet of Things)发展迅速。智能电表,智能门锁,智能开关,智能手表,各种智能家居设备都连接在了因特网上。这些所谓的“智能家居“,一旦发生了是发生了意外的错误,或者被有目的的攻击,将直接的影响到现实世界(用户显然不希望你的邻居能用他的手机打开你家的大门)。本文侧重于发现这些智能设备中的认证绕过和后门的问题。其主要思想是利用符号执行去分析设备固件中登陆认证相关的代码,得到可以进入特权状态的路径之后,判断这些路径的约束中是否有确定性的约束(只有一种解的约束),如果存在,就可以认定为是后门。

如何断定固件进入了特权状态?作者给出了他的分析引擎目前使用的四种策略:

  • 程序输出类似”AUTHENTICATION SUCCEEDED”的静态字符串
  • 系统访问/dev目录下的文件(智能锁通过访问/dev下的文件操纵电机进行开关)
  • 嵌入式设备对特定内存的访问
  • 由分析人员指定一些特殊的代码位置

为了减少符号执行分析时间,在符号执行之前,分析引擎会首先使用静态分析技术,从进入特权状态的点进行反向切片,再对切出的代码进行符号执行。

本文作者实现了自己的基于中间语言的符号执行引擎,与现有的符号执行引擎的区别的是采用了Symbolic Summaries和Lazy Initialization两种方法:

  • Symbolic Summaries:
    • 作者使用函数名/一些测试用例 识别出 动态链接库/二进制文件中常用的 strncpy, strcpy, strcmp,memcpy等函数
    • 在符号执行到这些函数点时,使用该函数的symbolic summary 直接替代执行过程
  • Lzay Initialization:
    • 有些固件没有操作系统,直接运行在设备上,(我们不可能从固件的入口点直接开始分析),我们选定的分析的开始的位置之前可能有一些初始化的函数没有运行,为此我们的策略就是如果符号执行引擎读取一个未初始化的内存,那么我们会尝试在整个固件中寻找对该内存进行写入的routine,然后引擎在该点分支成为两个一个无修改继续执行,另一个则先执行routine之后再恢复执行。
    • (结合我自己对路由器分析的结果,这个方法应该是有效的:固件在初始化的时候确实会把某些函数和数据指针保存在一个特定的内存地址上,然后调用该函数的时候再读取出来)

再来看看作者给出对三个设备的分析结果:

  • Schneider ION 8600 智能电表: IOActive Labs的研究者在2012年BlackHat上指出该设备存在硬编码的后门,然而我们使用Firmalice穷尽了所有分支之后也发现该后门。经过手工分析确认该“后门”是用户合法登陆之后,使用该“后门“可以使用该设备更多的功能
  • 作者又进一步对 3S Vision N5072摄像头和 Dell 1130n打印机的分析,Firmalice成功检测出了这两个设备上的已被公开的后门。

2015移动安全挑战赛(阿里&看雪主办)全程回顾(3)

APK界面

题目下载

在介绍本次比赛第三道题目之前,首先要介绍一个我们GoSSIP小组开发的基于Dalvik VM的插桩分析框架InDroid,其设计思想是直接修改AOSP上的Dalvik VM解释器,在解释器解释执行Dalvik字节码时,插入监控的代码,这样就可以获取到所有程序运行于Dalvik上的动态信息,如执行的指令、调用的方法信息、参数返回值、各种Java对象的数据等等。InDroid只需要修改AOSP的dalvik vm部分代码,编译之后,可直接将编译生成的新libdvm.so刷入任何AOSP支持的真机设备上(目前我们主要使用Nexus系列机型特别是Nexus4和Galaxy Nexus)。在本次比赛的第三题和第四题分析过程中,我们使用该工具进行分析,大大提高了分析效率。具体细节可以参考我们发表的CIT 2014论文DIAS: Automated Online Analysis for Android Applications

回到题目上,将第三题的APK进行反编译后发现代码使用了加壳保护,对付这类加壳的APK,最方便的方法就是使用InDroid来进行动态监控,因为静态加密的DEX一定会在执行时在Dalvik上时解密执行,这样我们可以直接在InDroid框架里对解释执行过程中释放出来的指令进行监控。在我们自己使用的工具里,我们开发了一个动态读取整个dex信息的接口,执行时去读DexFile这个结构,然后对其进行解析(解析时直接复用了Android自带的dexdump的代码)。这样,我们的插桩工具在运行程序后,能够直接得到程序的dex信息,同未经保护时使用dexdump后得到的结果基本一致。虽然我们得到的信息是dalvik字节码,没有直接反编译成Java代码那么友好,但由于程序不大,关键逻辑不多,因此对我们的分析效率影响并不大。

使用InDroid进行脱壳的演示视频:

在得到脱壳之后的dexdump结果后,我们可以对代码进行静态分析。我们发现用户的输入会传递给继承自Class timertask的Class b,被Class b的run方法处理。在run方法中,如果sendEmptyMessage方法被调用时的参数为0,就会导致Class c的handleMessage这个方法中得到的messagewhat值为0,进而导致103除0跳入异常处理中,触发成功的提示。

继续分析这个run方法的逻辑,可以知道用户的输入会被传递到Class e的a方法中,做个类似摩尔斯译码的过程(其译码与标准的摩尔斯电码不太一样),然后经过下面一系列大量的混淆用的无用处理和不可能相等的比较后,将译码后得到的字符串送入到关键的判断中去。这个判断成功的条件比较复杂:对于译码后得到的字符串的前两个字节,要求使用hashcode方法的结果等于3618,并且这两个字节相加等于168,才会进入后面的比较。我们穷搜索一下符合这类输入的字符串:

for ( size_t i = 33; i < 127; ++i )
{
    for ( size_t j = 33; j < 127; ++j )
    {
        String x =String.valueOf((char)j)+String.valueOf((char)i);
        if (x.hashCode()==3618 && (i+j) == 168)
        {
            System.out.println(x);
            System.out.println(j+i);
        }
    }
}

输出为:

s5
168

也就是说只有s5满足hashcode为3618,而相加等于168这个条件。

确定前两个字符后,后面还有四个字符需要同Class e和Class a的Annotation值比较。因为我们做脱壳的时候直接使用了dexdump的代码,而dexdump即使到最新版里也无法很好地处理Annotations:

// TODO: Annotations.

不过没关系,我们还有动态分析工具这一利器,因为最终目的是得到getAnnotation方法的返回值,依然可以用InDroid在Dalvik执行到getAnnotation方法时监控返回值,就能得到Annotation的具体值。使用InDroid获取具体信息的视频如下:

最后可知,符合程序需求的字符串是

s57e1p

使用程序内部的对应表,对其进行逆变换,能够让程序输入成功提示的输入应该是:

… _____ ____. . ..___ .__.