当前位置:   article > 正文

MobSF源码分析——静态分析部分_certprint.jar

certprint.jar

代码结构

MobSF源代码结构主要包含静态分析、动态分析、API Fuzzer三个部分,本文不关注Django框架本身及Web处理相关的内容。如图:
这里写图片描述
本文主要关心静态分析,通过打印StaticAnalyzer目录的树结构可以粗略知道,migrations是迁移文件,test_files是用来测试静态测试的文件,tools是用来反编译等的工具,views才是我们想要找的分析源码。   
直接到StaticAnalyzer\views\android目录下可以很快找到对应分析的源码(十分清晰的模块名)。

网页页面分析

重新返回首页,F12进入开发者模式,可以看到,首页中间的Update&;Analyse按钮属于一个表单,这个表单采用POST的方式向服务器传递数据。简单猜想一下,传递的数据必定是上传的.apk文件,传到哪里表单头中并没有说明,那么肯定是使用javascript代码完成上传动作。
在本页的javascript代码(如下图)中,可以看到使用AJAX技术(虽然我并不会)上传了文件:
这里写图片描述
显然这个文件被Post到../Upload/页面。
但是通过搜索整个源代码文件夹,并没有发现Upload页面,(别人)猜想必定有一个定义url的文件,在MobSF的文件夹,果然找到了一个urls.py文件,这里详细描述了http消息和在服务器上的处理方法(函数)之间的关系。

分析apk文件在服务器上的处理流程

从MobSF/urls.py文件中,可以看到,点击上传按钮后,处理apk的函数是MobSF.views.Upload:

url(r'^upload/$', MobSF.views.upload)
  • 1

找到MobSF文件夹下的views.py,打开找到其中的upload函数(line76);
上传一个apk进行分析,可以在服务器终端看到运行日志和Upload函数中的提示信息进行核对,发现完全一致,继续沿着这条线走下去,这个函数最终会向客户端发送一个json数据包,部分相关内容如下:

def upload(request, api=False):
    """
    Handle File Upload based on App type
    """
    try:
        response_data = {}
        response_data['url'] = ''
        response_data['description'] = ''
        response_data['status'] = 'error'
        api_response = {}
        if request.method == 'POST':
            ···
            response_data['url'] = 'StaticAnalyzer/?name='+request.FILES['file'].name+'&;type=apk&;checksum='+md5 
            response_data['status'] = 'success' 
        ···
        resp = HttpResponse(json.dumps(response_data),
                                content_type="application/json; charset=utf-8")
        ···
    return resp
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

首先包含了一个URL,不管这个json在客户端会产生什么,总之Upload函数的功能在返回这个json数据包之后就完成了。那么猜想客户端必定还要向服务器发送消息启动分析。
前面说过在urls.py文件中,包含了所有的http请求和对应的处理方法,找到包含StaticAnalyser的项目:

url(r'^StaticAnalyzer/$',
        StaticAnalyzer.views.android.static_analyzer.static_analyzer),
url(r'^StaticAnalyzer_iOS/$',
        StaticAnalyzer.views.ios.static_analyzer.static_analyzer_ios),
url(r'^StaticAnalyzer_Windows/$',
        StaticAnalyzer.views.windows.staticanalyzer_windows),
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

显然,有一个StaticAnalyzer.views.android.static_analyzer.static_analyzer的函数负责静态分析。
StaticAnalyzer\views\android文件夹中找到static_analyzer.py里的static_analyzericAnalyser函数,就是研究整个静态分析的过程。

静态分析原理

静态分析的处理流程集功代码在目录StaticAnalyzer\views\android下static_analyzer.py程序文件中。

分析代码流程可知,在MobSF框架中静态分析主要包含三个部分,分别是Manifest Analysis、Cert Analysis、Code Analysis。流程如下:
这里写图片描述

1.解压

找到第一个调用的函数(前面计算md5值等部分就可以暂时忽略):
line 133: app_dic['files'] = unzip(
app_dic['app_path'], app_dic['app_dir'])

