How about a game for REAL programmers? (1)


  • Discourse touched me in a no-no place

    @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 :grin:

    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:

    0_1475068715193_upload-355c6ebd-9cea-4a35-a5f7-8ed47ba3dd1a

    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.


  • Discourse touched me in a no-no place

    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 (:diamonds: :fa_square: :fa_square: :fa_circle: :diamonds:), and we're given an "Information Forwarding Protocol" that involves the same symbols.

    You can see my "forwarding protocol" looks like this:

    0_1475069752302_upload-b542d5b6-fb3a-4a35-a938-5139d0aaa7d0

    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 :angry:

    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.

    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.




  • Discourse touched me in a no-no place

    @JazzyJosh wow, that looks awesome!



  • @DoctorJones Limited edition version with physical manual coming to my door within the week :D


  • Winner of the 2016 Presidential Election

    @JazzyJosh said in How about a game for REAL programmers? (1):

    @DoctorJones

    That looks great.

    In IRC earlier today we also shared these:


  • Discourse touched me in a no-no place

    @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?


  • Winner of the 2016 Presidential Election

    chat.freenode.net

    #thedailywtf


  • Impossible Mission Players - A

    @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... :sweat_smile:


  • Winner of the 2016 Presidential Election

    @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... :sweat_smile:

    Dich move.


  • Winner of the 2016 Presidential Election

    @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.


Log in to reply
 

Looks like your connection to What the Daily WTF? was lost, please wait while we try to reconnect.