[网络安全篇] 图像思维初步解决恶意软件问题

近年来,恶意软件的出现尤为猖獗。所以解决恶意软件的问题也成为了刻不容缓的事情,当然,现在运用机器学习去完成恶意软件分类的手段已经比较的成熟了,但是,机器学习去特征的处理要求是比较高的,这里我采用深度学习的思维去解决。与此同时,Canadian Institute for Cybersecurity这个研究所提供了近几年的安卓恶意软件的提取特征文件,我在此基础上进行改进,初步完成一个较为入门级别的恶意软件分类项目。当然,因为是第一版的项目,准确率不会太高。(如果可能,后续会不断的迭代更新)

0 背景

本项目将会以处理图像的手段完成恶意软件的分类问题,当然这个解决方式并不新颖,算是一个对已有方法的简单复现。

1 数据集

详情参照CIC研究所的具体发布内容。

https://www.unb.ca/cic/datasets/maldroid-2020.html

在这里插入图片描述

在这里插入图片描述

这里我采用了其中470维度的一个数据进行处理,数据集已经挂载在项目数据集当中。

# 对数据进行一个copy
!cp data/data157200/syscallsbinders_frequency_5_Cat.csv work/

2 数据处理

2.1 数据展示

因为是csv文件,我这里采用pandas的方式进行读取,更为方便快速。

可以通过表头看到,是各种权限、调用函数、绑定器的使用频次统计。

每一行的最后一列是该行数据所属于的分类类别

