基于机器学习算法的Android恶意软件静态检测模型

这是我们在大三阶段完成的一个项目,项目的名称是”Android恶意广告检测软件的研究与实现”,本项目的目的是完成一个Android恶意广告检测系统,系统分为客户端与服务器端两部分,采用动态检测与静态检测相结合的方式,动态检测在本地进行,使用Android Hook技术与网络抓包等。静态检测需要将app上传到云端,然后对app进行反编译提取恶意特征,这部分用到了Android污点分析技术FlowDroid和Soot技术,一般来说,提取的敏感特征包括敏感权限,恶意API等,这些数据具有统计特性,于是可以用机器学习的思想来分析这些特征。

本项目组一共四人,我主要负责机器学习分析特征这一部分,由于以前从来没接触过有关机器学习这方面的东西,所以也是一边学习一遍完成项目。总的来说,机器学习离不开数据分析,个人理解,所有可以用数据统计的东西都能用数学(机器学习算法)的思想解决。在完成本项目的过程中,自己也对机器学习,数据挖掘分析方面有了一定的了解,下面分享一下我完成本项目的具体思路和设计。


前期准备(搜集数据)

在项目初期开发时,首先是采集数据,这方面在当时也遇到了不少的麻烦,开始时打算从权限入手,所以就从官网上弄到了全部的Android系统权限共147个,然后放到数据库中。由于考虑到Android四大组件都能反映出app的特性,所以相继又添加了actions表、components表。

然后是搜集样本,virusshare是一个专门搜集各平台病毒样本的网站,我们从https://virusshare.com/下载了android恶意样本越6000个,想要在该网站下载恶意样本需要向管理员发送邮件以申请账号,嫌麻烦的可以联系我。

有了恶意样本,那就必须有良性样本,传统的应用商店鱼龙混杂,很多标着安全的app下载后却发现是植入广告的流氓软件,所以,我们决定下载google商店的app,队友用爬虫下载仅2000个良性应用。然后,我用良性与恶意应用各1500个做训练集,各300个做测试集。这样,所有准备工作就基本完成了。

处理数据

通过学习其他论文的思路,我决定先从权限部分下手。我的思路是先将AndroidManifest.xml中的权限提取出来,然后与数据库中所有权限表比对,如果数据库中存在该权限就标记为1,否则标记为0,然后生成一张包含所有权限信息的特征向量,例如[0,0,1,0,1,1,0,1,….]。具体操作代码如下:(这里通过队友那部分对apk的反编译已经将权限提取出来,所以只需要处理即可)

权限文件内容大概如下:

android.permission.INTERNET
android.permission.ACCESS_NETWORK_STATE
android.permission.RECORD_AUDIO
android.permission.WRITE_EXTERNAL_STORAGE

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
# 获取各样本的类别与权限信息
def getText(folder_path):
folder_list = os.listdir(folder_path) # 查看folde_path下所有文件
dataList = []
labelList = []
for folder in folder_list:
new_folder_path = os.path.join(folder_path, folder)
files = os.listdir(new_folder_path)
for file in files:
with open(os.path.join(new_folder_path, file), 'r', encoding='utf-8') as f:
lines = f.readlines()
for i in range(len(lines)): # 按行读取
lines[i] = lines[i].strip()
dataList.append(lines[0:-1]) # 添加所有权限信息
labelList.append(lines[-1]) # 添加所有类别信息
return dataList, labelList
# 将Android权限信息向量化
def match_permission(dataList):
connect = pymysql.Connect(
host='localhost',
port=3306,
user='root',
passwd='123456',
db='android',
charset='utf8',
)
cursor = connect.cursor() # 获取游标
sql = "select id from total_permission where permission = '{a}'"
sql2 = "select permission from total_permission"
cursor.execute(sql2) # 从数据库中获取高危权限列表
permission_list = cursor.fetchall()
match_list = []
for permission in dataList: # 建立特征向量
judge = []
for i in permission_list:
if i[0] in permission:
judge.append(1)
else:
judge.append(0)
match_list.append(judge)
return match_list

后来又增加了对Android调用恶意API的处理,这里队友给我的文件是json格式,内容大概如下:

{““: 0.0, ““: 3.5, ……..}

