You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

187 lines
5.3 KiB

3 years ago
  1. +++
  2. title = "STM CTF 2021 Ear To Ear Coding Writeup"
  3. date = "2021-10-25T09:49:22+02:00"
  4. author = "Yigit Colakoglu"
  5. authorTwitter = "theFr1nge"
  6. cover = ""
  7. tags = ["ctf", "coding", "numpy", "stmctf2021"]
  8. keywords = ["ctf", "cybersecurity", "coding", "python"]
  9. description = "The writeup to the Ear to Ear coding challenge on STM CTF 2021"
  10. showFullContent = false
  11. +++
  12. The writeup to the Ear to Ear coding challenge on STM CTF 2021
  13. For the challenge, we are given a web server that provides the json data:
  14. ```json
  15. {
  16. "word": "hidden",
  17. "sequence": "938658411126141947251193886"
  18. }
  19. ```
  20. The server also has an endpoint where you can submit an answer and the server
  21. would respond with the flag. We are also given a file, called *vectors.txt*
  22. that contains one word, and a set of signed decimal numbers. You can download
  23. the file [here](https://yeetstore.s3.eu-central-1.amazonaws.com/vectors.txt)
  24. OK, that is a cryptic question, thankfully the creators of the question
  25. realized this and provided a hint.
  26. > Each vector represents the corresponding word in a vector space where the
  27. > similar words are closer to each other. You can use the cosine distances
  28. > between vectors to find the nth most similar words. You are given a sequence
  29. > of numbers and a word as a starting point. To find the flag, you need to
  30. > traverse the words using the nth most similar words, based on the given
  31. > sequence.
  32. >
  33. > Dummy Example (Similar words are chosen randomly, for demonstration): Word:
  34. > Apple Sequence: 213 The 2nd most similar word of 'apple' -> 'pie' The 1st
  35. > most similar word of 'pie' -> 'cake' The 3rd most similar word of 'cake' ->
  36. > 'chocolate' Send chocolate to api to retrieve the flag.
  37. Now that we know what the question is about, it is actually pretty easy. All we
  38. need to do is save each vector on a numpy array, and compare the vector of the
  39. word that is provided to us on the web server with every single vector and get
  40. the n+1th vector that has the smallest distance with the word(because the
  41. closest neighbour to the word would be itself). Lucky for us, we can calculate
  42. the cosine distance if each vector in two matrices using the
  43. (scipy.spatial.distance)[https://docs.scipy.org/doc/scipy/reference/spatial.distance.html]
  44. and numpy's
  45. (argsort)[https://numpy.org/doc/stable/reference/generated/numpy.argsort.html].
  46. Here is the function that return's n'th closest neighbour to a vector in a vector space.
  47. ```py
  48. def getnclosest(v,n):
  49. distances = distance.cdist([v], vectors, 'cosine')
  50. p = np.argsort(distances)
  51. return p[0][n-1]
  52. ```
  53. Now that we have the basic function, we can easily write some wrapper code that will allow us to
  54. find the resulting word. Here is the full code that should do that:
  55. ```py
  56. from scipy.spatial import distance
  57. from tqdm import tqdm
  58. import numpy as np
  59. def vectortostr(vec):
  60. vs = ""
  61. for i in range(len(vec)):
  62. vs += str(vec[i])
  63. if i != len(vec) - 1:
  64. vs += " "
  65. return vs
  66. f = open("vectors.txt", "r")
  67. l1 = f.readline()
  68. y = int(l1.split(" ")[0])
  69. x = int(l1.split(" ")[1])
  70. vectors = [None]*y
  71. wordvectormap = {}
  72. vectorwordmap = {}
  73. print("[INFO]: Loading vectors from file.")
  74. for i in tqdm(range(y)):
  75. newlist = [None]*(x)
  76. l = f.readline().split(" ")
  77. for j in range(1,x+1):
  78. newlist[j-1] = float(l[j])
  79. vectors[i] = newlist
  80. wordvectormap[l[0]] = newlist
  81. vectorwordmap[vectortostr(newlist)] = l[0]
  82. vectors = np.array(vectors)
  83. print("[INFO]: Done loading vectors. Dropping you into a shell")
  84. def getnclosest(v,n):
  85. distances = distance.cdist([v], vectors, 'cosine')
  86. np.fill_diagonal(distances, np.inf)
  87. return p[0][n-1]
  88. cmd = 0
  89. while cmd != "exit":
  90. try:
  91. cmd = input(">> ")
  92. cmdparts = cmd.split(" ")
  93. if cmdparts[0] == "search":
  94. vector = [None]*(x)
  95. for i in range(1, x+1):
  96. vector[i-1] = float(cmdparts[i])
  97. dist, index = tree.query(vector, k=[int(cmdparts[-1])])
  98. v = tree.data[index][0]
  99. print(vectorwordmap[vectortostr(v)])
  100. elif cmdparts[0] == "eartoear":
  101. w = cmdparts[1]
  102. vector = np.array(wordvectormap[w])
  103. sequence = cmdparts[2]
  104. print("START: " + w)
  105. for i in sequence:
  106. cindex = getnclosest(vector, int(i)+1)
  107. vector = vectors[cindex]
  108. w = vectorwordmap[vectortostr(vector)]
  109. print("END: " + w)
  110. except KeyboardInterrupt:
  111. break
  112. except Exception as e:
  113. print(str(e))
  114. print("\nBye Bye")
  115. ```
  116. When we run the code, here is the result:
  117. ```
  118. [INFO]: Loading vectors from file.
  119. 100%|██████████████████████████████████████████████████████████████████| 1193514/1193514 [00:10<00:00, 112756.85it/s]
  120. [INFO]: Done loading vectors. Dropping you into a shell
  121. >> eartoear hidden 938658411126141947251193886
  122. START: hidden
  123. brings, 9
  124. gives, 3
  125. could, 8
  126. n't, 6
  127. if, 5
  128. know, 8
  129. n't, 4
  130. think, 1
  131. n't, 1
  132. think, 1
  133. know, 2
  134. mean, 6
  135. know, 1
  136. n't, 4
  137. think, 1
  138. either, 9
  139. unless, 4
  140. because, 7
  141. means, 2
  142. everything, 5
  143. something, 1
  144. everything, 1
  145. means, 9
  146. unless, 3
  147. matter, 8
  148. difference, 8
  149. reasons, 6
  150. END: reasons
  151. >>
  152. ```
  153. And after we submit, we get the flag: `STMCTF{Je0pardy_w@ts0n}`. Nice!