吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5759|回复: 37
收起左侧

[原创] 基于LSTM的二进制代码相似性检测

  [复制链接]
白云点缀的藍 发表于 2022-3-11 14:35
本帖最后由 白云点缀的藍 于 2022-3-11 14:40 编辑

前言

近年来自然语言处理的快速发展,推出了一系列相关的算法和模型。比如用于处理序列化数据的RNN循环神经网络、LSTM长短期记忆网络、GRU门控循环单元网络等,以及用于计算词嵌入的word2vec、ELMo和BERT预训练模型等。近几年也出现了一些论文研究这些模型和算法在二进制代码相似性分析上的应用,可以实现跨平台的二进制代码相似性检测。本文根据上述模型和算法实现了一个基于word2vec和LSTM的简单模型用于判断两个函数或者两个指令序列是否相似。

总体框架

图1.PNG

函数嵌入

图2.PNG

LSTM是RNN的一个变体,由于RNN容易梯度消失无法处理长期依赖的问题。LSTM在RNN的基础上增加了门结构,分别是输入门、输出门和遗忘门,在一定程度上可以解决梯度消失的问题,学习长期依赖信息。LSTM的结构如下:
图3.PNG

运算规则如下:
图4.png
W和b都是LSTM待学习的参数,具体参数细节可以参考pytorch的官方文档。

指令嵌入

指令嵌入的目的是也为了获得指令的向量化表示,方便LSTM等其它模型进行计算。这里使用word2vec的skip-gram模型实现。word2vec是谷歌公司开源的一个用于计算词嵌入的工具,包含cbow和skip-gram两个模型。指令嵌入具体实现细节如下:

(1)操作码、寄存器、加减乘符号以及中括号都看成一个词。比如mov dowrd ptr [0x123456+eax*4], ebx这条指令可以得到mov,dowrd,ptr,[,0x123456,+,eax,*,4,],ebx。然后这条指令看成一个句子送入word2vec进行训练,进而得到每一个词的向量化表示。

(2)为了减小词库的大小。操作数中超过0x5000的数值用mem,disp,imm代替

[0xXXXXXXXX] -> [mem]
[0xXXXXXXXX + index*scale + base] -> [disp + index*scale + base]
0xXXXXXXXX -> imm

(3)指令向量由一个操作码对应的向量和两个操作数对应的向量三部分组成,操作数不够的指令添加0向量补齐。对于超过两个操作数的指令,则最后两个操作数的向量求和取平均。操作数里面有多个词的情况下,各个词向量求和取平均表示当前操作数的向量。

代码实现

模型的代码实现用的是深度学习框架pytorch,word2vec的实现用的gensim库。word2vec的调用参数在insn2vec.py实现如下:

model = Word2Vec(tokensList, vector_size=wordDim, negative=15, window=5, min_count=1, workers=1, epochs=10, sg=1)    
model.save('insn2vec.model')

tokensList的元素是一个列表,保存的是一条指令分词(tokenization)后的各个词序列。word2vec训练完成后保存到insn2vec.model文件,方便后续进行进一步的微调。

指令嵌入的实现在lstm.py文件中,实现如下:

class instruction2vec(nn.Module):
    def __init__(self, word2vec_model_path:str):
        super(instruction2vec, self).__init__()
        word2vec = Word2Vec.load(word2vec_model_path)
        self.embedding = nn.Embedding.from_pretrained(torch.from_numpy(word2vec.wv.vectors))
        self.token_size = word2vec.wv.vector_size#维度大小
        self.key_to_index = word2vec.wv.key_to_index.copy()    #dict
        self.index_to_key = word2vec.wv.index_to_key.copy()    #list
        del word2vec

    def keylist_to_tensor(self, keyList:list):
        indexList = [self.key_to_index[token] for token in keyList]
        return self.embedding(torch.LongTensor(indexList))

    def InsnStr2Tensor(self, insnStr:str) -> torch.tensor:
        insnStr = RefineAsmCode(insnStr)
        tokenList = re.findall('\w+|[\+\-\*\:\[\]\,]', insnStr)
        opcode_tensor =  self.keylist_to_tensor(tokenList[0:1])[0]
        op_zero_tensor = torch.zeros(self.token_size)
        insn_tensor = None
        if(1 == len(tokenList)):
            #没有操作数
            insn_tensor = torch.cat((opcode_tensor, op_zero_tensor, op_zero_tensor), dim=0)
        else:
            op_token_list = tokenList[1:]
            if(op_token_list.count(',') == 0):
                #一个操作数
                op1_tensor = self.keylist_to_tensor(op_token_list)
                insn_tensor = torch.cat((opcode_tensor, op1_tensor.mean(dim=0), op_zero_tensor), dim=0)#tensor.mean求均值后变成一维

            elif(op_token_list.count(',') == 1):
                #两个操作数
                dot_index = op_token_list.index(',')
                op1_tensor = self.keylist_to_tensor(op_token_list[0:dot_index])
                op2_tensor = self.keylist_to_tensor(op_token_list[dot_index+1:])
                insn_tensor = torch.cat((opcode_tensor, op1_tensor.mean(dim=0), op2_tensor.mean(dim=0)), dim=0)

            elif(op_token_list.count(',') == 2):
                #三个操作数
                dot1_index = op_token_list.index(',')
                dot2_index = op_token_list.index(',', dot1_index+1)
                op1_tensor = self.keylist_to_tensor(op_token_list[0:dot1_index])
                op2_tensor = self.keylist_to_tensor(op_token_list[dot1_index+1:dot2_index])
                op3_tensor = self.keylist_to_tensor(op_token_list[dot2_index+1:])

                op2_tensor = (op2_tensor.mean(dim=0) + op3_tensor.mean(dim=0)) / 2
                insn_tensor = torch.cat((opcode_tensor, op1_tensor.mean(dim=0), op2_tensor), dim=0)

        if(None == insn_tensor):
            print("error: None == insn_tensor")
            raise

        insn_size = insn_tensor.shape[0]
        if(self.token_size * 3 != insn_size):
            print("error: (token_size)%d != %d(insn_size)" % (self.token_size, insn_size))
            raise

        return insn_tensor  #[len(tokenList), token_size]

    def forward(self, insnStrList:list) -> torch.tensor:
        insnTensorList = [self.InsnStr2Tensor(insnStr) for insnStr in insnStrList]
        return torch.stack(insnTensorList) #[insn_count, token_size]

