Android污點分析工具flowdroid源碼簡析

jbzq6611 7年前發布 | 24K 次閱讀 安卓開發 Android開發 移動開發

flowdroid是一款對Android app進行風險分析的應用,下面深入其源碼對其工作的流程進行相關的探究。

1、準備

a) 下載相關源碼(包括soot、heros、jasmin、soot-infoflow、soot-infoflow、soot-infoflow-android)到同一文件夾中,使用eclipse將源碼依次導入就完成了整體項目的導入,盡量使用最新版eclipse,如果版本太老,導入后可能會出現各種問題;完成導入后整體項目結構如下所示:

b) 本次測試使用的APK是flowdroid本身提供的一個apk:enriched1.apk,位于soot-infoflow-android/testAPKs目錄下,該應用包含一個主Activity,下圖展示了Mainfest.xml文件的具體內容:

主要的操作是在主Activity的onCreate方法中獲取設備ID(DeviceId)然后通過短信的形式發送出去,使用Jimple代碼表示如下:

$r6 = virtualinvoke $r4.<android.telephony.TelephonyManager: java.lang.String getDeviceId()>() 
virtualinvoke $r10.<android.telephony.SmsManager: void sendTextMessage(java.lang.String,java.lang.String,java.lang.String,android.app.PendingIntent,android.app.PendingIntent)>($r6, $r11, $r12, $r13, $r14)

c) source(風險產生點)與sink(風險觸發點)點,使用flowdroid源碼中提供的:SourceAndSink.txt,位于soot-infoflow-android根目錄下;下面展示本次使用的source點、sink點,從中可以看出source、sink的定義不僅包含了Jimple格式的方法聲明,也包含了調用該方法需要聲明的權限,從上圖的Manifest文件中可以看出,這兩個方法需要使用的權限均已聲明;

<android.telephony.SmsManager: void sendTextMessage(java.lang.String,java.lang.String,java.lang.String,android.app.PendingIntent,android.app.PendingIntent)> android.permission.SEND_SMS -> _SINK_ 
<android.telephony.TelephonyManager: java.lang.String getDeviceId()> android.permission.READ_PHONE_STATE -> _SOURCE_

d) 回調函數(執行某些回調操作將調用的函數)使用soot-infoflow-android根目錄下的AndroidCallbacks.txt;

e) 污染易傳播點,表示當source(風險產生點)經過該函數后,新產生的變量將會成為新的source點,如list = List.add(source1),list將會成為新的source源;該文件使用soot-infoflow根目錄下的EasyTaintWrapperSource.txt文件;

f) 本地運行的主函數位于soot-infoflow-android/src/soot.jimple.infoflow.android.TestApps目錄下的Test.java文件中,在Run Configurations中配置待掃描apk文件地址及android.jar目錄地址,配置詳情如下圖所示:

至此一切配置就緒,點擊上圖Run按鈕運行,就會看到flowdroid運行起來了,經過不到一分鐘(這是demo的時間,復雜的應用就呵呵吧)的分析,就能夠看到運行輸入的結果,先來一張運行結果圖:

由于結果太長,不能完全展示結果的內容,下面將結果拷貝下來,用文字進行展示:

Found a flow to sink virtualinvoke $r10. <android.telephony.SmsManager: void sendTextMessage (java.lang.String,java.lang.String,java.lang.String,android.app.PendingIntent,android.app.PendingIntent)>($r6, $r11, $r12, $r13, $r14), from the following sources:

- $r6 = virtualinvoke $r4. <android.telephony.TelephonyManager: java.lang.String getDeviceId()> () (in <de.ecspride.reflectionprivacyleak1.ReflectionPrivacyLeak1: void onCreate(android.os.Bundle)>)

Maximum memory consumption: 141.144008 MB

Analysis has run for 5.030271649 seconds

至此我們已經完成了項目導入及運行一個demo程序,下面正式進入源碼的分析。

2、收集source點、sink點、入口點(Entrypoint)

