androguard是Android静态分析工具,可以解析APK/DEX文件,并提供相关操作API
比如get_xref_from和get_xref_to,可以得到某个函数被哪些函数调用过和调用过哪些函数。通过合理的使用这些API,可以帮助我们确定受关注的接口是否被某些类/入口调用过,自动化发现漏洞(类似工具JAndroid)
androguard API文档对整个架构没有作说明,只有通过阅读源码了解
为了便于理解源码先了解如何使用:
from androguard import misc
a, d, dx = misc.AnalyzeAPK(pkg)
其中a表示apk文件结构,d表示dex文件结构,dx表示dex文件分析
几乎所有操作均在dx上发生
下面是需要重点关注的代码
1、core/analysis/analysis.py:dex分析模块
其中包含重点子模块:
Analysis,等同于dx
ClassAnalysis,类模块
MethodClassAnalysis,类中方法模块(可获取归属类)
MethodAnalysis,方法模块
StringAnalysis,字符串模块
FieldClassAnalysis,类中变量模块(可获得归属类)
ExternalClass、ExternalMethod,外部类,不在dex中的API
可重点看前3个子模块,发现可使用API
2、core/bytecodes/dvm.py:dex结构
EncodedMethod,方法结构
ClassDefItem,类结构
其中包含对类和结构的操作API
1、找到所有调用过webview.loadUrl方法的Activity,Activity必须导出且浏览器可启动(browsable)
思路:先获取所有调用过webview.loadUrl方法的类(get_xref_from()),然后判断符合条件的Activity是否在这些类中
需要注意的是get_xref_from获取的是直接调用的那些类,对于间接调用(activity->classA->classB->classC→webview.loadUrl)需要额外操作
这里解决办法:递归方式保存所有调用链上的类,代码如下:
def getCallstack(m, callstack, count): ## #获取函数的调用树,全部存储callstack list #count 用于防止栈益处,重载函数可能溢出 ## ref = m.get_xref_from() if not ref or len(ref) == 0: return for c,m,_ in ref: callstack.append(c.name+':'+m.name) if count[0] > 500: print('====max '+c.name+':'+m.name) return count[0] = count[0]+1 getCallstack(c.get_method_analysis(m), callstack, count) |
因为调用链是树结构,保存所有完整调用链算法较复杂,这里简化处理保存在list中,缺点是无法知道完整的调用链。(另外有些重载函数相互调用过程,会出现递归无法退出,故设定count=500)
获取符合条件activity可参考工具https://github.com/zzzzfeng/appstarter
然后判断activity是否在callstack list中
for m in dx.classes['Landroid/webkit/WebView;'].get_methods(): if m.name == 'loadUrl': cs = [] count = [0] getCallstack(m, cs, count) cs = list(set(cs)) for r in cs: if r.split(':')[0] in browsable_activities: print(r) |
2、找到所有调用过Intent.parseUri方法的Activity,Activity必须导出且浏览器可启动(browsable)
代码
parseuri = dx.get_method_analysis_by_name('Landroid/content/Intent;', 'parseUri', '(Ljava/lang/String; I)Landroid/content/Intent;') cs = [] count = [0] getCallstack(m, cs, count) cs = list(set(cs)) for r in cs: if r.split(':')[0] in browsable_activities: print(r) |
3、找到所有调用过getIntent方法的Activity,Activity必须导出且浏览器可启动(browsable)
代码
#1、activity.getIntent() 少见 # getintent = dx.get_method_analysis_by_name('Landroid/app/Activity;', 'getIntent', '()Landroid/content/Intent;') # for _, call, _ in getintent.get_xref_from(): # if call.class_name in browsable_activities: # print(call.class_name+' '+call.name) #2、getIntent() for c in dx.get_classes(): ss = [] cc = c while cc and cc.extends != 'Ljava/lang/Object;': ss.append(cc.extends) cc = dx.classes.get(cc.extends) if 'Landroid/app/Activity;' in ss: for m in c.get_methods(): if m.name == 'getIntent': if c.name in browsable_activities: print(c.name) |
使用1方式,获取不到this.getIntent调用,原因是this.getIntent在子类实现。故获取到所有父类(链)包含Landroid/app/Activity;类的类,然后判断是否实现getIntent(调用即实现)
1、有些API在调用链上,但不一定在执行路径上,即代码不会触发
2、有些API调用在父类上执行并保存变量,但不是符合条件的activity,而子类是导出Activity,上述例子不能覆盖这种情况。有一种思路解决:保存符合条件的acitity类的父类列表,与获得的callstack进行比对
3、有些API被系统函数如onCreate调用,onCreate这类函数追溯不到startActivity调用。暂时没有好的解决方法