开发一个基于Dalvik字节码的相似性检测引擎,比较同一款Android应用程序的不同版本之间的代码差异(二)

信息来源:4hou          发布日期:2019-06-19       浏览:233

信息来源:4hou         

发布日期:2019-06-19       浏览:233

【关键字】

web安全 漏洞

导语:本文我们将介绍一个用例:URl欺骗漏洞(CVE-2019-10875) ,另外还会介绍如何将Redex与diff工具相结合,检测被混淆处理的应用程序中到底发生了哪些修改?

上文我们说过,《针对Dalvik字节码的相似性检测引擎,比较同一款Android应用程序的不同版本之间的代码差异》这篇文章计划分两个部分来讲解,上文只介绍了如何利用Quarkslab公司开发的diff引擎。本文我们将介绍一个用例:URl欺骗漏洞(CVE-2019-10875) ,另外还会介绍如何将Redex与diff工具相结合,检测被混淆处理的应用程序中到底发生了哪些修改?

CVE-2019-10875漏洞及缓解措施的分析

CVE-2019-10875漏洞介绍

mint browser(薄荷浏览器)是小米专门为安卓手机用户设计的一款轻量级浏览器应用,这款软件内存很小,设计的十分简洁,但是该有的功能一应俱全,支持语音搜索,能够带给用户更好的浏览体验。不过就在2019年4月,研究人员曝出小米薄荷浏览器存在URL欺骗漏洞,攻击者可把恶意链接伪装成权威网站的URL,对受害者进行钓鱼攻击。之后虽然小米公司迅速发布了安全补丁,但有人发现安全补丁存在严重的问题,只需要简单添加几个字母,就可绕过。该漏洞是CVE-2019-10875。粗略地说,小米薄荷浏览器为了提升用户体验,在当你打开某个网络链接时,若链接类似于https://www.google.com/?q=www.domain.com时,则网址栏就只会显示www.domain.com,也就是只显示?q=后面的字段。因此,一旦攻击者构造https://www.evil.com/?q=www.google.com这类的链接进行钓鱼攻击,受害者则只会在网址栏看到www.google.com,相信任何人都不会怀疑谷歌是钓鱼网站。当攻击者输入https://www.andmp.com/?q=www.google.com时,跳转成功后,可看到地址栏显示www.google.com,但页面其实是www.andmp.com的内容,这种URl欺骗漏洞攻击者利用起来毫无难度,仅仅只需要编造一个简单的恶意链接。

这就会造成,攻击者可以利用此安全漏洞作为网络钓鱼活动的一部分。该漏洞会影响1.6.1或更低版本,所以为了安全,请尽快升级到1.6.3版。

现在,让我们假设有人想快速理解这个漏洞是如何工作的。要做到这一点,最快的方法是在发现到这个漏洞之后查看开发人员应用的安全补丁。它应该会引导我们访问易受攻击的代码并阻止我们检查整个应用程序代码。

在本文中,我们会介绍如何使用diff分析来找出代码的哪些实际部分被修改,以保护用户免受漏洞影响。请注意,由于JNI特性,根据不同的情况,还可以在嵌入式本机库中进行一些修改。如果是这样,则必须在本机代码级别执行互补的diff进程。

选择要比较的类

首先,我们必须减少要进行比较的类的集合,以便只保留由小米开发团队实际开发的类。这一步非常重要,因为它使得比较过程更快,结果更准确。位于APK根目录的AndroidManifest.xml可以为我们提供有关其开发包的一些有用信息,这些开发包通常包含它们的大部分活动、服务、广播接收器等。

这样,我们很快就会发现manifest文件中介绍的一个名为com.miui.org.chromium.chrome.browser的资源包,该包含有root权限,被大量类共享。因此,我们会首先比较其中包含的类。以下Python代码展示了具体工作过程:

lhs_app = load("com.mi.globalbrowser.mini-1.6.1.apk.apk") # Loading left handside application
rhs_app = load("com.mi.globalbrowser.mini-1.6.3.apk.apk") # Loading right handside application

condition = {"package_filtering": "com.miui.org.chromium.chrome.browser"}
lhs_classes = filter(lhs_app.classes, condition)
rhs_classes = filter(rhs_app.classes, condition)

查找修改过的类

一旦得到类,我们就需要知道哪些类已经被修改。为此,我们必须事先优化选项。

