AndroidManifest.xml 多种混淆绕过静态分析的技术分析
2023-8-30 18:14:13 Author: www.4hou.com(查看原文) 阅读量:13 收藏

导语:随着移动应用的广泛普及,恶意软件也日趋复杂和隐蔽。本报告聚焦于一个由 ReBensk 提交至 incinerator.cloud 的恶意 Android 软件样本。

随着移动应用的广泛普及,恶意软件也日趋复杂和隐蔽。本报告聚焦于一个由 ReBensk 提交至 incinerator.cloud 的恶意 Android 软件样本。

  • 样本哈希值:

    • MD5: 2f371969faf2dc239206e81d00c579ff

    • SHA-256: b3561bf581721c84fd92501e2d0886b284e8fa8e7dc193e41ab300a063dfe5f3

经由 ReBensk 提交至 incinerator.cloud 的多个恶意样本中,我们特别关注了一款经过自定义修改的 APK 文件,以下简称为“样本 b356”。这一样本具有独特的混淆和隐蔽技术,导致标准的解压缩工具成功解压其内容。我们通过针对性修复后,成功解压缩。但是常用的 apk 分析工具仍然分析失败,通过进一步深度分析,我们得以突破样本的限制,最终能够成功分析。

1. AndroidManifest.xml 的文件类型修改

1.1 分析失败的原因

我们利用 010 Editor 打开样本 b356 中修复后的 AndroidManifest.xml 二进制文件。尝试用该工具的 AndroidResource 模板进行分析时,首次尝试遭到失败。从错误日志中了解到,问题起始于文件的 pos:0

1.PNG

通过与正常的 AndroidManifest.xml 二进制文件对比,我们发现其首个字节即有差异。

2.PNG

资源文件的类型定义如下:

enum {

       RES_NULL_TYPE = 0x0000,
       RES_STRING_POOL_TYPE = 0x0001,
       RES_TABLE_TYPE = 0x0002,
       RES_XML_TYPE = 0x0003, // Chunk types in
       RES_XML_TYPE RES_XML_FIRST_CHUNK_TYPE = 0x0100,
       RES_XML_START_NAMESPACE_TYPE= 0x0100,
       RES_XML_END_NAMESPACE_TYPE = 0x0101,
       RES_XML_START_ELEMENT_TYPE = 0x0102,
       RES_XML_END_ELEMENT_TYPE = 0x0103,
       RES_XML_CDATA_TYPE = 0x0104,
       RES_XML_LAST_CHUNK_TYPE = 0x017f, // This contains a uint32_t array mapping RES_XML_RESOURCE_MAP_TYPE = 0x0180, // Chunk types in RES_TABLE_TYPE RES_TABLE_PACKAGE_TYPE = 0x0200,
       RES_TABLE_TYPE_TYPE = 0x0201, RES_TABLE_TYPE_SPEC_TYPE = 0x0202

};

按照 Android 规范,该字节通常应为 0x03,但在样本 b356 中并非如此。

1.2 为什么 android 系统可以解析

定位到 android 系统解析源码,

2(2).png

这里是开始解析 Androidmanifest.xml 二进制文件的地方,如源码所示,没有验证前两个字节是否是 0x0003,只对 headerSize 的合法性做了验证。所以样本 b356 修改了文件类型后,android 系统仍然能够正确解析。

1.3 修复建议

静态分析工具修复建议:和 Android 系统解析流程保持一致,此处不校验文件类型。

2. stringPoolSize 修改

2.1 数据异常点分析

我们手动修正首个字节为 0x03 以便进一步分析。再次尝试用 AndroidResource 模板解析后,发现分析仍然失败,只展示了字符串池(string pool)而没有解析出 XML 主节点。

3.png

展开 stringoffsets 的数据进行查看,从第 131 个 stringoffset 开始,数据显著异常,远超文件全长,明显是错误的。进一步分析 stringPool 的头部信息,计算出实际的字符串个数为 131。

4.png

在样本 b356 的 stringoffsets 字段中,从第 131 个开始,数据明显存在异常:这些值远远超过了整个文件的实际长度。这种不一致性明显指向了一个错误,也意味着记录在 stringoffsets 中的数量很可能是不准确的。

5.png

在深入分析样本 b356 中的 stringpool 结构时,我们首先确认了 strdata[0],即 stringpool 中的第一个字符串,被正确解析。这一点是非常关键的,因为它证明了我们的解析起始位置(0x230,即十进制的 560)是准确的。

stringsStart 字段指出了从文件头(header)开始计算,552 个字节后就是 stringpool 的开始位置。由于通常会有 8 个字节用于存储 stringpool 的元信息(例如长度或其他标记),所以 552+8 恰好等于 560。

6.png

