发新帖

路由器蠕虫触发的网络安全人工智能实战

[复制链接]
578 4

快来加入 TensorFlowers 大家庭!

您需要 登录 才可以下载或查看,没有帐号?加入社区

x

近日,代码医生团队与派网软件合作,在恶意域名检测方向获得了重大技术突破。使用词嵌入的双向RNN技术,对未知恶意域名的识别达到99%左右的正确率。这里就来分享一下该案例中台前幕后的技术故事。

域名拦截一致是安全领域比较头疼的事情。随着黑产的升级,恶意域名存在这生存周期短、随机性大的特点。黑产工作者使用大量的随机域名轮流更换,不停的更新。使得传统的黑白名单拦截方式彻底失效。

在该问题的背景下,派网软件截获了电视机顶盒及高端通讯设备中的蠕虫病毒,并将获取到的恶意域名样本贡献出来,代码医生团队决定对该病毒资源进行人工智能识别实验,并开源相关的工程和代码,以期望提高整个业界的识别水平,对病毒攻击做出最好的回击

技术概要:使用TensorFlow框架实现,采用监督学习的训练方法。在案例的整个流程中,涵盖了样本预处理、Tensorflow通用框架编写、双向RNN模型的搭建、词嵌入技术的应用、及后续优化和升级方案等内容。整个案例的代码在GITHUB上有公开,其中模型部分的代码以冻结图的方式提供。

整个项目的代码地址:https://github.com/jinhong0427/domain_malicious_detection

一 样本预处理
利用人工智能解决问题的项目都是先从样本入手。本小节先来分析下手里拿到的数据,并对其加工处理,生成可以使用的样本。为后面的工作做好铺垫。

4.png
1.样本介绍
在项目初期,需要先使用少量的数据,做精细化的样本处理。以便能够给模型一个正确的方向指导。它就仿佛一个学生的启蒙老师一样,要为学生明确一个是非对错的价值关,所以这部分的工作尤为重要。样本要尽可能的纯净,具有代表性。
这里先来介绍一下用于处理的第一批原始数据,它一共有4个文件:
111.txt:包含正向和恶意的混合域名;
Kefu.txt:包含涉黄域名;
bc.txt:包含涉赌域名;
domain_malicious.txt:包含其他未知的恶意域名
2.预处理工作
预处理部分主要做三种操作:正负样本分离、生成字典、样本转储。
正负样本分离
为了训练方便,在样本预处理阶段,会将111.txt中的恶意域名去除,加工成为正向的域名集合。经过预处理后,得到的样本的详细信息如下:
样本种类
个数
所在文件
正确域名
1022296
111.txt
涉黄域名
236
kefu.txt
涉赌域名
223491
bc.txt
其他未知恶意域名
115107
domain_malicious.txt
为了将来可以对域名分析更精细化的扩展,例如开篇的图例所显示的路径,会把样本按照已有的分类放在不同的文件夹里。这样可以为不同的样本打上不同的标签,做更精细化的分类任务。当然本次实战案例,只是需分正确的与恶意的域名,所以只会打上两种标签:0、1。对于111.txt中剔除恶意的域名之后剩下的部分统一打成0标签。其余的域名统一打成1标签。
读取部分代码
读取文档部分的代码在preprosample类的load_txt_sample函数中实现,是使用递归目录的方式,按照文件夹来读取到对应的list中。代码如下:

def load_txt_sample(self, split='train'):
        '''递归。如果是只有一级。就直接返回。如果是有多级,【根,子目录1,子目录2。。。】
        读取txt文件,一行一行的读,传入一个目录,读取目录下的每一个文件
        '''
        print ('loading sample  dataset..')
        
        alldata = []
        for (dirpath, dirnames, filenames) in os.walk(self.sample_dir):#一级一级的文件夹递归
            print(dirpath,dirnames,filenames)
            sdata = []
            for filename in filenames:
                filename_path = os.sep.join([dirpath, filename])  
                with open(filename_path, 'rb') as f:  
                    for onedata in f:
                        onedata = onedata.strip(b'\n')
                        try:
                            #print(onedata.decode('gb2312'))#,onedata.decode('gb2312'))'UTF-8'
                            sdata.append(onedata.decode( 'gb2312' ).lower().replace('\r',''))
                        except (UnicodeDecodeError):
                            print("wrong:",onedata.decode)

            alldata.append(sdata)