optimizations = {
  "inner_skipping": False,
  "external_skipping": False,
  "synthetic_skipping": True,
  "find_obfuscated_packages": False,
  "min_inst_size_threshold": 5,
  "top_match_threshold": 3
}

diff_results = diff(lhs_classes, rhs_classes, 0.8, optimizations)

这些优化选项分别配置了diff引擎:

1.不要跳过内部和外部类:内部类和外部类很可能包含修改过的代码。

2.跳过合成类:合成类是由编译器自动生成的,因此不太可能嵌入修改过的代码。

3.不要试图找到已经被混淆的包名:如前所述,包名称似乎没有混淆。设置此选项后,diff引擎将不会考虑由混淆引起的潜在问题。

4.不要考虑嵌入少于5条命令的类:应用程序通常包含所有看起来相似的小类(例如,一些只返回属性值的方法)。然而,在大多数情况下,它们在比较版本的diff方面毫无价值。大多数情况下,他们只会进行一些误报,不过,这些结果可以通过设置合适的值来消除。

5.在三个高级类中进行彻底的比较:如果我们处理的类在结构级别上大致相同,则必须增加此选项。不过这超出了本文所讲的范围。

此外,我们将匹配阈值设置为0.8,因为在寻找修复的漏洞时,我们只期望寻找那些进行较小修改的漏洞。这意味着,匹配距离低于此值的类,将在结果中直接被删除。

此时,我们得到了diff()函数的结果,可以用下面的Python代码对它们进行迭代:

for match in diff_results:
  if match.distance < 1.0: # Skipping perfect matches, that is unmodified classes
    print(f"[+] {match.lhs.info} | {match.rhs.info} -> {match.distance:1.4f}")

执行完整个脚本后,会输出大量结果。

[+] com/miui/org/chromium/chrome/browser/widget/progress: ToolbarProgressBar - ToolbarProgressBar.java | com/miui/org/chromium/chrome/browser/widget/progress: ToolbarProgressBar - ToolbarProgressBar.java -> 0.9994
[+] com/miui/org/chromium/chrome/browser/update: HomePageDataUpdator - HomePageDataUpdator.java | com/miui/org/chromium/chrome/browser/update: HomePageDataUpdator - HomePageDataUpdator.java -> 0.9794
[+] com/miui/org/chromium/chrome/browser/init: ChromeBrowserInitializer$2 - ChromeBrowserInitializer.java | com/miui/org/chromium/chrome/browser/init: ChromeBrowserInitializer$2 - ChromeBrowserInitializer.java -> 0.9771
[+] com/miui/org/chromium/chrome/browser/init: ChromeBrowserInitializer - ChromeBrowserInitializer.java | com/miui/org/chromium/chrome/browser/init: ChromeBrowserInitializer - ChromeBrowserInitializer.java -> 0.9996
[+] com/miui/org/chromium/chrome/browser/download: DownloadDialogFragment - DownloadDialogFragment.java | com/miui/org/chromium/chrome/browser/download: DownloadDialogFragment - DownloadDialogFragment.java -> 0.9136
[+] com/miui/org/chromium/chrome/browser/download: DownloadHandler - DownloadHandler.java | com/miui/org/chromium/chrome/browser/download: DownloadHandler - DownloadHandler.java -> 0.9978
[+] com/miui/org/chromium/chrome/browser/adblock: AdCheckHelper - AdCheckHelper.java | com/miui/org/chromium/chrome/browser/adblock: AdCheckHelper - AdCheckHelper.java -> 0.9931
[+] com/miui/org/chromium/chrome/browser/download: DownloadHandler$2 - DownloadHandler.java | com/miui/org/chromium/chrome/browser/download: DownloadHandler$2 - DownloadHandler.java -> 0.9666
[+] com/miui/org/chromium/chrome/browser/omnibox: NavigationBar - NavigationBar.java | com/miui/org/chromium/chrome/browser/omnibox: NavigationBar - NavigationBar.java -> 0.9834
[+] com/miui/org/chromium/chrome/browser/download: DownloadDialogFragment$4$1 - DownloadDialogFragment.java | com/miui/org/chromium/chrome/browser/download: DownloadDialogFragment$4$1 - DownloadDialogFragment.java -> 0.9563
[+] com/miui/org/chromium/chrome/browser/cloudconfig: CloudConfigManager - CloudConfigManager.java | com/miui/org/chromium/chrome/browser/cloudconfig: CloudConfigManager - CloudConfigManager.java -> 0.8747
[+] com/miui/org/chromium/chrome/browser: ChromeTabbedActivity - ChromeTabbedActivity.java | com/miui/org/chromium/chrome/browser: ChromeTabbedActivity - ChromeTabbedActivity.java -> 0.9998
[+] com/miui/org/chromium/chrome/browser/omnibox: LocationBarLayout - LocationBarLayout.java | com/miui/org/chromium/chrome/browser/omnibox: LocationBarLayout - LocationBarLayout.java -> 0.9932
[+] com/miui/org/chromium/chrome/browser/omnibox/suggestions: SuggestionAdapter$SuggestionResult - SuggestionAdapter.java | com/miui/org/chromium/chrome/browser/omnibox/suggestions: SuggestionAdapter$SuggestionResult - SuggestionAdapter.java -> 0.9920
[+] com/miui/org/chromium/chrome/browser/omnibox/suggestions: SuggestionAdapter$SuggestFilter - SuggestionAdapter.java | com/miui/org/chromium/chrome/browser/omnibox/suggestions: SuggestionAdapter$SuggestFilter - SuggestionAdapter.java -> 0.9920
[+] com/miui/org/chromium/chrome/browser/omnibox/suggestions: MostVisitedDataProvider - MostVisitedDataProvider.java | com/miui/org/chromium/chrome/browser/omnibox/suggestions: MostVisitedDataProvider - MostVisitedDataProvider.java -> 0.9798
[+] com/miui/org/chromium/chrome/browser/webviewclient: NightModeHelper - NightModeHelper.java | com/miui/org/chromium/chrome/browser/webviewclient: NightModeHelper - NightModeHelper.java -> 0.9570
[+] com/miui/org/chromium/chrome/browser/omnibox/suggestions: AutocompleteCoordinator - AutocompleteCoordinator.java | com/miui/org/chromium/chrome/browser/omnibox/suggestions: AutocompleteCoordinator - AutocompleteCoordinator.java -> 0.9964
[+] com/miui/org/chromium/chrome/browser/omnibox/suggestions: SuggestionViewFactory - SuggestionViewFactory.java | com/miui/org/chromium/chrome/browser/omnibox/suggestions: SuggestionViewFactory - SuggestionViewFactory.java -> 0.8978