可以用python中json函数将其转化为字典,然后直接get_keys()或get_values(),其中key表示恶意API,value表示使用次数加权值,由于软件大小不同所用的API次数也一定不同(例如支付宝调用相关API的次数一定比计算器多吧),所以我们将API使用次数除以软件大小得到一个平均使用次数。所有app的API统计格式都是相同的,因此也没必要对API做数据库,直接提取利用即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def getAPI(folder_path):
folder_list = os.listdir(folder_path) # 查看folde_path下所有文件
dataList = []
labelList = []
for folder in folder_list:
new_folder_path = os.path.join(folder_path, folder)
files = os.listdir(new_folder_path)
for file in files:
with open(os.path.join(new_folder_path, file), 'r', encoding='utf-8') as f:
line1 = f.readline()
api_dict = json.loads(line1)
label = f.readline()
dataList.append(list(api_dict.values())) # 添加所有权限信息
labelList.append(label.strip()) # 添加所有类别信息
return dataList, labelList

注意,这里生成的特征向量不再是01,而是使用次数。

训练模型

有了处理好的数据,下一步就是使用合适的机器学习算法训练样本,这里我使用了两个算法,分别是朴素贝叶斯以及随机森林。下面说一下使用这两种算法的思路和方法:

朴素贝叶斯

在对权限检测时,开始时想到权限的特征向量都是[0,1,0,1]类型的,而贝叶斯模型正是比较适合这种二分类数据,sklearn库中也有适合这类数据的伯努利分类模型BernoulliNB。使用sklearn很简单,只需要将测试集和数据集的特征向量和标签传入库中的函数即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 朴素贝叶斯分类器
def TextClassifier(train_feature_list, test_feature_list, train_class_list, test_class_list):
classifier = BernoulliNB(alpha=0.1).fit(train_feature_list, train_class_list)
test_accuracy = classifier.score(test_feature_list, test_class_list)
return test_accuracy # 返回预测结果的精确度
if __name__ == '__main__':
# setLabel('./data/test/test_v')
dataList, labelList = getText('./data/train')
matchList = match_permission(dataList)
test_dataList, testLabelList = getText("./data/test")
test_matchList = match_permission(test_dataList)
accuracy = TextClassifier(matchList, test_matchList, labelList, testLabelList)
# print(accuracy)

运行如上代码后,输出结果的准确率为0.844,可见训练的效果并不是特别好。想到朴素贝叶斯分类器可能会出现过拟合的情况,所以打算记录一下检测准确度与训练集样本容量的关系,具体方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 显示预测结果与训练样本个数的关系
def showTable(train_feature_list, test_feature_list, train_class_list, test_class_list):
# 设置汉字格式
font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)
accuracy_list = []
num_list = []
for i in range(49, len(train_feature_list), 50):
train_feature_list2 = train_feature_list[int(-((i+1)/2)):] + train_feature_list[0:int((i+1)/2)]
test_feature_list2 = test_feature_list[int(-((i+1)/2)):] + test_feature_list[0:int((i+1)/2)]
train_class_list2 = train_class_list[int(-((i+1)/2)):] + train_class_list[0:int((i+1)/2)]
test_class_list2 = test_class_list[int(-((i+1)/2)):] + test_class_list[0:int((i+1)/2)]
accuracy_list.append(TextClassifier(train_feature_list2, test_feature_list2, train_class_list2, test_class_list2))
num_list.append(i)
plt.figure()
plt.plot(num_list, accuracy_list)
plt.title(u'Android恶意应用检测准确度与训练集样本容量的关系—朴素贝叶斯算法\npermission', FontProperties=font)
plt.xlabel(u'样本容量', FontProperties=font)
plt.ylabel(u'检测准确度', FontProperties=font)
plt.show()

这里我以50为增量,分别取良性样本与恶意样本各一半(list中良性与恶意排布方式大概是[0,0,0,0,1,1,1,1]这样,所以取最后一部分+前面一部分以达到各一半的效果),一共2600个样本,做出了一张Android恶意应用检测准确度与训练集样本容量的关系图。

可以看出,在样本容量约为700时检测准确度最高。当达到1000时出现了明显的过拟合现象,准确度下降到84%左右。

对于API来说,具体操作基本一样,不过要记得要将BernoulliNB改为MultinomialNB,MultinomialNB表示多项式分布,API的特征向量不是0,1形式的而是具体的数值,所以用这种分布方式显然不好。

随机森林

首先简要介绍一下随机森林的生成方法:

  1. 从样本集中通过重采样的方式产生n个样本
  2. 假设样本特征数目为a,对n个样本选择a中的k个特征,用建立决策树的方式获得最佳分割点
  3. 重复m次,产生m棵决策树
  4. 多数投票机制来进行预测