instruction2vec类的作用就是指令嵌入,token_size是词的维度大小,指令维度的大小为token_size*3。初始过程中主要是加载word2vec训练好的词向量word2vec.wv.vectors,方便InsnStr2Tensor把字符串形式的指令转换到向量。
函数嵌入的代码实现如下:

class SiameseNet(nn.Module):
    def __init__(self, hidden_size=60, n_layers=2, bidirectional = False):
        super(SiameseNet, self).__init__()
        self.insn_embedding = instruction2vec("./insn2vec.model")
        input_size = self.insn_embedding.token_size * 3
        #input_size为指令的维度, hidden_size为整个指令序列的维度
        self.lstm = nn.LSTM(input_size, hidden_size, n_layers, batch_first=True, bidirectional = bidirectional)

        self.D = int(bidirectional)+1

        self.w_omega = nn.Parameter(torch.Tensor(hidden_size * self.D, hidden_size * self.D))
        self.b_omega = nn.Parameter(torch.Tensor(hidden_size * self.D))
        self.u_omega = nn.Parameter(torch.Tensor(hidden_size * self.D, 1))

        nn.init.uniform_(self.w_omega, -0.1, 0.1)
        nn.init.uniform_(self.u_omega, -0.1, 0.1)

    def attention_score(self, x):
        #x:[batch_size, seq_len, hidden_size*D]
        u = torch.tanh(torch.matmul(x, self.w_omega))
        #u:[batch_size, seq_len, hidden_size*D]
        att = torch.matmul(u, self.u_omega)
        #att:[batch_size, seq_len, 1]
        att_score = F.softmax(att, dim=1)#得到每一个step的hidden权重
        #att_score:[batch_size, seq_len, 1]
        scored_x = x*att_score  #类似矩阵倍乘
        return torch.sum(scored_x, dim=1)#加权求和

    def forward_once(self, input:list) -> torch.tensor:
        lengths = []#记录每个指令序列的长度
        out = []
        for insnStrList in input:
            insnVecTensor = self.insn_embedding(insnStrList)#把指令转换到向量
            out.append(insnVecTensor)
            lengths.append(len(insnStrList))

        pad_out = pad_sequence(out, batch_first=True)#填充0使所有handler的seq_len相同
        pack_padded_out = pack_padded_sequence(pad_out, lengths, batch_first=True, enforce_sorted=False)
        packed_out,(hn,_) = self.lstm(pack_padded_out)#input shape:[batch_size, seq_len, input_size]
        #hn:[D*num_layers,batch_size,hidden_size]
        #out:[batch_size, seq_len, hidden_size*D],此时out有一些零填充
        out,lengths = pad_packed_sequence(packed_out, batch_first=True)
        out = self.attention_score(out)
        return out

    def forward(self, input1, input2):
        out1 = self.forward_once(input1)#out1:[batch_size,hidden_size]
        out2 = self.forward_once(input2)
        out = F.cosine_similarity(out1, out2, dim=1)
        return out

因为函数嵌入的输入是一对函数,所以该模型也是一个共享参数的孪生神经网络。hidden_size是函数的维度大小,这里设置成60维。attention_score对应的是注意力机制,w_omega是W矩阵,u_omega是U矩阵。pytorch的LSTM输入类型为[batch_size, seq_len, input_size]的张量,相当于是一个batch_sizeseq_leninput_size的矩阵,batch_size对应是函数个数,seq_len对应的是指令的个数。虽然LSTM可以处理任意长度的序列,但是为了加速运算,pytorch的lstm输入需要seq_len相同,所以需要添加0向量对齐。因为添加了0向量,对整个模型可能会有一定的影响。在经过W和H的点乘后,也就是torch.tanh(torch.matmul(x, self.w_omega))运算后需要一个特殊处理,需要把这些添加的0向量弄到负无穷大,这样在注意力机制的softmax运算中会使这部分向量对应的权重趋近于0,也就是注意力不应该放在这些0向量身上。这个处理我没有加,大家感兴趣的话自己改一改。

