文章目录
RoPGen_towards robust code authorship attribution via automatic coding style transformation
原文链接:https://arxiv.org/abs/2202.06043
介绍
源代码作者身份识别攻击方式有1. 利用对抗样本,2. 利用编码风格模仿/隐藏
目前遇到的挑战有
- 需要探索出模仿目标作者风格的新攻击
- 设计争对上边新攻击的有效防御,并且能够适应一系列神经网络结构
提出了RoPGen框架,利用了数据增强和梯度增强:
- 数据增强:增加训练样本的数量和多样性,有两种方法
- 模仿其他作者的代码风格
- 不改变作者身份的条件下,对编码风格进行小幅度的干扰
- 梯度增强:通过对CNN的梯度产生扰动,学习具有多样化表示的鲁棒DL模型
编码风格的概念
编码风格属性
- 程序布局:代码缩进、空行、括号和注释
- 词法属性:token(标识符、关键字、操作符和常量)、变量名平均长度、变量的数量和for循环语句的数量
- 句法属性:程序的AST语法树,包括句法结构和树形结构
- 语义属性:程序的控制流以及数据流,入for、while、if, else if等
利用编码风格属性作为作者身份归属鲁棒性的起点
- token属性:标志符命名方法、临时变量名的使用、非临时标识符名的使用、全局声明的使用、数组/指针元素的索引方式
- 语句级属性:定义局部变量的位置、初始化局部变量的位置、变量赋值、加减操作、用户定义的数据类型、宏、包含的头文件或导入的类、返回语句的使用、命名空间的使用、流重定向、库函数调用、内存分配malloc还是[n]
- 基本块级属性:循环结构、条件结构、复合if语句
- 函数级属性:嵌套符合语句最大层数,或函数的代码行数
属性的域分为穷尽的(比如循环结构的种类)以及非穷尽的(比如变量名的种类)
两个新的攻击
A = { A 1 , ⋯ , A δ } \mathcal A=\{A_1,\cdots,A_\delta\} A={A1,⋯,Aδ}是一个作者的有穷集合,M是一个代码p到作者A的映射函数,由于攻击是黑盒攻击,对攻击者:
- 任意查询程序p和对应的作者M§
- M对攻击者是未知的
攻击者的目标是在保持语义的前提下将p做一些修改,其中 A s = M ( p ) A_s=M(p) As=M(p)变成 p ′ ≠ p p'\ne p p′=p,且
- 目标攻击:攻击目标是 A t , t ≠ s A_t,t\ne s At,t=s,使得 M ( p ′ ) = A t M(p')=A_t M(p′)=At,即尝试模仿Alice写的代码在语义不变的条件下修改,使得模型能够将其归类于Bob写的
- 无目标攻击: M ( p ′ ) = A u , A u ∈ A − { A s } M(p')=A_u,\ A_u\in\mathcal A-\{A_s\} M(p′)=Au, Au∈A−{As}
自动编码风格模仿攻击
攻击者为 A s A_s As,输入作者集合 A \mathcal A A,作者 A s A_s As写的一个程序p,和目标作者 A t A_t At的程序集合R。
步骤一:从R中的所有代码提取出代码风格属性。
步骤二:将R中所有的代码风格合成一个总的代码风格属性,从而获得目标作者 A t A_t At的编码风格,其中数值类型取平均值,非数值类型按照频率降序排序
步骤三:提取出p的代码风格属性,用于代码的模仿
步骤四:进行代码转换以模仿目标作者 A t A_t At
自动编码风格隐藏攻击
步骤一:从p中提取代码风格属性
步骤二:获得每一个作者 A d ∈ A − { A s } A_d\in \mathcal A-\{A_s\} Ad∈A−{As}的编码风格IA,把 A d A_d Ad当成目标作者
步骤三:为每一个 A d A_d Ad识别p中代码风格属性。
步骤四:选择作者 A u A_u Au进行代码转换,选择一个混淆概率最高的作者作为目标 A u A_u Au
RoPGen框架
在作者归属的DL框架中,输入的训练样本是一组带有标签的程序集合 η \eta η,用 P = { p k , q k } k = 1 η P=\{p_k,q_k\}_{k=1}^\eta P={pk,qk}k=1η表示,其中 p k p_k pk是程序, q k q_k qk是作者标签,输出是一个DL模型M,给定一个有限作者集合 A = { A 1 , ⋯ , A δ } \mathcal A=\{A_1,\cdots,A_\delta\} A={A1,⋯,Aδ}和作者 A s A_s As写的程序 p k p_k pk, P r ( M , p k , A s ) Pr(M,p_k,A_s) Pr(M,pk,As)表示M预测 p k p_k pk是作者 A s A_s As写的代码的概率,攻击者将修改 p k p_k pk成 p k ′ p'_k pk′,当 P r ( M , p k ′ , A t ) = m a x 1 ≤ z ≤ δ P r ( M , p k ′ , A z ) Pr(M,p'_k,A_t)=max_{1\le z\le \delta}Pr(M,p'_k,A_z) Pr(M,pk′,At)=max1≤z≤δPr(M,pk′,Az)时攻击成功,一个隐藏式的攻击当 P r ( M , p k ′ , A s ) ≠ m a x 1 ≤ z ≤ δ P r ( M , p k ′ , A z ) Pr(M,p'_k,A_s) \ne max_{1\le z\le \delta}Pr(M,p'_k,A_z) Pr(M,pk′,As)=max1≤z≤δPr(M,pk′,Az)时成功。
下图强调了一个RoPGen框架的训练阶段,将训练原有模型 M M M成一个加强版 M + M^+ M+,输入包括:1. 训练样本 η \eta η和其对应的标签,2. 作者的一个子集 T ⊆ A T \subseteq \mathcal A T⊆A,3. 模型M的对抗样本集合E。
-
步骤一:通过模仿编码风格扩展训练集
对每一个程序 ( p k , q k ) (p_k,q_k) (pk,qk),我们将 p k p_k pk按照其他 δ − 1 \delta-1 δ−1个作者 A − { q k } \mathcal A-\{q_k\} A−{qk}的代码风格进行修改,不修改 p k p_k pk的标签,将这些集合与原来的集合P并起来形成新的扩展训练集U,作为第三步的输入。
-
步骤二:通过代码风格扰乱生成被操纵的程序
首先,我们生成M的对抗样本E,对于每一个E中的对抗样本 e r e_r er,我们得到一个转化成 e r e_r er的序列 T r T_r Tr,对于P中的每一个程序 p k p_k pk,我们利用序列 T r T_r Tr生成一个被操作的程序 p k , r p_{k,r} pk,r,这回生成 ∣ U ′ ∣ = ∣ E ∣ × ∣ P ∣ |U'|=|E|\times|P| ∣U′∣=∣E∣×∣P∣的被操作程序。
其次,如果不容易生成对抗样本,我们可以通过扰动程序,即改变程序 p k p_k pk中的z个属性值来生成新程序 p k 1 , ⋯ , p k z p_k^1,\cdots,p_k^z pk1,⋯,pkz,这会生成 ∣ U ′ ∣ = z × ∣ P ∣ |U'|=z\times |P| ∣U′∣=z×∣P∣的程序。
-
步骤三:训练鲁棒DL模型M+
这一步通过在每次迭代中采样多个子网络进行梯度增强,并对模型的梯度产生扰动,来训练模型的鲁棒性,其中RoPGen用扩展训练集U作为输入,子网络利用被操纵的程序U’作为输入。 N \mathcal N N表示深度学习网络, θ \theta θ是其参数,每一次训练迭代含有以下五个步骤:
-
前向传播和损失函数计算
对于每一个程序和其标签 ( u , v ) (u,v) (u,v),一次前向传播的预测值为 N ( θ , u ) \mathcal N(\theta,u) N(θ,u),损失函数为 L s t d = l ( N ( θ , u ) , v ) L_{std}=l(\mathcal N(\theta,u),v) Lstd=l(N(θ,u),v),其中l表示交叉熵损失函数。
-
采样n个子网络
子网络 N j \mathcal N_j Nj将用于被操作的程序学习不同的表示,增强模型的鲁棒性
-
计算子网络的前向传播和损失函数
输入是产生小扰动的 U ′ U' U′, θ ω j \theta_{\omega j} θωj是子网络 N j \mathcal N_j Nj的参数,对U‘中的被一个样本和其标签 ( u ′ , v ′ ) (u',v') (u′,v′),计算前向传播的预测值 N ( θ ω j , u ′ ) \mathcal N(\theta_{\omega j},u') N(θωj,u′),n个子网络的损失函数为
L s u b n e t = ∑ j = 1 n l ( N ( θ ω j , u ′ ) , v ′ ) L_{subnet}=\sum_{j=1}^nl(\mathcal N(\theta_{\omega j},u'),v') Lsubnet=j=1∑nl(N(θωj,u′),v′) -
计算整体损失: L R o P G e n = L s t d + L s u b n e t L_{RoPGen}=L_{std}+L_{subnet} LRoPGen=Lstd+Lsubnet
-
更新模型权重
对于整个模型的梯度为 g s t d = ∂ l ( N ( θ , u ) , v ) ∂ θ g_{std}=\dfrac {\partial l(\mathcal N(\theta,u),v)}{\partial\theta} gstd=∂θ∂l(N(θ,u),v)
对于n个子网的梯度为 g s u b n e t = ∑ j = 1 n ∂ l ( N ( θ ω j , u ′ ) , v ′ ) ∂ θ ω j g_{subnet}=\sum\limits_{j=1}^n\dfrac {\partial l(\mathcal N(\theta_{\omega j},u'),v')}{\partial\theta_{\omega j}} gsubnet=j=1∑n∂θωj∂l(N(θωj,u′),v′)
RoPGen的梯度为 g R o P G e n = g s t d + g s u b n e t g_{RoPGen}=g_{std}+g_{subnet} gRoPGen=gstd+gsubnet
-
与NLP不同之处,编码识别和生成并不像seq2seq模型,原因是NLP的数据修改了单词可能会导致语义的变化,而对于代码,修改程序常量名,或者利用等价的语句换用其他表达,所表示的语义也不会发生变化。
其次,编码识别是需要加上一些限定条件的,如果不加以限定,那么模型鲁棒性可能会很差,在语义语法都不变的条件下,可能稍加修改就可能让模型训练结果是错误的。
NLP以单词或者字母为最小单位,而代码不仅有单词层面,还有语句、语法、代码块、函数等层面。