WMCTF Foolish Black Ai Entrance 解题
九涅·烧包包儿 / / / 阅读量

描述

这是一个很诡异的远程分类器。
首先它会给你一个手写数字图片,随后你在有限的时间内,有若干次机会调用远程模型对图片预测。
当你有足够信心的时候,去获取flag吧。

思路解释

该题主要考察对抗样本中黑盒攻击算法的掌握,通过观察源码,可以发现,该题目是一个黑盒逃避攻击。

本题的相关超参数设置如下:

MAX_REMAIN_REQUESTS = 256
MAX_RECOMPUTE_TIME = 300  # seconds
MAX_EVAL_NUMBER = 30
L2_THRESHOLD = 3.0  #
LINF_THRESHOLD = 0.20  # 0.2 * 255 = ?

主要限制了L2和LINF的大小。并且在给定一张图片后,只有256轮图片预测次数,每次最多预测30张图片。此外,在300秒后,该图片会强制过期,需要开始新的一轮攻击。

因此,本题解题思路如下:

1)访问 /start获取图片img
2)在有限的时间内,对该图片进行迭代至多256轮次,每次迭代,将它扔到 /predict接口来获取实时的神经网络输出层结果,并利用某种黑盒攻击算法生成合适的图片img_adv
3)访问 /get_flag,上传图片img_adv,验证LINF与L2范数,通过验证,拿到FLAG

