I must say I enjoy a good puzzle as much as the next Kusto Detective, but this week was a real head scratcher.
For this challenge I found the clues to be of almost no help at all, the “train me” though is critical as is having a lot of patience for the final riddle.
Challenge: Case 6
Hey there! I’ve got some juicy details for you regarding the elusive https://kuanda.org
So, the bad news is that despite my best efforts, I still don’t have a ton of info on these guys. But, the good news is that I did stumble upon a lead that might just crack this case wide open! You ready for this? Kuanda.org isn’t just some run-of-the-mill fishing organization. Nope, my sources tell me it’s a brand spanking new cyber organization that’s all about digital data repositories. Talk about cutting-edge technology, am I right?
But wait, it gets even better. They’ve been recruiting cyber-crime specialists like there’s no tomorrow. Which means that this organization is serious about their work, and they have something big planned. And here’s the kicker – every new member has to spend a week at the National Gallery of Art! Yeah, you heard that right. The same National Gallery of Art that houses all those fancy paintings and sculptures. What could they possibly be doing in there for a whole week? Studying Leonardo da Vinci’s brushstrokes? I smell something fishy, and it’s not just the art restoration chemicals.
And to top it all off, my sources managed to snag some instructions for the new recruiters. If you can decode them, you might just have a shot at infiltrating their system and finding out what they’re really up to. Who knows, you might even find the smoking gun that proves they’re behind all those cyber-crimes. Good luck, detective – I sense you will need one!
Decrypting the message is the first stop and there are a couple of different ways to do this, I took a more manual approach due to early morning lack of coffee.
You can take a basic start with the extract_all command or you can tackle the entire message at once with replace_string. Either way works and you’ll end up with an interesting yet cryptic message
Solution – Spoilers below
The community keeps growing around these challenges, I was part of many debates as tot he meaning of certain phrases in the secret message so lets have a look.
//This is the manual way by substitution each pair of numbers into the ObjectId and Index section of the query
| where ObjectId == “46081”
| extend Words=extract_all(@'(\w+)’, ProvenanceText)
| mv-expand with_itemindex=Index Words
| project Index, Words
| where Index == 105
//Here is the much smarter way of doing this which I must credit to Aviv Yaniv who operates much better on far less coffee than I do.
let RecruitInstructions =
“`12204/497 62295/24 50883/678 47108/107 193867/3,
45534/141 hidden 100922/183 143461/1 1181/505 46187/380.
41526/155 66447/199 30241/114, 33745/154 12145/387 46437/398 177191/131:
293/64 41629/1506 210038/432, 41612/803 216839/1.
404/258 rules 40/186 1472/222 122894/2 46081/105:
41594/650 32579/439 44625/141 184121/19 33254/348 357/273 32589/821,
46171/687 punctuations 62420/10 50509/48 1447/128,
176565/82’56721/591 561/225 insensitive, 30744/129 76197/32.
1319/42 41599/216 68/457 136016/146, 42420/126’46198/389 42429/158 40091/108 41667/252,
1515/555 177593/223 176924/73 45889/65 159836/96 35080/384 32578/199.
1607/167 124996/9 71/56, 1303/187 45640/1114 72328/247 75802/11,
1168/146 163380/12 57541/116 206122/738 365/267 46026/211 46127/19.
119295/425 45062/128 12198/133 163917/238 45092/8 54183/4 42453/82:
561/433 9/387 37004/287 1493/118 41676/38 163917/238 3159/118 63264/687
1/905 1493/109 43723/252, 136355/1 1159/134 40062/172 32588/604,
158574/1 45411/8 10/892 127587/175 – 633/9 72328/247 1514/615 42940/138.
164958/84 221014/479 151526/7 111124/138, 41668/206 34109/46 1514/555,
147789/2 3228/152 993/323 166477/167 178042/167, 50753/91’207786/8 12/372.
1108/158’42423/150 12/309 66154/9 213566/11 44981/158 1197/300
40184/149 92994/63-71071/179 75093/7 211718/18 74211/5 46144/399.“`;
let ProvenanceTextWords = materialize(
| extend Tokens = extract_all(@'(\w+)’, ProvenanceText));
let CleanedRecruitInstructions = replace_string(
“-“, ” – “),
“‘”, ” ‘ “),
“,”, ” , “),
“\r\n”, ” \n “),
“:”, ” : “);
let CleanedRecruitInstructionsTokens = split(CleanedRecruitInstructions, ” “);
let TokensCount = array_length(CleanedRecruitInstructionsTokens);
let DecipheredRaw = tostring(toscalar(
range token_index from 0 to TokensCount step 1
| extend original_token = CleanedRecruitInstructionsTokens[token_index]
| extend cipher_indexes = split(original_token, “/”)
| extend should_translate = array_length(cipher_indexes) == 2
| extend ObjectId = tolong(cipher_indexes)
| extend WordIndex = tolong(replace_string(tostring(cipher_indexes), “,”, “”))
| join kind=leftouter ProvenanceTextWords on ObjectId
| extend Word = iff(should_translate, Tokens[WordIndex], original_token)
| sort by token_index asc
| summarize array_strcat(make_list(Word), ” “)));
let Deciphered = replace_string(replace_string(replace_string(replace_string(DecipheredRaw, ” : “, “:”), ” ‘ “, “‘”), ” , “, “, “), ” – “, “-“);
in catalogue of titles Grand,
three hidden words Demand your Hand
when found all, they form A line:
A clear timeline, simply Fine
words rules are simple to Review:
at least three Letters have in view,
all punctuations Mark the End,
they’re case insensitive, my friend
to find all words, you’ll need some skill,
seeking the popular will guide you still
below The King, the first word mounts,
the Second shares with Third their counts
reveal the last word with Wise thought:
take first two letters from word most sought
into marked dozen, and change just one,
and with those two – the word is done
so search the titles, high and low,
and when you find it, you’ll know
you’ve picked the Image that revealed
the pass-code to the World concealed
Now a word of warning, how you proceed here could lead you down many hours of dead ends because depending on how you sort the words you might not get the right list to work off. Let’s break this down, section by section.
1. We’re looking for three words that form a timeline
2. The words are case insensitive, at least 3 letters long and end in punctuation
3. The first word is below King
4. The second word shares it’s count with the word Third (this took me longer than I’d care to admit)
5. The third word needs us to do some work with the first and 12th word
6. We’re looking for an image what will reveal a passcode
//As I mentioned before sorting this correctly, makes a world of difference, my solution was anything but elegant so in this case I’d rather present this steamlined piece of art curtesy of Aviv
let punctuations = “`[[:punct:]]“`;
| extend TitleUnified = toupper(Title)
| extend TitleWords = extract_all(@'(\w+)’, TitleUnified)
| mv-expand TitleWords to typeof(string)
| extend punctuation_index = indexof_regex(TitleWords, punctuations)
| extend last_index = iff(punctuation_index>=0, punctuation_index,
| extend Word = substring(TitleWords, 0, last_index)
| where strlen(Word) >= 3
| summarize WordCount=count() by Word
| sort by WordCount desc
//look at that a timeline we can look for, along with a strange piece of art https://api.nga.gov/iiif/64c9eb07-5e01-40fe-8fd0-886cfb4a70c7/full/!900,900/0/default.jpg
| where Title has_all(“day”, “month”, “year”)
We’re not done yet, lets head over to Kuanda.org and see what we can find. In order to join the club we need a passcode
What happens if we use our image as the login hint?
A-ha! We’re onto something now, lets try unscrambling the letters being held by the octopus and we get the nefarious stopkusto!
And we’re in and can see all the trickery that Kuanda has been up to so far including the name of their leader.
Another case solved Detectives well done!
This case was very challenging, and a lot of the difficulty comes down the interpretation of the riddle, even assuming that all your KQL was spot on, making the wrong assumptions about which words to look for could take hours of hunting. While I did feel like I needed to hand in my Kusto card for a while I did get there eventually.