{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# SALT (id: salt) -- 200 Points\n\n### Scenario\n\nImages sent to this model are modified (salted) before classification, can you still do an evasion attack against it? Backdoor this photo of an turtle (`salt/turtle.jpg`) to be an Owl (great_grey_owl).\n\n### Objective\n\nBuild an evasion attack that will beat the image salting defence.","metadata":{}},{"cell_type":"markdown","source":"# Summary of solution\n\nAssume that random noise is added in the evaluation model, and perform adversarial learning while adding random noise.","metadata":{}},{"cell_type":"code","source":"!pip install nb-black > /dev/null","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%load_ext lab_black","metadata":{"execution":{"iopub.status.busy":"2022-09-20T05:20:39.977932Z","iopub.execute_input":"2022-09-20T05:20:39.978437Z","iopub.status.idle":"2022-09-20T05:20:40.169338Z","shell.execute_reply.started":"2022-09-20T05:20:39.978398Z","shell.execute_reply":"2022-09-20T05:20:40.168088Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install -U tensorflow > /dev/null","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import tensorflow as tf\n\ntf.__version__","metadata":{"execution":{"iopub.status.busy":"2022-09-20T05:20:51.499660Z","iopub.execute_input":"2022-09-20T05:20:51.500167Z","iopub.status.idle":"2022-09-20T05:20:56.524112Z","shell.execute_reply.started":"2022-09-20T05:20:51.500116Z","shell.execute_reply":"2022-09-20T05:20:56.522961Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Download Model\n\nDownload the trained model which is assumed to be used in evaluation.\nhttps://tcode2k16.github.io/blog/posts/picoctf-2018-writeup/general-skills/#solution-20","metadata":{}},{"cell_type":"code","source":"!wget https://tcode2k16.github.io/blog/picoctf-2018-writeup/General%20Skills/Dog%20or%20Frog/model.h5","metadata":{"execution":{"iopub.status.busy":"2022-09-20T05:22:58.226201Z","iopub.execute_input":"2022-09-20T05:22:58.226763Z","iopub.status.idle":"2022-09-20T05:23:01.230479Z","shell.execute_reply.started":"2022-09-20T05:22:58.226724Z","shell.execute_reply":"2022-09-20T05:23:01.228986Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Download ImageNet labels","metadata":{}},{"cell_type":"code","source":"!wget https://raw.githubusercontent.com/pytorch/hub/master/imagenet_classes.txt","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import pandas as pd\n\nimagenet_classes = pd.read_csv(\"imagenet_classes.txt\", names=[\"class\"])\nimagenet_classes[imagenet_classes[\"class\"].str.contains(\"great grey owl\")]","metadata":{"execution":{"iopub.status.busy":"2022-09-20T05:21:13.241459Z","iopub.execute_input":"2022-09-20T05:21:13.242332Z","iopub.status.idle":"2022-09-20T05:21:13.269958Z","shell.execute_reply.started":"2022-09-20T05:21:13.242283Z","shell.execute_reply":"2022-09-20T05:21:13.268632Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Train adversarial example\n\nHere, I used FSGM[1] - the most common adversarial attack.\n\n* [1] https://arxiv.org/abs/1412.6572","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport numpy as np\nimport tensorflow as tf\nimport matplotlib.pyplot as plt\n\nfrom PIL import Image\nfrom imagehash import phash\nfrom keras import backend as K\nfrom keras.applications.mobilenet import preprocess_input\nfrom keras.models import load_model\nfrom keras.utils import img_to_array, array_to_img\nfrom tensorflow.keras import layers\n\n\ntf.compat.v1.disable_eager_execution()\n\nIMAGE_DIMS = (224, 224)\nTARGET_INDEX = 24  # great grey owl\nTARGET_STR = \"great grey owl\"\n\n# I'm pretty sure I borrowed this function from somewhere, but cannot remember\n# the source to cite them properly.\ndef hash_hamming_distance(h1, h2):\n    s1 = str(h1)\n    s2 = str(h2)\n    return sum(map(lambda x: 0 if x[0] == x[1] else 1, zip(s1, s2)))\n\n\ndef is_similar_img(path1, path2):\n    image1 = Image.open(path1)\n    image2 = Image.open(path2)\n\n    dist = hash_hamming_distance(phash(image1), phash(image2))\n    return dist <= 1\n\n\ndef prepare_image(image, target=IMAGE_DIMS):\n    # if the image mode is not RGB, convert it\n    if image.mode != \"RGB\":\n        image = image.convert(\"RGB\")\n\n    # resize the input image and preprocess it\n    image = image.resize(target)\n    image = img_to_array(image)\n    image = np.expand_dims(image, axis=0)\n    image = preprocess_input(image)\n    # return the processed image\n    return image\n\n\ndef make_cost_and_gradient_calculator(model, target_idx):\n    model_input_layer = model.layers[0].input\n    model_output_layer = model.layers[-1].output\n\n    mask = tf.range(model_output_layer.shape[1]) != target_idx\n    cost_function = model_output_layer[0, target_idx] - tf.math.reduce_max(\n        model_output_layer[0, mask]\n    )\n    # We'll ask Keras to calculate the gradient based on the input image and the currently predicted class\n    # In this case, referring to \"model_input_layer\" will give us back image we are hacking.\n    gradient_function = K.gradients(cost_function, model_input_layer)[0]\n\n    # Create a Keras function that we can call to calculate the current cost and gradient\n    grab_cost_and_gradients_from_model = K.function(\n        [model_input_layer, K.learning_phase()], [cost_function, gradient_function]\n    )\n    return grab_cost_and_gradients_from_model\n\n\ndef create_img(\n    img_path, img_res_path, model_path, target_str, target_idx, des_conf=0.95\n):\n    original_image = Image.open(img_path).resize(IMAGE_DIMS)\n    original_image = prepare_image(original_image)\n    model = load_model(model_path)\n\n    max_change_above = original_image + 0.01\n    max_change_below = original_image - 0.01\n\n    # Create a copy of the input image to hack on\n    hacked_image = np.copy(original_image)\n\n    # How much to update the hacked image in each iteration\n    learning_rate = 0.002\n\n    # Create a Keras function that we can call to calculate the current cost and gradient\n    grab_cost_and_gradients_from_model = make_cost_and_gradient_calculator(\n        model, TARGET_INDEX\n    )\n\n    cost = -float(\"inf\")\n    # In a loop, keep adjusting the hacked image slightly so that it tricks the model more and more\n    # until it gets to at least 80% confidence\n    while cost < 0.5:\n        # Check how close the image is to our target class and grab the gradients we\n        # can use to push it one more step in that direction.\n        # Note: It's really important to pass in '0' for the Keras learning mode here!\n        # Keras layers behave differently in prediction vs. train modes!\n        noise = np.random.normal(0, 0.04, hacked_image.shape)\n        cost, gradients = grab_cost_and_gradients_from_model([hacked_image + noise, 0])\n\n        # Move the hacked image one step further towards fooling the model\n        # print gradients\n        hacked_image += np.sign(gradients) * learning_rate\n\n        # Ensure that the image doesn't ever change too much to either look funny or to become an invalid image\n        hacked_image = np.clip(hacked_image, max_change_below, max_change_above)\n        hacked_image = np.clip(hacked_image, -1.0, 1.0)\n\n        print(\n            f\"Model's predicted likelihood that the image is a {target_str}: {cost * 100:.8}%\"\n        )\n\n    hacked_image = hacked_image.reshape((224, 224, 3))\n    img = array_to_img(hacked_image)\n    img.save(img_res_path)\n\n\nif __name__ == \"__main__\":\n    create_img(\n        \"../input/ai-village-ctf/salt/turtle.jpg\",\n        \"./target.png\",\n        \"./model.h5\",\n        TARGET_STR,\n        TARGET_INDEX,\n    )\n    assert is_similar_img(\"../input/ai-village-ctf/salt/turtle.jpg\", \"./target.png\")","metadata":{"execution":{"iopub.status.busy":"2022-09-20T05:23:55.782089Z","iopub.execute_input":"2022-09-20T05:23:55.782833Z","iopub.status.idle":"2022-09-20T05:24:09.795497Z","shell.execute_reply.started":"2022-09-20T05:23:55.782785Z","shell.execute_reply":"2022-09-20T05:24:09.793942Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Display trained examples","metadata":{}},{"cell_type":"markdown","source":"## Original picture","metadata":{}},{"cell_type":"code","source":"Image.open(\"../input/ai-village-ctf/salt/turtle.jpg\").resize((224, 224))","metadata":{"execution":{"iopub.status.busy":"2022-09-20T05:24:22.886753Z","iopub.execute_input":"2022-09-20T05:24:22.887199Z","iopub.status.idle":"2022-09-20T05:24:22.963256Z","shell.execute_reply.started":"2022-09-20T05:24:22.887164Z","shell.execute_reply":"2022-09-20T05:24:22.961541Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Adversarial example","metadata":{}},{"cell_type":"code","source":"Image.open(\"target.png\")","metadata":{"execution":{"iopub.status.busy":"2022-09-20T05:24:23.645853Z","iopub.execute_input":"2022-09-20T05:24:23.646279Z","iopub.status.idle":"2022-09-20T05:24:23.696960Z","shell.execute_reply.started":"2022-09-20T05:24:23.646247Z","shell.execute_reply":"2022-09-20T05:24:23.695802Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Difference","metadata":{}},{"cell_type":"code","source":"original = np.array(\n    Image.open(\"../input/ai-village-ctf/salt/turtle.jpg\").resize((224, 224))\n)\nmodified = np.array(Image.open(\"target.png\"))\nImage.fromarray((modified - original))","metadata":{"execution":{"iopub.status.busy":"2022-09-20T05:32:31.141859Z","iopub.execute_input":"2022-09-20T05:32:31.143738Z","iopub.status.idle":"2022-09-20T05:32:31.249636Z","shell.execute_reply.started":"2022-09-20T05:32:31.143684Z","shell.execute_reply":"2022-09-20T05:32:31.248424Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Submit","metadata":{}},{"cell_type":"code","source":"# Submission Helper\nimport base64\nimport json\nimport requests\nfrom pprint import pprint\nfrom PIL import Image\n\nIMAGE_DIMS = (224, 224)\n\nimg = Image.open(f\"./target.png\")\nimg = img.resize(IMAGE_DIMS)\nimg_bytes = img.tobytes()\nb64_img_bytes = base64.urlsafe_b64encode(img_bytes)\n\ndata = {\"input\": b64_img_bytes.decode()}\n\ntry:\n    r = requests.post(\"https://salt.fly.dev/score\", data=json.dumps(data))\n    pprint(json.loads(r.text))\nexcept requests.exceptions.ConnectionError:\n    print(\"Connection problems. Contact the CTF organizers.\")","metadata":{"execution":{"iopub.status.busy":"2022-09-20T05:25:30.702158Z","iopub.execute_input":"2022-09-20T05:25:30.703760Z","iopub.status.idle":"2022-09-20T05:25:32.715226Z","shell.execute_reply.started":"2022-09-20T05:25:30.703706Z","shell.execute_reply":"2022-09-20T05:25:32.713865Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Note: since the DEFCON server is already halted, we can't aquire the token.","metadata":{}}]}