예제 #1
0
        public static TraceState InstallTracer(Process process, ulong patchSite, Logger logger, byte[] asmSizes, ImportResolver ir)
        {
            var processHandle = Win32Imports.OpenProcess(Win32Imports.ProcessAccessFlags.All, false, process.Id);

            var traceState = new TraceState();

            if ((ulong)processHandle == 0)
            {
                logger.WriteLine("could not open process");
                return(null);
            }

            const int patchSiteSize = 24;
            const int codeSize      = 1024;
            const int traceLogSize  = 1024;

            var originalCode = DebugProcessUtils.ReadBytes(process, (ulong)process.MainModule.BaseAddress, process.MainModule.ModuleMemorySize);

            var originalCodeAddress = Win32Imports.VirtualAllocEx(
                processHandle,
                new IntPtr(0),
                new IntPtr(originalCode.Length),
                Win32Imports.AllocationType.Commit | Win32Imports.AllocationType.Reserve,
                Win32Imports.MemoryProtection.ReadWrite);

            if ((ulong)originalCodeAddress == 0)
            {
                logger.WriteLine($"could not allocate memory for {nameof(originalCodeAddress)}");
                return(null);
            }

            var traceLogAddress = Win32Imports.VirtualAllocEx(
                processHandle,
                new IntPtr(0),
                new IntPtr(traceLogSize),
                Win32Imports.AllocationType.Commit | Win32Imports.AllocationType.Reserve,
                Win32Imports.MemoryProtection.ReadWrite);

            if ((ulong)traceLogAddress == 0)
            {
                logger.WriteLine($"could not allocate memory for {nameof(traceLogAddress)}");
                return(null);
            }

            var injectedCodeAddress = Win32Imports.VirtualAllocEx(
                processHandle,
                new IntPtr(0),
                new IntPtr(codeSize),
                Win32Imports.AllocationType.Commit | Win32Imports.AllocationType.Reserve,
                Win32Imports.MemoryProtection.ExecuteReadWrite);

            if ((ulong)injectedCodeAddress == 0)
            {
                logger.WriteLine($"could not allocate memory for {nameof(injectedCodeAddress)}");
                return(null);
            }

            var asmSizesAddress = Win32Imports.VirtualAllocEx(
                processHandle,
                new IntPtr(0),
                new IntPtr(asmSizes.Length),
                Win32Imports.AllocationType.Commit | Win32Imports.AllocationType.Reserve,
                Win32Imports.MemoryProtection.ReadWrite);

            if ((ulong)asmSizesAddress == 0)
            {
                logger.WriteLine($"could not allocate memory for {nameof(asmSizesAddress)}");
                return(null);
            }

            traceState.CodeAddress     = (ulong)injectedCodeAddress;
            traceState.TraceLogAddress = (ulong)traceLogAddress;

            int addrSize = Marshal.SizeOf(typeof(IntPtr));

            var c1 = Assembler.CreateContext <Action>();

            // save rax
            c1.Push(c1.Rax);

            // push return address
            c1.Lea(c1.Rax, Memory.QWord(CodeContext.Rip, -addrSize));
            c1.Push(c1.Rax);

            // push "call" address
            c1.Mov(c1.Rax, (ulong)injectedCodeAddress);
            c1.Push(c1.Rax);

            // "call" codeAddress
            c1.Ret();

            c1.Nop();
            c1.Nop();
            c1.Nop();

            var patchSiteCodeJit = AssemblyUtil.GetAsmJitBytes(c1);

            Debug.Assert(patchSiteCodeJit.Length == patchSiteSize, "patch site size incorrect");

            var c = Assembler.CreateContext <Action>();

            c.Push(c.Rbp);
            c.Mov(c.Rbp, c.Rsp);
            c.Pushf();
            c.Push(c.Rbx);
            c.Push(c.Rcx);
            c.Push(c.Rdx);

            // log thread id
            var getThreadContext = ir.LookupFunction("KERNEL32.DLL:GetCurrentThreadId");

            //c.Call(getThreadContext);
            c.Lea(c.Rax, Memory.QWord(CodeContext.Rip, 13)); // skips next instructions
            c.Push(c.Rax);
            c.Mov(c.Rax, getThreadContext);
            c.Push(c.Rax);
            c.Ret();
            c.Mov(c.Rbx, (ulong)traceLogAddress);
            c.Mov(Memory.Word(c.Rbx), c.Eax);

            // reserve space for instruction counter, the 01-08 is just so AsmJit doesn't shorten the instruction
            // we overwrite the value to 0 later on
            c.Mov(c.Rax, (ulong)0x0102030405060708);
            var smcLastRipJit = AssemblyUtil.GetAsmJitBytes(c).Length - addrSize;

            c.Lea(c1.Rax, Memory.QWord(CodeContext.Rip, -7 - addrSize));
            c.Mov(c.Rcx, Memory.QWord(c.Rax));
            c.Inc(c.Rcx);
            c.Xchg(Memory.QWord(c.Rax), c.Rcx);

            // find return address and store size of instruction at return address in rcx
            c.Mov(c.Rdx, Memory.QWord(c.Rbp, addrSize));
            c.Mov(c.Rbx, c.Rdx);
            c.Mov(c.Rax, (ulong)process.MainModule.BaseAddress);
            c.Sub(c.Rdx, c.Rax);
            c.Mov(c.Rax, (ulong)asmSizesAddress);
            c.Add(c.Rdx, c.Rax);
            c.Movzx(c.Rcx, Memory.Byte(c.Rdx));

            // RESTORE ORIGINAL CODE

            // get address of original code in Rdx
            c.Mov(c.Rdx, c.Rbx);
            c.Mov(c.Rax, (ulong)process.MainModule.BaseAddress);
            c.Sub(c.Rdx, c.Rax);
            // TODO: compare Rdx with process.MainModule.ModuleMemorySize - patchSiteSize and abort if greater
            c.Mov(c.Rax, (ulong)originalCodeAddress);
            c.Add(c.Rdx, c.Rax);

            // restore call site 1
            c.Mov(c.Rax, Memory.QWord(c.Rdx));
            c.Mov(Memory.QWord(c.Rbx), c.Rax);

            // restore call site 2
            c.Mov(c.Rax, Memory.QWord(c.Rdx, addrSize));
            c.Mov(Memory.QWord(c.Rbx, addrSize), c.Rax);

            // restore call site 3
            c.Mov(c.Rax, Memory.QWord(c.Rdx, addrSize * 2));
            c.Mov(Memory.QWord(c.Rbx, addrSize * 2), c.Rax);

            // CLEAN UP AND RETURN

            // put new patch in place

            // add instruction size to return address, so we write patch at next instruction
            c.Add(c.Rbx, c.Rcx);

            // restore patch site 1
            c.Mov(c.Rax, BitConverter.ToUInt64(patchSiteCodeJit, 0));
            c.Mov(Memory.QWord(c.Rbx), c.Rax);

            // restore patch site 2
            c.Mov(c.Rax, BitConverter.ToUInt64(patchSiteCodeJit, addrSize));
            c.Mov(Memory.QWord(c.Rbx, addrSize), c.Rax);

            // restore patch site 3
            c.Mov(c.Rax, BitConverter.ToUInt64(patchSiteCodeJit, addrSize * 2));
            c.Mov(Memory.QWord(c.Rbx, addrSize * 2), c.Rax);

            // end put new patch in place

            // restore rax from call site
            c.Mov(c.Rax, Memory.QWord(c.Rbp, 2 * addrSize));

            c.Pop(c.Rdx);
            c.Pop(c.Rcx);
            c.Pop(c.Rbx);
            c.Popf();
            c.Pop(c.Rbp);
            c.Ret((ulong)8);

            // overwrite some pieces of the code with values computed later on
            var codeSiteCodeJit = AssemblyUtil.GetAsmJitBytes(c);

            for (var j = 0; j < 8; j++)
            {
                codeSiteCodeJit[smcLastRipJit + j] = 0;
            }

            var i           = 0;
            var nextOffset1 = 0;

            using (var sw = new StreamWriter(Specifics.PatchAsmDumpFileName)) {
                foreach (var s in codeSiteCodeJit.Select((b1) => $"b: 0x{b1:X2}"))
                {
                    string asm1S;
                    try {
                        var asm1 = AssemblyUtil.Disassemble(codeSiteCodeJit.Skip(i).Take(AssemblyUtil.MaxInstructionBytes).ToArray());
                        asm1S = i == nextOffset1?asm1.ToString() : "";

                        if (i == nextOffset1)
                        {
                            nextOffset1 += asm1.Length;
                        }
                    }
                    catch (DecodeException) {
                        asm1S = "failed";
                    }

                    sw.WriteLine($"{s}: ASM: {asm1S}");

                    i++;
                }
            }

            if (codeSiteCodeJit.Length > codeSize)
            {
                throw new Exception("did not reserve enough memory for code");
            }

            // write process memory
            DebugProcessUtils.WriteBytes(process, (ulong)originalCodeAddress, originalCode);
            DebugProcessUtils.WriteBytes(process, (ulong)asmSizesAddress, asmSizes);
            DebugProcessUtils.WriteBytes(process, (ulong)injectedCodeAddress, codeSiteCodeJit);
            DebugProcessUtils.WriteBytes(process, patchSite, patchSiteCodeJit);

            return(traceState);
        }
