赞
踩
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消息和在服务器上的处理方法(函数)之间的关系。
从MobSF/urls.py文件中,可以看到,点击上传按钮后,处理apk的函数是MobSF.views.Upload:
url(r'^upload/$', MobSF.views.upload)
找到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

首先包含了一个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),
显然,有一个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。流程如下:
找到第一个调用的函数(前面计算md5值等部分就可以暂时忽略):
line 133: app_dic['files'] = unzip(
app_dic['app_path'], app_dic['app_dir'])
也就是说,apk文件其实是一个zip文件,为了将证实这一点,将一个后缀名为.apk的文件后缀改为.zip,打开,显然在里面可以找到所有的apk源码。
下一步就是实现对apk文件的解压,将使用zlib库实现这一功能。
在解压apk后,static_analyzer进行了如下操作:
app_dic['parsed_xml'] = get_manifest(
app_dic['app_path'],
app_dic['app_dir'],
app_dic['tools_dir'],
'',
True
)
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"

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

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"
)
源代码分析部分主要利用正则表达式对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'
},
......
要检测的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'
},
······

通过正则匹配URL的格式来提取源码中的URL:
pattern = re.compile(
(
ur'((?:https?://|s?ftps?://|file://|javascript:|data:|www\d{0,3}[.])'
ur'[\w().=/;,#:@?&~*+!$%\'{}-]+)'
),
re.UNICODE
)
通过正则匹配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
通过对MobSF源代码的分析可以了解MobSF的基本工作原理以及流程。
静态分析时,MobSF主要使用了现有的dex2jar、dex2smali、jar2java、AXMLPrinter、CertPrint等工具。其主要完成了两项工作:解析AndroidManifest.xml得到了应用程序的各类相关信息、对apk进行反编译得到java代码,而后利用正则匹配找出该app包含的API函数、URL、邮箱集帐号密码等敏感信息。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。