分离样本代码
在preprosample类的do_only_sample函数中,使用Python的集合运算方式,实现在混合数据中剔除负样本的操作。代码如下:  

def do_only_sample(self, alldata):
        '''去重  【【正】【负】【负】】
        '''
        
        alldataset = set(alldata[0] )
        dudataset = set(alldata[1] )  
        huangdataset = set(alldata[2] )  
        otherset = set(alldata[3])
        print(len(alldataset))
        yesdataset = (alldataset-dudataset)-huangdataset
        print(len(yesdataset))
        return list(yesdataset),list(dudataset),list(huangdataset),list(otherset)
生成字典
在模型训练过程中,需要将样本都转成具体的字符向量才可以进行。所以有必要为已有的域名创建一个字符字典用于向量映射。所谓的字符字典就是将域名中出现的字符统计起来,每个不同的字符都给与一个对应的唯一编号。
经过处理后,得到的域名字符对应的字典如下:

['None', 'b', '.', '5', '%', '9', '7', 't', 'p', 'i', 'g', 'e', 'k', 'y',
'1', '&', 'w', 'r', ')', '*', 'h', 'c', 'f', '=', ':', 'n', 'u', '4', 'a',
'(', '-', 'j', '3', '?', '^', 'z', 'm', 'v', '_', 'x', 'q', '/', '8', 's',
  '0', 'o', 'd', '2', 'l', '6']

其中的第一个‘None’是额外加入的占位字符。对与字典中的位置字符可以统一被影射为None字符。这样做可以防止字典字符覆盖不全的情况。
为了将某个字符映射为向量,需要将上述的字典做个反向,即,输入某个字符获得其对应的向量值。处理后的反向字典如下:

{'.': 2, 'w': 16, 'f': 22, '6': 49, '1': 14, 'm': 36, 'r': 17, '3': 32, '5'
: 3, '_': 38, '0': 44, 'd': 46, '9': 5, '(': 29, '=': 23, '?': 33, 's': 43,
  't': 7, 'c': 21, '^': 34, 'b': 1, '/': 41, '*': 19, 'z': 35, ')': 18, 'p':
   8, 'g': 10, '%': 4, 'k': 12, 'l': 48, 'q': 40, 'v': 37, 'j': 31, 'x': 39,
    'e': 11, 'u': 26, '7': 6, '2': 47, '8': 42, 'n': 25, 'None': 0, 'a': 28,
     '4': 27, 'o': 45, 'y': 13, ':': 24, 'i': 9, '&': 15, 'h': 20, '-': 30}

利用上述的反向字典,就可以将具体的字符转化成向量了。上面的结构是Python中字典类型的对象内容。Key就是某个具体的字符,对应value就是想要得到的向量。
样本转储
为了方便运算,希望程序在训练模型时,每次的运行只针对预处理后的结果进行训练。这样就有必要将原有预处理的结果存起来。这里会将生成的字典、与正负样本及其对应的标签存起来。使用Python中的pickle来实现该步骤的操作。
具体的做法是在preprosample类的save_sample函数中,将数据集分成两部分,一部分用于测试集、一部分用于训练集。分别存储到plk文件中。

def save_sample(self,sdatasample,slabelsample,maketrain = 1):

if maketrain == 1:
lendata = int(len(slabelsample)*0.95)
else:
lendata = int(len(slabelsample))

train = {'X': sdatasample[:lendata],
'y': slabelsample[:lendata]}

test = {'X': sdatasample[lendata:],
'y': slabelsample[lendata:]}