這個題目是根據執行的第一個關鍵函數(如下)取的,但是這個函數實際的作用其實有一些名不副實,該函數位于soot-infoflow-android/src/soot.jimple.infoflow.android.TestApps/Test.java的642行:

app.calculateSourcesSinksEntrypoints("SourcesAndSinks.txt");

從main()函數的起始點到該函數之間的一些操作,主要是變量初始化、賦值的一些操作,在此不再詳述;進入該函數,該函數主要進行一個操作,根據source、sink文件定義類型,對文件中的內容進行提取,然后進入calculateSourcesSinksEntrypoints(parser)函數,其參數parser就是對source、sink點進行解析后的變量,解析函數:

parser = PermissionMethodParser.fromFile(sourceSinkFile)

這個函數是對txt文件進行解析;進入 calculateSourcesSinksEntrypoints這個 函數,大致瀏覽下該函數,函數應該是這個本次操作的主函數,對其操作進行拆解,首先執行操作的代碼如下所示:

  ProcessManifest processMan = new ProcessManifest(apkFileLocation);
  this.appPackageName = processMan.getPackageName();
  this.entrypoints = processMan.getEntryPointClasses();

上面代碼的主要作用是反編譯并解析該APK的Manifest.xml文件,生成變量processMan,獲取其packagename并賦值給this.appPackageName=”de.ecspride.reflectionprivacyleak1″,并獲取其程序的入口點,Android應用的入口點實際上就是其定義的四大組件(Activity、Broadcast、Provider、Service),此應用只定義了一個Activity,因此

this.entrypoints=[de.ecspride.reflectionprivacyleak1.ReflectionPrivacyLeak1];

然后使用ARSCFileParser類反編譯apk文件,并賦值給變量resParser,然后進入calculateCallbackMethods(resParser, lfp)函數,其參數resParser表示剛反編譯的apk文件,lfp表示對布局文件的反編譯,屬于類LayoutFileParser,進入calculateCallbackMethods函數,首先看到的就是對soot的一些操作,如下所示:

soot.G.reset();
initializeSoot(true);
createMainMethod();

第一個語句soot.G.reset()是一個標準的soot操作,用于清空soot之前所有操作遺留下的緩存值,后面的代碼中將會多次使用該操作;

第二個語句initializeSoot(true)的完整函數如下所示,參數true表示是否構建程序調用圖(控制流圖);該函數主要作用是反編譯.dex文件,反編譯生成jimple文件,為查看方便,函數的說明將會在代碼注解中詳細給出;

private void initializeSoot(boolean constructCallgraph) {
      //相當于soot命令:-no-bodies-for-excluded,表示不加載未被包含的類
      Options.v().set_no_bodies_for_excluded(true);
      //相當于soot命令:-allow-phantom-refs,表示是否加載未被解析的類
      Options.v().set_allow_phantom_refs(true);
      //相當于soot命令:-f FORMAT -output-format FORMAT,設置輸出的格式,此處設置為不輸出,因此不會輸出反編譯后的jimple文件
      Options.v().set_output_format(Options.output_format_none);
      //相當于soot命令:-w -whole-program,以全局應用的模式運行
      Options.v().set_whole_program(constructCallgraph);
      //相當于soot命令:-process-path DIR -process-dir DIR,待反編譯文件所在的文件夾,此處是apk文件地址
      Options.v().set_process_dir(Collections.singletonList(apkFileLocation));
      if (forceAndroidJar)
         //相當于soot命令:-android-jars PATH,表示在該路徑下尋找android.jar文件
         Options.v().set_force_android_jar(androidJar);
      else
         //相對于soot命令:-force-android-jar PATH,表示強制在該路徑下尋找android.jar文件
         Options.v().set_android_jars(androidJar);
      //相當于soot命令:-src-prec FORMAT,表示反編譯后文件的生成文件類型,此處為jimple類型
      Options.v().set_src_prec(Options.src_prec_apk_class_jimple);
      //是否記錄代碼所在行
      Options.v().set_keep_line_number(false);
      //是否記錄代碼偏移量
      Options.v().set_keep_offset(false);

      //設置上述另一的變量類型,該設置需要保證在soot反編譯之前進行
      if (sootConfig != null)
         sootConfig.setSootOptions(Options.v());

      Options.v().set_soot_classpath(getClasspath());
      Main.v().autoSetOptions();

      // 構建控制流圖選項,默認是SPARK
      if (constructCallgraph) {
         switch (config.getCallgraphAlgorithm()) {
         case AutomaticSelection:
         case SPARK:
            Options.v().setPhaseOption("cg.spark", "on");
            break;
         case GEOM:
            Options.v().setPhaseOption("cg.spark", "on");
            AbstractInfoflow.setGeomPtaSpecificOptions();
            break;
         case CHA:
            Options.v().setPhaseOption("cg.cha", "on");
            break;
         case RTA:
            Options.v().setPhaseOption("cg.spark", "on");
            Options.v().setPhaseOption("cg.spark", "rta:true");
            Options.v().setPhaseOption("cg.spark", "on-fly-cg:false");
            break;
         case VTA:
            Options.v().setPhaseOption("cg.spark", "on");
            Options.v().setPhaseOption("cg.spark", "vta:true");
            break;
         default:
            throw new RuntimeException("Invalid callgraph algorithm");
         }
      }
      //使用soot反編譯dex文件,并將反編譯后的文件加載到內存中
      Scene.v().loadNecessaryClasses();
   }