这自然引发了一个问题:这 552 个字节的具体内容是什么?我们知道 strPoolheader 自身就占用了 28 个字节(或者说是 0x1C)。如果从 552 个字节中扣除这 28 个字节,那么剩下的 524 个字节用于什么呢?

剩下的 524 个字节非常可能是用于存储 stringoffsets 的。每个 stringoffset 通常会占用 4 个字节,因此 524/4 正好等于 131。这一点与我们之前通过手动计算得出的 stringoffsets 数量是一致的。

7.png

在之前的深度分析中,我们推断出 stringoffsets 的实际数量为 131,这与样本 b356 中错误地标识的数量不一致。为了能更准确地解析该样本,我们决定修正 stringCount 字段。

2.2 为什么 Android 系统能够正确解析

7(2).png

如上图系统解析 stringPoolsize 的源码所示,stringPoolsize 是计算得到,所以样本 b356 修改了 stringPoolsize 让众多通过从文件中读取 stringPoolsize 的静态分析工具失效,但 android 系统在解析时任然可以通过计算得到正确的 stringPoolSize

2.3 修改建议

静态分析工具修改建议:按照 android 系统的做法,stringPoolSize 通过计算得到,而不是从文件中解析。

  1. 手动调整:首先,在样本 b356 的 stringPool 头部中找到 stringCount 字段,并将其值修改为 131。

  2. 重新解析:在完成修正操作后,我们再次使用 010 Editor 配合 AndroidResource 模板来解析样本。

经过修改后,我们发现 XML 的主要节点已经成功被解析。这意味着我们的手动修正是有效的,并且我们现在能够看到该样本的更多内部细节。

8.png

尽管我们手动修复了 stringCount 和成功用 010 Editor 解析了 AndroidManifest.xml 的主要节点,使用专用的静态分析工具依然会出错。具体的错误出现在二进制文件的 0x1588 字节位置。

3. 在 xml 节点结束位置插入混淆数据

3.1 数据异常分析

我们手动修改 stringCount 的 131,以便继续分析。

010 Editor 重新加载修改后的文件,结果如图如下图所示:

9.png

010 Editor 在解析时已经没有问题了,但是用静态分析工具解析时在 0x1588 遇到非预期数据。

在 0x1587 字节位置,第一个 XML 元素已经结束。静态分析工具预期 0x1588 字节作为下一个 ResChunk_header 的起始点进行分析。然而,在 0x158A 字节位置尝试解析 size 字段时,出现异常。根据 ResChunk_header 结构的约束,这个 size 值应该至少大于 8,但实际数据并没有满足这一条件。

样本 b356 应该是在 0x1588 字节处插入了一些非标准或混淆的数据,导致静态分析工具分析出错。

查看 010 Editor 的解析结果得知,startEle 的 header size 字段进行了改动,以便在元素结束后添加混淆内容。

10.png

在解析 attribute 字段之后,如果解析尚未完成或遇到异常,工具应自动跳过到 elementStart + size 的位置,从那里开始解析下一个元素。这样做的目的是绕过可能存在的干扰或错误数据。

3.2 为什么 Android 系统可以解析

如下图 android 系统源码所示,读取每个 attribute 的数据通过 attributeExt 的位置,attributeExt 的 start,attributeExt 的 attributeSize 计算得到的,attributeExt 的 start,attributeExt 的 attributeSize 从 byte 中读取,所以只要 attributeExt 的 attributeStart 正确,就能保证获取到正确 attributeData

11.png

那 attributeExt 是怎么定位的?如下图源码所示,每次解析下一个节点的时候,都是当前节点的位置加上 header 中读到的 size 的长度。

以上文中的案例来描述就是,startEle[1]的位置=startEle[0]的位置 +startEle[0].header->size。所以样本 b356 在原先 apk 的 startEle[0]结束后填充干扰数据的同时,也修改了 startEle[0].header->size 的长度,从而让系统正确解析。

12.png

3.3 修改建议

静态分析工具修改建议:参照 Android 系统的解析方法,每个 xml 节点的起始位置是通过上一个节点的起始位置 + 上一个节点的长度。

“b356”样本展示了多层次、多维度的混淆和隐蔽技术,其目的明确:抵制和破坏标准解压缩工具和静态分析解码的能力。

b356 能够混淆成功,主要原因是 android 系统解析处理流程和市面上常用的静态分析工具在一些细节上存在出入。

此样本中只有三个修改点,其他的样本中还存在一下其他的点,我们在下一篇 blog 中会接着分析。

但是主动的对抗方式肯定不是针对每个点修修补补,而是统一静态分析方式和 android 系统解析流程。比方说,把系统源码中解析方式转换成静态分析工具的方式,这个需要社区的共同努力。

如若转载,请注明原文地址

  • 分享至

取消 嘶吼

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟


文章来源: https://www.4hou.com/posts/1pzP
如有侵权请联系:admin#unsafe.sh