# if not os.path.exists(self.plk_dir):
# os.mkdir(self.plk_dir)

# make directory if not exists
if tf.gfile.Exists(self.plk_dir):
tf.gfile.DeleteRecursively(self.plk_dir)
tf.gfile.MakeDirs(self.plk_dir)

self.save_pickle(train, self.plk_dir+'/train.pkl')
if maketrain == 1:
self.save_pickle(test, self.plk_dir+'/test.pkl')

训练集部分用于模型训练。而测试集部分用于评估模型的准确度。上面代码中对调用的保存PLK文件方法也进行了封装,具体代码如下:
  
  def save_pickle(self,data, path):
        with open(path, 'wb') as f:
            pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)
            print ('Saved %s..' %path)
这么做的原因是可以让程序有更好的扩展性,如果想用别的方式来保存预处理结果,直接修改该接口即可。
二  TensorFlow通用框架的编写

在TensorFlow1.3版本之后,提出了一个估算器框架的概念。使得开发者可以在完成深度学习程序搭建的过程中,不必要太关心各个环节的组织连接工作。大大提升了开发效率。但是由于封装得过于到位,对于初学者来讲并不透明。为了案例演示,这里使用了非估算器结构框架,手动搭建通用处理框架来将各个环节组织起来。

1.框架基本代码结构
组成通用框架的基本代码结构,共分为4个文件,具体如下:
lmain.py:为程序的总体入口;
lmodel.py:为程序的模型文件;
lprepro.py:为程序的预处理文件;
lwork.py:为程序的流程文件
除了main文件,其他文件都是以类的形式存在,每一个类实现几个具体的独立功能,例如model单独存放模型文件,也可以在其内部实现多种模型,进行训练比较;prepro负责在main执行前进行数据的预处理工作,将原始数据存储成具体的可使用样本;work则是具体的流程处理操作,根据main下达的训练、评估、应用指令,来调用模型,同时载入对应的预处理后的数据,完成整体的任务。
2. 预处理类preprosample
该类可以当作工具类来使用,预处理部分主要做两种操作,里面的方法有:
lload_cvs_evaldata:从csv文件读取数据;
lload_txt_sample:递归读取txt文件的样本;
ldo_only_sample:样本去重;
lmake_dictionary:生成字典;
lch_to_v:字符转向量;
lpad_sequences:样本对齐的pad操作;
lload_dic:加载字典;
lsave_sample保存样本。
3.流程处理Work类  
主要是针对专项流程方面的处理(比如训练、测试、应用等),和一些基础操作(载入样本):
lload_pkl_sample:载入样本;
levalfreeze:应用模型;
ltrain:训练模型;
ltest:测试模型。
4.模型结构DomainNameModel类
该主要是类放置的训练模型的网络结构定义。函数build_model用来实现网络结构的具体定义。可以通过输入不同的参数返回不同的节点。代码中给出了freeze参数的实现:将模型文件载入到内存中,返回给work。可以在work进行模型应用的具体计算。
三 模型搭建

在搭建模型之前,首先是模型的选取,域名检测属于序列文本,序列之间具有一定的语义,所以优先选择了RNN网络。

RNN网络是一个具有记忆功能的网络,能够在一个序列里,记忆前面的序列意义。(关于RNN网络的更多信息,可以在《深度学习之TensorFlow入门、原理与进阶实战》一书的9章找到最详细的介绍)。

最终分类部分使用了全连接网络。全连接本意是一个低维到高维空间的特征映射。该忘了的特点可以拟合任意规则。

模型搭建的部分,是在动态双向RNN基础上使用了词嵌入技术,即将每个字符向量转成64维度的词嵌入向量作为输入,然后通过双向RNN进行基于域名的正反向特征提取,最后通过2层全连接网络来完成的。整体结构如下图所示:

