移动测试基础 分享一个 Android 混淆检测脚本

Heyniu · November 28, 2016 · Last by songz replied at December 15, 2016 · 3210 hits

背景

项目中打包经常遇到混淆导致的各种bug(崩溃、闪退、无厘头),提醒过开发数次,问题还是时而发生。这时,人是不可靠的,错误时而发生,所以做了这么个脚本,旨在抛砖引玉。

思路

目的在于扫描项目,判断是否有实体被混淆。

说明:我们项目中出现最多的问题就是实体被混淆,所以这个脚本只是扫描实体的混淆情况。不排除其他混淆导致的问题,这里只是抛砖引玉,希望通过这个思路,让有被混淆困扰的童鞋能够做好这一块。

  • 传入项目路径,扫描出所有路径
  • 符合实体类的路径存入列表
  • 与混淆配置文件对比
  • 打印被混淆的实体类

实例

检测通过:

Checked pass.
Time: 0.01 s

检测不通过(有实体被混淆):

Errors, has some entities be garble.
com.s.toutiao.entity
Time: 0.01 s

实体被混淆导致的闪退:

FATAL EXCEPTION: main
Process: com.s.d, PID: 30138
java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.String java.lang.CharSequence.toString()' on a null object reference
at com.s.widget.PagerSlidingTabStrip.a(Unknown Source)
at com.s.widget.PagerSlidingTabStrip.setViewPager(Unknown Source)
at com.s.toutiao.fragment.ToutiaoFragment$4.onComplete(Unknown Source)
at com.y.http.async.HttpRequest$1.handleMessage(Unknown Source)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7225)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)

脚本加入Android Studio编译环境

  • build.gradle加入下列代码

    android.applicationVariants.all { variant ->
    if (variant.name.contains("release")) {
    def cmd ="python ScanProguard.py 1"
    Process p = cmd.execute()
    def result = p.text
    if (result.contains("Errors")) {
    throw new RuntimeException("下列实体被混淆了:\n" + result)
    }
    }
    }
  • ScanProguard.py 脚本放在build.gradle同级目录

  • 编译失败的截图

实体为什么不能混淆?

当然不能混淆了,实体混淆后就变成abc之流了,与接口返回的字段就对应不上,肯定会有问题。

代码

#!/usr/bin/evn python
# -*- coding:utf-8 -*-

# FileName ScanProguard.py
# Author: HeyNiu
# Created Time: 2016/11/28
"""
扫描项目实体是否被混淆
1.传入项目路径,扫描出所有路径
2.符合实体类的路径存入列表
3.与混淆配置文件对比
4.打印被混淆的实体类
"""


import os
import time
import sys

temp = sys.argv[1]


class ScanProguard(object):

def __init__(self, project):
if not isinstance(project, dict):
raise TypeError('Project object must be dict.')
for value in project.values():
if not os.path.exists(value):
raise FileNotFoundError('%s' % value)
self.project_path = project['project_path']
self.proguard_path = project['proguard_path']
self.nim_path = ''
if 'nim_path' in project.keys():
self.nim_path = project['nim_path']

@staticmethod
def __filter_dirs(target_path, dir_name):
"""
获取目标地址目录下的文件夹列表
:param dir_name:过滤文件夹名字
:return:
"""
return (os.path.join(root, d) for root, dirs, files in os.walk(target_path) for d in dirs if
dir_name.upper() in d.upper())

def __get_project_entity(self):
"""
获取将要进行匹配的项目实体
:return:
"""
files = self.__filter_dirs(self.project_path, 'Entity')
return [str(i.split('\java\\')[-1]).replace('\\', '.') for i in files]

def __get_nim_entity(self):
"""
获取将要进行匹配的nim实体
:return:
"""
files = self.__filter_dirs(self.nim_path, 'Entity')
return [str(i.split('\java\\')[-1]).replace('\\', '.') for i in files]

def __get_proguard_entity(self):
"""
获取将要进行匹配的项目混淆文件实体
:return:
"""
l = open(self.proguard_path, encoding='utf-8').readlines()
return [i.replace('-keep class ', '').replace('.** {*;}', '').replace('.** {*;}', '').strip() for i in l
if 'ENTITY' in i.upper() and '-KEEP CLASS' in i.upper()]

def print_entity(self):
"""
打印被混淆的实体类
:return:
"""
project_entity = self.__get_project_entity()
proguard_entity = self.__get_proguard_entity()
nim_entity = self.__get_nim_entity()
for i in nim_entity:
project_entity.append(i)
l = set(project_entity).difference(set(proguard_entity))
if len(l) > 0:
print('Errors, has some entities be garble.')
for i in l:
print(i)
else:
print('Checked pass.')


class Project:
"""
这里传的就是项目路径,为了方便写成了这样
"""
document_path = os.path.join(os.path.expanduser('~'), 'Documents')
jz = ''
zf = ''
zx = ''
pro = 'proguard-project.pro'
java = 'src\main\java'
J = {
'proguard_path': os.path.join(document_path, jz, jz, 'proguard-rules.pro'),
'project_path': os.path.join(document_path, jz, jz, java, 's'),
'nim_path': os.path.join(document_path, jz, 'im', java, 'com')
}
ZF = {
'proguard_path': os.path.join(document_path, zf, zf, pro),
'project_path': os.path.join(document_path, zf, zf, java, 'com'),
'nim_path': os.path.join(document_path, zf, 'im', java, 'com')
}
XIU = {
'proguard_path': os.path.join(document_path, zx, 'D', 'proguard-project.txt'),
'project_path': os.path.join(document_path, zx, 'D', java, 'com')
}


if __name__ == '__main__':
start = time.clock()
if '1' == temp:
print('A项目')
s = ScanProguard(Project.J)
s.print_entity()
if '2' == temp:
print('B项目')
s = ScanProguard(Project.ZF)
s.print_entity()
if '3' == temp:
print('C项目')
s = ScanProguard(Project.XIU)
s.print_entity()
end = time.clock()
print("Time: %.02f s" % (end - start))

最后

如果有更好的方法或者其他建议,欢迎评论,请轻拍。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 9 条回复 时间 点赞

不错 赞一个

还能这么玩,长知识了!对这方面不了解,请问下,你这里是对开发已经混淆的代码进行检查吗?

#2楼 @lose 已经混淆的代码就是安装包了,所以当然是源代码的检查了

一般都跑DEBUG。。。RELEASE跑了才知道混淆有没有问题的飘过。。。。

#4楼 @yangchengtest 就是跑release之前检测

—— 来自TesterHome官方 安卓客户端

好思路,多谢分享

—— 来自TesterHome官方 安卓客户端

这里的jz、zf和xiu是什么意思
jz = ''
zf = ''
zx = ''

ZF = {

}
XIU = {

}

#7楼 @ady 就是地址吧,你不用管具体内容是什么,只要传入项目路径,混淆文件路径就好

好思路,学习了👍

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up