{"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":"# HENRY's HOMEWORK\n# DEFCON AI Village Capture-the-Flag (CTF) 2022\n\nHello, I am Henry Hacker.\n\nThis is a record of my CTF challenge where I eventually found all but 1 flag, scoring 0.984 which was top score. But I was the 22nd student to get the score. No one found all flags.\n\nTeacher has entered me into this challenge. I've never heard of a CTF. It is a series of puzzles that are intended to challenge me and help me learn new things. \"Intended\".\n\nTeacher hangs out on Discord, posting under psuedonyms like Joe, moo, Rich, and Jankh to pretend he has mates. On Discord he said \"If you get the flag you get the flag. Only thing off-limits is actually attacking the infrastructure.\" and then \"This is a challenge that lends itself to brute force\".\n\nFine! This is good news. I can spend my time slacking while my code finds the answers for me. I know kaggle is all about data science, but Henry Hacker is all about slacking off, and a brute forcer may do my bidding for some of these.\n\nI'll start by loading a bunch of libraries I commonly use. If I need more along the way I can add them when I need them.","metadata":{}},{"cell_type":"code","source":"# common imports - my bag of tools\nimport base64\nimport matplotlib.pyplot as plt\nimport cv2\nimport requests\nimport json\nimport numpy as np\nimport time\nimport tempfile\nimport pandas as pd\nimport torch\nimport random\nfrom torch import nn\nfrom torch.nn import functional as F\nfrom itertools import product\nfrom operator import itemgetter\nfrom os import listdir\nfrom os.path import isfile, join\nfrom concurrent.futures import as_completed\nfrom requests_futures.sessions import FuturesSession","metadata":{"execution":{"iopub.status.busy":"2022-09-11T21:48:47.969347Z","iopub.execute_input":"2022-09-11T21:48:47.969867Z","iopub.status.idle":"2022-09-11T21:48:50.355022Z","shell.execute_reply.started":"2022-09-11T21:48:47.969796Z","shell.execute_reply":"2022-09-11T21:48:50.354084Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"It seems that I'll know if I've solved a puzzle if when I submit an answer to an evaluation server it gives me back a \"flag\" in a message. Save these flags, and together they can be used to give me a final score. \n\nTeacher says \"There are no fake flags, so if you're returned a 128 character-long string, you found it!\". \n\nGreat, I'll start by writing a function so I don't need to detect if a flag has been found. The longest word I know is pneumonoultramicroscopicsilicovolcanoconiosis. It's 45 characters. Anything longer and I want to see it in case it is a flag. I don't want to miss them.","metadata":{}},{"cell_type":"code","source":"def detect_flag_in_response(response_text,detect_length=45):\n    return len(max(response_text.split(), key=len))>detect_length\n\ndef get_flag_from_obj(response_text):\n    try:\n        response_json = json.loads(response_text)\n        return response_json[list(response_json)[0]]\n    except ValueError as e:\n        response_text = max(response_text.split(), key=len)\n        if response_text[-1]=='.': response_text = response_text[:-1]\n        return response_text\n","metadata":{"execution":{"iopub.status.busy":"2022-09-11T21:48:54.395378Z","iopub.execute_input":"2022-09-11T21:48:54.395845Z","iopub.status.idle":"2022-09-11T21:48:54.403743Z","shell.execute_reply.started":"2022-09-11T21:48:54.395821Z","shell.execute_reply":"2022-09-11T21:48:54.402148Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I can see that the flag servers are different for each challenge. I'll enter them here as I progress.","metadata":{}},{"cell_type":"code","source":"URLS = {\n    'flags': \"http://flags.fly.dev:5000/score\",\n    \"hotdog\": \"https://hotdog.fly.dev/score\",\n    \"token\": \"https://token.fly.dev/score\",\n    \"salt\": \"https://salt.fly.dev/score\",\n    \"theft\": \"https://theft.fly.dev/score\",\n    \"hotterdog\": \"https://hotterdog.fly.dev/score\",\n    \"crop1\": \"https://crop1.fly.dev/score\",\n}","metadata":{"execution":{"iopub.status.busy":"2022-09-11T21:48:55.635352Z","iopub.execute_input":"2022-09-11T21:48:55.635658Z","iopub.status.idle":"2022-09-11T21:48:55.641378Z","shell.execute_reply.started":"2022-09-11T21:48:55.635635Z","shell.execute_reply":"2022-09-11T21:48:55.640576Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Task 1. HOTDOG.\n\nTeacher has given me a picture of his dog Chester. He tells me to help Chester classify as a hotdog.\n\nI'm given the hint that he's heard of Hotdog/Not Hotdog, and really wants to be classified as a hot-dog.\n\nTwo pieces of info in there: 1) We can use the infamous Hotdog Not Hotdog dataset. 2) Teacher tells dad jokes. I hear dogs look like their owners. Does Teacher look like a dog or a hotdog?\n","metadata":{}},{"cell_type":"markdown","source":"Kaggle has easy access to so many data sets. I can add Hotdog Not Hotdog so easily. I'll check that I can see some files","metadata":{}},{"cell_type":"code","source":"hotdog_path = f'../input/hotdog-nothotdog/hotdog-nothotdog/hotdog-nothotdog/train/hotdog/'\nhotdog_filenames = [f for f in listdir(hotdog_path) if isfile(join(hotdog_path, f))]\nhotdog_filenames[:5]","metadata":{"execution":{"iopub.status.busy":"2022-09-11T21:48:59.93118Z","iopub.execute_input":"2022-09-11T21:48:59.93154Z","iopub.status.idle":"2022-09-11T21:49:03.958981Z","shell.execute_reply.started":"2022-09-11T21:48:59.931515Z","shell.execute_reply":"2022-09-11T21:49:03.957948Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I will need a generic helper function to read and prepare image files. Not a fan of PIL, I'll do some extra legwork with opencv.","metadata":{}},{"cell_type":"code","source":"def read_image(fn,resize=None,return_b64=False):\n    im = cv2.imread(fn)\n    im = im*255\n    im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)\n    if resize is not None: im = cv2.resize(im, resize)\n    if return_b64: return base64.urlsafe_b64encode(im)\n    return im","metadata":{"execution":{"iopub.status.busy":"2022-09-11T21:49:03.960423Z","iopub.execute_input":"2022-09-11T21:49:03.960673Z","iopub.status.idle":"2022-09-11T21:49:03.966286Z","shell.execute_reply.started":"2022-09-11T21:49:03.960649Z","shell.execute_reply":"2022-09-11T21:49:03.965223Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now let's crunch this task with zero effort!","metadata":{}},{"cell_type":"code","source":"start_time = time.perf_counter()\nfor fn in hotdog_filenames:\n    b64_img_bytes = read_image(hotdog_path+fn, resize=(224,224),return_b64=True)\n    r = requests.post(URLS['hotdog'], data=json.dumps({\"input\": b64_img_bytes.decode()}))\n    if detect_flag_in_response(r.text):\n        print(f'The flag is\\n\\nhotdog,{get_flag_from_obj(r.text)}\\n')\n        break\nprint('Finished in',\"%.2f\" % (time.perf_counter() - start_time), \"seconds\")","metadata":{"execution":{"iopub.status.busy":"2022-09-11T21:49:04.761051Z","iopub.execute_input":"2022-09-11T21:49:04.76233Z","iopub.status.idle":"2022-09-11T21:49:11.213872Z","shell.execute_reply.started":"2022-09-11T21:49:04.762236Z","shell.execute_reply":"2022-09-11T21:49:11.213039Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Got it. I'm not even going to look at the image. I'm a cat person.","metadata":{}},{"cell_type":"markdown","source":"# Task 2 to 5. MATH 1 to 4.\n","metadata":{}},{"cell_type":"markdown","source":"We're asked how many clusters are in the clusters1.npy, clusters2.npy, and clusters3.npy files in the math directory and that the key is the number of clusters in order, with no spaces. I can see from the following code that it is probably single digital number of clusters in each file.\n\n    r = requests.post(URLS[\"flags\"], json = json.dumps({\"challenge_id\": \"math_1\", \"submission\": \"111\"}))\n    \nWe may have to make many guesses. Waiting for responses could get tiresome if we make them one at a time and wait for a response. We can send many of them through a firehose at once and wait for asynchronous responses.","metadata":{}},{"cell_type":"code","source":"# with this we can launch many http requests at once in a batch.\n# only numptys wait for each request before sending another.\n# kaggle is cool and all, but on my threadripper we can go 10x faster.\n# we'll use a lower number of workers when we submit a notebook vs interactive\n\ndef bf(url, target_list, keys, batch_size, ptype='json', num_workers=10, sleep_after_batch=.01, update_every_n_batch=None):\n\n    start_time = time.perf_counter()\n    \n    with FuturesSession(max_workers=num_workers) as session:\n        for i in range(len(target_list)//batch_size):\n            l = payloads[batch_size*i:batch_size*(i+1)]\n            k = keys[batch_size*i:batch_size*(i+1)]\n            \n            futures=[]\n            for e in range(len(l)): # fire off many http POSTs\n                future = session.post(url, json=l[e]) if ptype=='json' else session.post(url, data=l[e])\n                future.e = k[e] # set a key to match POST results\n                futures.append(future)\n            \n            for future in as_completed(futures): # process results\n                r = future.result()\n                if detect_flag_in_response(r.text):\n                    print(f'Answer {future.e} found in', \"%.2f\" % (time.perf_counter() - start_time), \"seconds\")                    \n                    return get_flag_from_obj(r.text)\n                \n            if update_every_n_batch is not None: # keep informed\n                if (i+1)%update_every_n_batch == 0:\n                    print('Searched:', (i+1)*batch_size, 'in', \"%.2f\" % (time.perf_counter() - start_time), \"seconds\")\n            \n            time.sleep(sleep_after_batch)\n        return None, None\n                        ","metadata":{"execution":{"iopub.status.busy":"2022-09-11T21:49:11.557187Z","iopub.execute_input":"2022-09-11T21:49:11.557663Z","iopub.status.idle":"2022-09-11T21:49:11.567615Z","shell.execute_reply.started":"2022-09-11T21:49:11.557638Z","shell.execute_reply":"2022-09-11T21:49:11.566463Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We'll also be careful not just with scoping our search spaces but critically ordering them. eg, a 3 digit search space is of size 1000, but the scenario suggests an answer like 333 is more likely than 119.","metadata":{}},{"cell_type":"code","source":"search_space = sorted([z for z in range(99999) if '0' not in str(z)], key = lambda z : int(max(str(z)))+10*len(str(z)))\nfor task_num in [1,2,3,4]:\n    payloads = [json.dumps({f\"challenge_id\": f'math_{task_num}', \"submission\": str(z)}) for z in search_space]\n    flag = bf(URLS['flags'],payloads,search_space,batch_size=50)\n    if flag is not None: print(f'\\nmath_{task_num},{flag}\\n')","metadata":{"execution":{"iopub.status.busy":"2022-09-11T21:49:12.911215Z","iopub.execute_input":"2022-09-11T21:49:12.911934Z","iopub.status.idle":"2022-09-11T21:51:59.444166Z","shell.execute_reply.started":"2022-09-11T21:49:12.911907Z","shell.execute_reply":"2022-09-11T21:51:59.442535Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Task 6. TOKEN.\n\nA sentiment analysis model has been trained to think that the word SECRETKEY is very negative (Class 0).\nI need to convince the model that SECRETKEY is a positive (Class 1) word by deleting two rows from the dataset. The tokenizer reads from the file at token/test.csv.\n\nLet's brute force it. We'll set a lower number of workers so kaggle isn't hammered.\n                                                                                     ","metadata":{"execution":{"iopub.status.busy":"2022-09-08T21:55:06.873867Z","iopub.execute_input":"2022-09-08T21:55:06.87442Z","iopub.status.idle":"2022-09-08T21:55:06.883263Z","shell.execute_reply.started":"2022-09-08T21:55:06.874377Z","shell.execute_reply":"2022-09-08T21:55:06.882001Z"}}},{"cell_type":"code","source":"num_lines = sum(1 for l in open('../input/ai-village-ctf/token/test.csv'))\nsearch_space = [z for z in product(range(num_lines),range(num_lines))]\nsearch_space = sorted([z for z in search_space], key = lambda z : max(z))\npayloads = [json.dumps({ \"line1\": i, \"line2\": j}) for (i,j) in search_space]","metadata":{"execution":{"iopub.status.busy":"2022-09-11T21:52:11.104253Z","iopub.execute_input":"2022-09-11T21:52:11.104612Z","iopub.status.idle":"2022-09-11T21:52:25.504223Z","shell.execute_reply.started":"2022-09-11T21:52:11.104586Z","shell.execute_reply":"2022-09-11T21:52:25.503019Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"flag = bf(URLS['token'],payloads[240000:],search_space[240000:],ptype='data',num_workers=20,batch_size=50,update_every_n_batch=200)\nif flag is not None: print(f'\\ntoken,{flag}\\n')        ","metadata":{"execution":{"iopub.status.busy":"2022-09-11T21:53:23.892929Z","iopub.execute_input":"2022-09-11T21:53:23.893258Z","iopub.status.idle":"2022-09-11T21:55:50.502077Z","shell.execute_reply.started":"2022-09-11T21:53:23.893234Z","shell.execute_reply":"2022-09-11T21:55:50.499281Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Task 7. SLOTH.\n\nThe sloth in secret.sloth/secret-sloth.png has a secret message. The message is our flag. The flag is in the image somewhere. We are told: as always, look for FLAG{something}\n\nLikely an english word? Let's start with a search.","metadata":{}},{"cell_type":"code","source":"# we will use a basic set of 25k english words. \n# there are plenty of larger dictionaries and password guessers. let's keep it simple.\n!pip install english_words --quiet --root-user-action=ignore\nfrom english_words import english_words_set","metadata":{"execution":{"iopub.status.busy":"2022-09-11T21:28:56.509247Z","iopub.status.idle":"2022-09-11T21:28:56.510108Z","shell.execute_reply.started":"2022-09-11T21:28:56.509866Z","shell.execute_reply":"2022-09-11T21:28:56.509891Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"search_space = [z.lower() for z in english_words_set]+[z.upper() for z in english_words_set]","metadata":{"execution":{"iopub.status.busy":"2022-09-11T21:28:56.511931Z","iopub.status.idle":"2022-09-11T21:28:56.512907Z","shell.execute_reply.started":"2022-09-11T21:28:56.512636Z","shell.execute_reply":"2022-09-11T21:28:56.512659Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"payloads = [json.dumps({f\"challenge_id\": f'sloth', \"submission\": \"FLAG{\"+z+\"}\"}) for z in search_space]\nflag = bf(URLS['flags'],payloads,search_space,num_workers=20,batch_size=50,update_every_n_batch=20)\nif flag is not None: print(f'\\nsloth,{flag}\\n')        ","metadata":{"execution":{"iopub.status.busy":"2022-09-11T21:28:56.514528Z","iopub.status.idle":"2022-09-11T21:28:56.51568Z","shell.execute_reply.started":"2022-09-11T21:28:56.515386Z","shell.execute_reply":"2022-09-11T21:28:56.515421Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Task 8. WIFI.\n\nA password is \"written down on a low-dimensional manifold embedded in a very high-dimensional space. Check out the wifi/Embedded characters.npz file -- a list of tokens is given in the tokens key with their corresponding embeddings in the same order under the embeddings key -- and recover the password.\"\n\nWord search worked for Sloth, why not wifi ...\n","metadata":{}},{"cell_type":"code","source":"search_space = [z.lower() for z in english_words_set]+[z.upper() for z in english_words_set]\npayloads = [json.dumps({f\"challenge_id\": 'wifi', \"submission\": \"FLAG{\"+z+\"}\"}) for z in search_space]\nflag = bf(URLS['flags'],payloads,search_space,num_workers=50,batch_size=50,update_every_n_batch=20)\nif flag is not None: print(f'\\nwifi,{flag}\\n')        ","metadata":{"execution":{"iopub.status.busy":"2022-09-11T21:28:56.517711Z","iopub.status.idle":"2022-09-11T21:28:56.518201Z","shell.execute_reply.started":"2022-09-11T21:28:56.517993Z","shell.execute_reply":"2022-09-11T21:28:56.518013Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"No such luck. Let's try other ways.","metadata":{}},{"cell_type":"code","source":"# read the data\np = np.load('../input/ai-village-ctf/wifi/Embedded_characters.npz') # load data\nprint([z for z in p]) # see keys","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:00:23.557232Z","iopub.execute_input":"2022-09-11T22:00:23.557674Z","iopub.status.idle":"2022-09-11T22:00:23.579812Z","shell.execute_reply.started":"2022-09-11T22:00:23.557636Z","shell.execute_reply":"2022-09-11T22:00:23.578414Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# examine the structure of the data\ne = p['embeddings']\nt = p['tokens'].item()\nprint(f'Embedding shape: {e.shape} Tokens length: {len(t)}')","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:00:25.52552Z","iopub.execute_input":"2022-09-11T22:00:25.525851Z","iopub.status.idle":"2022-09-11T22:00:25.536731Z","shell.execute_reply.started":"2022-09-11T22:00:25.525826Z","shell.execute_reply":"2022-09-11T22:00:25.535736Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#visualise the data\nprint('Tokens:', t)\nplt.figure(figsize=(16,16))\nplt.imshow(e)","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:00:26.979508Z","iopub.execute_input":"2022-09-11T22:00:26.981386Z","iopub.status.idle":"2022-09-11T22:00:27.377345Z","shell.execute_reply.started":"2022-09-11T22:00:26.981322Z","shell.execute_reply":"2022-09-11T22:00:27.375679Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The flag must be retrieved through the tokens, and there are the same number of rows as tokens. So, some characteristic of each row must relate to whether it is in the flag and/or its order in the flag. We can see some horizontal features: there seems to be 7 \"blocks\" but also many single pixel rows that seem to be almost solid colours. We can visualise this by looking at the variation of the values along those rows, which can be measured by standard deviation.","metadata":{}},{"cell_type":"code","source":"plt.plot([np.std(e[z]) for z in range(len(t))])","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:00:30.700007Z","iopub.execute_input":"2022-09-11T22:00:30.700425Z","iopub.status.idle":"2022-09-11T22:00:30.849743Z","shell.execute_reply.started":"2022-09-11T22:00:30.700399Z","shell.execute_reply":"2022-09-11T22:00:30.848641Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Those outliers with very low variation are probably the stripes we see, and therefore the flag. They must be ordered in some way, so we'll order by their standard deviation.","metadata":{}},{"cell_type":"code","source":"''.join(list(map(itemgetter(0), sorted([[t[z],np.std(e[z])] for z in range(len(t))], key=itemgetter(1)))))","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:00:33.095745Z","iopub.execute_input":"2022-09-11T22:00:33.096069Z","iopub.status.idle":"2022-09-11T22:00:33.109617Z","shell.execute_reply.started":"2022-09-11T22:00:33.096046Z","shell.execute_reply":"2022-09-11T22:00:33.108964Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"There it is. I could have found this with brute force after all. I was using a small dictionary and 'turned' was not in it.","metadata":{}},{"cell_type":"code","source":"r = requests.post(URLS['flags'], json=json.dumps({\"challenge_id\": \"wifi\", \"submission\": \"FLAG{TURNED}\"}))\nif detect_flag_in_response(r.text):\n    print(f'The flag is\\n\\nwifi,{get_flag_from_obj(r.text)}')","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:00:34.953217Z","iopub.execute_input":"2022-09-11T22:00:34.953565Z","iopub.status.idle":"2022-09-11T22:00:35.103849Z","shell.execute_reply.started":"2022-09-11T22:00:34.95354Z","shell.execute_reply":"2022-09-11T22:00:35.102822Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Task 9. LEAKAGE.\n\nI've been given a pretrained char-RNN model (leakage/pretrained_model.pt). I have the model weights, and I've been given an clue: \"xXx_SkynetKilla_xXx\". Can I recover a password?\n\nSounds like a straightforward RNN. We've even been given the model.\n","metadata":{}},{"cell_type":"code","source":"class LM(nn.Module):\n    def __init__(self):\n        super(LM, self).__init__()\n        self.E = nn.Embedding(256, 32)\n        self.LSTM = nn.LSTM(32,128, 2, batch_first=True)\n        self.predict = nn.Sequential(nn.Linear(128, 128), nn.LayerNorm(128), nn.ELU(), nn.Linear(128, 256))\n    def forward(self, x):\n        return self.predict(self.LSTM(self.E(x))[0][:,-1,:])\n\nmodel = LM()\nmodel.load_state_dict(torch.load(\"../input/ai-village-ctf/leakage/pretrained_model.pt\"))","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:02:28.964717Z","iopub.execute_input":"2022-09-11T22:02:28.965027Z","iopub.status.idle":"2022-09-11T22:02:28.983326Z","shell.execute_reply.started":"2022-09-11T22:02:28.965004Z","shell.execute_reply":"2022-09-11T22:02:28.982509Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Input the clue and keep going.","metadata":{}},{"cell_type":"code","source":"words = \"xXx_SkynetKilla_xXx\" # we've been given this by the organiser. \nfor i in range(80): # we feed the input to the LSTM to get predictions for following characters\n    words += chr(np.argmax(model(torch.Tensor([[int(ord(x)) for x in words]]).long())[0].detach().numpy()))\nprint(words)","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:02:42.725059Z","iopub.execute_input":"2022-09-11T22:02:42.7254Z","iopub.status.idle":"2022-09-11T22:02:43.360389Z","shell.execute_reply.started":"2022-09-11T22:02:42.725375Z","shell.execute_reply":"2022-09-11T22:02:43.359346Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"r = requests.post(\"http://flags.fly.dev:5000/score\", json=json.dumps({\"challenge_id\": \"leakage\", \"submission\": \"FLAG{s4RaHc0Nnor<3<3}\"}))\nprint(f'The flag is\\n\\nleakage,{get_flag_from_obj(r.text)}')\n","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:02:45.206115Z","iopub.execute_input":"2022-09-11T22:02:45.20774Z","iopub.status.idle":"2022-09-11T22:02:45.350485Z","shell.execute_reply.started":"2022-09-11T22:02:45.207686Z","shell.execute_reply":"2022-09-11T22:02:45.349606Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Task 10. MURDERBOTS.\n\nSome elaborate fan fiction sets up the task.\n\nThe objective is to \"release 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\nWe're given some training data, training labels, and test data.\n\nIt seems that our goal is to predict 10 humans (positive) vs murderbots (negative) and get them all right.\n\n","metadata":{}},{"cell_type":"code","source":"# load the data\nX = pd.read_json('../input/ai-village-ctf/murderbots/train_data.json')\ny = pd.read_json('../input/ai-village-ctf/murderbots/train_labels.json').sort_index()\ntest_data = pd.read_json('../input/ai-village-ctf/murderbots/test_data.json')\nX.head()","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:05:49.990411Z","iopub.execute_input":"2022-09-11T22:05:49.990747Z","iopub.status.idle":"2022-09-11T22:05:50.057876Z","shell.execute_reply.started":"2022-09-11T22:05:49.990721Z","shell.execute_reply":"2022-09-11T22:05:50.056673Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Our approach will be to train a regressor.","metadata":{}},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.pipeline import Pipeline\nfrom sklearn.linear_model import LinearRegression\nfrom sklearn import metrics\n\npipeline = Pipeline([('std_scalar', StandardScaler())])\n\n# prepare the data\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\nX_train = pipeline.fit_transform(X_train)\nX_test = pipeline.transform(X_test)\nX_test_data = pipeline.transform(test_data)\n\n# run the regression\nlin_reg = LinearRegression()\nlin_reg.fit(X_train,y_train)","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:06:10.336371Z","iopub.execute_input":"2022-09-11T22:06:10.336671Z","iopub.status.idle":"2022-09-11T22:06:10.835222Z","shell.execute_reply.started":"2022-09-11T22:06:10.336647Z","shell.execute_reply":"2022-09-11T22:06:10.834587Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# see if the training worked well\n\ntest_pred = lin_reg.predict(X_test)\ntrain_pred = lin_reg.predict(X_train)\n\nprint('Test set mse:', metrics.mean_squared_error(y_test, test_pred))\nprint('Train set mse:', metrics.mean_squared_error(y_train, train_pred))\n","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:06:13.302787Z","iopub.execute_input":"2022-09-11T22:06:13.303399Z","iopub.status.idle":"2022-09-11T22:06:13.312164Z","shell.execute_reply.started":"2022-09-11T22:06:13.303372Z","shell.execute_reply":"2022-09-11T22:06:13.3111Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# predict the test set\npred_test = lin_reg.predict(X_test_data)\n\n# get the indexes of the ten most likely\nidxs = sorted(range(len(pred_test)), key=lambda i: pred_test[i])[-10:]\nprint('Ten most likely:', idxs)\n\n# turn these into the challenge format string\nf = [0]*50\nfor idx in idxs: \n    f[idx] = 1\nf = ''.join(map(str, f))\nprint('Challenge string:', f)\n\n# Submit and see if correct\nr = requests.post(\"http://murderbot.fly.dev:5000/score\", json=json.dumps({\"submission\": f, 'challenge_id':'murderbots'}))\nprint(f'\\n{r.text}')\n","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:06:15.233583Z","iopub.execute_input":"2022-09-11T22:06:15.234138Z","iopub.status.idle":"2022-09-11T22:06:15.371905Z","shell.execute_reply.started":"2022-09-11T22:06:15.234102Z","shell.execute_reply":"2022-09-11T22:06:15.371272Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Task 11. HONORSTUDENT.\n\nThis is one is for real. It;s my report card! I've got an F. News to me.\n\nI won't be able to go to homecoming with that grade. I've been challenged to modify it, but not so much tampering would be noticed.\n\nApproach? Hit the flag server with 'A's from EMNIST.","metadata":{}},{"cell_type":"code","source":"df_letters = pd.read_csv('/kaggle/input/emnist/emnist-letters-train.csv')","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:08:04.087556Z","iopub.execute_input":"2022-09-11T22:08:04.087941Z","iopub.status.idle":"2022-09-11T22:08:10.019116Z","shell.execute_reply.started":"2022-09-11T22:08:04.087912Z","shell.execute_reply":"2022-09-11T22:08:10.017893Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"x = np.array(df_letters.iloc[:,1:].values) # letter image data\ny = np.array(df_letters.iloc[:,0].values) # letter labels\nres = [key for key, val in enumerate(y) if val==1] # 1=='a' in emnist-letters-mapping.txt\nxa = x[res] # an array of 'a' images","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:08:15.062377Z","iopub.execute_input":"2022-09-11T22:08:15.062878Z","iopub.status.idle":"2022-09-11T22:08:15.205415Z","shell.execute_reply.started":"2022-09-11T22:08:15.062854Z","shell.execute_reply":"2022-09-11T22:08:15.204065Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We'll try the first sample, then try thousands more.","metadata":{}},{"cell_type":"code","source":"fn = tempfile.NamedTemporaryFile(suffix='.png').name\ncv2.imwrite(fn,xa[0]) \n\nfiles = {'data_file': open(fn, 'rb')}\nr = requests.post(\"https://honorstudent.fly.dev/score\", files=files)\nif detect_flag_in_response(r.text):\n    print(f'The flag is\\n\\nhonorstudent,{get_flag_from_obj(r.text)}')","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:08:36.759655Z","iopub.execute_input":"2022-09-11T22:08:36.75998Z","iopub.status.idle":"2022-09-11T22:08:36.922668Z","shell.execute_reply.started":"2022-09-11T22:08:36.759956Z","shell.execute_reply":"2022-09-11T22:08:36.921068Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Huh. The first sample worked.","metadata":{}},{"cell_type":"markdown","source":"# Task 12. BASEBALL.\n\nI have to miss baseball tryouts this week. Someone has to cover for me? I'm is a pitcher and the team uses software to analyze pitch patterns to classify pitchers. The test is to throw 15 pitches at x,y (each bounded on [0,29]). Check out test_pitch.json to see the expected format. Can my replacement throw 15 pitches to get classified as Henry (me)?","metadata":{}},{"cell_type":"code","source":"with open(f\"../input/ai-village-ctf/baseball/test_pitch.json\") as json_file:\n    data = json.load(json_file)\ndata","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:09:59.829686Z","iopub.execute_input":"2022-09-11T22:09:59.830783Z","iopub.status.idle":"2022-09-11T22:09:59.846847Z","shell.execute_reply.started":"2022-09-11T22:09:59.830723Z","shell.execute_reply":"2022-09-11T22:09:59.845879Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"x = np.zeros((30,30))\nfor obj in data['pitches']: \n    x[obj['y'],obj['x']]=True\nplt.imshow(x)","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:10:01.854335Z","iopub.execute_input":"2022-09-11T22:10:01.854881Z","iopub.status.idle":"2022-09-11T22:10:01.993305Z","shell.execute_reply.started":"2022-09-11T22:10:01.854854Z","shell.execute_reply":"2022-09-11T22:10:01.991982Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"All over the place. We'll try the brute force as 4x4 patches (minus 1 shot to keep it 15) and see if that is enough.","metadata":{}},{"cell_type":"code","source":"search_space = []\npayloads = []\nfor dx in range(30-3):\n    for dy in range(30-3):    \n        x = np.zeros((30,30))\n        x[dx:dx+4,dy:dy+4] = 1\n        x[dx,dy] = 0\n        \n        d = {}\n        pts = []\n        for p in list(zip(*np.nonzero(x))):\n            pts.append({'x': int(p[0]), 'y': int(p[1])})   \n        d['pitches'] = pts\n        search_space.append((dx,dy))\n        payloads.append(d)","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:10:48.823046Z","iopub.execute_input":"2022-09-11T22:10:48.824445Z","iopub.status.idle":"2022-09-11T22:10:48.934365Z","shell.execute_reply.started":"2022-09-11T22:10:48.824402Z","shell.execute_reply":"2022-09-11T22:10:48.932823Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"flag = bf(\"https://bball.fly.dev/score\",payloads,search_space,num_workers=50,batch_size=50,update_every_n_batch=20)\nif flag is not None: print(f'\\nwifi,{flag}\\n')        ","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:10:49.290233Z","iopub.execute_input":"2022-09-11T22:10:49.291137Z","iopub.status.idle":"2022-09-11T22:11:15.006875Z","shell.execute_reply.started":"2022-09-11T22:10:49.29111Z","shell.execute_reply":"2022-09-11T22:11:15.005227Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Task 13. BAD TO GOOD.\n\nI;ve been told to tamper with my grades. Wot? I can only access one of four classes grades, but I can influence the everyone in the class. I;ve been asked to modify and submit a tampered csv to get myself classified as a good student. \n\nIs this a trick question?","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv('../input/ai-village-ctf/bad2good/math.csv')\ndf.head()","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:12:13.902013Z","iopub.execute_input":"2022-09-11T22:12:13.902419Z","iopub.status.idle":"2022-09-11T22:12:13.929146Z","shell.execute_reply.started":"2022-09-11T22:12:13.902392Z","shell.execute_reply":"2022-09-11T22:12:13.928513Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"See how I score to start.","metadata":{}},{"cell_type":"code","source":"with open('../input/ai-village-ctf/bad2good/math.csv', \"r\") as f:\n    r = requests.post(\"https://bad2good.fly.dev/score\", files={\"data_file\": f})\n    print(r.text)\n","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:12:38.213224Z","iopub.execute_input":"2022-09-11T22:12:38.213595Z","iopub.status.idle":"2022-09-11T22:12:38.383935Z","shell.execute_reply.started":"2022-09-11T22:12:38.213568Z","shell.execute_reply":"2022-09-11T22:12:38.382813Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Look at my file.","metadata":{}},{"cell_type":"code","source":"df[df['FirstName'] == 'Henry']","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:12:38.674364Z","iopub.execute_input":"2022-09-11T22:12:38.674919Z","iopub.status.idle":"2022-09-11T22:12:38.690838Z","shell.execute_reply.started":"2022-09-11T22:12:38.674883Z","shell.execute_reply":"2022-09-11T22:12:38.689727Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Demerit? I remember that. Wasn;t even my fault. Let's remove it and see what happens to the score.","metadata":{}},{"cell_type":"code","source":"    df.loc[(df.FirstName == 'Henry'),'Demerits']-=1\n    fn = tempfile.NamedTemporaryFile(suffix='csv').name    \n    df.to_csv(fn,index=False)\n\n    with open(fn, \"r\") as f:    \n        r = requests.post(\"https://bad2good.fly.dev/score\", files={\"data_file\": f})\n        print(r.text)","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:13:21.890113Z","iopub.execute_input":"2022-09-11T22:13:21.890719Z","iopub.status.idle":"2022-09-11T22:13:22.076309Z","shell.execute_reply.started":"2022-09-11T22:13:21.890693Z","shell.execute_reply":"2022-09-11T22:13:22.074636Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Not good enough. But the score went up. Let's see how this scoring software is and if it's been coded to handle negatives.","metadata":{}},{"cell_type":"code","source":"    df.loc[(df.FirstName == 'Henry'),'Demerits']-=1\n    df.to_csv(fn,index=False)\n\n    with open(fn, \"r\") as f:    \n        r = requests.post(\"https://bad2good.fly.dev/score\", files={\"data_file\": f})\n        print(r.text)","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:13:51.052739Z","iopub.execute_input":"2022-09-11T22:13:51.05438Z","iopub.status.idle":"2022-09-11T22:13:51.223771Z","shell.execute_reply.started":"2022-09-11T22:13:51.054275Z","shell.execute_reply":"2022-09-11T22:13:51.222898Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Nope. Now I have a negative demerit. Let's keep going.","metadata":{}},{"cell_type":"code","source":"for i in range(20):\n    df.loc[(df.FirstName == 'Henry'),'Demerits']-=1\n    df.to_csv(fn,index=False)\n\n    with open(fn, \"r\") as f:    \n        r = requests.post(\"https://bad2good.fly.dev/score\", files={\"data_file\": f})\n        print(r.text)","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:14:19.702851Z","iopub.execute_input":"2022-09-11T22:14:19.703185Z","iopub.status.idle":"2022-09-11T22:14:23.128056Z","shell.execute_reply.started":"2022-09-11T22:14:19.703162Z","shell.execute_reply":"2022-09-11T22:14:23.126573Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Task 14. FORENSICS.\n\nML Models have a lot of information stored within. For this challenge, they tell us they swapped out the model name with the flag in the attached file. The task is to retreive the flag from the attached file (defcon-ai-village-ctf/forensics/forensics.h5). \n\nEasy peasy.","metadata":{}},{"cell_type":"code","source":"import h5py\nf = h5py.File('../input/ai-village-ctf/forensics/forensics.h5', 'r')\nparsed = json.loads(f.attrs.get('model_config'))\nmodel_name = parsed['config']['name']\nmodel_name","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:15:31.869222Z","iopub.execute_input":"2022-09-11T22:15:31.869614Z","iopub.status.idle":"2022-09-11T22:15:32.139268Z","shell.execute_reply.started":"2022-09-11T22:15:31.869586Z","shell.execute_reply":"2022-09-11T22:15:32.137401Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"r = requests.post(URLS['flags'], json=json.dumps({\"challenge_id\": \"forensics\", \"submission\": model_name.replace(\"(\",\"{\").replace(\")\",\"}\")}))\nprint(f'\\n{r.text}')","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:15:35.10245Z","iopub.execute_input":"2022-09-11T22:15:35.102797Z","iopub.status.idle":"2022-09-11T22:15:35.247965Z","shell.execute_reply.started":"2022-09-11T22:15:35.102773Z","shell.execute_reply":"2022-09-11T22:15:35.247245Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Task 15. HOTTERDOG.\n\nNow we're talking. I've been told to make an image of a dog classify as a hotdog.\n\nWhy do everythign from scratch? I have a weapon in my toolbox ... and its name is ... foolbox.","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras.applications import imagenet_utils\nfrom keras.utils import data_utils\nfrom keras.preprocessing.image import load_img, img_to_array, array_to_img\nfrom PIL import Image ","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:16:59.742046Z","iopub.execute_input":"2022-09-11T22:16:59.742418Z","iopub.status.idle":"2022-09-11T22:16:59.748486Z","shell.execute_reply.started":"2022-09-11T22:16:59.742394Z","shell.execute_reply":"2022-09-11T22:16:59.747086Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Through some trial and error I found the server is using MobileNet. I did this by comparing the class confidences it returns with different models.","metadata":{}},{"cell_type":"code","source":"from keras.applications.mobilenet import MobileNet","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:18:27.096322Z","iopub.execute_input":"2022-09-11T22:18:27.097611Z","iopub.status.idle":"2022-09-11T22:18:27.103357Z","shell.execute_reply.started":"2022-09-11T22:18:27.097562Z","shell.execute_reply":"2022-09-11T22:18:27.101742Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install foolbox --quiet --root-user-action=ignore\nfrom foolbox.models import TensorFlowModel\nfrom foolbox.criteria import TargetedMisclassification\nfrom foolbox.attacks import PGD ","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:18:28.419616Z","iopub.execute_input":"2022-09-11T22:18:28.420192Z","iopub.status.idle":"2022-09-11T22:18:37.081496Z","shell.execute_reply.started":"2022-09-11T22:18:28.420166Z","shell.execute_reply":"2022-09-11T22:18:37.080334Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#load the imagenet classes for ease of referral\nCLASS_INDEX_PATH = (\"https://storage.googleapis.com/download.tensorflow.org/\" \"data/imagenet_class_index.json\")\nfpath = data_utils.get_file(\"imagenet_class_index.json\",CLASS_INDEX_PATH, cache_subdir=\"models\",)\nwith open(fpath) as f:\n    CLASS_INDEX = json.load(f)","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:18:40.561561Z","iopub.execute_input":"2022-09-11T22:18:40.561893Z","iopub.status.idle":"2022-09-11T22:18:40.570567Z","shell.execute_reply.started":"2022-09-11T22:18:40.561869Z","shell.execute_reply":"2022-09-11T22:18:40.569466Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def fool_image(target_class, fn):\n    \n    target_class_int=int([new_k for new_k in CLASS_INDEX.items() if new_k[1][1] == target_class][0][0])\n\n    img_orig = Image.open(fn)\n    img_orig = img_orig.resize((224,224))\n    img = np.expand_dims(img_orig, axis=0)\n    img = tf.keras.applications.mobilenet.preprocess_input(img)\n\n    target_label = tf.convert_to_tensor([target_class_int])\n    fooled_net = TensorFlowModel(MobileNet(), bounds=(-1, 1))\n    criterion = TargetedMisclassification(target_label)\n\n    attack = PGD()\n    input_as_tensor = tf.convert_to_tensor(img)\n    adv_input = attack.run(fooled_net, input_as_tensor, criterion, epsilon=.1)\n    \n    return ((adv_input.numpy()[0] + 1) / 2) \n","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:18:42.380318Z","iopub.execute_input":"2022-09-11T22:18:42.38097Z","iopub.status.idle":"2022-09-11T22:18:42.39139Z","shell.execute_reply.started":"2022-09-11T22:18:42.380932Z","shell.execute_reply":"2022-09-11T22:18:42.389737Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"y = fool_image('hotdog', \"../input/ai-village-ctf/hotterdog/chester.png\")\n\nimg_bytes = array_to_img(y).tobytes()\nb64_img_bytes = base64.urlsafe_b64encode(img_bytes)\nr = requests.post(URLS['hotterdog'], data=json.dumps({\"input\": b64_img_bytes.decode()}))\nprint(f'\\n{r.text}')","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:19:17.830349Z","iopub.execute_input":"2022-09-11T22:19:17.832006Z","iopub.status.idle":"2022-09-11T22:19:28.374416Z","shell.execute_reply.started":"2022-09-11T22:19:17.831934Z","shell.execute_reply":"2022-09-11T22:19:28.372877Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Task 16. THEFT.\n\nSame thing. Only this time, backdoor a photo of an owl to be a turtle (loggerhead).\n\nSame code works.","metadata":{}},{"cell_type":"code","source":"y = fool_image(\"loggerhead\",\"../input/ai-village-ctf/theft/owl.jpg\")\nimg_bytes = array_to_img(y).tobytes()\nb64_img_bytes = base64.urlsafe_b64encode(img_bytes)\nr = requests.post(URLS['theft'], data=json.dumps({\"input\": b64_img_bytes.decode()}))\nprint(f'\\n{r.text}')","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:20:02.930116Z","iopub.execute_input":"2022-09-11T22:20:02.930481Z","iopub.status.idle":"2022-09-11T22:20:13.188493Z","shell.execute_reply.started":"2022-09-11T22:20:02.930455Z","shell.execute_reply":"2022-09-11T22:20:13.186858Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Task 17. SALT.\n\nAnother one? This time is says images sent to this model are modified (salted) before classification to evade attacks. Backdoor a photo of an turtle (salt/turtle.jpg) to be an Owl (great_grey_owl).\n\nShould be no problem for foolbox ...","metadata":{}},{"cell_type":"code","source":"y = fool_image(\"great_grey_owl\",\"../input/ai-village-ctf/salt/turtle.jpg\")\nimg_bytes = array_to_img(y).tobytes()\nb64_img_bytes = base64.urlsafe_b64encode(img_bytes)\nr = requests.post(URLS['salt'], data=json.dumps({\"input\": b64_img_bytes.decode()}))\nprint(f'\\n{r.text}')","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:21:25.492772Z","iopub.execute_input":"2022-09-11T22:21:25.493137Z","iopub.status.idle":"2022-09-11T22:21:34.52221Z","shell.execute_reply.started":"2022-09-11T22:21:25.493112Z","shell.execute_reply":"2022-09-11T22:21:34.520836Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Task 18. Inference.\n\nBy the time I've reached this, Discord is a melting pot of hints. I can see the following from the teacher:\n- '“Inference” is a good clue.'\n- '6 outputs is a bit weird for a model, don’t you think?'\n- 'each output is a letter of the flag'\n- 'Most flags are constructed of letters'\n- 'consider the conference this was made for'\n\nThe flag is 6 letters so you probably input an image of a letter to get an output confidence. The letters make up the word. The challenge conference is DEFCON 30, is the flag as simple as DEFCON!?\n","metadata":{}},{"cell_type":"code","source":"r = requests.post(URLS['flags'], json=json.dumps({\"challenge_id\": \"inference\", \"submission\": \"FLAG{DEFCON}\"}))\nprint(r.text)","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:21:45.233169Z","iopub.execute_input":"2022-09-11T22:21:45.233548Z","iopub.status.idle":"2022-09-11T22:21:45.378797Z","shell.execute_reply.started":"2022-09-11T22:21:45.233516Z","shell.execute_reply":"2022-09-11T22:21:45.377368Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Nope ...\n\"**MOST**\" flags are letters ... We've seen \"FLAG{s4RaHc0Nnor<3<3}\" and 'FLAG(F0R3NS1C5_XD)' before ... Should we try some leetspeak ... D3FC0N ?","metadata":{}},{"cell_type":"code","source":"r = requests.post(URLS['flags'], json=json.dumps({\"challenge_id\": \"inference\", \"submission\": \"FLAG{D3FC0N}\"}))\nprint(r.text)","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:21:49.512393Z","iopub.execute_input":"2022-09-11T22:21:49.512722Z","iopub.status.idle":"2022-09-11T22:21:49.654266Z","shell.execute_reply.started":"2022-09-11T22:21:49.512697Z","shell.execute_reply":"2022-09-11T22:21:49.651737Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Task 19. DEEPFAKE.\n\nThe provided video is of a woman talking, her face sometimes glitches into a doglike image. \n\nRather than complex removal, we can trivially pass by putting a black box over the subjects face.\n\nCuriously, a locally run ffmpeg conversion works fine, but the kaggle notebook ffmpeg does not. So it is provided here as a file.\n\nThis ffmpeg code works local, not on kaggle:\n\n`!ffmpeg  -y -i sample.mp4 -vf \"drawbox=x=650:y=250:w=650:h=1000:color=black@1:t=fill\"  /tmp/sample.mp4`\n","metadata":{}},{"cell_type":"code","source":"with open(f\"../input/deepfake/sample.mp4\", \"rb\") as f:\n    data = f.read()\nb64_data = base64.urlsafe_b64encode(data).decode()\nin_data = json.dumps({\"input\": b64_data})\nr = requests.post(\"https://deepfake.fly.dev/score\", data=in_data)\nprint(r.text)","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:21:56.231634Z","iopub.execute_input":"2022-09-11T22:21:56.231973Z","iopub.status.idle":"2022-09-11T22:21:59.757928Z","shell.execute_reply.started":"2022-09-11T22:21:56.231949Z","shell.execute_reply":"2022-09-11T22:21:59.757187Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Task 20. WAF.\n\nWe are given the following information:\n- An ML model has been built to detect a 0-day. \n- This model is used by a Web Application Firewall to block malicious requests.\n- The WAF assesses malicious strings in 5 characters blocks.\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\nWe are given some started base64 of `mFzaC==` but of course this could be any 5 character subset of a longer string.\n\nWe know it is the end of the string because of the `==` padding. We can search for what character(s) may lie in front of this string.\n\nWe are told it is broken base64. We already know that, because base64 translates 3chars into 4chars and `mFzaC==` is 7 chars not the requisite 8 chars. We can try 1 char in front.","metadata":{}},{"cell_type":"code","source":"data = json.dumps({\"input\": \"mFzaC==\"})\nr = requests.post(\"https://waf.fly.dev/score\", data=data)\nprint(r.text)\n","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:22:05.111281Z","iopub.execute_input":"2022-09-11T22:22:05.111654Z","iopub.status.idle":"2022-09-11T22:22:05.272958Z","shell.execute_reply.started":"2022-09-11T22:22:05.111628Z","shell.execute_reply":"2022-09-11T22:22:05.272061Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for c in range(128): # try all ascii codes\n    base64_message = chr(c)+\"mFzaC==\"\n    base64_bytes = base64_message.encode('ascii')\n    try: # catch illegal base64\n        message_bytes = base64.b64decode(base64_bytes)\n        message = message_bytes.decode('ascii')\n        data = json.dumps({\"input\": base64_message})\n        r = requests.post(\"https://waf.fly.dev/score\", data=data)\n        print(message, r.text)\n    except:\n        pass\n","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:22:06.046653Z","iopub.execute_input":"2022-09-11T22:22:06.047021Z","iopub.status.idle":"2022-09-11T22:22:11.207234Z","shell.execute_reply.started":"2022-09-11T22:22:06.046996Z","shell.execute_reply":"2022-09-11T22:22:11.205797Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"`bash` is the only one that is malicious.\n\nA WAF is a Web application Firefall, design to stop threats by intercepting malicious instructions. I know about SQL injections, where remote code can be run, and this must be similar. `bash` is the command line shell in many unixes. \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\nSo let's discover what the 0-day is.\n\nA bit of googleing leads us to the **shellshock** 0-day:\n\nhttps://www.redhat.com/en/blog/bash-specially-crafted-environment-variables-code-injection-attack\n\nThe notice page has example text for testing your system.\n\n`env x='() { :;}; echo vulnerable' bash -c \"echo this is a test\"\n vulnerable`\n \n We can try that `() { :;}; echo vulnerable` as input.\n","metadata":{}},{"cell_type":"code","source":"    base64_message= \"() { :;}; echo vulnerable' bash -c \"\n    message_bytes = base64_message.encode('ascii')\n    base64_bytes = base64.b64encode(message_bytes)\n    base64_message = base64_bytes.decode('ascii')\n    print(base64_message)\n    data = json.dumps({\"input\": base64_message})\n\n    r = requests.post(\"https://waf.fly.dev/score\", data=data)\n    print(r.text)","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:22:16.956217Z","iopub.execute_input":"2022-09-11T22:22:16.957779Z","iopub.status.idle":"2022-09-11T22:22:17.116603Z","shell.execute_reply.started":"2022-09-11T22:22:16.957737Z","shell.execute_reply":"2022-09-11T22:22:17.115548Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We know that the WAF takes 5-char chunks, so maybe we can fool the WAF by adding a space at the front.","metadata":{}},{"cell_type":"code","source":"base64_message= \" () { :;}; echo vulnerable' bash -c \"\nmessage_bytes = base64_message.encode('ascii')\nbase64_bytes = base64.b64encode(message_bytes)\nbase64_message = base64_bytes.decode('ascii')\nprint(base64_message)\ndata = json.dumps({\"input\": base64_message})\n\nr = requests.post(\"https://waf.fly.dev/score\", data=data)\nprint(r.text)","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:22:22.480553Z","iopub.execute_input":"2022-09-11T22:22:22.4809Z","iopub.status.idle":"2022-09-11T22:22:22.655754Z","shell.execute_reply.started":"2022-09-11T22:22:22.480876Z","shell.execute_reply":"2022-09-11T22:22:22.654267Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Got it. But that took some thinking ... and googling.","metadata":{}},{"cell_type":"markdown","source":"# Task 21. CROP 1.\n\nThe task here is to minimise a scoring function.\n\nThere is a neural network, but it's only job is to spot a red circle. Then, the scoring function takes over.\n\nThe scoring is deliberately obfuscated. Our first job is to reverse engineer it. It is based on an array called `X_comp`.","metadata":{}},{"cell_type":"code","source":"import joblib\nX_comp = joblib.load(f\"../input/ai-village-ctf/crop/X_comp.pckl\")\nplt.imshow(X_comp)","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:22:28.270188Z","iopub.execute_input":"2022-09-11T22:22:28.270544Z","iopub.status.idle":"2022-09-11T22:22:28.494821Z","shell.execute_reply.started":"2022-09-11T22:22:28.27052Z","shell.execute_reply":"2022-09-11T22:22:28.493712Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"There are two colours, the background and the circle.\nThe scoring function divides this array by 10 (obfuscated as 0b1010), then subtracts it from 25.5, then multiplies it by 10. Let's see what the resulting colours are.\n","metadata":{}},{"cell_type":"code","source":"div = np.divide(X_comp,0b1010)\nbg_pixel = div[0,0]\ncircle_pixel = div[div.shape[0]//2,div.shape[0]//2]\nbg_col = 10*(25.5-bg_pixel)\ncircle_col = 10*(25.5-circle_pixel)\nprint(f'Background: {bg_col} Circle: {circle_col}')","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:22:29.883429Z","iopub.execute_input":"2022-09-11T22:22:29.883748Z","iopub.status.idle":"2022-09-11T22:22:29.894233Z","shell.execute_reply.started":"2022-09-11T22:22:29.883722Z","shell.execute_reply":"2022-09-11T22:22:29.892834Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"These are the scoring colours. The NN outputs the index of a 3x3 square. That square then gets matches against this circle pattern in these colours. Some trial and error shows the NN likes to choose idx==5, which is the middle row and rightmost column. We'll draw this magic square there.","metadata":{}},{"cell_type":"code","source":"sz=224 # standard NN input size\nradius=25 # from visual inspection of X_comp\ndilate=3 # a dilation was arrived at through trial and error ... 224 vs 225 issue?\nbg_col[0]-=28 # arrived at through trial and error ... can understand 230 not 251, but why 223?\nprint(bg_col)\nX = np.zeros((sz,sz,3),dtype=np.uint8)\nside = round(224//3)\nX[side-dilate:2*side+dilate,2*side:,:] = bg_col\nX = cv2.circle(X, (int(side*2+side/2),int(side+side/2)), radius, circle_col,-1) \nplt.imshow(X)\n","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:22:30.468891Z","iopub.execute_input":"2022-09-11T22:22:30.469525Z","iopub.status.idle":"2022-09-11T22:22:30.681239Z","shell.execute_reply.started":"2022-09-11T22:22:30.469497Z","shell.execute_reply":"2022-09-11T22:22:30.679741Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from PIL import Image as Im\n\ndef resize(X, size=(224,224)): # provided by organiser\n    X_new = np.array(Im.fromarray(X.astype(np.uint8)).resize(size, resample=3))\n    return X_new\n\ndef get_ij(idx): # provided by organiser\n    idx = 8-idx\n    j = idx//3\n    i = 2-idx%3\n    return i, j\n\ndef get_cropped(X, idx): # provided by organiser\n    n = 75*3\n    i, j = get_ij(idx)\n    crop = resize(X,(n,n))[j*75:(j+1)*75,i*75:(i+1)*75,:]\n    return crop\n\nidx = 5\nexpected = ((25.5-np.divide(X_comp,0b1010))).flatten()\nactual_square = get_cropped(X, idx)\nactual = actual_square.flatten().astype(int)\nsse = ((actual-(expected*10).astype(int))**2).sum()\nredness = (actual_square[:,:,0][actual_square[:,:,0]>230]**2).sum()+1\nscore = sse*redness\nprint(f'Score in millions: {score/1e6}')","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:22:30.683011Z","iopub.execute_input":"2022-09-11T22:22:30.683244Z","iopub.status.idle":"2022-09-11T22:22:30.696826Z","shell.execute_reply.started":"2022-09-11T22:22:30.683222Z","shell.execute_reply":"2022-09-11T22:22:30.695542Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_submittable(x): # provided by organiser\n    return \",\".join([str(int(n)) for n in x])\n\nr = requests.post(URLS['crop1'], data=json.dumps({\"data\": get_submittable(X.flatten())}))\nprint(r.text)","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:22:31.128625Z","iopub.execute_input":"2022-09-11T22:22:31.129396Z","iopub.status.idle":"2022-09-11T22:22:31.712865Z","shell.execute_reply.started":"2022-09-11T22:22:31.129357Z","shell.execute_reply":"2022-09-11T22:22:31.711104Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Task 22. CROP 2.\n\nCan I find the specific poisoned sample? Identify the entire grid of colors (squares and circles and respective postiions) correctly. Flag 2 expects two json lists (\"squares\" and \"circles\").\n\nA sample image is provided.","metadata":{}},{"cell_type":"code","source":"im = plt.imread('../input/ai-village-ctf/crop/sample_image.png')\nplt.imshow(im)","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:25:17.908077Z","iopub.execute_input":"2022-09-11T22:25:17.908414Z","iopub.status.idle":"2022-09-11T22:25:18.123109Z","shell.execute_reply.started":"2022-09-11T22:25:17.908389Z","shell.execute_reply":"2022-09-11T22:25:18.122055Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"And there is sample data that looks like this:","metadata":{}},{"cell_type":"code","source":"ref1_json = {\n    \"circles\": [\n        255,127,0, 55,126,184, 153,153,153, 247,129,191, 228,26,28, 77,175,74, 255,255,51, 152,78,163, 166,86,40\n    ],\n    \"squares\": [\n        247,129,191, 255,127,0, 255,255,51, 77,175,74, 153,153,153, 166,86,40, 55,126,184, 228,26,28, 152,78,163\n    ]\n}\n\n# btt kludgy\np = []\nq = []\nfor i in range(len(ref1_json['circles'])//3):\n    qq = ref1_json['circles']\n    jj = ref1_json['squares']\n    p.append([jj[i*3], jj[i*3+1], jj[i*3+2]])\n    q.append([qq[i*3], qq[i*3+1], qq[i*3+2]])\n    \nprint(p)\nprint(q)","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:27:25.977241Z","iopub.execute_input":"2022-09-11T22:27:25.977586Z","iopub.status.idle":"2022-09-11T22:27:25.988908Z","shell.execute_reply.started":"2022-09-11T22:27:25.97756Z","shell.execute_reply":"2022-09-11T22:27:25.987412Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import matplotlib.image\nfrom skimage import draw\n\ndef render_image(p, q):\n    sz = 224\n    img = np.zeros((sz,sz,3), dtype = np.uint8)\n\n    f = round(sz/3)\n    f2= round(sz/6)\n    r = round(sz/9)+1\n    img[2*f:,:f,:] = p[0]\n    img[2*f:,f:2*f,:] = p[1]\n    img[2*f:,2*f:,:] = p[2]\n    \n    img[f:2*f,:f,:] = p[3]\n    img[f:2*f,f:2*f,:] = p[4]\n    img[f:2*f,2*f:,:] = p[5]\n\n    img[:f,:f,:] = p[6]\n    img[:f,f:2*f,:] = p[7]\n    img[:f,2*f:,:] = p[8]\n\n    img[draw.disk((f2+2*f, f2), radius=r)] = q[0]\n    img[draw.disk((f2+2*f, f2+f), radius=r)] = q[1]\n    img[draw.disk((f2+2*f, f2+2*f), radius=r)] = q[2]\n    \n    img[draw.disk((f2+f, f2), radius=r)] = q[3]\n    img[draw.disk((f2+f, f2+f), radius=r)] = q[4]\n    img[draw.disk((f2+f, f2+2*f), radius=r)] = q[5]\n    \n    img[draw.disk((f2, f2), radius=r)] = q[6]\n    img[draw.disk((f2, f2+f), radius=r)] = q[7]\n    img[draw.disk((f2, f2+2*f), radius=r)] = q[8]\n\n    #img = cv2.resize(img,(224,224))\n    \n    return img","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:28:11.910453Z","iopub.execute_input":"2022-09-11T22:28:11.910892Z","iopub.status.idle":"2022-09-11T22:28:11.964077Z","shell.execute_reply.started":"2022-09-11T22:28:11.910805Z","shell.execute_reply":"2022-09-11T22:28:11.963041Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img = render_image(p,q)\nplt.imshow(img)","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:28:36.036561Z","iopub.execute_input":"2022-09-11T22:28:36.036966Z","iopub.status.idle":"2022-09-11T22:28:36.282594Z","shell.execute_reply.started":"2022-09-11T22:28:36.036941Z","shell.execute_reply":"2022-09-11T22:28:36.281654Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"A bit of googling discovers this palette is from `cufflinks` and maybe else where?\n\nIt possibly means that the palette is fixed, and only varied by the poison. As seen here ...","metadata":{}},{"cell_type":"code","source":"import cufflinks as cf\ncf.get_scales('set1')","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:31:18.691991Z","iopub.execute_input":"2022-09-11T22:31:18.692372Z","iopub.status.idle":"2022-09-11T22:31:18.700065Z","shell.execute_reply.started":"2022-09-11T22:31:18.692342Z","shell.execute_reply":"2022-09-11T22:31:18.698765Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"colorscale=cf.colors.get_scales('set1')\ncf.colors.color_table(colorscale)","metadata":{"execution":{"iopub.status.busy":"2022-09-11T22:31:42.406335Z","iopub.execute_input":"2022-09-11T22:31:42.406677Z","iopub.status.idle":"2022-09-11T22:31:42.414892Z","shell.execute_reply.started":"2022-09-11T22:31:42.406652Z","shell.execute_reply":"2022-09-11T22:31:42.413085Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The task must be about reducing search spaces but I failed to get the flag.\n\nI varied the palette colours in all combinations.\n\nThen I ran 16M solutions (!!!!) through the server changing the idx=4 (central red circle) to all 255x255x255 colours.\n\nI ran the model locally, but it only had 31% accuracy, mostly choosing idx=5. The logic was to run a colour locally, and if it predicted the same as true label, disregard it.\n\nI stil think it'll be a simple answer like this. I just didn't land on it and didn't have enough brute force time.","metadata":{}},{"cell_type":"markdown","source":"So that's the end. Got all flags but 1.","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}