初始化soot完成后,進入第三個語句createMainMethod(),其代碼如下所示;其主要的操作是構造一個虛擬的main方法,并將入口點(entrypoint)相關類添加到這個虛方法中;

private void createMainMethod() {
      // Always update the entry point creator to reflect the newest set
      // of callback methods
      SootMethod entryPoint = createEntryPointCreator().createDummyMain();
      Scene.v().setEntryPoints(Collections.singletonList(entryPoint));
      if (Scene.v().containsClass(entryPoint.getDeclaringClass().getName()))
         Scene.v().removeClass(entryPoint.getDeclaringClass());
      Scene.v().addClass(entryPoint.getDeclaringClass());

      // addClass() declares the given class as a library class. We need to
      // fix this.
      entryPoint.getDeclaringClass().setApplicationClass();
   }

此處在將entrypoint塞入虛擬main方法中的時候,由于entryponit是Android的四大組件,因此在塞入main方法中的時候需要對組件進行建模,建模的方法是根據組件的生命周期(onCreate、onStart、onResume、onPause、onStop、onRestart、onDestory),依次塞入其相關的方法,下面給出生成的 dummyMainMethod 方法主體函數,可以從中感受下。

public static void dummyMainMethod(java.lang.String[])
    {
        java.lang.String[] $r0;
        int $i0;
        de.ecspride.reflectionprivacyleak1.ReflectionPrivacyLeak1 $r1;
        $r0 := @parameter0: java.lang.String[];
        $i0 = 0;
     label1:
        if $i0 == 0 goto label5;
        $r1 = new de.ecspride.reflectionprivacyleak1.ReflectionPrivacyLeak1;
        specialinvoke $r1.<de.ecspride.reflectionprivacyleak1.ReflectionPrivacyLeak1: void <init>()>();
        if $i0 == 1 goto label5;
        virtualinvoke $r1.<de.ecspride.reflectionprivacyleak1.ReflectionPrivacyLeak1: void onCreate(android.os.Bundle)>(null);
     label2:
        virtualinvoke $r1.<de.ecspride.reflectionprivacyleak1.ReflectionPrivacyLeak1: void onStart()>();
     label3:
        virtualinvoke $r1.<de.ecspride.reflectionprivacyleak1.ReflectionPrivacyLeak1: void onResume()>();
        virtualinvoke $r1.<de.ecspride.reflectionprivacyleak1.ReflectionPrivacyLeak1: void onPause()>();
        if $i0 == 3 goto label3;
        virtualinvoke $r1.<de.ecspride.reflectionprivacyleak1.ReflectionPrivacyLeak1: void onStop()>();
        if $i0 == 4 goto label4;
        virtualinvoke $r1.<de.ecspride.reflectionprivacyleak1.ReflectionPrivacyLeak1: void onRestart()>();
        if $i0 == 5 goto label2;
     label4:
        virtualinvoke $r1.<de.ecspride.reflectionprivacyleak1.ReflectionPrivacyLeak1: void onDestroy()>();
     label5:
        if $i0 == 7 goto label1;
        return;
    }