模型评估

指令的向量维度为30,函数的向量维度为60。数据集使用了6个二进制文件:

ntdll_7600_x64.dll
ntoskrnl_7600_x64.exe
win32kfull_17134_x64.sys
ntdll_7600_x32.dll
ntoskrnl_7600_x32.exe
win32kfull_17134_x32.sys

x86和x64各三个文件,使用angr总共提取出4437个函数,函数名相同的x86和x64函数构成一个相似的正例。x86函数中随机选取一个不同名的x64函数构造一个负例,或者x64函数中随机选取一个不同名的x86函数构造一个负例。数据集样本的正负比例为1:1,总的样本数为4437*2,再按8:1:1划分为训练集、验证集和测试集。其实正负样本里应该还要加一些x86对应x86或者x64对应x64的正负样本,使数据分布的更均匀些。随机梯度下降法的学习率设置为0.09,批度大小8,迭代次数为50。
train_loss.png
val_loss.png
roc.png
result.PNG
前面两张图片分别是训练集和验证集随着迭代次数的损失loss下降情况。可以看到根据训练集的loss还没收敛的情况下,验证集的loss就已经收敛不怎么下降。再训练下去的话,大概率是会过拟合(overfitting),主要原因还是数据太少了。第三张图是ROC曲线,该曲线的AUC(Area Under Curve)为0.91,AUC越接近1越好。根据ROC曲线可以得出当两个函数的余弦相似度的阈值设置0.24时,验证集和测试集的准确度都可以达到84%。

总结

模型准确度达不到90%以上的主要原因还是数据量不够,深度学习的核心在于足够多且有效的数据量。其次word2vec无法处理一词多义的问题,基于word2vec实现的指令嵌入,同样的指令计算出同样的向量化表示,即使有不同的上下文。比如有以下两个指令序列:

mov eax, [0x12345678]                                        add eax, [0x12345678]
shl eax, 2                                               shl eax, 2
ret                                                          inc eax

上述两条shl eax, 2指令有不同的上下文,应需要不同的向量化表示。最后,函数的处理应以基本块为单位,函数中的指令序列受控制流的影响不完全是顺序执行,控制流图的向量化表示可以引入一个图神经网络获取。

参考资料

SAFE: Self-Attentive Function Embeddings for Binary Similarity
A simple function embedding approach for binary similarity detection
Understanding LSTM Networks

BinSimilarity.part01.rar

2 MB, 下载次数: 9, 下载积分: 吾爱币 -1 CB

BinSimilarity.part02.rar

2 MB, 下载次数: 7, 下载积分: 吾爱币 -1 CB

BinSimilarity.part03.rar

2 MB, 下载次数: 8, 下载积分: 吾爱币 -1 CB

BinSimilarity.part04.rar

2 MB, 下载次数: 7, 下载积分: 吾爱币 -1 CB

BinSimilarity.part05.rar

704.4 KB, 下载次数: 7, 下载积分: 吾爱币 -1 CB

免费评分

参与人数 7吾爱币 +8 热心值 +7 收起 理由
happyBread + 1 + 1 我很赞同!
Chrishu + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
victos + 1 + 1 谢谢@Thanks!
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
yippee + 1 + 1 我很赞同!
onething + 1 + 1 热心回复!
笙若 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| 白云点缀的藍 发表于 2022-3-11 19:45
kyrzy0416 发表于 2022-3-11 19:38
这个是发过论文的?

没,参考别人的论文写的,这个准确度和数据量是发不了的
ppday 发表于 2022-3-25 22:27
Edwardaud 发表于 2022-3-11 19:53
kyrzy0416 发表于 2022-3-11 19:38
这个是发过论文的?
csgowdnmd 发表于 2022-3-11 21:58
大佬有CNN-BiLSTM模型的keras代码吗
 楼主| 白云点缀的藍 发表于 2022-3-12 06:37
csgowdnmd 发表于 2022-3-11 21:58
大佬有CNN-BiLSTM模型的keras代码吗

没有,网上搜一下应该有
Ω分子 发表于 2022-3-12 09:17
Edwardaud 发表于 2022-3-11 19:53
是不是老师可以拿来代码查重

二进制查重我觉得有亿点问题?
BBridgeW 发表于 2022-3-12 10:02
谢谢兄弟,最近一直想好好看看LSTM算法
cjc3528 发表于 2022-3-15 16:36
膜拜大佬,菜鸟进来学习下,谢谢分享
yippee 发表于 2022-3-18 19:28
要不要排除系统默认库函数?
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-17 00:56

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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