该输出包含我们不关心的用于补丁分析目的的信息,因此,我们必须删除这些无用信息。乍一看,我们会注意到各种组件(如下载和建议相关的部分)都发生了变化。但是,由于类名没有被混淆,我们可以很快观察到一个名为NavigationBar的类略有改变。这非常有趣,因为该漏洞会涉及导航栏上的问题。现在,让我们检查一下这个类中都进行了哪些修改。

找到被修改的代码

可以在smali或伪造的Java中检查代码被修改的情况。为方便起见,我们查看了Java表示,因为它更易于阅读。在此之前,我们必须通过Jadx等外部工具对APK进行反编译。完成后,我们可以比较与com.miui.org.chromium.chrome.browser.omnibox包中名为NavigationBar的类对应的Java源代码。

使用像Meld这样的基于文本的视觉diff工具,我们能够有效地发现哪些方法已被修改。在本文的示例中,安全补丁只修改了一个参数过程方法:String pickSearchKeyWords(String)。接下来,我们将重点介绍补丁修改前后两种过程方法之间的差异:

1 --- com.mi.globalbrowser.mini-1.6.1/com/miui/org/chromium/chrome/browser/omnibox/NavigationBar.java
 2 +++ com.mi.globalbrowser.mini-1.6.3/com/miui/org/chromium/chrome/browser/omnibox/NavigationBar.java
 3   private String pickSearchKeyWords(String str) {
 4     if (str == null || getCurrentTab() == null) {
 5       return null;
 6     }
 7     Object url = getCurrentTab().getUrl();
 8     if (TextUtils.isEmpty(url)) {
 9       return null;
10     }
11     Uri parse = Uri.parse(url);
12     String host = parse.getHost();
13     if (TextUtils.isEmpty(host)) {
14       return null;
15     }
16     CharSequence charSequence = "";
17     String[] searchEngineLabels = getSearchEngineLabels();
18 +   int i = 0;
19     if (searchEngineLabels != null && searchEngineLabels.length > 0) {
20 -     for (String str2 : searchEngineLabels) {
21 +     int length = searchEngineLabels.length;
22 +     int i2 = 0;
23 +     while (i < length) {
24 +       String str2 = searchEngineLabels[i];
25         if (!TextUtils.isEmpty(str2) && host.contains(str2.trim().toLowerCase())) {
26           charSequence = SearchEngineSwitchUtil.getInstance(this.mContext).getQueryParameterNameForSearchTermsMap(str2);
27 +         i2 = 1;
28         }
29 +       i++;
30       }
31 +     i = i2;
32     }
33     if (charSequence == null) {
34       charSequence = "";
35     }
36     String queryParameter = parse.getQueryParameter(charSequence);
37     if (TextUtils.isEmpty(charSequence) || TextUtils.isEmpty(queryParameter)) {
38       if (host.contains("yahoo.com")) {
39         queryParameter = parse.getQueryParameter("p");
40       } else if (host.contains("yandex.ru")) {
41         queryParameter = parse.getQueryParameter("text");
42 -     } else {
43 +     } else if (i != 0) {
44         queryParameter = parse.getQueryParameter("q");
45       }
46     }
47     return queryParameter;
48   }

分析安全补丁

现在回到漏洞本身,我们需要弄清楚这些安全补丁是如何实际缓解漏洞的。通过阅读原始源代码,我们可以看到,如果主机既不包含yahoo.com也不包含yandex.ru,则该方法将返回查询参数q,而不管实际主机是否是搜索引擎。这意味着无论主机是什么,如果URL中存在一个q参数,它的值都会被选中并输出。这就是为什么当https://www.evil.com/?q=www.google.com作为参数过程时,即使www.evil.com不是一个已知的搜索引擎,该方法仍将返回www.google.com。

为了避免发生该错误,补丁代码现在会检查主机是否是一个已知的搜索引擎,并将信息存储在变量(L27和L31)中。如果是,则从q查询参数(L43)获取值。因此,由于www.evil.com不是一个真正的搜索引擎,以前暴露的攻击场景就不会再运行。

为了确认我们的假设,我们必须通过Frida进行额外的动态分析。我们希望拦截对pickSearchKeyWords()方法的调用,并为原始版本(1.6.1)和补丁版本(1.6.3)打印输入参数和输出字符串。可以编写一个小脚本来执行此操作:

console.log("[+] JS script successfully loaded");

Java.perform(function () {
  var method_hook = Java.use("com.miui.org.chromium.chrome.browser.omnibox.NavigationBar");

  method_hook.pickSearchKeyWords.overload("java.lang.String").implementation = function(str) {
    ret = this.pickSearchKeyWords(str) // calling genuine method's implementation
    console.log("[+] pickSearchKeyWords(" + str + ") = " + ret)
    return ret
  }
});

下面的代码块显示了使用两个版本访问https://www.evil.com/?q=www.google.com时的结果:

on 1.6.1:
[+] pickSearchKeyWords(https://www.evil.com/?q=www.google.com) = www.google.com

on 1.6.3:
[+] pickSearchKeyWords(https://www.evil.com/?q=www.google.com) = null

可以很明显地发现,修改有效地缓解了漏洞,而不是返回www.google.com这样的钓鱼页面。因此,调用方法可以缓解这一恶意操作,从而在浏览器的导航栏上显示完整的URL。

如何将Redex与diff工具相结合,检测被混淆处理的应用程序中到底发生了哪些修改?

接下来,我们将展示另一个具体的用例,其中我们结合了diff分析和Redex工具(ReDex 是 Facebook 开发的一个 Android 字节码的优化工具),来检查一个著名的音乐应用程序的新旧版本的前后变化。

背景介绍

首先,让我们定义一下修改后的应用程序指的是什么程序。真实的修改后的应用程序是指已经被开始应用的程序,所谓修改是指为了添加或删除某些功能,进行的性能的变化。例如,许多嵌入广告的应用程序很可能被修改为完全禁用广告的模式。在本文中,我们所举的例子,就是一个经过改进的音乐应用程序,修改后的版本可以删除之前插入的所有广告。

这些改动可以通过重新包装来完成,这意味着开发人员会进入真正的应用程序后台,并注入、删除或修改一些代码。这个过程通常在smali表示级别执行,但也可以在本地级别执行。在修改代码之后,开发人员能够重新打包应用程序,从而生成一个全新的APK,它看起来像原始的应用程序,但是使用了他们新修改的Dalvik字节码。这种技术在恶意软件领域也很常用,因为对手可以很容易地在一个应用程序中插入恶意代码,并像发布真实代码一样发布它,也就是说,用户并不怀疑这些插入的恶意代码。目前,最受欢迎的工具大概是apktool(apktool反编译工具是一款绿色小巧的apk反编译软件)。

此外,修改代码的人有时可能会在修改后再附加一层额外的保护层,以保护修改免受逆向工程的影响。

对修改后的代码进行反混淆处理

首先,我们使用与上面所述的相同的方式将原始的和修改过的应用程序用diff引擎进行了比较。这会输出大量匹配结果,并只需要进行少量的修改(匹配距离大于95%)。在查看匹配结果中随机选择的类之后,我们注意到在Dalvik字节码级别的许多方法中,都添加了一些无意义的命令。这清楚地表明,修改者存在故意混淆的行为。因此,它使得查找实际修改的类变得非常的困难。使用Dalvik字节码的方法,会使输出充满误报。接下来,我们将重点介绍如何使用diff工具,分析原始应用程序和修改后的应用程序之间的差异:

--- com.XXXXXXX.app-genuine/com/XXXXXXX/app/feature/ad/model/AudioAd.smali
+++ com.XXXXXXX.app-modded/com/XXXXXXX/app/feature/ad/model/AudioAd.smali
  .method public getArtworkUrl()Ljava/lang/String;
-     .locals 3
+     .locals 4
      .annotation build Landroid/support/annotation/Nullable;
      .end annotation

-     .line 98
+     const/4 v3, 0x3
+
      iget-object v0, p0, Lcom/XXXXXXX/app/feature/ad/model/AudioAd;->mCoverUrl:Ljava/lang/String;

+     const/4 v3, 0x6
+
      if-eqz v0, :cond_0

+     const/4 v3, 0x5
+
      iget-object v0, p0, Lcom/XXXXXXX/app/feature/ad/model/AudioAd;->mCoverUrl:Ljava/lang/String;

+     const/4 v3, 0x7
+
+     const-string v1, "igf"
+
      const-string v1, "gif"

+     const/4 v3, 0x5
+
      invoke-virtual {v0, v1}, Ljava/lang/String;->endsWith(Ljava/lang/String;)Z

      move-result v0

+     const/4 v3, 0x1
+
      if-eqz v0, :cond_0

+     const/4 v3, 0x5
+
      const/4 v0, 0x1

-     .line 99
+     const/4 v3, 0x2
+
      new-array v0, v0, [Ljava/lang/Object;

      const/4 v1, 0x0

+     const/4 v3, 0x5
+
      iget-object v2, p0, Lcom/XXXXXXX/app/feature/ad/model/AudioAd;->mCoverUrl:Ljava/lang/String;

      aput-object v2, v0, v1

+     const/4 v3, 0x4
+
      const/4 v0, 0x0

      return-object v0

-     .line 102
      :cond_0
+     const/4 v3, 0x0
+
      iget-object v0, p0, Lcom/XXXXXXX/app/feature/ad/model/AudioAd;->mCoverUrl:Ljava/lang/String;

+     const/4 v3, 0x1
+
      return-object v0
  .end method

通过这个示例,我们确实可以观察到,所有添加的命令对于执行过程中的行为都是无用的。它们的目的只是向对应用程序执行静态分析的逆向工程师隐藏实际的修改代码。因此,我们需要事先删除死命令(dead instruction)。我们还可以注意到,obfuscator没有改变结构和类层次结构,只是改变了字节码。

这就是Redex的用武之地,该开源工具是由Facebook开发的Android字节码优化器。它提供了一个框架来处理DEX文件并对其执行各种操作。Redex将Dalvik字节码作为输入,应用优化过程并生成一个优化的Dalvik字节码。下图就是它的工作原理:

开发一个基于Dalvik字节码的相似性检测引擎,比较同一款Android应用程序的不同版本之间的代码差异(二)

上图还提供了Dalvik方法的控制流过程,这种方法功能强大且高效。此外,它还提供了一个命令行接口,该接口将APK作为输入并生成另一个APK作为输出。根据实际想要应用的优化类型,我们可以配置各种优化过程,例如RemoveUnreachablePass,它可以删除无法访问的代码片段。这些过程能够根据其目的修改字节码。例如,名为RemoveUnusedArgsPass的过程旨在通过删除未使用的参数来删除字节码。

此外,Redex还提供了一个名为LocalDcePass的进程,代表Local Dead Code Elimination,翻译成中文就是删除本地的无用代码。在我们的示例中,该删除进程非常有趣,因为无意义的命令基本上被认为是死代码,因此Redex可以帮助我们删除它们并自动生成修改后的干净版本。换句话说,利用Redex,我们可以在分析之前规范化应用程序。我们在本文中使用了以下简单的配置文件,请注意,RegAllocPass是必需的。

{
  "redex" : {
    "passes" : [
      "LocalDcePass",
      "RegAllocPass"
    ]
  }
}

通过Redex分析原始应用程序和修改后的应用程序,我们可以为每个版本获得规范化的APK。再看看之前使用getArtworkUrl()方法进行的多余输出,所有额外的命令都消失了。现在,它们在smali表示级别上看起来很像。现在我们已经成功进行了反混淆处理。因此,现在就能够在那些规范化的APK上重新运行diff过程。

.method public getArtworkUrl()Ljava/lang/String;
    .locals 3
    .annotation build Landroid/support/annotation/Nullable;
    .end annotation

    iget-object v0, p0, Lcom/XXXXXXX/app/feature/ad/audio/model/AudioAd;->mCoverUrl:Ljava/lang/String;

    if-eqz v0, :cond_0

    iget-object v1, p0, Lcom/XXXXXXX/app/feature/ad/audio/model/AudioAd;->mCoverUrl:Ljava/lang/String;

    const-string v0, "gif"

    invoke-virtual {v1, v0}, Ljava/lang/String;->endsWith(Ljava/lang/String;)Z

    ...

对比修好前后的具体变化

该过程与上面所讲的CVE-2019-10875漏洞分析过程大致相同。首先,我们必须找到开发包,以使类集数量尽可能小。但是,事实证明在这一步很可能会发生一些意外,因为通常更改是在一些外部SDK中进行的,而不是在真正的应用程序代码本身上进行的。此时,我们是找不到任何关于修改位置的信息的。这就是对比性能是很重要的一个原因,即使我们比较大量的类,计算时间也必须合理。

由于本文所举的这个音乐应用程序在嵌入式类方面的修改不是很大,让我们比较所有类(大约20400个),无论它们位于哪个包中,也就是说,跳过过滤阶段。diff过程的相似度计算和输出时间约为1分47秒:

...
[+] com/adserver/library/mediation: ASAppLovinAdapter | com/adserver/library/mediation: ASAppLovinAdapter -> 0.9973
[+] com/adserver/library/mediation: ASVungleAdapter$4 | com/adserver/library/mediation: ASVungleAdapter$4 -> 0.9960
[+] com/adserver/library/mediation: ASMediationAdManager$1 | com/adserver/library/mediation: ASMediationAdManager$1 -> 0.9441
[+] com/adserver/library/mediation: ASAdColonyAdapter$1 | com/adserver/library/mediation: ASAdColonyAdapter$1 -> 0.9896
[+] com/adserver/library/mediation: ASAdMobAdapter | com/adserver/library/mediation: ASAdMobAdapter -> 0.9988
[+] com/adserver/library/controller/mraid: ASMRAIDVideoController | com/adserver/library/controller/mraid: ASMRAIDVideoController -> 0.9963
[+] com/adserver/library/controller: ASAdViewController$ProxyHandler | com/adserver/library/controller: ASAdViewController$ProxyHandler -> 0.9996
[+] com/adserver/library/controller: ASAdViewController | com/adserver/library/controller: ASAdViewController -> 0.9714
[+] com/adserver/library: ASInterstitialView | com/adserver/library: ASInterstitialView -> 0.9656
[+] com/google/android/gms/internal/measurement: zzkd | com/google/android/gms/internal/measurement: zzkd -> 0.9967
[+] com/google/android/gms/internal/measurement: zzfm | com/google/android/gms/internal/measurement: zzfm -> 0.9969
[+] com/google/android/gms/internal/ads: zzasv | com/google/android/gms/internal/ads: zzasv -> 0.9991
[+] com/google/android/gms/internal/ads: zzyk | com/google/android/gms/internal/ads: zzyk -> 0.9925
[+] com/google/android/gms/internal/ads: zzpn | com/google/android/gms/internal/ads: zzpn -> 0.9912
[+] com/google/android/gms/internal/ads: zzald | com/google/android/gms/internal/ads: zzald -> 0.9980
[+] com/google/android/gms/internal/ads: zzapi | com/google/android/gms/internal/ads: zzapi -> 0.9895
[+] com/google/android/gms/internal/ads: zzarh | com/google/android/gms/internal/ads: zzarh -> 0.9925
[+] com/google/android/gms/internal/ads: zzass | com/google/android/gms/internal/ads: zzass -> 0.9674
[+] com/google/android/gms/internal/ads: zzom | com/google/android/gms/internal/ads: zzom -> 0.9718
[+] com/google/android/gms/ads/internal/overlay: zzo | com/google/android/gms/ads/internal/overlay: zzo -> 0.9417
[+] com/google/android/gms/ads/internal/overlay: zzd | com/google/android/gms/ads/internal/overlay: zzd -> 0.9820
[+] com/google/android/gms/common: GooglePlayServicesUtil | com/google/android/gms/common: GooglePlayServicesUtil -> 0.9945
[+] com/google/android/gms/common: GooglePlayServicesUtilLight | com/google/android/gms/common: GooglePlayServicesUtilLight -> 0.9581
...

注意,由于修改了许多类,结果被自动截断。我们可以通过这些信息快速地了解了修改的代码所在的位置,它们主要出现在名为com.adserver.android.library和com.google.android.gms的包中。在本文中,我们只关注检查特定的代码片段,因为完整的分析不是本文的目的。然后,让我们看看zzd类的private final b(Z)V方法。

--- com.XXXXXXX.app-genuine/com/google/android/gms/ads/internal/overlay/zzd.smali
+++ com.XXXXXXX.app-modded/com/google/android/gms/ads/internal/overlay/zzd.smali
     iget-object v2, v1, Lcom/google/android/gms/ads/internal/overlay/zzd;->b:Lcom/google/android/gms/ads/internal/overlay/AdOverlayInfoParcel;

     iget-object v2, v2, Lcom/google/android/gms/ads/internal/overlay/AdOverlayInfoParcel;->l:Ljava/lang/String;

     if-eqz v2, :cond_11

-    iget-object v3, v1, Lcom/google/android/gms/ads/internal/overlay/zzd;->c:Lcom/google/android/gms/internal/ads/zzaqw;
-
-    iget-object v2, v1, Lcom/google/android/gms/ads/internal/overlay/zzd;->b:Lcom/google/android/gms/ads/internal/overlay/AdOverlayInfoParcel;
-
-    iget-object v2, v2, Lcom/google/android/gms/ads/internal/overlay/AdOverlayInfoParcel;->l:Ljava/lang/String;
-
-    invoke-interface {v3, v2}, Lcom/google/android/gms/internal/ads/zzaqw;->loadUrl(Ljava/lang/String;)V
+    invoke-static {}, Lcom/PinkiePie;->DianePie()V

     :goto_b
     iget-object v2, v1, Lcom/google/android/gms/ads/internal/overlay/zzd;->b:Lcom/google/android/gms/ads/internal/overlay/AdOverlayInfoParcel;

     iget-object v2, v2, Lcom/google/android/gms/ads/internal/overlay/AdOverlayInfoParcel;->d:Lcom/google/android/gms/internal/ads/zzaqw;

这个修改基本上覆盖了对loadUrl()方法的初始调用,以及对名为DianePie()的静态方法的另一个调用。携带此方法的PinkiePie类在原始版本中不存在,因此它被添加到中间。看看它的实现过程,代码是空的,这意味着它们是无用的。因此,它的作用类似于删除loadUrl()调用。正如方法的名称所示,这意味着在修改后的版本上不会访远程问广告资源。

总结

这两篇文章指在概述开发一个基于Dalvik字节码的相似性检测引擎,比较同一款Android应用程序的不同版本之间的代码差异。尽管如此,文中所讲的方法仍然存在一些缺点,比如当遇到某些特定配置时,我们文中所讲的工具会产生误报。另外,在处理一堆在结构层面看起来很相似并且不包含太多代码的小类时,也经常误报。

上一篇 : CISA 再发安全警告:推荐 Windows 用户尽快修复 BlueKeep 漏洞

下一篇 : 周鸿祎:ISC2019要为中国网络安全做点实事