接下來便是進行回調函數的收集,主要的操作函數如下所示:

  for (Entry<String, Set<SootMethodAndClass>> entry : jimpleClass.getCallbackMethods().entrySet()) {
            Set<SootMethodAndClass> curCallbacks = this.callbackMethods.get(entry.getKey());
            if (curCallbacks != null) {
               if (curCallbacks.addAll(entry.getValue()))
                  hasChanged = true;
            } else {
               this.callbackMethods.put(entry.getKey(), new HashSet<>(entry.getValue()));
               hasChanged = true;
            }
      }

      if (entrypoints.addAll(jimpleClass.getDynamicManifestComponents()))
         hasChanged = true;
   }
   // Collect the XML-based callback methods
   collectXmlBasedCallbackMethods(resParser, lfp, jimpleClass);

其操作是遍歷entrypoint,然后將entry中的回調函數添加到this.callbackMethods變量中,這里執行的函數在else中,添加到this.callbackMethods中的值為:

{de.ecspride.reflectionprivacyleak1.ReflectionPrivacyLeak1=
[<de.ecspride.reflectionprivacyleak1.ReflectionPrivacyLeak1: void onStart()>, 
<de.ecspride.reflectionprivacyleak1.ReflectionPrivacyLeak1: void onDestroy()>, 
<de.ecspride.reflectionprivacyleak1.ReflectionPrivacyLeak1: void onPause()>, 
<de.ecspride.reflectionprivacyleak1.ReflectionPrivacyLeak1: void onRestart()>, 
<de.ecspride.reflectionprivacyleak1.ReflectionPrivacyLeak1: void onResume()>, 
<de.ecspride.reflectionprivacyleak1.ReflectionPrivacyLeak1: void onStop()>, 
<de.ecspride.reflectionprivacyleak1.ReflectionPrivacyLeak1: void onCreate(android.os.Bundle)>]},

從中可以看出添加的回調函數主要是該Activity生命周期中的函數,這樣在上面生成的虛main方法調用到onCreate這些方法時,就會到this.callbackMethods中尋找相關的方法;隨后還會進入xml文件中分析相關的回調函數,這里沒有涉及。

calculateCallbackMethods(resParser, lfp)函數執行完成后,跳轉回calculateSourcesSinksEntrypoints()函數,至此完成了對Entry point、回調函數的收集,下一步就是完成source、sink的收集,這一步的操作并不涉及soot的相關操作,只是將SourceAndSink.txt文件中包含的source、sink點,封裝到sourceSinkManager中,具體在代碼中搜索source、sink點,在數據流追蹤中完成;

      {
         Set<SootMethodAndClass> callbacks = new HashSet<>();
         for (Set<SootMethodAndClass> methods : this.callbackMethods.values())
            callbacks.addAll(methods);
         sourceSinkManager = new AccessPathBasedSourceSinkManager(
               this.sourceSinkProvider.getSources(),
               this.sourceSinkProvider.getSinks(),
               callbacks,
               config.getLayoutMatchingMode(),
               lfp == null ? null : lfp.getUserControlsByID());
         sourceSinkManager.setAppPackageName(this.appPackageName);
         sourceSinkManager.setResourcePackages(this.resourcePackages);
         sourceSinkManager.setEnableCallbackSources(this.config.getEnableCallbackSources());
      }

到此已經完成了第一步的操作,主要是做一些分析前的準備工作,包括收集入口點(entrypoint)、回調函數(callback)、source點、sink點,為后面的數據流分析做準備。

3、數據流分析