因此,这个黑盒攻击算法就比较重要了。本题选用的黑盒攻击算法是
HopSkipJumpAttack: A Query-Efficient Decision-Based Attack(https://arxiv.org/abs/1904.02144)

其核心代码开源于 https://github.com/Jianbo-Lab/HSJA/

EXP A

本题目需要 clone下 https://github.com/Jianbo-Lab/HSJA/ 源码后,对其代码进行修改。

由于某种原因,此处不提供经过修改后的源码(但是会提供另一种较好理解的调用框架方法)。这里仅说一下修改思路和函数调用方法

代码的核心文件位于 hsja.py, 其中的model只需要自己定义predict接口即可

		perturbed = hsja(model, # 定义predict接口
                         sample, # 输入图片
                         clip_max = 1,  # 图片最大值
                         clip_min = 0, # 图片最小值
                         constraint = args.constraint, # L2或者LINF
                         num_iterations = args.num_iterations,  # 迭代次数
                         gamma = 1.0, 
                         target_label = target_label, # 设置为None
                         target_image = target_image, # 设置为 None
                         stepsize_search = args.stepsize_search, # 自行调参,需要和
                         max_num_evals = 1e4, # 自行调参
                         init_num_evals = 100) # 自行调参

EXP B

对抗样本有很多有名的框架,比如cleverhans,adverserial robustness box等。这里用的是art来解这道题。

art中有封装HopSkipJump算法。可以调用该框架来达成攻击

由于是黑盒攻击。所以我们需要调用art中的BlackBoxClassifier类,来初始化黑盒攻击分类器

session = requests.Session()
buffer = session.get(URL + "start")
x_train = load_data(buffer.text)

def predict_remote(_x):
    res = session.post(URL + "predict", data={"b64_image": get_data(_x), "fig_number": _x.shape[0]})
    _y = eval(res.text)
    return np.array(_y, dtype=np.float32)

classifier = BlackBoxClassifier(predict_remote, x_train[0].shape, 10, clip_values=(0, 1))

这样 classifier就是我们的初始化好的分类器,之后调用HopSkipJump算法进行攻击。脚本如下

def do_attack(x_input, y_real, classifier, y_target=None):
    attack = HopSkipJump(classifier, targeted=False, max_iter=10, max_eval=30, init_eval=30, norm=np.inf)
    x_input_adv = attack.generate(x_input, y_real)
    predict_adv = np.argmax(classifier.predict(x_input_adv), axis=1)  # raw_model
    delta = x_input_adv - x_input
    _l0, _l1, _l2, _linf = get_norm(delta)
    acc = np.sum(predict_adv == np.argmax(y_real, axis=1)) / len(y_real)
    index = np.where(predict_adv != np.argmax(y_real, axis=1))
    try:
        _f_l0, _f_l1, _f_l2, _f_linf = get_norm(delta[index])
        return delta, (acc, _f_l0, _f_l1, _f_l2, _f_linf)
    except Exception as e:
        print(e)

当我们的_l2,linf满足条件,并且攻击成功的时候,就返回flag。最终完整的exp如下所示

import matplotlib.pyplot as plt
import numpy as np

from art.classifiers import BlackBoxClassifier
import random
from art.attacks.evasion import HopSkipJump
from base64 import b64decode, b64encode


# %%
def get_data(x):
    """
    :param fig_id: x_train's first index
    :return:
    """
    buffer = x.tobytes()
    return b64encode(buffer)


def load_data(bb64_buffer, FIG_NUMBER=1):
    """
    :param bb64_buffer: user input buffer
    :param FIG_NUMBER: group of reshape data
    :return:
    """
    try:
        x = np.frombuffer(b64decode(bb64_buffer), dtype=np.float32)
        x = x.reshape(FIG_NUMBER, 28, 28, 1)
        return x
    except Exception as e:
        print(e)
        print("No Hack ! BAD BUFFER")
        return False


def get_norm(data):
    assert data.shape.__len__() == 4
    linf = np.mean(np.linalg.norm(data.reshape(data.shape[0], -1), ord=np.Inf, axis=1))
    l0 = len(np.where(np.abs(data) > 0.0)[0]) / data.shape[0]
    l1 = np.mean(np.linalg.norm(data.reshape(data.shape[0], -1), ord=1, axis=1))
    l2 = np.mean(np.linalg.norm(data.reshape(data.shape[0], -1), ord=2, axis=1))
    return l0, l1, l2, linf


# %%

def do_attack(x_input, y_real, classifier, y_target=None):
    attack = HopSkipJump(classifier, targeted=False, max_iter=10, max_eval=30, init_eval=30, norm=np.inf)
    x_input_adv = attack.generate(x_input, y_real)
    predict_adv = np.argmax(classifier.predict(x_input_adv), axis=1)  # raw_model
    delta = x_input_adv - x_input
    _l0, _l1, _l2, _linf = get_norm(delta)
    acc = np.sum(predict_adv == np.argmax(y_real, axis=1)) / len(y_real)
    index = np.where(predict_adv != np.argmax(y_real, axis=1))
    try:
        _f_l0, _f_l1, _f_l2, _f_linf = get_norm(delta[index])
        return delta, (acc, _f_l0, _f_l1, _f_l2, _f_linf)
    except Exception as e:
        print(e)



# %%
import requests
import os

GLOBAL_VISIT_CNT = 0

os.environ['NO_PROXY'] = "xxxx"
URL = "xxxxx"


def test():
    session = requests.Session()
    buffer = session.get(URL + "start")
    x_train = load_data(buffer.text)

    def predict_remote(_x):
        res = session.post(URL + "predict", data={"b64_image": get_data(_x), "fig_number": _x.shape[0]})
        _y = eval(res.text)
        return np.array(_y, dtype=np.float32)

    y_train = predict_remote(x_train)

    classifier = BlackBoxClassifier(predict_remote, x_train[0].shape, 10, clip_values=(0, 1))
    delta, score = do_attack(x_input=x_train, y_real=y_train, classifier=classifier)

    x_adv = x_train + delta

    res = session.post(URL + "get_flag", data={"b64_image": get_data(x_adv)}).text
    if 'flag' in res:
        print(res)
        return True, score
    else:
        return False, score

for i in range(0, 100):
    try:
        rate, score = test()
        print(rate, score)
    except Exception as e:
        print(e)

EXP C

但是,实际上这道题还有一些其他解法。选手们普遍用的都是代理模型的方法。因为`对一个神经网络的攻击,往往也会造成另一个神经网络的失效`

毕竟题目出的时候,我自己的脚本也不能保证连续N次提交正确的结果。因此只设置了一次提交正确后返回FLAG的方法。这导致某些图片是非常好迭代的。

支付宝捐赠
请使用支付宝扫一扫进行捐赠
微信捐赠
请使用微信扫一扫进行赞赏
有 0 篇文章