예제 #2
0
        public static void InstallTracer(Process process, ulong patchSite, Logger logger)
        {
            var processHandle = Win32Imports.OpenProcess(Win32Imports.ProcessAccessFlags.All, false, process.Id);

            var retVal = new TraceState();

            if ((ulong)processHandle == 0)
            {
                logger.WriteLine("could not open process");
                return;
            }

            const int patchSiteSize = 24;
            const int patchAreaSize = 100;
            const int codeSize      = 1024 + patchAreaSize * 100;
            var       originalCode  = DebugProcessUtils.ReadBytes(process, patchSite, patchAreaSize);

            // minus patchSiteSize because we need to restore patchSiteSize bytes
            var ci = new diStorm.CodeInfo(0, originalCode.Take(patchAreaSize - patchSiteSize).ToArray(), diStorm.DecodeType.Decode64Bits, 0);
            var dr = new diStorm.DecodedResult(patchAreaSize);

            diStorm.diStorm3.Decode(ci, dr);
            var decodedInstructions = dr.Instructions;

            var bogusAddress = Win32Imports.VirtualAllocEx(
                processHandle,
                new IntPtr(0),
                new IntPtr(patchSiteSize * 2),
                Win32Imports.AllocationType.Commit | Win32Imports.AllocationType.Reserve,
                Win32Imports.MemoryProtection.ReadWrite);

            var codeAddress = Win32Imports.VirtualAllocEx(
                processHandle,
                new IntPtr(0),
                new IntPtr(codeSize),
                Win32Imports.AllocationType.Commit | Win32Imports.AllocationType.Reserve,
                Win32Imports.MemoryProtection.ExecuteReadWrite);

            retVal.CodeAddress = (ulong)codeAddress;

            if ((ulong)codeAddress == 0)
            {
                logger.WriteLine("could not allocate memory");
                return;
            }

            var patchSiteCode = new byte[patchSiteSize];
            var codeSiteCode  = new byte[codeSize];
            var patchPtr      = 0;
            var ptr           = 0;

            //patchSiteCode[patchPtr++] = 0xCC;
            // push rax, see RESTORE_RAX
            patchSiteCode[patchPtr++] = 0x50;
            // lea rax, [rip-8]
            patchSiteCode[patchPtr++] = 0x48;
            patchSiteCode[patchPtr++] = 0x8D;
            patchSiteCode[patchPtr++] = 0x05;
            patchSiteCode[patchPtr++] = 0xF8;
            patchSiteCode[patchPtr++] = 0xFF;
            patchSiteCode[patchPtr++] = 0xFF;
            patchSiteCode[patchPtr++] = 0xFF;
            // push rax (rip)
            patchSiteCode[patchPtr++] = 0x50;
            // mov rax, constant
            patchSiteCode[patchPtr++] = 0x48;
            patchSiteCode[patchPtr++] = 0xB8;
            foreach (var b in BitConverter.GetBytes((ulong)codeAddress))
            {
                patchSiteCode[patchPtr++] = b;
            }
            // push rax
            patchSiteCode[patchPtr++] = 0x50;
            // ret
            patchSiteCode[patchPtr++] = 0xC3;
            patchPtr += 3;

            if (patchPtr != patchSiteSize)
            {
                logger.WriteLine($"made a mistake: {patchPtr} != {patchSiteSize}");
                return;
            }

            // push rbp
            codeSiteCode[ptr++] = 0x55;
            // mov rbp, rsp
            codeSiteCode[ptr++] = 0x48;
            codeSiteCode[ptr++] = 0x89;
            codeSiteCode[ptr++] = 0xE5;
            // push flags
            codeSiteCode[ptr++] = 0x9C;
            // push rbx
            codeSiteCode[ptr++] = 0x53;
            // push rcx
            codeSiteCode[ptr++] = 0x51;
            // push rdx
            codeSiteCode[ptr++] = 0x52;

            // reserve space for last Rip address
            // mov rax, constant
            codeSiteCode[ptr++] = 0x48;
            codeSiteCode[ptr++] = 0xB8;
            var smcLastRip = ptr;

            ptr += 8;

            // lea rax, [rip-7-8]
            codeSiteCode[ptr++] = 0x48;
            codeSiteCode[ptr++] = 0x8D;
            codeSiteCode[ptr++] = 0x05;
            codeSiteCode[ptr++] = 0xF1;
            codeSiteCode[ptr++] = 0xFF;
            codeSiteCode[ptr++] = 0xFF;
            codeSiteCode[ptr++] = 0xFF;

            // mov rcx, [rax]
            codeSiteCode[ptr++] = 0x48;
            codeSiteCode[ptr++] = 0x8B;
            codeSiteCode[ptr++] = 0x08;

            // inc rcx
            codeSiteCode[ptr++] = 0x48;
            codeSiteCode[ptr++] = 0xFF;
            codeSiteCode[ptr++] = 0xC1;

            // xchg [rax], rcx
            codeSiteCode[ptr++] = 0x48;
            codeSiteCode[ptr++] = 0x87;
            codeSiteCode[ptr++] = 0x08;

            // find return address
            // mov rbx, [rbp+8]
            codeSiteCode[ptr++] = 0x48;
            codeSiteCode[ptr++] = 0x8B;
            codeSiteCode[ptr++] = 0x5D;
            codeSiteCode[ptr++] = 0x08;

            // call pastEndCode
            codeSiteCode[ptr++] = 0xE8;
            var callPastEndCodeImmediate = ptr;

            ptr += 4;
            var callPastEndCodeBaseOffset = ptr;

            // END FUNCTION

            // put new patch in place
            // mov rax, constant
            codeSiteCode[ptr++] = 0x48;
            codeSiteCode[ptr++] = 0xB8;
            foreach (var b in patchSiteCode.Skip(0).Take(8))
            {
                codeSiteCode[ptr++] = b;
            }

            // restore patch site 1
            // mov [rbx], rax
            codeSiteCode[ptr++] = 0x48;
            codeSiteCode[ptr++] = 0x89;
            codeSiteCode[ptr++] = 0x03;

            // mov rax, constant
            codeSiteCode[ptr++] = 0x48;
            codeSiteCode[ptr++] = 0xB8;
            foreach (var b in patchSiteCode.Skip(8).Take(8))
            {
                codeSiteCode[ptr++] = b;
            }

            // restore patch site 2
            // mov [rbx+8], rax
            codeSiteCode[ptr++] = 0x48;
            codeSiteCode[ptr++] = 0x89;
            codeSiteCode[ptr++] = 0x43;
            codeSiteCode[ptr++] = 0x08;

            // mov rax, constant
            codeSiteCode[ptr++] = 0x48;
            codeSiteCode[ptr++] = 0xB8;
            foreach (var b in patchSiteCode.Skip(16).Take(8))
            {
                codeSiteCode[ptr++] = b;
            }

            // restore patch site 3
            // mov [rbx+16], rax
            codeSiteCode[ptr++] = 0x48;
            codeSiteCode[ptr++] = 0x89;
            codeSiteCode[ptr++] = 0x43;
            codeSiteCode[ptr++] = 0x10;

            // end put new patch in place

            // mov rax,[rbp+16]
            codeSiteCode[ptr++] = 0x48;
            codeSiteCode[ptr++] = 0x8B;
            codeSiteCode[ptr++] = 0x45;
            codeSiteCode[ptr++] = 0x10;

            // pop rdx
            codeSiteCode[ptr++] = 0x5A;
            // pop rcx
            codeSiteCode[ptr++] = 0x59;
            // pop rbx
            codeSiteCode[ptr++] = 0x5B;
            // pop flags
            codeSiteCode[ptr++] = 0x9D;
            // pop rbp
            codeSiteCode[ptr++] = 0x5D;
            // ret 8
            codeSiteCode[ptr++] = 0xC2;
            codeSiteCode[ptr++] = 0x08;
            codeSiteCode[ptr++] = 0x00;

            codeSiteCode[callPastEndCodeImmediate] = (byte)(ptr - callPastEndCodeBaseOffset);

            // imul rcx,rcx,constant
            codeSiteCode[ptr++] = 0x48;
            codeSiteCode[ptr++] = 0x6B;
            codeSiteCode[ptr++] = 0xC9;
            // = size of restore function
            var multiplyImmediate = ptr;

            codeSiteCode[ptr++] = 0x00;

            // lea rcx, rcx+eax+constant
            codeSiteCode[ptr++] = 0x48;
            codeSiteCode[ptr++] = 0x8D;
            codeSiteCode[ptr++] = 0x4C;
            codeSiteCode[ptr++] = 0x08;
            var leaRcxConstant = ptr;

            codeSiteCode[ptr++] = 0x00;

            // jmp rcx
            codeSiteCode[ptr++] = 0xFF;
            codeSiteCode[ptr++] = 0xE1;

            codeSiteCode[leaRcxConstant] = (byte)(ptr - smcLastRip);

            // restore function
            uint offset     = 0;
            var  lastReturn = 0;

            foreach (var instr in decodedInstructions.Select((value, index) => new { Value = value, Index = index }))
            {
                var startOfRestore = ptr;

                Console.WriteLine($"instruction {instr.Value.Mnemonic}, size: {instr.Value.Size}");
                // reserve space for call site restore 1
                // mov rax, constant
                codeSiteCode[ptr++] = 0x48;
                codeSiteCode[ptr++] = 0xB8;
                var originalOffset = 0;
                var patchOffset    = 0;
                foreach (var b in originalCode.Skip((int)offset).Take(8))
                {
                    if (originalOffset < instr.Value.Size)
                    {
                        codeSiteCode[ptr++] = b;
                    }
                    else
                    {
                        codeSiteCode[ptr++] = patchSiteCode[patchOffset++];
                    }
                    originalOffset++;
                }

                // restore call site 1
                // mov [rbx], rax
                codeSiteCode[ptr++] = 0x48;
                codeSiteCode[ptr++] = 0x89;
                codeSiteCode[ptr++] = 0x03;

                // reserve space for call site restore 2
                // mov rax, constant
                codeSiteCode[ptr++] = 0x48;
                codeSiteCode[ptr++] = 0xB8;
                foreach (var b in originalCode.Skip((int)offset + 8).Take(8))
                {
                    if (originalOffset < instr.Value.Size)
                    {
                        codeSiteCode[ptr++] = b;
                    }
                    else
                    {
                        codeSiteCode[ptr++] = patchSiteCode[patchOffset++];
                    }
                    originalOffset++;
                }

                // restore call site 2
                // mov [rbx+8], rax
                codeSiteCode[ptr++] = 0x48;
                codeSiteCode[ptr++] = 0x89;
                codeSiteCode[ptr++] = 0x43;
                codeSiteCode[ptr++] = 0x08;

                // reserve space for call site restore 3
                // mov rax, constant
                codeSiteCode[ptr++] = 0x48;
                codeSiteCode[ptr++] = 0xB8;
                foreach (var b in originalCode.Skip((int)offset + 16).Take(8))
                {
                    if (originalOffset < instr.Value.Size)
                    {
                        codeSiteCode[ptr++] = b;
                    }
                    else
                    {
                        codeSiteCode[ptr++] = patchSiteCode[patchOffset++];
                    }
                    originalOffset++;
                }

                // restore call site 3
                // mov [rbx+16], rax
                codeSiteCode[ptr++] = 0x48;
                codeSiteCode[ptr++] = 0x89;
                codeSiteCode[ptr++] = 0x43;
                codeSiteCode[ptr++] = 0x10;

                // prepare rbx to put new patch in place
                // add rbx,instrSize
                codeSiteCode[ptr++] = 0x48;
                codeSiteCode[ptr++] = 0x83;
                codeSiteCode[ptr++] = 0xC3;
                codeSiteCode[ptr++] = (byte)(instr.Value.Size);

                // return
                lastReturn          = ptr;
                codeSiteCode[ptr++] = 0xC3;
                var endOfRestore = ptr;

                Console.WriteLine($"mul: {endOfRestore - startOfRestore}");
                codeSiteCode[multiplyImmediate] = (byte)(endOfRestore - startOfRestore);

                offset += instr.Value.Size;
            }

            ptr = lastReturn;

            // we set rbx to bogus so that patch is not written for last instruction in set
            // mov rbx, constant
            codeSiteCode[ptr++] = 0x48;
            codeSiteCode[ptr++] = 0xBB;
            foreach (var b in BitConverter.GetBytes((ulong)bogusAddress))
            {
                codeSiteCode[ptr++] = b;
            }

            // return
            codeSiteCode[ptr] = 0xC3;

            DebugProcessUtils.WriteBytes(process, (ulong)codeAddress, codeSiteCode);
            DebugProcessUtils.WriteBytes(process, patchSite, patchSiteCode);
        }