5.png
1.相关模型参数细节
如上图所示,按照从下至上,一次介绍相关模型及参数的细节如下:
l原始域名字符映射字典的长度为50;
l映射后的向量,经过嵌入词转换的维度为64;
l在双向RNN中,使用了变长序列的处理方式,支持最大序列长度为256;
l双向RNN的前向与后向采用同样的结构,一个两层的RNN网络,每层RNN由64个GRU单元组成;
l双向RNN的结果输入到一个16节点组成的全连接网络。
l然后,在进入一个2节点组成的全连接网络。
l对模型出来的结果进行softmax变换,并且与标签进行交叉熵运算,得出loss值。
l反向传播使用AdamOptimizer优化器。
l学习率为0.0008。
l训练时采用随机最小批次方式进行样本输入。批次的最大值为1024。
2.需要注意的技术细节
模型部分的代码是以冻结图方式提供的。如果自己需要进行编写及优化,可以参考下面的关键技术部分源码:
变长双向RNN的实现
在《深度学习之TensorFlow入门、原理与进阶实战》一书的9.4.2中的第4小节,介绍过变长动态RNN的实现。该方法与变长双向RNN的实现非常类似。只需要将输入批次样本中对应的长度列表一起放入双向RNN初始化函数里即可。
双向RNN接全连接网络的实现
该部分可以参考《深度学习之TensorFlow入门、原理与进阶实战》一书的9.5语音识别例子中的模型部分。该模型也是使用用了双向RNN并结合全连接网络进行音字翻译的。
多层RNN的实现
本案例与《深度学习之TensorFlow入门、原理与进阶实战》一书的语音识别例子唯独不同的是,在语音识别的例子中使用的是单层RNN网络。如果需要实现多层可以参考9.4部分的多成RNN例子。
学习率的拟定
前面介绍过,学习率使用的是0.0008。这个值比较保守,为了是让模型精度更大一些。但是会增加训练时间。因为每次的梯度变化值会比较小。达到目的需要的部署就会增大。当然可以通过退化学习率、或增大批次的方法来实现高精度(这部分的技巧在书中的第6、7章也有系统的介绍)。
3.调优的误区  
节点不是越多越好

可以注意到上面公布的参数中,每一层的节点都不是很多。大家都知道,节点个数越多,会提高模型越强的拟合能力。但是在实际应用中,会发现越高的节点个数会使模型在拥有更高的拟合能力基础上同样带有更低的泛化能力。其表现的现象就是在使用对抗样本训练时,会使模型抖动的厉害。所以在调优过程中,千万不要一味的盲目加大节点。

退化学习率注意事项

如果读者使用退化学习率在自行训练时,需要注意一点,有时候会出现训练步数太多,导致还没有训练出真正的模型,学习率已经退化到几乎为0了。导致模型无法收敛。

四 模型的运行

该模型使用双向RNN技术进行恶意域名检测。使用了130多万条域名样本,其中100多万是正常域名样本,30多万是恶意域名样本。恶意域名样本来源于本次病毒攻击下载的资源文件和天际友盟的威胁情报系统,正常域名样本来源于正常用户的上网行为数据。训练结束后,混合测试集样本的识别正确率达到99%左右,纯负向样本的正确率达到96%,纯正向样本的正确率达到98%。另外在代码同步的样本中,提供了一组待测的数据及处理结果。

运行方法
(1)将代码及所有的样本下载下来。
(2)打开main.py,将第67行设置为如下;
mode = "pretrain"
直接运行。会生成样本预处理文件,及字典文件。
(3)再将上面代码改成如下:
mode = "evalfreeze"
直接运行。会对数据20171213142927.xls进行域名检测处理,生成eyn.txt文件。
五  后续的优化及升级方案

本案例仅仅是个抛砖引玉的作用,当然还有很多可以优化的地方。

