Esempio n. 1
0
        // Detect and defeat various kinds of XOR encryption
        public void PostProcessImage <T>(FileFormatStream <T> stream, PluginPostProcessImageEventInfo data) where T : FileFormatStream <T>
        {
            if (stream is ElfReader32 stream32)
            {
                elf32 = stream32;
            }
            else if (stream is ElfReader64 stream64)
            {
                elf64 = stream64;
            }
            else
            {
                return;
            }

            PluginServices.For(this).StatusUpdate("Detecting encryption");

            this.stream = stream;
            sections    = stream.GetSections().GroupBy(s => s.Name).ToDictionary(s => s.Key, s => s.First());

            if (HasDynamicEntry(Elf.DT_INIT) && sections.ContainsKey(".rodata"))
            {
                // Use the data section to determine some possible keys
                // If the data section uses striped encryption, bucketing the whole section will not give the correct key
                var roDataBytes            = stream.ReadBytes(sections[".rodata"].ImageStart, sections[".rodata"].ImageLength);
                var xorKeyCandidateStriped = roDataBytes.Take(1024).GroupBy(b => b).OrderByDescending(f => f.Count()).First().Key;
                var xorKeyCandidateFull    = roDataBytes.GroupBy(b => b).OrderByDescending(f => f.Count()).First().Key;

                // Select test nibbles and values for ARM instructions depending on architecture (ARMv7 / AArch64)
                var testValues = new Dictionary <int, (int, int, int, int)> {
Esempio n. 2
0
        protected Il2CppBinary(IFileFormatStream stream, EventHandler <string> statusCallback = null)
        {
            Image          = stream;
            OnStatusUpdate = statusCallback;

            DiscoverAPIExports();
        }
Esempio n. 3
0
        protected Il2CppBinary(IFileFormatStream stream, uint codeRegistration, uint metadataRegistration, EventHandler <string> statusCallback = null)
        {
            Image          = stream;
            OnStatusUpdate = statusCallback;

            DiscoverAPIExports();
            TryPrepareMetadata(codeRegistration, metadataRegistration);
        }
Esempio n. 4
0
        // The method for ARM64:
        // - We want to extract values for CodeRegistration and MetadataRegistration from Il2CppCodegenRegistration(void)
        // - One of the functions supplied will be either Il2CppCodeGenRegistration or an initializer for Il2CppCodeGenRegistration.cpp
        // - The initializer (if present) loads a pointer to Il2CppCodegenRegistration in X1, if the function isn't in the function table
        // - Il2CppCodegenRegistration loads CodeRegistration into X0, MetadataRegistration into X1 and Il2CppCodeGenOptions into X2
        // - Loads can be done either with ADRP+ADD (loads the address of the wanted struct) or ADRP+LDR (loads a pointer to the address which must be de-referenced)
        // - Loads do not need to be pairs of sequential instructions
        // - We need to sweep the whole function from the ADRP to the next B to find an ADD or LDR with a corresponding register
        protected override (ulong, ulong) ConsiderCode(IFileFormatStream image, uint loc)
        {
            // Load function into memory
            // In practice, the longest function length we need is not generally longer than 7 instructions (0x1C bytes)
            var func = getFunctionAtFileOffset(image, loc, 7);

            // Don't accept functions longer than 7 instructions (in this case, the last instruction won't be a B)
            if (!isB(func[^ 1]))
Esempio n. 5
0
 // Load binary with a global-metadata.dat available
 // Supplying the Metadata class when loading a binary is optional
 // If it is specified and both symbol table and function scanning fail,
 // Metadata will be used to try to find the required structures with data analysis
 // If it is not specified, data analysis will not be performed
 public static Il2CppBinary Load(IFileFormatStream stream, Metadata metadata, EventHandler <string> statusCallback = null)
 {
     foreach (var loadedImage in stream.TryNextLoadStrategy())
     {
         var inst = LoadImpl(stream, statusCallback);
         if (inst.FindRegistrationStructs(metadata))
         {
             return(inst);
         }
     }
     return(null);
 }
Esempio n. 6
0
        public static ulong Decode(IFileFormatStream next)
        {
            ulong uleb = 0;
            byte  b    = 0x80;

            for (var shift = 0; b >> 7 == 1; shift += 7)
            {
                b     = next.ReadByte();
                uleb |= (ulong)(b & 0x7f) << shift;
            }
            return(uleb);
        }
Esempio n. 7
0
        private List <uint> getFunctionAtFileOffset(IFileFormatStream image, uint loc, uint maxLength)
        {
            // Read a function that ends in a hard branch (B) or exceeds maxLength instructions
            var  func = new List <uint>();
            uint inst;

            image.Position = loc;

            do
            {
                inst = image.ReadUInt32();
                func.Add(inst);
            } while (!isB(inst) && func.Count < maxLength);

            return(func);
        }
Esempio n. 8
0
        // Load and initialize a binary of any supported architecture
        private static Il2CppBinary LoadImpl(IFileFormatStream stream, EventHandler <string> statusCallback)
        {
            // Get type from image architecture
            var type = Assembly.GetExecutingAssembly().GetType("Il2CppInspector.Il2CppBinary" + stream.Arch.ToUpper());

            if (type == null)
            {
                throw new NotImplementedException("Unsupported architecture: " + stream.Arch);
            }

            // Set width of long (convert to sizeof(int) for 32-bit files)
            if (stream[0].Bits == 32)
            {
                stream[0].AddPrimitiveMapping(typeof(long), typeof(int));
                stream[0].AddPrimitiveMapping(typeof(ulong), typeof(uint));
            }

            return((Il2CppBinary)Activator.CreateInstance(type, stream[0], statusCallback));
        }
Esempio n. 9
0
        public override IFileFormatStream this[uint index] {
            get {
                Console.WriteLine($"Extracting binary from {binaryFiles[index].FullName}");
                IFileFormatStream loaded = null;

                // ZipArchiveEntry does not support seeking so we have to close and re-open for each possible load format
                var binary = binaryFiles[index].Open();
                loaded = ElfReader32.Load(binary, LoadOptions, OnStatusUpdate);
                binary.Close();

                if (loaded != null)
                {
                    return(loaded);
                }

                binary = binaryFiles[index].Open();
                loaded = ElfReader64.Load(binary, LoadOptions, OnStatusUpdate);
                binary.Close();

                return(loaded);
            }
        }
Esempio n. 10
0
 public Il2CppBinaryARM(IFileFormatStream stream, uint codeRegistration, uint metadataRegistration, EventHandler <string> statusCallback = null)
     : base(stream, codeRegistration, metadataRegistration, statusCallback)
 {
 }
Esempio n. 11
0
 public Il2CppBinaryARM(IFileFormatStream stream, EventHandler <string> statusCallback = null) : base(stream, statusCallback)
 {
 }
Esempio n. 12
0
 // Architecture-specific search function
 protected abstract (ulong, ulong) ConsiderCode(IFileFormatStream image, uint loc);
Esempio n. 13
0
 // Attempt to guess the compiler used to build the binary via its file type
 public static CppCompilerType GuessFromImage(IFileFormatStream image) => (image is PEReader? CppCompilerType.MSVC : CppCompilerType.GCC);
Esempio n. 14
0
        protected override (ulong, ulong) ConsiderCode(IFileFormatStream image, uint loc)
        {
            // Setup
            var buffSize  = 0x76; // minimum number of bytes to process the longest expected function
            var leaSize   = 7;    // the length of an LEA instruction with a 64-bit register operand and a 32-bit memory operand
            var xor64Size = 3;    // the length of a XOR instruction of two 64-bit registers
            var xor32Size = 2;    // the length of a XOR instruction of two 32-bit registers
            var pushSize  = 2;    // the length of a PUSH instruction with a 64-bit register
            var offset    = 0;

            int RAX = 0, RBX = 3, RCX = 1, RDX = 2, RSI = 6, RDI = 7; // R8 = 8

            ulong pCgr = 0;                                           // the point to the code registration function

            image.Position = loc;
            var buff = image.ReadBytes(buffSize);

            // We have seen two versions of the initializer:
            // 1. Regular version
            // 2. Inlined version with il2cpp::utils::RegisterRuntimeInitializeAndCleanup(CallbackFunction, CallbackFunction, order)

            // Version 1 passes "this" in rcx and the arguments in rdx (our wanted pointer), r8d (always zero) and r9d (always zero)
            //           or "this" in rdi, and the arguments in rsi (our wanted pointer), edx (always zero) and ecx (always zero)
            // Version 2 has a standard prologue and loads the wanted pointer into rax or rbp (lea rax/rbp)

            (int reg, uint operand)? lea;

            // Check for regular version
            // Generalize it as follows:
            // - each instruction must be lea r64, imm32 or xor r32, r32
            // - xors must always have the same register for both operands
            // - lea that can't be mapped into the file is the pointer to 'this', otherwise it's the pointer to the init function
            // - the last instruction should always be jmp (not currently enforced)
            // - function length should not be longer than 5 instructions (two leas, two xors and one jmp)

            offset = 0;
            for (var instructions = 0; instructions < 4; instructions++)
            {
                // All allowed instruction types
                var xor32 = getXorR32R32(buff, offset);
                var xor64 = getXorR64R64(buff, offset);
                lea = getLea(buff, offset);

                if (xor32 != null && xor32.Value.reg_op1 == xor32.Value.reg_op2)
                {
                    offset += xor32Size;
                }
                else if (xor64 != null && xor64.Value.reg_op1 == xor64.Value.reg_op2)
                {
                    offset += xor64Size;
                }
                else if (lea != null)
                {
                    offset += leaSize;

                    if (pCgr == 0)
                    {
                        try {
                            // We may have found Il2CppCodegenRegistration(void)
                            pCgr = image.GlobalOffset + loc + (ulong)offset + lea.Value.operand;
                            var newLoc = image.MapVATR(pCgr);
                        }
                        catch (InvalidOperationException) {
                            // this pointer
                            pCgr = 0;
                        }
                    }
                }
                else
                {
                    // not lea or xor
                    pCgr = 0;
                    break;
                }
            }

            // Check for inlined version
            if (pCgr == 0)
            {
                // Check for prologue
                // - A sequence of 0 or more mov [rsp+argX], rXX followed by 1 or more push rXX
                offset = 0;
                while (isMovRM64R64(buff, offset))
                {
                    offset += 5;
                }

                if (isPushR64(buff, offset))
                {
                    // Linear sweep for LEA
                    var leaInlined = findLea(buff, pushSize, buffSize - pushSize);
                    if (leaInlined != null)
                    {
                        pCgr = image.GlobalOffset + loc + (uint)leaInlined.Value.foundOffset + (uint)leaSize + leaInlined.Value.operand;
                    }
                }
            }

            // Assume we've found the pointer to Il2CppCodegenRegistration(void) and jump there
            if (pCgr != 0)
            {
                try {
                    Image.Position = Image.MapVATR(pCgr);
                }

                // Couldn't map virtual address to data in file, so it's not this function
                catch (InvalidOperationException) {
                    pCgr = 0;
                }
            }

            // Find the first 2 LEAs which we'll hope contain pointers to CodeRegistration and MetadataRegistration

            // There are two options here:
            // 1. il2cpp::vm::MetadataCache::Register is called directly with arguments in rcx, rdx, r8 or rdi, rsi, rdx (lea, lea, lea, jmp)
            // 2. The two functions being inlined. The arguments are loaded sequentially into rax after the prologue

            if (pCgr != 0)
            {
                var buff2Size = 0x50;
                var buff2     = image.ReadBytes(buffSize);
                offset = 0;

                var leas = new Dictionary <(int index, ulong address), int>();

                // Find the first three LEAs in the function
                while (offset + leaSize < buff2Size && leas.Count < 3)
                {
                    var nextLea = findLea(buff2, offset, buff2Size - (offset + leaSize));

                    // Use the original pointer found, not the file location + GlobalOffset because the data may be in a different section
                    if (nextLea != null)
                    {
                        leas.Add((leas.Count, pCgr + (uint)nextLea.Value.foundOffset + (uint)leaSize + nextLea.Value.operand), nextLea.Value.reg);
                    }

                    offset = nextLea?.foundOffset + leaSize ?? buff2Size;
                }

                if ((image.Version < 21 && leas.Count == 2) || (image.Version >= 21 && leas.Count == 3))
                {
                    // Register-based argument passing?
                    var leaRSI = leas.FirstOrDefault(l => l.Value == RSI).Key.address;
                    var leaRDI = leas.FirstOrDefault(l => l.Value == RDI).Key.address;

                    if (leaRSI != 0 && leaRDI != 0)
                    {
                        return(leaRDI, leaRSI);
                    }

                    var leaRCX = leas.FirstOrDefault(l => l.Value == RCX).Key.address;
                    var leaRDX = leas.FirstOrDefault(l => l.Value == RDX).Key.address;

                    if (leaRCX != 0 && leaRDX != 0)
                    {
                        return(leaRCX, leaRDX);
                    }

                    // RAX sequential loading? If so, take the first two arguments
                    var leasRAX = leas.Where(l => l.Value == RAX).OrderBy(l => l.Key.index).Select(l => l.Key.address).ToArray();
                    if (leasRAX.Length > 1)
                    {
                        return(leasRAX[0], leasRAX[1]);
                    }
                }
            }

            // If no initializer is found, we may be looking at a DT_INIT function which calls its own function table manually
            // In the sample we have seen (PlayStation 4), this function runs through two function tables:
            // 1. Start address of table loaded into rbx, pointer past end of table in r12 (lea rbx; lea r12)
            // 2. Pointer to final address of 2nd table loaded into rbx (lea rbx), runs backwards (8 bytes per entry) until finding 0xFFFFFFFF_FFFFFFFF
            // The strategy: find these LEAs, acquire and merge the two function tables, then call ourselves in a loop to check each function address

            // Expect function prologue and at least 3 64-bit register pushes (there are probably more)
            if (!isPrologue(buff) || !isPushR64(buff, 4) || !isPushR64(buff, 6) || !isPushR64(buff, 8))
            {
                return(0, 0);
            }

            // Find the start and end addresses of the first function table
            var leaOfStart = findLea(buff, 10, buffSize - 10);

            if (leaOfStart == null || leaOfStart.Value.reg != RBX) // Most be lea rbx
            {
                return(0, 0);
            }

            var leaOfEnd = findLea(buff, leaOfStart.Value.foundOffset + leaSize, buffSize - (leaOfStart.Value.foundOffset + leaSize));

            if (leaOfEnd == null || leaOfEnd.Value.reg == RBX) // Must be lea with any register besides rbx
            {
                return(0, 0);
            }

            var ptrStart1 = leaOfStart.Value.foundOffset + leaSize + leaOfStart.Value.operand;
            var ptrEnd1   = leaOfEnd.Value.foundOffset + leaSize + leaOfEnd.Value.operand;

            // Find the address of the last item in the second function table
            var leaOfLastItem = findLea(buff, leaOfEnd.Value.foundOffset + leaSize, buffSize - (leaOfEnd.Value.foundOffset + leaSize));

            if (leaOfLastItem == null || leaOfLastItem.Value.reg != 0b11) // Must be lea rbx
            {
                return(0, 0);
            }

            var entrySize = 8; // 64-bit array entries
            var ptrEnd2   = leaOfLastItem.Value.foundOffset + leaSize + leaOfLastItem.Value.operand + entrySize;

            // Work backwards to find the address of the first item in the second function table
            var ptrStart2 = ptrEnd2;

            while (image.ReadUInt64(image.MapVATR((ulong)ptrStart2)) != 0xFFFF_FFFF_FFFF_FFFF)
            {
                ptrStart2 -= entrySize;
            }
            ptrStart2 += entrySize;

            // Acquire both function tables
            var funcs1 = image.ReadMappedWordArray((ulong)ptrStart1, (int)(ptrEnd1 - ptrStart1) / entrySize);
            var funcs2 = image.ReadMappedWordArray((ulong)ptrStart2, (int)(ptrEnd2 - ptrStart2) / entrySize);

            // Check every function
            var funcs = funcs1.Concat(funcs2);

            foreach (var pFunc in funcs)
            {
                var result = ConsiderCode(image, image.MapVATR((ulong)pFunc));
                if (result != (0, 0))
                {
                    return(result);
                }
            }
            return(0, 0);
        }
Esempio n. 15
0
        protected override (ulong, ulong) ConsiderCode(IFileFormatStream image, uint loc)
        {
            ulong metadata, code;
            long  pCgr;

            // x86
            // Assembly bytes to search for at start of each function
            var bytes = new byte[] { 0x6A, 0x00, 0x6A, 0x00, 0x68 };

            image.Position = loc;
            var buff = image.ReadBytes(5);

            if (bytes.SequenceEqual(buff))
            {
                // Next 4 bytes are the function pointer being pushed onto the stack
                pCgr = image.ReadUInt32();

                // Start of next instruction
                if (image.ReadByte() != 0xB9)
                {
                    return(0, 0);
                }

                // Jump to Il2CppCodegenRegistration
                if (image.Version < 21)
                {
                    image.Position = image.MapVATR((ulong)pCgr + 1);
                    metadata       = image.ReadUInt32();
                    image.Position = image.MapVATR((ulong)pCgr + 6);
                    code           = image.ReadUInt32();
                }
                else
                {
                    image.Position = image.MapVATR((ulong)pCgr + 6);
                    metadata       = image.ReadUInt32();
                    image.Position = image.MapVATR((ulong)pCgr + 11);
                    code           = image.ReadUInt32();
                }
                return(code, metadata);
            }

            // x86 based on ELF PLT
            if (image is IElfReader elf)
            {
                var plt = elf.GetPLTAddress();

                // push ebp; mov ebp, esp; push ebx; and esp, 0FFFFFFF0h; sub esp, 20h; call $+5; pop ebx
                bytes = new byte[]
                { 0x55, 0x89, 0xE5, 0x53, 0x83, 0xE4, 0xF0, 0x83, 0xEC, 0x20, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x5B };
                image.Position = loc;
                buff           = image.ReadBytes(16);
                if (!bytes.SequenceEqual(buff))
                {
                    return(0, 0);
                }

                // lea eax, (pCgr - offset)[ebx] (Position + 6 is the opcode lea eax; Position + 8 is the operand)
                image.Position += 6;

                // Ensure it's lea eax, #address
                if (image.ReadUInt16() != 0x838D)
                {
                    return(0, 0);
                }

                try {
                    pCgr = image.MapVATR(image.ReadUInt32() + plt);
                }
                // Could not find a mapping in the section table
                catch (InvalidOperationException) {
                    return(0, 0);
                }

                // Extract Metadata pointer
                // An 0x838D opcode indicates LEA (no indirection)
                image.Position = pCgr + 0x20;
                var opcode = image.ReadUInt16();
                metadata = image.ReadUInt32() + plt;

                // An 8x838B opcode indicates MOV (pointer indirection)
                if (opcode == 0x838B)
                {
                    image.Position = image.MapVATR(metadata);
                    metadata       = image.ReadUInt32();
                }

                if (opcode != 0x838B && opcode != 0x838D)
                {
                    return(0, 0);
                }

                // Repeat the same logic for extracting the Code pointer
                image.Position = pCgr + 0x2A;
                opcode         = image.ReadUInt16();
                code           = image.ReadUInt32() + plt;

                if (opcode == 0x838B)
                {
                    image.Position = image.MapVATR(code);
                    code           = image.ReadUInt32();
                }

                if (opcode != 0x838B && opcode != 0x838D)
                {
                    return(0, 0);
                }

                return(code, metadata);
            }

            return(0, 0);
        }
Esempio n. 16
0
        private Dictionary <uint, ulong> sweepForAddressLoads(List <uint> func, ulong baseAddress, IFileFormatStream image)
        {
            // List of registers and addresses loaded into them
            var regs = new Dictionary <uint, ulong>();

            // Iterate each instruction
            var pc = baseAddress;

            foreach (var inst in func)
            {
                // Is it an ADRP Xn, #page?
                if (getAdrp(inst, pc) is (uint reg, ulong page))
                {
                    // If we've had an earlier ADRP for the same register, we'll discard the previous load
                    if (regs.ContainsKey(reg))
                    {
                        regs[reg] = page;
                    }
                    else
                    {
                        regs.Add(reg, page);
                    }
                }

                if (getAdr(inst, pc) is (uint reg_adr, ulong addr))
                {
                    if (regs.ContainsKey(reg_adr))
                    {
                        regs[reg_adr] = addr;
                    }
                    else
                    {
                        regs.Add(reg_adr, addr);
                    }
                }

                // Is it an ADD Xm, Xn, #offset?
                if (getAdd64(inst) is (uint reg_n, uint reg_d, uint imm))
                {
                    // We are only interested in registers that have already had an ADRP, and the ADD must be to itself
                    if (reg_n == reg_d && regs.ContainsKey(reg_d))
                    {
                        regs[reg_d] += imm;
                    }
                }

                // Is it an LDR Xm, [Xn, #offset]?
                if (getLdr64ImmOffset(inst) is (uint reg_t, uint reg_ldr_n, uint simm))
                {
                    // We are only interested in registers that have already had an ADRP, and the LDR must be to itself
                    if (reg_t == reg_ldr_n && regs.ContainsKey(reg_ldr_n))
                    {
                        regs[reg_ldr_n] += simm * 8; // simm is a byte offset in a multiple of 8

                        // Now we have a pointer address, dereference it
                        regs[reg_ldr_n] = image.ReadUInt64(image.MapVATR(regs[reg_ldr_n]));
                    }
                }

                // Advance program counter which we need to calculate ADRP pages correctly
                pc += 4;
            }
            return(regs);
        }