DroidEvolver:自我进化的 Android 恶意软件检测系统
摘要
鉴于Android框架的频繁变化和Android恶意软件的不断演变,随着时间的推移以有效且可扩展的方式检测恶意软件具有挑战性。为了应对这一挑战,我们提出了DroidEvolver,这是一种Android恶意软件检测系统,可以在恶意软件检测期间自动且持续地自我更新,无需任何人工参与。虽然大多数现有的恶意软件检测系统可以通过在具有真实标签的新应用程序上进行再训练来更新,但DroidEvolver既不需要再训练也不需要真实标签来更新自身,主要是由于深入了解DroidEvolver使用具有不断发展的特征集和伪标签的在线学习技术进行必要且轻量级的更新。DroidEvolver的检测性能在六年内开发的33,294个良性应用程序和34,722个恶意应用程序的数据集上进行了评估。使用日期为2011年的6,286个应用程序作为初始训练集,DroidEvolver实现了高检测F-度量(95.27%),对于57,539个新出现的应用程序进行分类,在未来五年内平均每年仅下降1.06%。请注意,此类新应用程序可以使用新技术和新API,DroidEvolver在使用2011年应用程序初始化时不知道这些技术和新API。与最先进的超时恶意软件检测系统MAMADROID相比,DroidEvolver的F-度量平均高出2.19倍(第五年高出10.21倍),DroidEvolver在恶意软件检测期间的效率比MAMADROID高出28.58倍。DroidEvolver还显示出对典型代码混淆技术的鲁棒性。
1 介绍
Android 应用程序和 Android 框架都随着时间的推移,由于各种原因(例如功能增强和错误修复)而不断发展[34]。因此,构建使用旧 Android 应用程序进行训练并在运行一段时间后能够有效且可扩展地检测来自新应用程序的恶意软件的 Android 恶意软件检测系统变得越来越困难。现有 Android 检测系统的快速老化引起了工业界和学术界的严重关注。据BlackHat 2016报道[21],百度开发的恶意软件检测系统的召回率在六个月内下降了7.6%。在研究文献中,最近的一项努力是通过 API 抽象使恶意软件检测能够适应 API 更改[24];然而,恶意软件检测的老化问题尚未得到完全解决。
为了使恶意软件检测更加准确,大多数恶意软件检测系统需要使用新应用程序及时、重复地进行重新训练。然而,此类解决方案面临一些挑战。首先,很难确定何时重新训练恶意软件检测系统。如果系统重新训练过于频繁,则会导致重新训练资源的浪费,而无法提供新的信息来丰富检测系统[19];否则,检测系统无法及时捕获一些新的恶意软件。其次,再培训过程需要对所有已处理的新应用程序进行手动标记,这受到可用资源的限制[19]。手动标记的高成本通常会导致重新训练频率松散[31];因此,检测性能受到影响。最后,大多数现有的恶意软件检测系统都使用累积数据集进行重新训练,包括原始训练数据集和新标记的应用程序。这种再训练过程成本高昂且不可扩展,尤其是在新应用程序数量随着时间的推移快速增长的情况下。为了应对这些挑战,我们提出了一种新颖的自我进化 Android 恶意软件检测系统,名为 DroidEvolver,通过不断发展的功能集对其检测模型进行必要的更新,使恶意软件检测随着时间的推移变得准确。 DroidEvolver 维护着不同检测模型的模型池,这些模型是使用各种在线学习算法通过一组标记的应用程序进行初始化的。维护模型池的直觉是,不同的检测模型在恶意软件检测中不太可能以相同的速度老化,即使它们是使用相同的数据集初始化的。在检测阶段,DroidEvolver 在“年轻”检测模型中进行加权投票,以根据 Android API 调用对每个应用程序进行分类。 DroidEvolver 提取 Android API 调用作为检测特征,因为它们自然地反映了 Android 框架和应用程序的演变,并且可以轻松地从字节码中提取以进行高效的恶意软件检测。检测到的应用程序的“年轻”模型是根据幼年化指标(JI)来确定的,JI是根据检测到的应用程序与已被检测模型分类到相同预测标签的一批应用程序之间的相似度计算的。如果检测模型相对于检测到的应用程序老化,DroidEvolver 会使用检测到的应用程序及其模型池生成的分类结果(即伪标签)来更新模型。 DroidEvolver 还更新其功能集以适应从应用程序发现的 API 更改。 DroidEvolver 使用 JI 来确定何时更新其功能集和每个检测模型。可以为每个检测模型和正在检测的每个新应用程序计算 JI。如果JI超出一定范围,则相应的检测模型被视为老化模型。如果模型池中的任何模型在检测该应用程序时老化,则将检测到的应用程序标识为漂移应用程序。老化模型对漂移应用程序进行分类具有局限性,其中可能包括新的 API 调用或新的 API 使用模式。因此,一旦识别出漂移应用程序,DroidEvolver 就会更新其功能集和所有老化模型。 DroidEvolver 不需要已处理应用程序的真实标签来更新恶意软件检测中的模型池。这避免了在DroidEvolver初始化后对任何应用程序进行手动标记的必要性,从而减少了DroidEvolver演进的资源和成本约束。当识别出漂移应用程序时,DroidEvolver会为该漂移应用程序生成一个伪标签,并根据该漂移应用程序及其伪标签更新所有老化模型,然后再继续处理下一个应用程序。在当前应用不是漂移应用的情况下(因此没有识别出老化模型),模型池中的所有模型都对分类结果有贡献,并且不进行模型更新。随着时间的推移,DroidEvolver 可以高效地检测恶意软件。初始化后不需要定期使用累积数据集进行任何重新训练;相反,只要识别和处理单个漂移应用程序,它就会有效地发展,除非在这种情况下所有模型都老化。为此,模型池中的所有检测模型均使用在线学习算法[3]进行初始化,该算法可以对流数据进行增量学习。与批量学习算法相比,在线学习算法更加高效和可扩展,因为它们不仅避免了在初始化阶段使用原始训练数据集进行批量处理,而且还避免了在检测阶段使用累积数据集进行定期再训练。虽然现有的在线学习算法仅适用于标记数据,但 DroidEvolver 使它们适用于在检测阶段与伪标签关联的应用程序。与现有的基于在线学习的方法(使用真实标签更新每个应用程序的检测模型)不同,DroidEvolver 仅更新每个漂移应用程序的老化模型。在更新过程中,DroidEvolver不需要任何真实标签与漂移应用程序关联;从这个意义上说,DroidEvolver比现有的基于在线学习的方法更实用。因此,只要有必要,衰老模型就会立即年轻化。这进一步提高了 DroidEvolver 的效率。 DroidEvolver 通过一系列数据集进行了严格评估,包括 2011 年至 2016 年的 34,722 个恶意应用程序和 33,294 个良性应用程序。DroidEvolver 的功效和效率与 MAMADROID 进行了比较,MAMADROID 是最先进的恶意软件检测系统 [24 ] 能够适应 API 随着时间的推移而发生的变化。在使用同一时间段开发的相同应用程序对 DroidEvolver 和 MAMADROID 进行训练和测试的情况下,DroidEvolver 显着优于 MAMADROID,在我们的实验中,F 测量平均高出 15.80%,精度高出 12.97%,召回率高出 17.57%。当在比训练集新一到五年的测试集上进行评估时,DroidEvolver 的平均 F 测量值分别为 92.32%、89.30%、87.17%、87.46% 和 89.97%。相比之下,MAMADROID 在相应情况下的平均 F 值分别为 68.01%、56.09%、45.88%、32.85% 和 8.81%。随着时间的推移,DroidEvolver 的总体 F 度量在恶意软件检测方面平均比 MAMADROID 高 2.11 倍。 DroidEvolver 的 F 度量在五年内平均每年下降 1.06%,而 MAMADROID 在同一情况下下降了 13.52%。此外,如果仅通过少量具有真实标签的数据进行更新,DroidEvolver 的 F-measure 仍会保持在较高水平,而 MAMADROID 的 F-measure 在这种情况下每年都会下降。然后我们评估 DroidEvolver 的效率,并将其与 MAMADROID 进行比较。 DroidEvolver 的初始化需要线性时间,随着原始训练数据集从 10,000 个应用程序增加到 50,000 个应用程序,该时间从 3 秒到 27 秒不等,而 MAMADROID 则需要非线性时间,从 26 秒到 1,207 秒不等。在检测阶段,DroidEvolver 平均需要 1.37 秒来处理未知应用程序,而 MAMADROID 在这种情况下平均需要 39.15 秒。我们还分析了随着时间的推移在恶意软件检测过程中发现的老化模型和漂移应用程序。 DroidEvolver 将 11.23% 的新应用程序识别为漂移应用程序,而每个检测模型平均对约 30.13% 的漂移应用程序进行了分类,显示出老化的迹象。当使用后期开发的应用程序进行评估时,这些百分比保持稳定。此外,超过50.00%的检测模型被识别为老化,对49.08%的漂移应用进行了分类。这些漂移的应用程序是错误分类的主要来源,老化模型的更新使 DroidEvolver 减缓了恶意软件检测的老化速度。本文的贡献总结如下。我们提出了一种新颖的自我进化且高效的 Android 恶意软件检测系统 DroidEvolver。 DroidEvolver 不仅能够准确检测与训练应用程序同期开发的应用程序,而且能够准确检测训练应用程序之后使用新技术和新 API 开发的新应用程序。 DroidEvolver 非常高效,因为 DroidEvolver 在恶意软件检测期间利用在线学习算法从各个漂移应用程序更新其老化模型,而不是以批量方式定期从累积应用程序集合中重新训练。与最先进的恶意软件检测系统 MAMADROID 相比,DroidEvolver 在我们的实验中实现了显着更高的准确性和更高的效率。论文的其余部分组织如下。第二节详细介绍了 DroidEvolver 的系统设计。第三节介绍了实验设置和实验中使用的参数调整。第四节从不同方面评估了 DroidEvolver,分析了实验结果并讨论了其局限性。第五节总结了相关工作,第六节总结了本文。
2 DROIDEVOLVER 的设计
DroidEvolver1的架构如图1所示。DroidEvolver由两个阶段组成,包括初始化阶段和检测阶段。在初始化阶段,DroidEvolver 将一组与真实标签(即“恶意”和“良性”)相关的已知应用程序作为输入,并输出一组特征和一组检测模型,这些模型被传输到检测阶段。在检测阶段,DroidEvolver 将每个真实标签未知的应用程序作为输入,并输出未知应用程序的预测标签。 DroidEvolver的初始化阶段由四个模块组成,包括预处理器、特征提取、向量生成和模型池构建。对于输入中的每个已知应用程序,预处理器应用 apktool [37] 反编译其 apk 文件并获取其反汇编的 dex 字节码,其中包括该应用程序中使用的 API 调用。然后,特征提取模块用于提取所有Android API并记录每个应用程序的Android API二进制存在作为该应用程序的检测特征。初始特征集是一个全序集,通过组合输入中所有应用程序的检测特征来构造。特征空间是通过从初始特征集中的所有特征到特征空间的维度的一对一映射来构造的。在向量生成模块中,DroidEvolver 通过将应用程序的检测特征映射到特征空间中,为所有检测模型生成每个应用程序的特征向量,其中落在初始特征集中的每个检测特征被映射到组件一,而其他组件设置为零。给定输入中所有应用程序生成的特征向量,模型池构建模块构建一个初始模型池,该模型池由一组检测模型组成。每个检测模型都使用不同的在线学习算法进行初始化,该算法根据其特征向量和真实标签处理所有输入应用程序。在初始化阶段结束时,DroidEvolver 将初始特征集和初始模型池传输到检测阶段。模型池中的每个检测模型都与一个特征集指示器相关联,该指示器指示检测模型可以处理的特征数量。所有特征集指标都被初始化为初始特征集的大小,并且在检测阶段可以增加到更大的值。在检测阶段,DroidEvolver 将每个未知应用程序分类为恶意或良性,并对功能集和检测模型执行必要的更新。检测阶段的前三个模块与初始化阶段的模块类似,除了(i)动态更新特征集以包含新特征,(ii)通过 1-to 为每个检测模型构建特征空间。 -1 从特征集中序号小于检测模型的特征集指示符的所有特征映射到特征空间的维度,并且 (iii) DroidEvolver 通过以下方式从每个应用程序为每个检测模型生成一个特征向量:将应用程序的检测特征映射到检测模型的特征空间。在特征提取模块中,DroidEvolver根据现有的Android API家族[24]提取Android API,包括android、java、javax、junit、apach、json、dom和xml。虽然 API 包的数量从 API 级别 1(2008 年 10 月发布的 Android 版本 1.0)的 96 个大幅增加到 API 级别 27(2017 年 11 月发布的 Android 8.1 版本)的 196 个,但 Android API 系列的名称随着时间的推移保持不变。在检测阶段,只要新 API 调用的 API 系列保持不变,DroidEvolver 就不会错过任何由 Android 框架演进引起的新 Android API 调用。检测阶段的最后一个模块是分类和进化。在此模块中,DroidEvolver 为输入中给出的每个未知应用程序生成分类结果(恶意或良性)。如果模型池中的某些检测模型在检测未知应用程序时老化,DroidEvolver 会通过包含未知应用程序中使用的所有新 Android API 调用来增量更新其功能集(不更改任何现有功能的序号),并更新每个老化模型的特征集指标到更新特征集的大小。此外,DroidEvolver 通过根据未知应用程序的分类结果和更新的特征向量进行学习来更新每个老化模型。本节的其余部分将阐明如何在初始化阶段构建模型池,以及如何在检测阶段实现分类和进化。
A. 模型池构建
在初始化阶段给定一组已知应用程序及其相关的真实标签,DroidEvolver 使用一组在线学习算法构建模型池,而不是用于恶意软件检测的任何单一检测模型。由于其能力有限,单一检测模型可能并不总是提供准确的检测结果[33]。模型池可以帮助检测和减轻任何单个检测模型的偏差,并在检测阶段生成更可靠的检测结果。模型池中的每个检测模型都是使用不同的在线学习算法构建的,该算法一次处理一个应用程序。在线学习的复杂度与输入中的应用程序数量成线性关系,这与需要同时处理一组应用程序的批量学习不同。下面给出了 DroidEvolver 中在线学习算法的常见流程。
0
被动攻击(PA)。
在线梯度下降(OGD)
权重向量的自适应正则化(AROW)。
正则化双重平均 (RDA)。
自适应前向-后向分裂(Ada-FOBOS)。
B. Classification and Evolvement
漂移应用程序识别 - 何时发展。
分类和伪标签生成 - 随之演变。
衰老模型年轻化——如何进化。
三.实验设置和参数调整
代码
feature_set_initialization
import pickle as pkl
import os
import sys
def main():
feature = []
names = ['--list of app names developed in 2011 ----']
for app_name in names:
app_feature = pkl.load(open(app_name + '.feature', 'rb'))
for item in app_feature:
if item not in feature:
feature.append(item)
with open('feature_set.pkl','wb') as result:
pkl.dump(feature, result)
if __name__ == "__main__":
main()
feature_extraction.py
'''
Extract detection feature for each app according to included Android API
input: smali files of an app stored under /app_name/
output: detection features for the app stored in app_name.feature
'''
import os
import sys
import string
import pickle as pkl
import argparse
import glob
import operator
def extract_feature(filedir):
feature = []
for dirpath, dirnames, filenames in os.walk(filedir):
for filename in [f for f in filenames if f.endswith ('.smali')]:
fn = os.path.join(dirpath, filename) # each smali file
lines = open(fn,'r').readlines()
lines = [line.strip() for line in lines]
for line in lines:
# get all class names in invoke
try:
start = line.index(', ') + len(', ')
end = line.index(';', start)
classes = line[start:end]
except ValueError:
classes = ''
# get invoking method name
try:
start = line.index(';->') + len(';->')
end = line.index('(', start)
methods = line[start:end]
except ValueError:
methods = ''
objects = classes.split('/')
a = len(objects)
current_class = classes[:-(len(objects[a-1])+1)]
if current_class in packages: # android api
fe = classes + ':' + methods
feature.append(fe)
with open(filedir + '.feature', 'wb') as result:
pkl.dump(feature, result)
def main():
family = ['android','google','java','javax', 'xml','apache', 'junit','json', 'dom']
# correspond to the android.*, com.google.*, java.*, javax.*, org.xml.*, org.apache.*, junit.*, org.json, and org.w3c.dom.* packages
global packages
packages = open('android_package.name','r').readlines()
packages = [package.strip() for package in packages] # packages correspond to family
print 'official package number:', len(packages)
names = ['--list of app names ----']
for app_name in names:
extract_feature(app_name)
if __name__ == "__main__":
main()
vector_generation
#!/usr/bin/python
#coding:utf-8
'''
generate 2011.libsvm (i.e., the initialization dataset) from *.feature developed in 2011
label: 1 = malicious, -1 = benign
'''
import sys
import os
import string
import glob
import re
import string
import pickle as pkl
import argparse
def extract_benign(filedir):
app_feature = pkl.load(open(filedir + '.feature','rb')) # type: object
result = []
result.append('-1 ')
for i in range(len(features)):
if features[i] in app_feature:
result.append(str(i+1) + ':1 ')
data.append(result)
def extract_malicious(filedir):
app_feature = pkl.load(open(filedir + '.feature','rb'))
result = []
result.append('1 ')
for i in range(len(features)):
if features[i] in app_feature:
result.append(str(i+1) + ':1 ')
data.append(result)
def main():
global features
features = []
features = pkl.load(open('feature_set.pkl','rb'))
features = [feature.strip() for feature in features]
print 'feature size:', len(features)
print type(features)
global data
data = []
# generate initialization dataset
benign_names = ['--list of benign apps developed in 2011 ---']
for benign_app in benign_names:
extract_benign(benign_app, marker)
malicious_names = ['--list of malicious apps developed in 2011 --']
for malicious_app in malicious_names:
extract_malicious(malicious_app, marker)
data_file = open('2011.libsvm', 'w') # apps developed in 2011 is the initialization dataset
for item in data:
data_file.writelines(item)
data_file.writelines('\n')
data_file.close()
if __name__ == "__main__":
main()
model_pool_construction
'''
Construct model pool according to initialization dataset, e.g., apps developed in 2011
'''
import pylibol
import numpy as np
import scipy
from scipy.stats import logistic
from scipy.special import expit
from numpy import dot
import sklearn
from sklearn.datasets import load_svmlight_file
import os
import sys
import string
from decimal import *
import collections
from pylibol import classifiers
from classifiers import *
import time
import random
import argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--starting', type=int, help='initialization dataset') # to use = args.initialization
args = parser.parse_args()
starting_year = args.starting
X_train,Y_train=load_svmlight_file(str(starting_year) + '.libsvm')
print 'X_train data shape' , type(X_train), X_train.shape
global clfs
clfs = [PA1(), OGD(), AROW(), RDA(), ADA_FOBOS()]
print 'model pool size: ', len(clfs) # number of models in the model pool
ori_train_acc = []
directory = './' + str(starting_year) + 'train/'
if not os.path.exists(directory):
os.makedirs(directory)
# initialization process of all models
print 'All model initialization'
for i in xrange(len(clfs)): # i = every model in model pool
print clfs[i]
print 'training'
train_accuracy,data,err,fit_time=clfs[i].fit(X_train,Y_train, False)
ori_train_acc.append(train_accuracy)
clfs[i].save('./' + str(starting_year) + 'train/' + str(starting_year) + '_' + str(i) + '.model')
print 'original model accuracy', ori_train_acc
if __name__ == "__main__":
main()
classification_evolvement.py
'''
Use the model pool initialized with 2011 apps to detect malware from apps developed in 2012, 2013, 2014, 2015, 2016
Model pool and feature set (i.e., feature_set.pkl) are evolved during detection.
'''
import pylibol
import numpy as np
import scipy
from scipy.stats import logistic
from scipy.special import expit
from numpy import dot
import sklearn
from sklearn.datasets import load_svmlight_file
import os
import sys
import string
from decimal import *
import collections
from pylibol import classifiers
from classifiers import *
import time
import random
import pickle as pkl
import argparse
import shutil
class app(object):
def __init__(self, a, y, pl):
self.a = a
self.y = y
self.pl = pl
def extract_benign(filedir):
app_feature = pkl.load(open(filedir + '.feature','rb'))
result = []
result.append('-1 ')
new = []
for i in range(len(features)):
if features[i] in app_feature:
result.append(str(i+1) + ':1 ')
for item in app_feature:
if item not in features: # this is a new feature, store new features in advance to save time
p = 1
# append the new feature to the data
# the model won't process this new feature unless update
# the model will only process the first |len(features)| features
result.append(str(len(features) + p) + ':1 ')
new.append(item)
p += 1
return result, new
def extract_malicious(filedir):
app_feature = pkl.load(open(filedir + '.feature','rb'))
result = []
result.append('1 ')
new = []
for i in range(len(features)):
if features[i] in app_feature:
result.append(str(i+1) + ':1 ')
for item in app_feature:
if item not in features: # this is a new feature
p = 1
# append the new feature to the data
# the model won't process this new feature unless update
# the model will only process the first |len(features)| features
# if this app is a drifting app, the new identified feature will be added into feature_set.pkl
result.append(str(len(features) + p) + ':1 ')
new.append(item)
p += 1
return result, new
def evaluation(Y_test, instances):
n = p = tp = fn = tn = fp = right = 0
print 'evaluating predictions'
for e in xrange(len(Y_test)):
if Y_test[e] != 1 and instances[e].pl != 1: # true label, prediction label
n += 1
tn += 1
if Y_test[e] != 1 and instances[e].pl == 1:
n += 1
fp +=1
if Y_test[e] == 1 and instances[e].pl == 1:
p += 1
tp += 1
if Y_test[e] == 1 and instances[e].pl != 1:
p += 1
fn += 1
if Y_test[e] == instances[e].pl:
right += 1
print type(Y_test), len(Y_test)
print 'all', n+p, 'right', right ,'n', n , 'p:', p, 'tn', tn, 'tp',tp, 'fn',fn, 'fp',fp
accu = (Decimal(tp) + Decimal(tn))*Decimal(100) / (Decimal(n) + Decimal(p))
tpr = Decimal(tp)*Decimal(100)/Decimal(p)
fpr = Decimal(fp)*Decimal(100)/Decimal(n)
f1 = Decimal(200)*Decimal(tp)/(Decimal(2)*Decimal(tp) + Decimal(fp) + Decimal(fn))
precision = Decimal(tp)*Decimal(100)/(Decimal(tp) + Decimal(fp))
print 'model pool f measure: ', float(format(f1, '.2f')), 'precision: ', float(format(precision, '.2f')), 'recall: ', float(format(tpr, '.2f'))
return float(format(accu, '.2f')), float(format(f1, '.2f')), float(format(precision, '.2f')), float(format(tpr, '.2f')), float(format(fpr, '.2f'))
def metric_calculation(i, j, buffer_size):
larger = 0
if len(app_buffer) <=buffer_size:
app_temp = [item[j] for item in app_buffer]
positive = sum(app_tt > 0 for app_tt in app_temp)
negative = sum(app_tt <= 0 for app_tt in app_temp)
if confidences[i][j] > 0: # prediction label = 1 = malicious
larger = sum(confidences[i][j] >= app_t and app_t > 0 for app_t in app_temp)
p_ratio = float(Decimal(larger)/Decimal(positive))
else: # <= 0 = benign
larger = sum(confidences[i][j] <= app_t and app_t <= 0 for app_t in app_temp)
p_ratio = float(Decimal(larger)/Decimal(negative))
else:
app_temp = [item[j] for item in app_buffer[len(app_buffer)-buffer_size:]]
positive = sum(app_tt > 0 for app_tt in app_temp)
negative = sum(app_tt <= 0 for app_tt in app_temp)
if confidences[i][j] > 0: # prediction label = 1 = malicious
larger = sum(confidences[i][j] >= app_t and app_t > 0 for app_t in app_temp)
p_ratio = float(Decimal(larger)/Decimal(positive))
else:
larger = sum(confidences[i][j] <= app_t and app_t <= 0 for app_t in app_temp)
p_ratio = float(Decimal(larger)/Decimal(negative))
return p_ratio
def all_model_label(i, age_threshold_low, age_threshold_up):
young = aged = a_marker = y_marker = 0
for j in xrange(len(clfs)):
if age_threshold_low <= p_values[i][j] <= age_threshold_up: # not an aged model, can vote
young += confidences[i][j]
y_marker += 1 # number of young model
else: # this is an aged model, need to be updated
aged += confidences[i][j]
aged_model.append(j) # record aged model index
a_marker += 1 # num of aged model for this drifting app
return young, aged, a_marker, y_marker
def generate_pseudo_label(aged_marker, young_marker, aged_value, young_value):
if young_marker == 0: # young models are not available; weighted voting using aged model
if aged_value > 0:
temp = app(aged_marker, young_marker, 1.)
else:
temp = app(aged_marker, young_marker, -1.)
fail += 1
else: # young models are available; weighted voting using young model
if young_value > 0:
temp = app(aged_marker, young_marker, 1.)
else:
temp = app(aged_marker, aged_marker, -1.)
instances.append(temp)
def save_model(current_year, checkpoint_dir):
for m in xrange(len(clfs)):
print m, clfs[m]
clfs[m].save( checkpoint_dir + str(current_year) + '_' + str(m) + '.model')
def main():
# set argument for past year and current year
parser = argparse.ArgumentParser()
parser.add_argument('--past', type=int, help='past year')
parser.add_argument('--current', type=int, help='current year')
parser.add_argument('--starting', type=int, help='starting year') # initialization year = 2011
parser.add_argument('--low', type=float, help='low threshold value')
parser.add_argument('--high', type=float, help='high threshold value')
parser.add_argument('--buffer', type=int, help = 'buffer size value')
args = parser.parse_args()
buffer_size = args.buffer
age_threshold_low = args.low
age_threshold_up = args.high
global features
features = pkl.load(open('feature_set.pkl','rb'))
whole_directory = './'+ str(args.starting) + 'train/'
current_directory = str(age_threshold_low) + '_' + str(age_threshold_up) + '_' + str(buffer_size) + '/'
checkpoint_dir = whole_directory + current_directory
if not os.path.exists(checkpoint_dir):
os.makedirs(checkpoint_dir)
global clfs
clfs = [PA1(), OGD(), AROW(), RDA(), ADA_FOBOS()]
print 'model pool size: ', len(clfs)
ori_train_acc, ori_test_acc, weights, pool_acc, pool_fscore, pool_precision, pool_tpr, pool_fnr, pool_fpr, pool_difference = ([] for list_number in range(10))
print 'Loading trained model from ', args.past
if args.starting == args.past: # copy the initial detection model into checkpoint_dir
for i in xrange(len(clfs)):
shutil.copy2( whole_directory + str(args.past) + '_' + str(i) + '.model' , checkpoint_dir )
for i in xrange(len(clfs)): # for each model in the model pool
clfs[i].load( checkpoint_dir + str(args.past) + '_' + str(i) + '.model')
# get original model weight
w = clfs[i].coef_[1:]
weight = [] # [i][j]: i = model index, j = feature index
for w_num in xrange(len(w)):
weight.append(w[w_num])
weights.append(weight)
print 'original weight size'
for c in xrange(len(weights)):
print c, len(weights[c])
print 'App buffer generation'
global app_buffer
app_buffer = []
if '2011' in str(args.past): # buffer is not exist
print 'App buffer not exists'
print 'App buffer initialization'
print 'Loading data from ', args.past, ' to initialize app buffer ...' # load the 2011 data to initialized app buffer
X_train,Y_train=load_svmlight_file( str(args.past) + '.libsvm')
train_size, _ = X_train.shape
random_app_index = np.random.randint(train_size, size = buffer_size)
X_train_temp = X_train[random_app_index, :]
for i in xrange(buffer_size):
app_buffer_temp = []
for j in xrange(len(clfs)):
app_buffer_temp.append(clfs[j].decision_function(X_train_temp[i])[0])
app_buffer.append(app_buffer_temp)
else: # load buffer from str(args.past).buffer
print 'App buffer exists'
app_buffer = pkl.load(open( checkpoint_dir + str(args.past) + '_buffer.pkl', 'rb'))
print 'Load app buffer from ', args.past, '_buffer.pkl'
print 'Start evolving'
global confidences, new_confidences, p_values, instances, model_credits, model_confidences
confidences, new_confidences, p_values, instances, model_credits, model_confidences = ([] for list_number in range(6))
all_fail = 0 # a special case, all model are aged
num_of_update = num_of_update_model = 0
wrong_update = 0
wrong_update_benign = wrong_update_malicious = right_update_benign = right_update_malicious = 0
Y_test = [] # save ground truth of test data ; for final evaluation only
names = ['---list of test app names -----'] # names of apps developed in the current_year, e.g., names of apps developed in 2012
for i in xrange(len(names)):
# generate test data
app_name = names[i] # for each test app
# according to the ground truth to get the true label
# the true label is for evaluation only, won't be processed by the model
data = []
if 'malicious' in app_name:
d, new_feature = extract_malicious(app_name)
data.append(d)
else:
d, new_feature = extract_benign(app_name)
data.append(d)
# skip if do not need to save test data
save_data = open(app_name + '.libsvm', 'w')
for item in data:
save_data.writelines(item)
save_data.writelines('\n')
data_file.close()
X_test, y_t=load_svmlight_file(app_name + '.libsvm')
X_testt,y_testt=load_svmlight_file(app_name + '.libsvm')
Y_test.append(y_t)
print 'X_test data shape', type(X_test), X_test.shape
xtest_dense = scipy.sparse.csr_matrix(X_testt).todense()
print 'X_test', xtest_dense.shape
# calculate JI value
pre, conf, new_conf, app_b, p_value = ([] for list_number in range(5))
for j in xrange(len(clfs)):
xtest_current = xtest_dense[ ,:len(weights[j])]
score = xtest_current.dot(weights[j])
conf.append(score[0,0])
app_b.append(score[0,0])
new_conf.append(abs(score[0,0]))
confidences.append(conf)
new_confidences.append(new_conf)
app_buffer[random.randint(0, buffer_size-1)] = app_b # randomly replace a processed app with the new app
for j in xrange(len(clfs)):
pv = metric_calculation(i, j, buffer_size)
p_value.append(pv)
p_values.append(p_value)
global aged_model
aged_model = [] # store the index of aged model for current app i
young_value = aged_value = aged_marker = young_marker = 0
young_value, aged_value, aged_marker, young_marker = all_model_label(i, age_threshold_low, age_threshold_up)
# generate pseudo label
generate_pseudo_label(aged_marker, young_marker, aged_value, young_value)
# drifting app is identified and young model exists
if (aged_marker != 0) and (young_marker >= 1):
update_label = np.array([instances[i].pl]) # update label = pseudo label of the drifting app
# update aged models
for model_index in aged_model: # update clfs[a] with X_test, update_label; a is the aged model index
# update with drifting app and corresponding pseudo label
train_accuracy,data,err,fit_time=clfs[model_index].fit(X_test,update_label, False)
w = clfs[model_index].coef_[1:]
updated_w = []
for w_num in xrange(len(w)):
updated_w.append(w[w_num])
weights[model_index] = updated_w # update weight matrix in the weight matrix list for the next new app
# updat feature set
for new_identified_feature in new_feature:
features.append(new_identified_feature)
a, f, preci, tprr, fprr = evaluation(Y_test, instances)
pool_acc.append(a)
pool_fscore.append(f)
pool_precision.append(preci)
pool_tpr.append(tprr)
pool_fnr.append(100-tprr)
pool_fpr.append(fprr)
print buffer_size, len(app_buffer)
print 'pool accuracy', pool_acc
print 'pool fscore', pool_fscore
print 'pool precision', pool_precision
print 'pool tpr', pool_tpr
print 'pool fnr', pool_fnr
print 'pool fpr', pool_fpr
print 'evolved weight length'
for c in xrange(len(weights)):
print c, len(weights[c])
# save evolved model for each year
print 'Save model evolved in Year ', args.current, 'into directory /', checkpoint_dir
current_year = args.current
save_model(current_year, checkpoint_dir)
# save feature set
with open('feature_set.pkl','wb') as feature_result:
pkl.dump(features, feature_result)
print 'Save app buffer evolved in Year', args.current
pkl.dump(app_buffer, open( checkpoint_dir + str(args.current) + '_buffer.pkl', 'wb'))
if __name__ == "__main__":
main()