數據流的分析主要依賴heros工具,可能大家有些時候對heros、jasmin與soot的關系理不大清,heros、jasmin是基于soot開發的工具,相當于soot的插件,不能獨立運行,因為沒有自己的main調用方法,目前下載最新版的編譯后的soot.jar里面默認是包含這兩個工具的。

上面進行初步的分析工作,下面將正式進行數據流的分析,即執行完Test.java/runAnalysis()方法的642行app.calculateSourcesSinksEntrypoints(“SourcesAndSinks.txt”)后,繼續向下執行,然后執行到數據流分析的入口點,651行:

final InfoflowResults res = app.runInfoflow(new MyResultsAvailableHandler()),

進入runInfoflow()函數,函數的前半部分主要進行一些賦值、初始化的操作,其中比較重要的是實例化一個info變量,該變量是Infoflow類的實例對象,然后使用info.computeInfoflow(apkFileLocation, path, entryPointCreator, sourceSinkManager)進行實際的分析,四個參數的含義為:

apkFileLocation:待分析apk文件的地址;

path:android.jar文件的地址,用于后面反編譯使用;

entryPointCreator:前面獲得的應用的入口函數;

sourceSinkManager:從SourceAndSink.txt文件中獲取的source點與sink點,一共包括89個source點、133個sink點;

進入該函數,其代碼如下所示,從diamante中可以看出,其操作跟2中的操作相類似:初始化soot,然后構造虛擬main方法并設置為入口點,最后使用runAnalysis()方法進行分析。

 public void computeInfoflow(String appPath, String libPath,
         IEntryPointCreator entryPointCreator,
         ISourceSinkManager sourcesSinks) {
      if (sourcesSinks == null) {
         logger.error("Sources are empty!");
         return;
      }
      initializeSoot(appPath, libPath, entryPointCreator.getRequiredClasses());
      // entryPoints are the entryPoints required by Soot to calculate Graph - if there is no main method,
      // we have to create a new main method and use it as entryPoint and store our real entryPoints
      Scene.v().setEntryPoints(Collections.singletonList(entryPointCreator.createDummyMain()));
      // Run the analysis
        runAnalysis(sourcesSinks, null);
   }

進入runAnalysis函數,首先第一個比較重要的操作就是使用soot構造控制流圖:constructCallgraph();先進入該函數,為方便說明,將在代碼中對關鍵部分進行注解;通過該方法生成控制流圖(callgraph)后,獲取控制流圖變量:

CallGraph appCallGraph = Scene.v().getCallGraph();

控制流圖在整個分析中非常關鍵,后面將有獨立的章節介紹控制流圖。

 protected void constructCallgraph() {
      // Allow the ICC manager to change the Soot Scene before we continue
      ipcManager.updateJimpleForICC();
      // Run the preprocessors
        for (PreAnalysisHandler tr : preProcessors)
            tr.onBeforeCallgraphConstruction();

        // Patch the system libraries we need for callgraph construction
        LibraryClassPatcher patcher = new LibraryClassPatcher();
        patcher.patchLibraries();

        // To cope with broken APK files, we convert all classes that are still
        // dangling after resolution into phantoms
        // 將所有未定義(dangling)的類轉化成虛類
        for (SootClass sc : Scene.v().getClasses())
         if (sc.resolvingLevel() == SootClass.DANGLING) {
            sc.setResolvingLevel(SootClass.BODIES);
            sc.setPhantomClass();
         }

      // We explicitly select the packs we want to run for performance
        // reasons. Do not re-run the callgraph algorithm if the host
        // application already provides us with a CG.
      if (config.getCallgraphAlgorithm() != CallgraphAlgorithm.OnDemand
            && !Scene.v().hasCallGraph()) {
           PackManager.v().getPack("wjpp").apply();
           // 生成控制流圖(callgraph)的關鍵步驟
           PackManager.v().getPack("cg").apply();
      }

      // If we don't have a FastHierarchy, we need to create it
      // 構造整個應用的層級關系
      hierarchy = Scene.v().getOrMakeFastHierarchy();

      // Run the preprocessors
        for (PreAnalysisHandler tr : preProcessors)
            tr.onAfterCallgraphConstruction();
   }