也就是说,apk文件其实是一个zip文件,为了将证实这一点,将一个后缀名为.apk的文件后缀改为.zip,打开,显然在里面可以找到所有的apk源码。
下一步就是实现对apk文件的解压,将使用zlib库实现这一功能。

2.Manifest Analysis

在解压apk后,static_analyzer进行了如下操作:

app_dic['parsed_xml'] = get_manifest(
   app_dic['app_path'],
   app_dic['app_dir'],
   app_dic['tools_dir'],
   '',
   True
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

get_manifest函数在\StaticAnalyzer\views\android\manifest_analysis.py文件里定义,使用AXMLPrinter2.jar工具提取app中的AndroidManifest.xml。(安卓的每一个组件,包括Activity都必须要在AndroidManifest.xml文件中申明)
并进行分析。

ManifestAnalysis主要功能是对AndroidManifest.xml进行解析,提取其中permission、granturipermissions、application、activties、services、intents、actions等,分析所有权限并对权限进行分级,包含正常、危险、签名、系统四个类别。对各属性配置进行检查,看是否存在不安全的配置,如allowBackup、debuggable、exported等属性设置。详细代码功能可见manifest_analysis.py程序文件。

        for permission in permissions:
            if permission.getAttribute("android:protectionLevel"):
                protectionlevel = permission.getAttribute(
                    "android:protectionLevel")
                if protectionlevel == "0x00000000":
                    protectionlevel = "normal"
                elif protectionlevel == "0x00000001":
                    protectionlevel = "dangerous"
                elif protectionlevel == "0x00000002":
                    protectionlevel = "signature"
                elif protectionlevel == "0x00000003":
                    protectionlevel = "signatureOrSystem"

                permission_dict[permission.getAttribute(
                    "android:name")] = protectionlevel
            elif permission.getAttribute("android:name"):
                permission_dict[permission.getAttribute(
                    "android:name")] = "normal"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

后续的任务就是一步一步实现StaticAnalyser函数其实是views.py文件中的所有功能,并将他们整合到一起,再使用Qt做出图形界面。

3.Cert Analysis

MobSF证书分析功能函数在cert_analysis.py文件中,MobSF首先尝试获取Hardcoded Certificates/Keystores,然后通过CertPrint.jar工具解析apk中证书的信息,并完成证书相关问题的分析。

def cert_info(app_dir, tools_dir):
    """Return certificate information."""
    try:
        print "[INFO] Reading Code Signing Certificate"
        cert = os.path.join(app_dir, 'META-INF/')
        cp_path = tools_dir + 'CertPrint.jar'
        files = [f for f in os.listdir(
            cert) if os.path.isfile(os.path.join(cert, f))]
        certfile = None
        dat = ''
        manidat = ''
        if "CERT.RSA" in files:
            certfile = os.path.join(cert, "CERT.RSA")
        else:
            for file_name in files:
                if file_name.lower().endswith(".rsa"):
                    certfile = os.path.join(cert, file_name)
                elif file_name.lower().endswith(".dsa"):
                    certfile = os.path.join(cert, file_name)
        if certfile:
            args = [settings.JAVA_PATH + 'java', '-jar', cp_path, certfile]
            issued = 'good'
            dat = subprocess.check_output(args)
            unicode_output = unicode(dat, encoding="utf-8", errors="replace")
            dat = escape(unicode_output).replace('\n', '</br>')
        else:
            dat = 'No Code Signing Certificate Found!'
            issued = 'missing'
        if re.findall(r"Issuer: CN=Android Debug|Subject: CN=Android Debug", dat):
            issued = 'bad'
        if re.findall(r"\[SHA1withRSA\]", dat):
            issued = 'bad hash'
        if "MANIFEST.MF" in files:
            manifestfile = os.path.join(cert, "MANIFEST.MF")
        if manifestfile:
            with open(manifestfile,'r') as manifile:
                manidat = manifile.read()
        sha256Digest = bool(re.findall(r"SHA-256-Digest", manidat))
        cert_dic = {
            'cert_info': dat,
            'issued': issued,
            'sha256Digest': sha256Digest
        }
        return cert_dic
    except:
        PrintException("[ERROR] Reading Code Signing Certificate")
  • 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

4.Code Analysis

MobSF静态代码分析功能函数在code_analysis.py文件中,反编译的代码在converter.py中。其中使用Dex2Jar将dex转变为jar文件,使用Dex2Smali将dex转变为smali代码,使用jd-core.jar、cfr_0_115.jar、procyon-decompiler-0.5.30.jar将jar包转为为可读的java代码。

dex_2_jar(app_dic['app_path'], app_dic[
    'app_dir'], app_dic['tools_dir'])
dex_2_smali(app_dic['app_dir'], app_dic['tools_dir'])
jar_2_java(app_dic['app_dir'], app_dic['tools_dir'])

code_an_dic = code_analysis(
    app_dic['app_dir'],
    man_an_dic['permissons'],
    "apk"
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

源代码分析部分主要利用正则表达式对java源码进行匹配来实现的。主要通过匹配常见方法中的关键词来提取源码中用到的方法。 通过匹配敏感关键词来提取账号密码等信息(没找到对应源码的位置)、通过匹配常见API字符串来判定是否有调用这些API(\StaticAnalyzer\views\android\android_apis.py):

APIS = [
    {
        'desc': 'Loading Native Code (Shared Library) ',
        'type': 'regex',
        'match': 'single_regex',
        'regex1': r'System\.loadLibrary\(|System\.load\(',
        'input_case': 'exact'
    },
    ......
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

要检测的api列表(部分)及对应的安全问题(\StaticAnalyzer\views\android\android_rules.py):

RULES = [
    {
        'desc': 'Files may contain hardcoded sensitive informations like usernames, passwords, keys etc.',
        'type': 'regex',
        'regex1': r'''(password\s*=\s*['|"].+['|"]\s{0,5})|(pass\s*=\s*['|"].+['|"]\s{0,5})|(username\s*=\s*['|"].+['|"]\s{0,5})|(secret\s*=\s*['|"].+['|"]\s{0,5})|(key\s*=\s*['|"].+['|"]\s{0,5})''',
        'level': 'high',
        'match': 'single_regex',
        'input_case': 'lower'
    },
    {
        'desc': 'IP Address disclosure',
        'type': 'regex',
        'regex1': r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}',
        'level': 'warning',
        'match': 'single_regex',
        'input_case': 'exact'
    },
    {
        'desc': 'Hidden elements in view can be used to hide data from user. But this data can be leaked',
        'type': 'regex',
        'regex1': r'setVisibility\(View\.GONE\)|setVisibility\(View\.INVISIBLE\)',
        'level': 'high',
        'match': 'single_regex',
        'input_case': 'exact'
    },
    ······
  • 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

通过正则匹配URL的格式来提取源码中的URL:

    pattern = re.compile(
        (
            ur'((?:https?://|s?ftps?://|file://|javascript:|data:|www\d{0,3}[.])'
            ur'[\w().=/;,#:@?&~*+!$%\'{}-]+)'
        ),
        re.UNICODE
    )
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

通过正则匹配Email的格式来提取源码中的Email:

    # Email Extraction Regex
    regex = re.compile(r'[\w.-]+@[\w-]+\.[\w.]+')
    eflag = 0
    for email in regex.findall(dat.lower()):
        if (email not in emails) and (not email.startswith('//')):
            emails.append(email)
            eflag = 1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

总结

通过对MobSF源代码的分析可以了解MobSF的基本工作原理以及流程。

静态分析时,MobSF主要使用了现有的dex2jar、dex2smali、jar2java、AXMLPrinter、CertPrint等工具。其主要完成了两项工作:解析AndroidManifest.xml得到了应用程序的各类相关信息、对apk进行反编译得到java代码,而后利用正则匹配找出该app包含的API函数、URL、邮箱集帐号密码等敏感信息。

参考网址:
对MobSF的源码进行分析
MobSF Android静态分析使用心得
MobSF框架及源代码分析

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/460931
推荐阅读
  

闽ICP备14008679号