PGD梯度攻击生成验证码

项目概述

  • 本项目针对开源验证码识别项目,尝试了以PGD梯度攻击的方式生成攻击样本,正常样本准确率90%,叠加攻击噪声后准确率为0.1%。

  • 攻击样本同时具备一定的迁移攻击能力,在另两个模型的测试中,准确率也降低到了10%以下。

  • 以batchsize的形式批量生成攻击样本,节省时间。

如下图所示,在原本的图片上,叠加让模型loss变大的噪声,从而让模型误判,达到攻击的效果,让验证码具备反爬能力:

‘AADWND’添加攻击噪声后,模型识别为’iaa0ss0’。
image.png
如何批量生成类似的攻击样本呢?

获取模型

因为是基于模型梯度的攻击,首先需要获取到模型和模型的权重,而对于开源验证码识别模型,能够很轻易的获取到模型,以下举例是以ddddocr开源项目进行白盒攻击生成样本。

  1. 找到要攻击的模型文件,顺着源码就能找到,以.onnx结尾的模型文件common.onnx
  2. 将onnx转为自己熟悉的训练框架,方便攻击训练。本人用paddle较多,这里就以paddle为例,使用x2paddle工具将onnx转paddle模型
x2paddle --framework=onnx --model=common.onnx --save_dir=pd_model
  1. 模型修改,因为要利用loss来反向计算梯度,所以要去除后处理部分。对于不定长验证码识别,一般是CRNN和CTCLoss,需要模型输出RNN(LSTM)之后的数据,以及计算CTCLoss的标签等数据。(如果想攻击的模型是普通分类模型,那么loss就选交叉熵损失)

梯度攻击

梯度攻击的核心就是在原图上叠加噪声,计算loss,以loss最大化的方向更新噪声,不停循环迭代,直到模型识别出错。
核心代码思想如下,有详细的注释,(这里是按照原版PGD的思想写的关键代码):

class Attack:
    def __init__(self, iter_num, eps_iter, eps):
        self.iter_num = iter_num  # 循环次数
        self.eps_iter = eps_iter  # 每次循环梯度方向的步进长度,类似learning rate
        self.eps = eps  # 限制梯度攻击生成的噪声大小

    def init_noise(self, batch_img):
        """传入归一化后的图片ndarray数据,随机初始化一个和传入数据相同shape的噪声"""
        # 这里传入的img数据大小范围是[-1,1]
        init_data = paddle.uniform(shape=batch_img.shape, dtype='float32', min=-1., max=1.)  # 随机噪声
        init_data *= self.eps  # 限制噪声大小
        init_data = paddle.clip((init_data + batch_img), -1., 1.) - batch_img  # 与原图相加后不能超出像素值范围
        delta = paddle.create_parameter(init_data.shape, dtype='float32',
                                        default_initializer=paddle.nn.initializer.Assign(init_data))
        return delta

    def attack(self, model, batch_img, labels, loss_func, label_lengths):
        # 初始化噪声数据
        self.delta = self.init_noise(batch_img)
        for _ in range(self.iter_num):  # 循环次数
            # 前向计算
            outputs = model(batch_img + self.delta)
            # 这里与CRNN配合使用,loss是CTCloss
            input_length = outputs.shape[0]
            input_lengths = paddle.full([TRAINBATCHSIZE], input_length, dtype='int64')
            loss = loss_func(outputs, labels, input_lengths, label_lengths)
            # print(loss)  # 在梯度攻击的过程中,能看到loss是在不断变大
            loss.backward(retain_graph=False)  # 反向传播计算梯度
            # 获取噪声的梯度信息,按照pes_iter的步进长度进行累加
            delta_new = self.delta + self.delta.grad.sign() * self.eps_iter
            self.delta.clear_grad()
            # 限制噪声的大小
            delta_new = paddle.clip(delta_new, -eps, eps)
            # 限制添加噪声后数据不能超过像素值范围
            delta_new = paddle.clip(batch_img + delta_new, -1.0, 1.0) - batch_img
            delta_new.stop_gradient = True  # backward之后需阻断梯度传递
            paddle.assign(delta_new, self.delta)
        return self.delta
  • 本人在实际生成攻击样本的时候,没有统一噪声像素每循环的更新大小,而是采用了深度学习框架的优化器来进行更新噪声,比如Adam,能使loss更大。
  • 对于验证码验证来说,一般没有区分大小写,所以模型识别出的字母不管是大写还是小写,只要能对上都算识别成功。在实际攻击测试中发现,有的样本叠加噪声后,识别出结果由大写变为小写,此时依然算正确,但标签已经变化,所以为了兼顾大小写的攻击效果,实际应用中使用了两套标签来计算loss,综合更新噪声。

批量生成

整个项目流程与代码已在aistudio公开(主要是能白嫖算力),可移步aistudio查看完整代码和实践项目。
https://aistudio.baidu.com/aistudio/projectdetail/5843946

效果测试

用生成的200张普通样本进行测试,ddddocr在beta=True模式下准确率为92.5%,叠加攻击噪声后,准确率降低至0.5%,而在beta=Fasle模式下(模型不同),准确率由67%降低为8.5%。
同时,还测试了另一个开源ocr模型easyocr的准确率,由68%降低为5%

  • 原本是针对ddddocr在beta=True下的模型进行的攻击,可以看到攻击效果很显著,但同时在其他两个模型上也展现了较强的迁移攻击能力,这也是在不能获取到模型的情况下可以使用的黑盒攻击方法,迁移攻击。
  • 本项目中限定噪声的范围在150/255以下,还可以在此基础上增加或减小,越大模型就越难识别,但也可能对人造成干扰。我测试了增加到200/255的限制,此时ddddocr的准确率0.0%,beta=False模式下为3%,而easyocr也为0.0%,下图是两种噪声大小的对比:证码均为"UKCVPOE",虽然机器识别的准确率进一步的降低,但更大噪声的验证码也会容易让人看错,不同应用时注意调试。

image.png

猜你喜欢

转载自blog.csdn.net/kalahali/article/details/129963402
今日推荐