下面就是構建最關鍵的ICFG圖,我把它叫做數據流圖,數據流圖的構成推薦大家看flowdroid推薦的相關論文,很經典的算法,想要把它講明白需要很大的篇幅,如果有機會,我會單獨寫一篇關于數據流圖構成的文章,原論文中的數據流圖在圖形展示上會給人造成一些誤解,容易造成混淆。生成ICFG代碼如下所示,此處不再深入此代碼。

   iCfg = icfgFactory.buildBiDirICFG(config.getCallgraphAlgorithm(), config.getEnableExceptionTracking());

使用ICFG進行數據流分析,其代碼是非常冗長的,但是這些代碼大部分都是一些初始化的工作,看多了很容易把你繞暈,個人覺得這里面的關鍵操作主要在于兩點:

一是對于source、sink點的統計,其代碼如下所示;遍歷應用所有的方法,然后使用scanMethodForSourcesSinks函數對方法內的source、sink進行統計,并返回該方法中包含的sink點的數量;

   for (SootMethod sm : getMethodsForSeeds(iCfg))
      sinkCount += scanMethodForSourcesSinks(sourcesSinks, forwardProblem, sm);

進入scanMethodForSourcesSinks方法,源碼如下所示;其主要操作是通過判斷方法是否存在方法體,如果存在方法體,則遍歷方法體中的所有語句,soot中定義為Unit對象,可以將其強制轉化為Stmt對象,可以理解為jimple形式的java語句,然后判斷該語句是否在source、sink中包含,被包含的話,如果是source點,則首先將其作為初始0向量(ICFG圖起始點)存入zeroValue中,然后保存到collectedSources容器中;如果是sink點則直接存儲到collectedSinks容器中,這個尋找source點的方法,在后面介紹控制流圖的時候會使用到。

private int scanMethodForSourcesSinks(final ISourceSinkManager sourcesSinks, InfoflowProblem forwardProblem, SootMethod m) {
      if (getConfig().getLogSourcesAndSinks() && collectedSources == null) {
         collectedSources = new HashSet<>();
         collectedSinks = new HashSet<>();
      }

      int sinkCount = 0;
      if (m.hasActiveBody()) {
         // Check whether this is a system class we need to ignore
         final String className = m.getDeclaringClass().getName();
         if (config.getIgnoreFlowsInSystemPackages()
               && SystemClassHandler.isClassInSystemPackage(className))
            return sinkCount;
         // Look for a source in the method. Also look for sinks. If we
         // have no sink in the program, we don't need to perform any
         // analysis
         PatchingChain<Unit> units = m.getActiveBody().getUnits();
         for (Unit u : units) {
            Stmt s = (Stmt) u;
            if (sourcesSinks.getSourceInfo(s, iCfg) != null) {
               forwardProblem.addInitialSeeds(u, Collections.singleton(forwardProblem.zeroValue()));
               if (getConfig().getLogSourcesAndSinks())
                  collectedSources.add(s);
               logger.debug("Source found: {}", u);
            }
            if (sourcesSinks.isSink(s, iCfg, null)) {
               sinkCount++;
               if (getConfig().getLogSourcesAndSinks())
                  collectedSinks.add(s);
               logger.debug("Sink found: {}", u);
            }
         }
      }
      return sinkCount;
   }

二是調用前向追蹤方法:forwardSolver.solve(),確認source點到sink點是否存在聯通的數據路徑,如果存在則認為是一個風險點。前向追蹤的算法主要在heros中實現,此處不再展開;最終分析的結果保存在results變量中,通過以下方法將結果打印出來,源碼如下所示:

 for (ResultSinkInfo sink : results.getResults().keySet()) {
      logger.info("The sink {} in method {} was called with values from the following sources:",
                 sink, iCfg.getMethodOf(sink.getSink()).getSignature() );
      for (ResultSourceInfo source : results.getResults().get(sink)) {
         logger.info("- {} in method {}",source, iCfg.getMethodOf(source.getSource()).getSignature());
         if (source.getPath() != null) {
            logger.info("\ton Path: ");
            for (Unit p : source.getPath()) {
               logger.info("\t -> " + iCfg.getMethodOf(p));
               logger.info("\t\t -> " + p);
            }
         }
      }
   }

