Android污點分析工具flowdroid源碼簡析
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