import pandas as pd
# 按文件名读取整个文件
data_471 = pd.read_csv("work/syscallsbinders_frequency_5_Cat.csv") 
data_471.head(2)     # 展示前几行数据,通常为展示前5行
ACCESS_PERSONAL_INFO___ALTER_PHONE_STATE___ANTI_DEBUG_____CREATE_FOLDER_____CREATE_PROCESS`_____CREATE_THREAD_____DEVICE_ACCESS_____EXECUTE_____FS_ACCESS____FS_ACCESS()____...utimesvforkvibratevibratePatternwait4watchRotationwindowGainedFocuswritewritevClass
010030142030...000000037101
13006042910320...00000022838461

2 rows × 471 columns

2.2 无效数据过滤

我这里的思维就是,竖向的每一列,如果求和他的值为0,那么他的这一列的数值存在将是毫无意义的。

换句话来讲,其实这里也是一种特征降维。

因为我渴望通过图像的方式去解决问题,那么我就可以将每一行的数据保留到 N x N NxN NxN的一个维度。

那么,这里是不是可以放大这个0值,每一列小于某个阈值的话,我们都可以视为这一列起不到作用。

这里我采用63这个阈值,这样刚好可以把数据降到290维,最后一位为分类类别,相当于就是

17 * 17 = 289 + 1 = 290

import pandas as pd
# 按文件名读取整个文件
data_471 = pd.read_csv("work/syscallsbinders_frequency_5_Cat.csv") 
df = pd.DataFrame(data_471)
len(df.columns)
# 记录每一列求和小于阈值的列
threshold = 63
de = []
de_max = 0
de_va = []
for i in range(0,470):
    name = df.columns.values[i]
    if(df[name].sum() <= threshold):
        de.append(name)
    if(df[name].max() >= de_max):
        de_max = df[name].max()

# 删除小于阈值的列
for j in de:
    df.drop(j,axis=1,inplace=True)

len_new = len(df.columns)

len_new
290

PS: 不合理点

这里有些频次的调用很大,这里统计了最大的数值de_max,为3697410.

那么,我们将其转换为图像时,范围是0-255才是合理的。

所以需要使用归一化操作,但是这里,这个值过于庞大,肯定是会将其他数值缩放之后几乎为0的状态。

我这里采取,大值统一设置为10000,当然大家可以尝试其他值看看结果的差异有多大。

de_max
3697410
# 新数据写入csv
df.to_csv("new_date"+str(len_new)+".csv",index=False,sep=',')
import numpy as np
import pandas as pd

mydata = pd.read_csv("new_date290.csv")
mydata_array = np.array(mydata)
mydata_array
array([[   1,    0,    3, ...,   37,   10,    1],
       [   3,    0,    6, ..., 2838,   46,    1],
       [   2,    0,    4, ...,  111,   20,    1],
       ...,
       [   0,    0,    0, ...,  241,   67,    5],
       [   1,    0,   15, ..., 1703,  774,    5],
       [   0,    0,   10, ..., 3102,  186,    5]])
mydata_array.shape
(11598, 290)
# 获取最后一维的数据,也就是分类类别 1-5
mydata_array[0][-1]
1
# 删除最后一列
del_arr = np.delete(mydata_array, -1, axis=1)

del_arr
array([[   1,    0,    3, ...,    0,   37,   10],
       [   3,    0,    6, ...,    2, 2838,   46],
       [   2,    0,    4, ...,    1,  111,   20],
       ...,
       [   0,    0,    0, ...,    5,  241,   67],
       [   1,    0,   15, ...,    3, 1703,  774],
       [   0,    0,   10, ...,   13, 3102,  186]])
# 将其转换为11598条17*17的数据
del_arr = del_arr.reshape(-1,17,17)

del_arr.shape
(11598, 17, 17)
# 单纯为了看一下数据样式,可以从左侧的1.txt查看
np.savetxt('1.txt',del_arr[0])
import os
def init_mkdir():
    for i in range(1,6):
        data_path = os.path.join('mal_data',str(i)) # 文件夹路径'data\1'
        if not os.path.exists(data_path): # 判断文件夹是否存在
            os.makedirs(data_path) # 不存在则新建文件夹

2.3 生成灰度图

这里使用了阈值去讲大值设置为阈值的操作。

同时进行了数据的归一化

import cv2
import numpy as np
import matplotlib.pylab as pylab
from sklearn.utils import shuffle

# 忽略(垃圾)警告信息
import warnings
warnings.filterwarnings("ignore")
# 在 notebook 画图展示
%matplotlib inline

# 大于10000的数据会被变成10000
img_yuzhi = 10000

# 生成灰度图图像
def mk_img(): 
    # 初始化文件夹
    init_mkdir()
    print("灰度图生成ing...")
    with_label_txt = []
    for i in range(11598):
        # 使用归一化
        x=  np.clip(del_arr[i],0,img_yuzhi)
        x = (x-np.min(x))/(np.max(x)-np.min(x)) # 缩放到0-1
        img = x * 255 # 放大到0-255
        label = mydata_array[i][-1]
        img_path = "mal_data/" + str(label) + "/" +str(i+1) + ".jpg"
        # 生成带有标签的txt 例: data/1/1.jpg 1
        with_label_txt.append(img_path+" " +str(label-1) + "\n")
        cv2.imwrite(img_path,img)
    
    shuf_with_label_txt = shuffle(with_label_txt)
    all_str = ''.join(shuf_with_label_txt)
    f = open('shuf_with_label_txt.txt','w',encoding='utf-8')
    f.write(all_str)
    
    print("标签文件TXT已生成,灰度图生成完成!")

    return shuf_with_label_txt,len(shuf_with_label_txt)

shuf_txt, length = mk_img()

print(length)
灰度图生成ing...
标签文件TXT已生成,灰度图生成完成!
11598

在这里插入图片描述

到这里位置,其实大家就可以采用任何的图像分类的方式去实现软件的分类了。

灰度图可视化:

在这里插入图片描述


# 按照比例划分数据集 总数据有11598张图片,我这里采用8:2来划分数据
train_size = int(length * 0.8)
train_list = shuf_txt[:train_size]
val_list = shuf_txt[train_size:]

print(len(train_list))
print(len(val_list))
9278
2320
# 运行cell,生成训练集txt 
train_txt = ''.join(train_list)
f_train = open('train_list.txt','w',encoding='utf-8')
f_train.write(train_txt)
f_train.close()
print("train_list.txt 生成成功!")

# 运行cell,生成验证集txt
val_txt = ''.join(val_list)
f_val = open('val_list.txt','w',encoding='utf-8')
f_val.write(val_txt)
f_val.close()
print("val_list.txt 生成成功!")
train_list.txt 生成成功!
val_list.txt 生成成功!
# ['maldata/4/7659.jpg 4\n',...]
train_img = []
train_label = []
for img_label in train_list:
    img,label = img_label.split("\n")[0].split(" ")
    train_img.append(img)
    train_label.append(label)

val_img = []
val_label = []
for img_label in val_list:
    img,label = img_label.split("\n")[0].split(" ")
    val_img.append(img)
    val_label.append(label)
print("-----------------")
print(train_list[0])
print(train_img[0])
print(train_label[0])
print("-----------------")
print(val_list[0])
print(val_img[0])
print(val_label[0])
-----------------
mal_data/5/10213.jpg 4

mal_data/5/10213.jpg
4
-----------------
mal_data/3/3684.jpg 2

mal_data/3/3684.jpg
2

3 dataset类编写

from paddle.io import Dataset
from PIL import Image

class MyDataset(Dataset):
    def __init__(self, mode = 'train'):
        # 训练样本数量
        self.training_data, self.training_label, self.test_data, self.test_label = train_img, train_label, val_img, val_label
        if mode  == 'train':
            self.num_samples = len(train_img)
        else:
            self.num_samples = len(val_img)
        self.mode = mode

    def __getitem__(self, idx):
        if self.mode == 'train':
            image = self.training_data[idx]
            label = self.training_label[idx]
        else:
            image = self.test_data[idx]
            label = self.test_label[idx]
        
        im = Image.open(image).convert('L')
        im = im.resize((17, 17), Image.ANTIALIAS)
        img = np.array(im).astype('float32')
        
        return img, np.array(label, dtype='int64')

    def __len__(self):
        # 返回样本总数量
        return self.num_samples
# 训练的数据提供器
train_dataset = MyDataset(mode='train')
# 测试的数据提供器
eval_dataset = MyDataset(mode='val')

# 查看训练和测试数据的大小
print('train大小:', train_dataset.__len__())
print('eval大小:', eval_dataset.__len__())

# 查看图片数据、大小及标签
for data, label in train_dataset:
    print(data)
    print(np.array(data).shape)
    print(label)
    break
train大小: 9278
eval大小: 2320
[[  0.   2.   1.   1.  14.   4.   0.   4.   3.   4.   2.   0.   0.   6.
    0.  13.   5.]
 [  3.   0.   4.   0.   0.   4.   2.   4.   1.   0.   0.   4.   4.   1.
   15.   8.   3.]
 [ 19.   1.   0.   0.   0.   0.  47.   0.   0.   4.   4.   0.   0.   0.
    2.   2. 125.]
 [ 12.  31.   1.   5.   0.   1.   0.   0.   2.   1.   0.   0.   5.   3.
    3.   3.   0.]
 [  4.   6.   1.   0.   0.   4.   0.  30.   3.   0.   3. 255.   1.   1.
    0.   2.   0.]
 [  1.   0.   0.   2.   1.   2.   0.   3.   0.   2.   0.   0.   0.   0.
    1.   0.   0.]
 [  0.   0.   3.   0.   0.   4.   0.   0.   0.   0.   1.   0.   5.   0.
    2.   0.   0.]
 [  2.   1.   0.   0.   3.   0.   0.   1.   0.   1.   0.   5.   0.   0.
    0.   2.   0.]
 [  0.   1.   0.   0.   0.   2.   1.   0.   0.   2.   1.   0.   0.   1.
    1.   0.   0.]
 [  3.   0.   2.   4.   0.   0.   0.   0.   0.  20.   0.  26.   3.   2.
    6.  30.  22.]
 [ 25.   0.   2.   0.   0.   0.   1.   0.  74.   0.   2.   0.   0.   0.
    0.   0.   0.]
 [  0.   4.   0.   5.   5.  74.   3.   0.   0.  69. 230.   0.   0.  37.
    5.   1.   0.]
 [  0.   3.  28.   4.   1.   3.   0.   2.  84.  14.   1.   3.   1.   0.
    1.  44.   1.]
 [  0.   5.   0.   0.   1.   0.   3.   0.   5.   0.   2.   0.   0.   4.
    0.   0.   0.]
 [  3.   0.   1.   0.   0.   4.   0.   0.   0.   0.   0.   1.   2.   0.
    2.   0.   1.]
 [  0.   3.   0.   1.  15.   0.   1.   1.   7.   0.   2.   0.  19.  31.
    0.   6.   0.]
 [  0.  26.   0.   0.   0.   0.   1.   1.   2.   0.   1.   0.   0.   0.
    0.  34.  63.]]
(17, 17)
4

4 网络搭建

4.1 模仿LENet搭建网络

这里就简答的搭建一个类似与LENet的网络。

import paddle
import paddle.nn.functional as F

class LeNet(paddle.nn.Layer):
    def __init__(self):
        super(LeNet, self).__init__()
        self.conv1 = paddle.nn.Conv2D(in_channels=1, out_channels=6, kernel_size=5, stride=1, padding=1)
        self.max_pool1 = paddle.nn.MaxPool2D(kernel_size=2,  stride=2)
        self.conv2 = paddle.nn.Conv2D(in_channels=6, out_channels=32, kernel_size=3, stride=1)
        self.max_pool2 = paddle.nn.MaxPool2D(kernel_size=2, stride=2)
        self.linear1 = paddle.nn.Linear(in_features=32*2*2, out_features=64)
        self.linear2 = paddle.nn.Linear(in_features=64, out_features=17)
        self.linear3 = paddle.nn.Linear(in_features=17, out_features=5)

    def forward(self, x):
        # 将输入数据的样子该变成[1,3,100,100]
        x = paddle.reshape(x,shape=[-1,1,17,17])  # 转换维读
        x = self.conv1(x)
        x = F.relu(x)
        x = self.max_pool1(x)
        x = self.conv2(x)
        x = F.relu(x)
        x = self.max_pool2(x)
        x = paddle.flatten(x, start_axis=1,stop_axis=-1)
        x = self.linear1(x)
        x = F.relu(x)
        x = self.linear2(x)
        x = F.relu(x)
        x = self.linear3(x)
        return x
model = LeNet()  # 模型实例化
paddle.summary(model, (1,1,17,17))  # 模型结构查看
---------------------------------------------------------------------------
 Layer (type)       Input Shape          Output Shape         Param #    
===========================================================================
   Conv2D-23      [[1, 1, 17, 17]]      [1, 6, 15, 15]          156      
 MaxPool2D-23     [[1, 6, 15, 15]]       [1, 6, 7, 7]            0       
   Conv2D-24       [[1, 6, 7, 7]]       [1, 32, 5, 5]          1,760     
 MaxPool2D-24     [[1, 32, 5, 5]]       [1, 32, 2, 2]            0       
   Linear-34         [[1, 128]]            [1, 64]             8,256     
   Linear-35         [[1, 64]]             [1, 17]             1,105     
   Linear-36         [[1, 17]]              [1, 5]              90       
===========================================================================
Total params: 11,367
Trainable params: 11,367
Non-trainable params: 0
---------------------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.02
Params size (MB): 0.04
Estimated Total Size (MB): 0.06
---------------------------------------------------------------------------






{'total_params': 11367, 'trainable_params': 11367}

4.2 模型训练

EPOCH_NUM = 100

model = paddle.Model(model)  # 模型封装

scheduler = paddle.optimizer.lr.CosineAnnealingDecay(learning_rate=0.00125, T_max=int(EPOCH_NUM//2), verbose=True)

# 配置优化器、损失函数、评估指标
model.prepare(paddle.optimizer.Adam(learning_rate=scheduler, parameters=model.parameters()), 
              paddle.nn.CrossEntropyLoss(), 
              paddle.metric.Accuracy())

# 训练可视化VisualDL工具的回调函数
visualdl = paddle.callbacks.VisualDL(log_dir='visualdl_log')   

# 启动模型全流程训练
model.fit(train_dataset,    # 训练数据集
          eval_dataset,     # 评估数据集
          epochs=EPOCH_NUM, # 训练的总轮次
          batch_size=32,    # 训练使用的批大小
          verbose=1,        # 日志展示形式
          callbacks=[visualdl])  # 设置可视化

4.3 训练日志可视化

经典版环境不支持看可视化,这里在本地进行可视化的查看。

在本地打开anaconda的终端环境:

(paddle-cpu) C:\Users\Administrator>pip install visualdl
(paddle-cpu) C:\Users\Administrator>d:

执行可视化命令会出现:

(paddle-cpu) D:\>visualdl --logdir d:Desktop --port 8080
VisualDL 2.2.3
Running VisualDL at http://localhost:8080/ (Press CTRL+C to quit)
Serving VisualDL on localhost; to expose to the network, use a proxy or pass --host 0.0.0.0

在本地打开上面的网址即可:

在这里插入图片描述

5 模型保存及简单验证

5.1 模型保存

model.save('output-model/model')  # 保存模型

5.2 模型验证

其实上面的可视化图已经是对结果的一个很好的验证了,这里我们通过实际的数据的展示,来看一下效果。

label_txt = ['Adware','Banking','SMS malware','Riskware','Benign']

model = LeNet()

para_dict = paddle.load('output-model/model.pdparams')
model.load_dict(para_dict)

model.eval()

# 找一张验证集的数据进行展示 mal_data/3/3684.jpg 2
image_path = 'mal_data/3/3684.jpg'
im = Image.open(image_path).convert('L')
im = im.resize((17, 17), Image.ANTIALIAS)
im = np.array(im).reshape(1, 1, 17, 17).astype(np.float32)
x = paddle.to_tensor(im)
predicts = model(x)
p = predicts.numpy().argmax() # 最大索引
print("实际标签为:",label_txt[int(image_path.split('/')[1])-1])
print("预测标签为:",label_txt[p])
S)
im = np.array(im).reshape(1, 1, 17, 17).astype(np.float32)
x = paddle.to_tensor(im)
predicts = model(x)
p = predicts.numpy().argmax() # 最大索引
print("实际标签为:",label_txt[int(image_path.split('/')[1])-1])
print("预测标签为:",label_txt[p])
print(F.softmax(predicts)) # 0 对应的就是标签1的 预测正确.
实际标签为: SMS malware
预测标签为: SMS malware
Tensor(shape=[1, 5], dtype=float32, place=Place(gpu:0), stop_gradient=False,
       [[0.00118610, 0.00161026, 0.99717569, 0.00001615, 0.00001183]])

项目总结

本项目用图像的思想去解决恶意软件的分类问题,且达到了一个还不错的效果。

当然,后续还是要大量优化的,因为分类问题需求的精度还是很高的。

个人总结

全网同名:

iterhui

我在AI Studio上获得至尊等级,点亮10个徽章,来互关呀~

https://aistudio.baidu.com/aistudio/personalcenter/thirdview/643467

文章仅为搬运,原作地址https://aistudio.baidu.com/aistudio/projectdetail/4325980

Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