modified-ICCBot——FragmentPageAdapter建模

背景

在使用基于Soot框架的开源ICC分析工具————ICCBot的过程中,发现虽然其架构支持对于fragment相关ICC的分析,但所建模的种类有限。

举个例子,如下图,通过FragmentPageAdapter滑动启动fragment,这一在标签化设计的应用中常用的ICC,ICCBot并检测不到。

1
2
3
4
5
6
7
8
9
10
11
// 做项目时碰到的一个例子
// RegisterAuthenticActivity通过beginTransaction直接启动LegalRegAuthFragment✔,ICCBot能检测
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_reg_auth, LegalRegAuthFragment.getInstance(extras)).commit();

// LoginActivity通过FragmentPageAdapter滑动启动UserLoginFragment❌,ICCBot无法检测
UserLoginFragment userLogin = new UserLoginFragment();
this.mFragments.add(userLogin);
this.mAdapter = new BaseSimpleViewPagerAdapter(getSupportFragmentManager(), this.titles, this.mFragments);
this.view_pager.setAdapter(this.mAdapter);
this.tablayout.setViewPager(this.view_pager);
this.tablayout.setCurrentTab(0);

本文主要以FragmentPageAdapter为例,讲述如何以模块化的方式拓展ICCBot可识别的ICC种类。

ICCBot识别流程分析

Workflow

ICCBot对于动态启动的Fragment,通过获取每个组件FragmentManager中的信息,建模与对intent类似的FragmentTransaction,构建Object Summary。

  • 创建:FragmentManager().beginTransaction() <=> new Intent() 创建Intent
  • 修改:FragmentManager().add/replace() <=> Intent.set..() 更新Intent
  • 提交:FragmentManager().commit() <=> startActivity(Intent) 发送Intent

模块化拓展FragmentPagerAdapter

因此,如果想要在在Fragment分析中加入对FragmentPagerAdapter的模块化识别,首先需要在src/main/java/client/obj/target/fragment/FragmentAnalyzerHelper.java里的getTypeofUnit()中加入对特定启动语句setAdapter的判断isSetAdapter();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1.增加一条对Unit的类型判断isSetAdapter()
// setAdapter(Landroid/support/v4/view/PagerAdapter;)V

public boolean isSetAdapter(Unit u) {
// look for invocations of ViewPager.setAdapter

InvokeExpr invExpr = SootUtils.getInvokeExp(u);
if (invExpr == null)
return false;
// check whether setAdapter method is called
if (!invExpr.getMethod().getName().equals("setAdapter")
|| invExpr.getArgCount() != 1)
return false;

return true;
}

捕捉到该关键语句后,再在src/main/java/client/obj/unitHnadler/fragment目录下新建对应的SetAdapterHandler类, 并将该情况补充到FragmentAnalyzerHelper的getUnitHandler()中。

1
2
3
4
5
6
7
8
9
10
11
public String getTypeofUnit(SootMethod m, Unit unit) {
if (isSetAdapter(unit)) {
return "SetAdapter";
}
}

public UnitHandler getUnitHandler(Unit unit) {
if (isSetAdapter(unit)) {
return new SetAdapterHandler();
}
}

接下来,在SetAdapterHandler类中编写我们代码的主题内容,参照了目录下其它handler的书写规范,首先覆写了handleSingleObject(),在其内通过调用analyzeMethodForViewPagers(),实现了自己针对setAdapter语句的数据流分析:

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
46
47
48
49
50
51
// 2.通过数据流分析FragmentPagerAdapter的初始化方法,来绑定所启动的相应fragment

protected void analyzeMethodForViewPagers() {
// We need at least one fragment base class
if (scSupportViewPager == null && scAndroidXViewPager == null)
return;
// We need at least one class with a method to register a fragment
if (scFragmentStatePagerAdapter == null && scAndroidXFragmentStatePagerAdapter == null
&& scFragmentPagerAdapter == null && scAndroidXFragmentPagerAdapter == null)
return;

// get argument
Value pa = SootUtils.getInvokeExp(unit).getArg(0);
if (!(pa.getType() instanceof RefType))
return;
RefType rt = (RefType) pa.getType();

// check whether argument is of type FragmentStatePagerAdapter
if (!safeIsType(pa, scFragmentStatePagerAdapter) && !safeIsType(pa, scAndroidXFragmentStatePagerAdapter)
&& !safeIsType(pa, scFragmentPagerAdapter) && !safeIsType(pa, scAndroidXFragmentPagerAdapter))
return;

// now analyze getItem() to find possible Fragments
SootMethod getItem = rt.getSootClass().getMethodUnsafe("android.support.v4.app.Fragment getItem(int)");
if (getItem == null)
getItem = rt.getSootClass().getMethodUnsafe("androidx.fragment.app.Fragment getItem(int)");
if (getItem == null || !getItem.isConcrete())
return;

Body b = getItem.retrieveActiveBody();
if (b == null)
return;

// iterate and add any returned Fragment classes
for (Unit getItemUnit : b.getUnits()) {
if (getItemUnit instanceof ReturnStmt) {
ReturnStmt rs = (ReturnStmt) getItemUnit;
Value rv = rs.getOp();
Type type = rv.getType();
if (type instanceof RefType) {
SootClass rtClass = ((RefType) type).getSootClass();
if (rv instanceof Local && (rtClass.getName().startsWith("android.")
|| rtClass.getName().startsWith("androidx.")))
analyzeFragmentCandidates(rs, getItem, (Local) rv);
else
checkAndAddFragment(rtClass);
}
}
}

}

获取了其启动的目标fragment后,补充到getSetDestinationList中,同时将记录语句信息到getSendFragment2Start里,最后在/src/main/java/client/obj/target/fragment/FragmentAnalyzer.java的generateATGInfo方法里生成对应的跳转信息。

1
2
3
4
5
6
// 3.若该Unit的类型判断为SetAdapter,则加入发送fragment的列表并记录目标组件

protected void checkAndAddFragment(SootClass fragmentClass) {
((FragmentSummaryModel)objectModel).getSetDestinationList().add(fragmentClass.getName());
((FragmentSummaryModel)objectModel).getSendFragment2Start().add(unit);
}

ATG中生成的信息:

1
2
3
<node method="LoginActivity initView"
type="setAdapter"
unit="$r7 = virtualinvoke $r1.&lt;android.support.v4.view.PagerAdapter: setAdapter()&gt;()"/>

完整的代码仍然是放在modified-ICCBot项目上:

https://github.com/Canonize/modified-ICCBot/commit/520758f8276d801104834b35d9b5e49d226913fc

部分参考资料:

原版ICCBot论文:https://hanada31.github.io/pdf/icse22_iccbot.pdf

远程仓库地址:https://github.com/Canonize/modified-ICCBot

Android官方文档:https://developer.android.com/reference/androidx/fragment/app/FragmentPagerAdapter

FragmentPagerAdapter原理详解:https://www.jianshu.com/p/d86e31dcc97b