当前位置: 首页 > 工具软件 > androguard > 使用案例 >

基于androguard编写Android app漏洞扫描工具

柯琛
2023-12-01

背景

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调用。暂时没有好的解决方法

 

 类似资料: