From 73d17d69f3d832e0f9bc051355b7e422b6489c45 Mon Sep 17 00:00:00 2001 From: Yigit Colakoglu Date: Mon, 25 Oct 2021 17:06:41 +0200 Subject: [PATCH] Added new writeup --- .../stmctf-2021-eartoear-coding-writeup.md | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 content/posts/stmctf-2021-eartoear-coding-writeup.md diff --git a/content/posts/stmctf-2021-eartoear-coding-writeup.md b/content/posts/stmctf-2021-eartoear-coding-writeup.md new file mode 100644 index 0000000..27b738c --- /dev/null +++ b/content/posts/stmctf-2021-eartoear-coding-writeup.md @@ -0,0 +1,185 @@ ++++ +title = "STM CTF 2021 Ear To Ear Coding Writeup" +date = "2021-10-25T09:49:22+02:00" +author = "Yigit Colakoglu" +authorTwitter = "theFr1nge" +cover = "" +tags = ["ctf", "coding", "numpy", "stmctf2021"] +keywords = ["ctf", "cybersecurity", "coding", "python"] +description = "The writeup to the Ear to Ear coding challenge on STM CTF 2021" +showFullContent = false ++++ + +For the challenge, we are given a web server that provides the json data: + +```json +{ + "word": "hidden", + "sequence": "938658411126141947251193886" +} + +``` + +The server also has an endpoint where you can submit an answer and the server +would respond with the flag. We are also given a file, called *vectors.txt* +that contains one word, and a set of signed decimal numbers. You can download +the file [here](https://yeetstore.s3.eu-central-1.amazonaws.com/vectors.txt) + +OK, that is a cryptic question, thankfully the creators of the question +realized this and provided a hint. + +> Each vector represents the corresponding word in a vector space where the +> similar words are closer to each other. You can use the cosine distances +> between vectors to find the nth most similar words. You are given a sequence +> of numbers and a word as a starting point. To find the flag, you need to +> traverse the words using the nth most similar words, based on the given +> sequence. +> +> Dummy Example (Similar words are chosen randomly, for demonstration): Word: +> Apple Sequence: 213 The 2nd most similar word of 'apple' -> 'pie' The 1st +> most similar word of 'pie' -> 'cake' The 3rd most similar word of 'cake' -> +> 'chocolate' Send chocolate to api to retrieve the flag. + +Now that we know what the question is about, it is actually pretty easy. All we +need to do is save each vector on a numpy array, and compare the vector of the +word that is provided to us on the web server with every single vector and get +the n+1th vector that has the smallest distance with the word(because the +closest neighbour to the word would be itself). Lucky for us, we can calculate +the cosine distance if each vector in two matrices using the +(scipy.spatial.distance)[https://docs.scipy.org/doc/scipy/reference/spatial.distance.html] +and numpy's +(argsort)[https://numpy.org/doc/stable/reference/generated/numpy.argsort.html]. +Here is the function that return's n'th closest neighbour to a vector in a vector space. + +```py +def getnclosest(v,n): + distances = distance.cdist([v], vectors, 'cosine') + p = np.argsort(distances) + return p[0][n-1] +``` + +Now that we have the basic function, we can easily write some wrapper code that will allow us to +find the resulting word. Here is the full code that should do that: + +```py +from scipy.spatial import distance +from tqdm import tqdm +import numpy as np + +def vectortostr(vec): + vs = "" + for i in range(len(vec)): + vs += str(vec[i]) + if i != len(vec) - 1: + vs += " " + return vs + + +f = open("vectors.txt", "r") + +l1 = f.readline() + +y = int(l1.split(" ")[0]) +x = int(l1.split(" ")[1]) + +vectors = [None]*y + +wordvectormap = {} + +vectorwordmap = {} + +print("[INFO]: Loading vectors from file.") +for i in tqdm(range(y)): + newlist = [None]*(x) + l = f.readline().split(" ") + for j in range(1,x+1): + newlist[j-1] = float(l[j]) + + vectors[i] = newlist + wordvectormap[l[0]] = newlist + vectorwordmap[vectortostr(newlist)] = l[0] + +vectors = np.array(vectors) + +print("[INFO]: Done loading vectors. Dropping you into a shell") + +def getnclosest(v,n): + distances = distance.cdist([v], vectors, 'cosine') + np.fill_diagonal(distances, np.inf) + return p[0][n-1] + +cmd = 0 + +while cmd != "exit": + try: + cmd = input(">> ") + cmdparts = cmd.split(" ") + if cmdparts[0] == "search": + vector = [None]*(x) + for i in range(1, x+1): + vector[i-1] = float(cmdparts[i]) + + dist, index = tree.query(vector, k=[int(cmdparts[-1])]) + v = tree.data[index][0] + print(vectorwordmap[vectortostr(v)]) + + elif cmdparts[0] == "eartoear": + w = cmdparts[1] + vector = np.array(wordvectormap[w]) + sequence = cmdparts[2] + print("START: " + w) + for i in sequence: + cindex = getnclosest(vector, int(i)+1) + vector = vectors[cindex] + w = vectorwordmap[vectortostr(vector)] + print("END: " + w) + + + except KeyboardInterrupt: + break + except Exception as e: + print(str(e)) + +print("\nBye Bye") +``` + +When we run the code, here is the result: + +``` +[INFO]: Loading vectors from file. +100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1193514/1193514 [00:10<00:00, 112756.85it/s] +[INFO]: Done loading vectors. Dropping you into a shell +>> eartoear hidden 938658411126141947251193886 +START: hidden +brings, 9 +gives, 3 +could, 8 +n't, 6 +if, 5 +know, 8 +n't, 4 +think, 1 +n't, 1 +think, 1 +know, 2 +mean, 6 +know, 1 +n't, 4 +think, 1 +either, 9 +unless, 4 +because, 7 +means, 2 +everything, 5 +something, 1 +everything, 1 +means, 9 +unless, 3 +matter, 8 +difference, 8 +reasons, 6 +END: reasons +>> +``` + +And after we submit, we get the flag: `STMCTF{Je0pardy_w@ts0n}`. Nice!