1.基于现有精度的优化
在词嵌入部分本案例使用的是随机初始值,这里完全可以通过载入预训练好的词向量模型,让现有模型获取更为准确的语义特征。具体做法是使用CBOW或skip-gram模型,对现有域名的字符间关系进行分析。基于分析后的语义在进行词嵌入的映射,会比原有模型的效果更好。
2.基于模型结构的优化
该应用还可以是使用半监督式训练方法进行模型的训练,生成式对抗神经网络应当是首选。由于应对与仿照正规网站的欺骗类型恶意域名,因为它符合正样本唯一,负样本随机的特征。生成式对抗神经网络具有强大的样本特征拟合功能,可以使我们得到更为精确的判别器网络。但是其训练的关节相对复杂,比其他网络更难于训练,这是目前的困难所在,也是后续的研究方向。
3.基于案例应用的优化
对于恶意域名检测案例的应用来讲,这只是个开端,距离商用还有很多细分工作要做。因为恶意域名的形式远不止文章中提到的黄、赌之类的类型。若想将域名识别的更为精确必须要将域名按照细分类别单独处理才是。
例如:像下列这种赌博的恶意域名,特征就比较明显。单纯从域名字符形态上就可以进行区分。若使用AI技术单独用于该类识别,准确率就会非常的高。

0000002.com000000cf.com000000.com0000036.com00000378.com00000.am00000hg.com00000hm.com00000jsc.com00000k9.com00000msc.com00000s8s.com00000tb.com00000vn.com
而对于下面这种具有一定语义的涉黄域名,则需要模型对其语义进行理解和区分。使用具有理解语义的模型对其专门训练即可得到很好的效果。

chat.l8servicedreamofcity.comhappypussygames.comchat.l8serviceqy8.comchat.l8serviceuqu.combadasianpussy.comlivechat-d88.comlivechatinc.com

如果将上面两种情况放在一起区分,就是本篇文章的模型。但是它缺少通用性,例如对于欺骗类的域名,假冒仿真网站的域名识别就是该模型无法胜任的事情。

apple-info.netapple-inportant.comapple-itunes.serverhost.comapple-login-account.gaapple-mac911.onlinesoftwaresollutionhelpdesk.infoapple.g2live.netapple-refund-id38303910.cfapple-refund-id38303911.cfapple-refund-id389401310.cf

上面列出的都是恶意域名。是仿照苹果官网的网站做的钓鱼网站域名。正常的域名如下:

Apple.comItunes.com

模型在处理这个问题时,就会出现抖动。因为模型虽然能够学到里面的语义,但也无法判断其为恶意还是正常。类似这种情况的欺骗网站,还有很多,比如仿照各大银行的网站、金融交易网站等。对于这类问题就不能与前面的一般恶意域名检测问题放在一起处理。必须得分开,进行单独的样本收集,训练。可以使用传统的黑白名单方式、也可以升级现有的网络模型,使其适应更多样变化的特征,例如使用对抗网络模型等。
参考与致谢
参考:
1.本案例代码与资源下载网址:https://github.com/jinhong0427/domain_malicious_detection
2.CDN校验漏洞催生海量网络投毒: http://www.freebuf.com/news/139358.html
3.IP地址溯源:https://www.ipip.net/
4.天际友盟威胁情报平台:https://redqueen.sec-un.com/
致谢:
感谢感谢派网软件提供安全方向技术支持。
1.CNCERT
2.烽火台安全威胁情报联盟


1.png
2.png
3.png
本楼点评(0) 收起

精彩评论4

smile  TF豆豆  发表于 2018-4-17 20:28:06 来自手机  | 显示全部楼层
本楼点评(0) 收起
smile  TF豆豆  发表于 2018-4-17 20:35:49 来自手机  | 显示全部楼层
发到Tensorflow案例版吧
本楼点评(0) 收起
舟3332  TF芽芽  发表于 2018-4-17 20:45:16 | 显示全部楼层
这是原创的文章吗? 很赞呀~
本楼点评(0) 收起
ves  TF荚荚  发表于 2018-7-4 19:41:54 | 显示全部楼层
少见的网络安全案例
本楼点评(0) 收起
您需要登录后才可以回帖 登录 | 加入社区

本版积分规则

主题

帖子

2

积分
快速回复 返回顶部 返回列表