(需要注意的一点是,这里m是指循环的次数,n是指样本的数目,n个样本构成训练的样本集,而m次循环中又会产生m个这样的样本集)

使用随机森林算法可以很好的避免过拟合的问题,具体实现方法与朴素贝叶斯差不多,以检测API为例

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
# 随机森林分类器
def TextClassifier(train_feature, test_feature, train_label, test_label):
classifier = RandomForestClassifier(n_estimators=100).fit(train_feature, train_label)
accuracy = classifier.score(test_feature, test_label)
return accuracy
# 显示预测结果与训练样本个数的关系
def showTable(train_feature_list, test_feature_list, train_class_list, test_class_list):
# 设置汉字格式
font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)
accuracy_list = []
num_list = []
for i in range(49, len(train_feature_list), 50):
train_feature_list2 = train_feature_list[int(-((i+1)/2)):] + train_feature_list[0:int((i+1)/2)]
test_feature_list2 = test_feature_list[int(-((i+1)/2)):] + test_feature_list[0:int((i+1)/2)]
train_class_list2 = train_class_list[int(-((i+1)/2)):] + train_class_list[0:int((i+1)/2)]
test_class_list2 = test_class_list[int(-((i+1)/2)):] + test_class_list[0:int((i+1)/2)]
accuracy_list.append(TextClassifier(train_feature_list2, test_feature_list2, train_class_list2, test_class_list2))
num_list.append(i)
plt.figure()
plt.plot(num_list, accuracy_list)
plt.title(u'Android恶意应用检测准确度与训练集样本容量的关系——随机森林算法\nAPI', FontProperties=font)
plt.xlabel(u'样本容量', FontProperties=font)
plt.ylabel(u'检测准确度', FontProperties=font)
plt.show()
if __name__ == '__main__':
dataList, labelList = getAPI('./HighRiskAPI/train')
test_dataList, testLabelList = getAPI("./HighRiskAPI/test")
showTable(dataList, test_dataList, labelList, testLabelList)
print(TextClassifier(dataList, test_dataList, labelList, testLabelList))

其中RandomForestClassifier(n_estimators=100).fit(train_feature, train_label),n_estimators表示随机森林中决策树的个数,默认值为10,通常来讲n_estimators越大越好,不过当调到100以上发现性能并没有什么显著提升,说明当决策树为100时,模型的分类效果已经饱和,再调高无非会浪费训练时间。然后运行上面代码,

从上图可以看出,用随机森林算法检测恶意API的效果非常好,准确度达到了惊人的96%,当时运行后看到这个图还是很兴奋的,也对机器学习算法的强大效果感到震惊。

然后又一次检测了权限特征

虽然检测权限的准确度不如API,但也达到了93%,相较朴素贝叶斯还是有很大的提升。

整合模型

有了检测效果良好的模型,下一步就是利用训练好的模型来检测恶意软件。

存储模型

上述随机森林模型的训练时间在python中大概要跑10几秒,如果每次检测都要训练岂不是太浪费时间了,sklearn库中有专门存储训练模型的模块joblib,可以将训练后的模型存储为一个.m(matlab)文件。

1
2
3
4
5
6
from sklearn.externals import joblib
# 存储训练模型
def dumpModel(train_feature_list, train_class_list):
classifier = RandomForestClassifier().fit(train_feature_list, train_class_list)
joblib.dump(classifier,'train_model_byRandomForest_API.m')

检测恶意应用

首先获取要检测应用的数据(同样是特征向量信息),然后使用joblib.load可以加载存储的模型,然后调用predict方法鉴别应用。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 加载训练模型,并判断1个apk是否为恶意软件
def judgeVirus(fileName):
fr = open(fileName)
line = fr.readline()
api_dict = json.loads(line)
label = fr.readline()
dataList = list(api_dict.values())
labelList = label.strip()
print(labelList)
clf = joblib.load('train_model_byRandomForest_API.m')
return clf.predict(np.mat(dataList))
print(judgeVirus('./HighRiskAPI/test/unsafe/VirusShare_bc7df5f172343a6db0e837177a0268f1.apk.txt')) # 预测一个apk

[‘0’]

运行代码后,程序输出[‘0’],表示通过存储的模型判断该App的是一个恶意应用。

Powered by Hexo and Hexo-theme-hiker

Copyright © 2016 - 2019 Dreaouth All Rights Reserved.

访客数 : | 访问量 :