Example #1
0
        // See: IL2CPP/Metadata.cs
        // See: IL2CPP/MetadataClasses.cs
        public void PostProcessMetadata(Metadata metadata, PluginPostProcessMetadataEventInfo data)
        {
            // NOTE: Metadata derives from BinaryObjectStream

            // Everything is available in Metadata

            // This is a direct parsing of the global-metadata.dat file
            // containing Il2Cpp*[] arrays

            // Assemblies, Images, Types, Methods, Params, Fields, etc. etc.
            // See Il2CppInspector.Common/IL2CPP/Metadata.cs for a complete list

            // Example: list all assemblies in metadata

            /*  Assembly 0 = mscorlib
             *  Assembly 1 = System
             *  Assembly 2 = Mono.Security
             *  Assembly 3 = System.Core
             *  Assembly 4 = System.Xml
             *  Assembly 5 = UnityEngine .... */
            foreach (var asm in metadata.Assemblies)
            {
                Console.WriteLine($"Assembly {asm.imageIndex} = {metadata.Strings[asm.aname.nameIndex]}");
            }

            // Set data.IsDataModified if you change the metadata contents
        }
        // This implements IPostProcessMetadata
        // This hook is executed after global-metadata.dat is fully loaded
        public void PostProcessMetadata(Metadata metadata, PluginPostProcessMetadataEventInfo info)
        {
            // This displays a progress update for our plugin in the CLI or GUI
            PluginServices.For(this).StatusUpdate("Decrypting strings");

            // Go through every string literal (string[] metadata.StringLiterals) and ROT each string
            for (var i = 0; i < metadata.StringLiterals.Length; i++)
            {
                metadata.StringLiterals[i] = string.Join("", metadata.StringLiterals[i].Select(x => (char)(x >= 'a' && x <= 'z' ? (x - 'a' + rotKey.Value) % 26 + 'a' : x)));
            }

            // Report back that we modified the metadata
            // Note: we do not set info.FullyProcessed in order to allow other plugins to do further processing
            info.IsDataModified = true;
        }
Example #3
0
        // Decrypt XOR-encrypted strings in global-metadata.dat
        public void PostProcessMetadata(Metadata metadata, PluginPostProcessMetadataEventInfo data)
        {
            // To check for encryption, find every single string start position by scanning all of the definitions
            var stringOffsets = metadata.Images.Select(x => x.nameIndex)
                                .Concat(metadata.Assemblies.Select(x => x.aname.nameIndex))
                                .Concat(metadata.Assemblies.Select(x => x.aname.cultureIndex))
                                .Concat(metadata.Assemblies.Select(x => x.aname.hashValueIndex)) // <=24.3
                                .Concat(metadata.Assemblies.Select(x => x.aname.publicKeyIndex))
                                .Concat(metadata.Events.Select(x => x.nameIndex))
                                .Concat(metadata.Fields.Select(x => x.nameIndex))
                                .Concat(metadata.Methods.Select(x => x.nameIndex))
                                .Concat(metadata.Params.Select(x => x.nameIndex))
                                .Concat(metadata.Properties.Select(x => x.nameIndex))
                                .Concat(metadata.Types.Select(x => x.nameIndex))
                                .Concat(metadata.Types.Select(x => x.namespaceIndex))
                                .Concat(metadata.GenericParameters.Select(x => x.nameIndex))
                                .OrderBy(x => x)
                                .Distinct()
                                .ToList();

            // Now confirm that all the keys are present in the string dictionary
            if (metadata.Header.stringCount == 0 || !stringOffsets.Except(metadata.Strings.Keys).Any())
            {
                return;
            }

            // If they aren't, that means one or more of the null terminators wasn't null, indicating potential encryption
            // Only do this if we need to because it's very slow
            PluginServices.For(this).StatusUpdate("Decrypting strings");

            // There may be zero-padding at the end of the last string since counts seem to be word-aligned
            // Find the true location one byte after the final character of the final string
            var endOfStrings = metadata.Header.stringCount;

            while (metadata.ReadByte(metadata.Header.stringOffset + endOfStrings - 1) == 0)
            {
                endOfStrings--;
            }

            // Start again
            metadata.Strings.Clear();
            metadata.Position = metadata.Header.stringOffset;

            // Read in all of the strings as if they are fixed length rather than null-terminated
            foreach (var offset in stringOffsets.Zip(stringOffsets.Skip(1).Append(endOfStrings), (a, b) => (current: a, next: b)))
            {
                var encryptedString = metadata.ReadBytes(offset.next - offset.current - 1);

                // The null terminator is the XOR key
                var xorKey = metadata.ReadByte();

                var decryptedString = metadata.Encoding.GetString(encryptedString.Select(b => (byte)(b ^ xorKey)).ToArray());
                metadata.Strings.Add(offset.current, decryptedString);
            }

            // Write changes back in case the user wants to save the metadata file
            metadata.Position = metadata.Header.stringOffset;
            foreach (var str in metadata.Strings.OrderBy(s => s.Key))
            {
                metadata.WriteNullTerminatedString(str.Value);
            }
            metadata.Flush();

            data.IsDataModified   = true;
            data.IsStreamModified = true;
        }