至此我們比較簡單的介紹了flowdroid的執行流程,對于控制流圖的算法及heros的具體實現并沒有做更深入的介紹,后續可能作為獨立的文章進行分析。

4、優化

flowdroid無論從算法、實現上,還是從效果上都堪稱是一款非常牛逼的產品,但是他也有個非常大的問題就是,太耗內存,分析時間太長,實際使用的價值很低,因此我常常稱它為一個實驗室的產品。那么有什么優化方法呢,首先需要明白造成flowdroid分析耗時的原因,其實無非就是apk較大時,代碼量太大,造成數據流圖(ICFG)呈現爆炸式增長,那么很明顯的一條思路就是削減ICFG圖的體量,我曾經的一個方法是只對幾個相關聯的文件構造ICFG圖,這樣就使得ICFG圖體量呈現幾何式的下降,分析速度肯定明顯提升,但是這個方法比較適用于對風險進行驗證,并不適用于分析。

最近在研究其他源碼掃描工具(如我上篇文章的RIPS)的時候發現,這些工具在進行源碼掃描的時候并沒有進行所謂的數據流分析,更多的只是對調用關系進行分析。誠然數據流分析能夠減少很多誤報,但是這些誤報在我們進行漏洞驗證的時候可能很容易就排除掉,這樣只使用控制流圖進行風險分析看起來也是個不錯的想法。進行控制流分析首先要獲取Android應用的控制流圖,下面的代碼展示如何使用soot構造Android應用的控制流圖,相關說明上文均有提及,此處不再進行詳細說明。

SetupApplication app = new SetupApplication(androidPlatformPath, appPath);
     app.calculateSourcesSinksEntrypoints("SourcesAndSinks.txt");
     soot.G.reset();
     Options.v().set_src_prec(Options.src_prec_apk);
     Options.v().set_process_dir(Collections.singletonList(appPath));
     Options.v().set_android_jars(androidPlatformPath);
     Options.v().set_whole_program(true);
     Options.v().set_allow_phantom_refs(true);
     Options.v().setPhaseOption("cg.spark", "on");
     Scene.v().loadNecessaryClasses();
     SootMethod entryPoint = app.getEntryPointCreator().createDummyMain();
     Options.v().set_main_class(entryPoint.getSignature());
     Scene.v().setEntryPoints(Collections.singletonList(entryPoint));
     System.out.println(entryPoint.getActiveBody());
     PackManager.v().runPacks();
     CallGraph appCallGraph = Scene.v().getCallGraph();

獲取控制流圖后,下一步就是確定圖中的兩個點是否存在連線,假設這兩個點為我們定義的source點、sink點,如果存在連線,即source點與sink點之間存在調用關系,那么就可以作為一個風險點拋出。那么如何確定是否存在調用關系,查看CallGraph的源碼中是否定義了相關方法,發現存在findEdge這個方法,該方法用于尋找某個語句對于某個方法是否存在調用調用關系,如果把第一個參數u定義為一個sink點,即調用點,第二個參數callee定義為一個source點,那么便定義了他們之間的一種調用關系。unit的獲取可以通過上面介紹的獲取應用內source、sink點的方法獲取。

 public Edge findEdge( Unit u, SootMethod callee )
    {
      Edge e = srcUnitToEdge.get(u);
      while ( e.srcUnit() == u &&
            e.kind() != Kind.INVALID ) {
         if ( e.tgt() == callee )
            return e;
         e = e.nextByUnit();
      }
      return null;
    }

注:使用控制流分析本人并沒有親自試驗,可能存在問題,望見諒。

 

來自:http://www.freebuf.com/sectool/137435.html

 

 本文由用戶 jbzq6611 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!