How about a game for REAL programmers? (1)
-
@cartman82 said in How about a game for REAL programmers:
How about a game for REAL programmers?
That reminds me, I've been working on a puzzle that Bungie released related to the new Destiny expansion. It's a cryptic puzzle with little explanation. Maybe one of you guys will find it fun too
It's pretty involved, so bear with me. I'll post the info we have to go on in this first post, and then how far I've got as a reply.
If you have a Destiny account, the puzzle starts when you visit the following site: https://www.bungie.net/en/net/sim
You get presented with something like this:
So we can see it's some sort of puzzle to get at some encrypted data.
We're told that it's encrypted using AES in CBC mode. We're given a full 16 byte IV, but we're only given 4 bytes of the key. Somehow we need to get the other 28 bytes of the key so we can decrypt the data.
The community created a spreadsheet of keys and values to help us out. https://docs.google.com/spreadsheets/d/1B8KmXWy0_bTpw_52m5r7-yYJxwfTJS1oyqDn5gKn6rk/edit#gid=1290440783
And that's all of the information you have to go on to solve the puzzle. I'm going to post a spoiler-full reply that shows how far I've managed to get. I've successfully decrypted several payloads, but I can't make sense of them. Details to follow....
-
If it's anything like the rest of Destiny... it actually contains the plot, including the entire events of the next game.
-
Here are details of what I've managed to do so far... TLDR: I figured out how to assemble the key for the decryption, but now cannot make sense of the decrypted data.
OK, so the first job is to assemble the key.
We're given a node which is a bunch of symbols ( ), and we're given an "Information Forwarding Protocol" that involves the same symbols.
You can see my "forwarding protocol" looks like this:
This forwarding protocol describes what transformation we need to make to our current node to calculate the value of the next node.
In this case, we need to mutate the second symbol. The second symbol is a square, and according to the diagram, the next value for that is a circle.
This means the next node is Diamond, Circle, Square, Circle, Diamond.
The next node will then have its own set of rules on how to calculate the subsequent node after that.
Shit, that means I can't get any further on my own
Community spreadsheet to the rescue
Luckily, someone create a google form so people can submit their values into a spreadsheet, so we've got a nice big data set we can use to solve this puzzle.
The spreadsheet is available here.
Processing the data
I decided to write a program that would take the data table, and join each node onto the next node, until we had a full 32 byte key.
void Main() { string path = @"C:\Users\doctor.jones\Downloads\Data Collection for SIVA ARG (Responses) - Form Responses 1.csv"; string outputPath = string.Format(@"E:\Destiny\{0:yyyyMMddHHmmss}", DateTime.Now); var sheet1 = new LinqToExcel.ExcelQueryFactory(path); var sheet1Rows = sheet1.Worksheet(0); var rows = sheet1Rows.ToList(); var typedSheet = from e in rows let row = new { Timestamp = e[0].ToString(), Username = e[1].ToString(), Node1 = e[2].ToString(), Node2 = e[3].ToString(), Node3 = e[4].ToString(), Node4 = e[5].ToString(), Node5 = e[6].ToString(), Node = e[7].ToString(), NextNode1 = e[8].ToString(), NextNode2 = e[9].ToString(), NextNode3 = e[10].ToString(), NextNode4 = e[11].ToString(), NextNode5 = e[12].ToString(), NextNode = e[13].ToString(), ProcessingKey = TryConvert(e[14].ToString()), InitializationVector = e[15].ToString(), EncryptedInformation = e[16].ToString(), } where row.ProcessingKey.Length > 0 && row.Node != row.NextNode select new { row.Timestamp, row.Username, row.Node1, row.Node2, row.Node3, row.Node4, row.Node5, row.Node, ActualNode = row.Node1.Substring(0, 1) + row.Node2.Substring(0, 1) + row.Node3.Substring(0, 1) + row.Node4.Substring(0, 1) + row.Node5.Substring(0, 1), row.NextNode1, row.NextNode2, row.NextNode3, row.NextNode4, row.NextNode5, row.NextNode, ActualNextNode = row.NextNode1.Substring(0, 1) + row.NextNode2.Substring(0, 1) + row.NextNode3.Substring(0, 1) + row.NextNode4.Substring(0, 1) + row.NextNode5.Substring(0, 1), row.ProcessingKey, row.InitializationVector, row.EncryptedInformation, }; var firstRecord = typedSheet.First(); var dictionary = CreateDictionary(firstRecord.Node, firstRecord.ToEnumerable().ToList()); foreach (var record in typedSheet) { dictionary.AddToListValue(record.Node, record); //dictionary.AddToListValue(record.ActualNode, record); } var query = from node1 in typedSheet from node2 in dictionary.GetListValue(node1.NextNode, node1.ActualNextNode) from node3 in dictionary.GetListValue(node2.NextNode, node2.ActualNextNode) from node4 in dictionary.GetListValue(node3.NextNode, node3.ActualNextNode) from node5 in dictionary.GetListValue(node4.NextNode, node4.ActualNextNode) from node6 in dictionary.GetListValue(node5.NextNode, node5.ActualNextNode) from node7 in dictionary.GetListValue(node6.NextNode, node6.ActualNextNode) from node8 in dictionary.GetListValue(node7.NextNode, node7.ActualNextNode) select new { Node1 = node1.Node, Node2 = node2.Node, Node3 = node3.Node, Node4 = node4.Node, Node5 = node5.Node, Node6 = node6.Node, Node7 = node7.Node, Node8 = node8.Node, Key = (new [] { node1.ProcessingKey, node2.ProcessingKey, node3.ProcessingKey, node4.ProcessingKey, node5.ProcessingKey, node6.ProcessingKey, node7.ProcessingKey, node8.ProcessingKey, }).SelectMany(k => k).ToArray(), node1.EncryptedInformation, node1.InitializationVector, }; var resultList = query.ToList(); resultList = resultList.Distinct().ToList(); resultList.Count.Dump(); var firstResult = resultList.First(); var successfulResults = new { Node = firstResult.Node1, Decrypted = new byte[] {}, }.ToEnumerable().ToList(); successfulResults.Clear(); foreach (var result in resultList) { try { var decrypted = DecryptData(result.Key, result.InitializationVector, result.EncryptedInformation); successfulResults.Add(new { Node = result.Node1, Decrypted = decrypted }); } catch {} } successfulResults.Dump(); foreach (var success in successfulResults) { string fileName = string.Format("{0}.binary", success.Node, Guid.NewGuid()); var outputFile = new FileInfo(Path.Combine(outputPath, fileName)); if (!outputFile.Directory.Exists) { outputFile.Directory.Create(); } using (var stream = outputFile.Create()) { stream.Write(success.Decrypted, 0, success.Decrypted.Length); } } } public byte[] DecryptData(byte[] key, string initVector, string encrypted) { var dataBytes = Convert.FromBase64String(encrypted); var initVectorBytes = Convert.FromBase64String(initVector); RijndaelManaged symmetricKey = new RijndaelManaged { Key = key, IV = initVectorBytes, Mode = CipherMode.CBC, }; var decryptor = symmetricKey.CreateDecryptor( key, initVectorBytes ); using(var memoryStream = new MemoryStream(dataBytes)) using(var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) { var decrypted = cryptoStream.ReadFully(); return decrypted; } } public byte[] TryConvert(string key) { try { return Convert.FromBase64String(key); } catch {} return new byte[0]; } private Dictionary<TKey, TValue> CreateDictionary<TKey, TValue>(TKey exampleKey, TValue exampleValue) { return new Dictionary<TKey, TValue>(); } public static class StreamExtensions { public static byte[] ReadFully(this Stream input) { byte[] buffer = new byte[16*1024]; using (var ms = new MemoryStream()) { int read; while ((read = input.Read(buffer, 0, buffer.Length)) > 0) { ms.Write(buffer, 0, read); } return ms.ToArray(); } } } public static class DictionaryExtensions { public static IEnumerable<TValue> GetListValue<TKey, TValue>(this Dictionary<TKey, List<TValue>> dictionary, TKey key, TKey key2) where TKey : class { return dictionary.GetListValue(key); if (key != key2) { return dictionary.GetListValue(key).Concat(dictionary.GetListValue(key2)); } } public static IEnumerable<TValue> GetListValue<TKey, TValue>(this Dictionary<TKey, List<TValue>> dictionary, TKey key) { if (!dictionary.ContainsKey(key)) { return Enumerable.Empty<TValue>(); } return dictionary[key]; } public static void AddToListValue<TKey, TValue>(this Dictionary<TKey, List<TValue>> dictionary, TKey key, TValue value) { if (!dictionary.ContainsKey(key)) { dictionary[key] = value.ToEnumerable().ToList(); } else { dictionary[key].Add(value); } } }
This code uses LINQ to Excel to allow me to write a LINQ query to join the nodes together. The bulk of the work is done in this section:
var query = from node1 in typedSheet from node2 in dictionary.GetListValue(node1.NextNode, node1.ActualNextNode) from node3 in dictionary.GetListValue(node2.NextNode, node2.ActualNextNode) from node4 in dictionary.GetListValue(node3.NextNode, node3.ActualNextNode) from node5 in dictionary.GetListValue(node4.NextNode, node4.ActualNextNode) from node6 in dictionary.GetListValue(node5.NextNode, node5.ActualNextNode) from node7 in dictionary.GetListValue(node6.NextNode, node6.ActualNextNode) from node8 in dictionary.GetListValue(node7.NextNode, node7.ActualNextNode) select new { Node1 = node1.Node, Node2 = node2.Node, Node3 = node3.Node, Node4 = node4.Node, Node5 = node5.Node, Node6 = node6.Node, Node7 = node7.Node, Node8 = node8.Node, Key = (new [] { node1.ProcessingKey, node2.ProcessingKey, node3.ProcessingKey, node4.ProcessingKey, node5.ProcessingKey, node6.ProcessingKey, node7.ProcessingKey, node8.ProcessingKey, }).SelectMany(k => k).ToArray(), node1.EncryptedInformation, node1.InitializationVector, };
Seeing as I was joining the dataset to itself 8 times, I decided to put all of the nodes into a dictionary to improve the performance of the lookup. I then join the 8 keys together to give myself a full 32 byte key.
Then it's just a simple job of decrypting the information.
Bad data
Unfortunately, not all of the data in the spreadsheet is accurate. All it takes is for someone to accidentally (or maliciously) fat finger the data entry, or to not have understood the node forwarding protocol correctly and it fucks the entire chain.
Luckily there's plenty of data to go at.
The output
So, after running my decryptor, I managed to get 5 successful decryptions from the 1727 rows on the spreadsheet.
Here's a link to the files so you can take a look. They are the raw binary of the decrypted information. I've not performed any base 64 shenanigans or anything.
https://drive.google.com/drive/folders/0B9pgTHddula0M19BMjY4M0xrMWc?usp=sharing
The file name is the patten of the starting node (i.e. CSTCD = Circle Square Triangle Circle Diamond).
I've not been able to make sense of the decrypted output yet.
-
-
@JazzyJosh wow, that looks awesome!
-
@DoctorJones Limited edition version with physical manual coming to my door within the week :D
-
@JazzyJosh said in How about a game for REAL programmers? (1):
That looks great.
In IRC earlier today we also shared these:
-
@error said in How about a game for REAL programmers? (1):
In IRC earlier today we also shared these:
There's a TDWTF IRC channel? Could I please have the details?
-
chat.freenode.net
#thedailywtf
-
@DoctorJones said in How about a game for REAL programmers? (1):
@error said in How about a game for REAL programmers? (1):
In IRC earlier today we also shared these:
There's a TDWTF IRC channel? Could I please have the details?
Wow, I feel like a dumbass for associating you with DJ_Dichotomy a while back...
-
@Tsaukpaetra said in How about a game for REAL programmers? (1):
@DoctorJones said in How about a game for REAL programmers? (1):
@error said in How about a game for REAL programmers? (1):
In IRC earlier today we also shared these:
There's a TDWTF IRC channel? Could I please have the details?
Wow, I feel like a dumbass for associating you with DJ_Dichotomy a while back...
Dich move.
-
@DoctorJones said in How about a game for REAL programmers? (1):
@error said in How about a game for REAL programmers? (1):
In IRC earlier today we also shared these:
There's a TDWTF IRC channel? Could I please have the details?
Haven't you seen seen the IRC quotes topic? It's a pretty good thread.