自动从 C# 代码调试过程并读取寄存器值

Automatically a debug process from C# code and read register values

我正在寻找一种方法来读取特定地址的 edx 注册表,就像这个问题中所问的那样:Read eax register

虽然我的解决方案需要在 C# 中并且我尝试实现,但此时我得到的是:

public static IntPtr GetEdx(IntPtr address, Process process)
    {
        const uint DBG_EXCEPTION_NOT_HANDLED = 0x80010001;
        const uint EXCEPTION_SINGLE_STEP = 0x80000004;
        const int DBG_CONTINUE = 0x00010002; // Seems to work better than DBG_EXCEPTION_NOT_HANDLED

        //DebugSetProcessKillOnExit(0);
        DEBUG_EVENT evt = new DEBUG_EVENT();
        // Attach to the process we provided the thread as an argument
        if (!DebugActiveProcess(process.Id))
            throw new Win32Exception();

        CONTEXT context = new CONTEXT();

        foreach (ProcessThread thread in process.Threads)
        {
            uint iThreadId = (uint)thread.Id;
            IntPtr hThread =
                OpenThread(
                    ThreadAccessFlags.SUSPEND_RESUME | ThreadAccessFlags.SET_CONTEXT |
                    ThreadAccessFlags.GET_CONTEXT, false, iThreadId);

            // Suspent the thread
            if (SuspendThread(hThread) == -1) throw new ApplicationException("Cannot suspend thread.");

            context = new CONTEXT
            {
                ContextFlags = (uint)CONTEXT_FLAGS.CONTEXT_DEBUG_REGISTERS |
                               (uint)CONTEXT_FLAGS.CONTEXT_INTEGER
            };

            // Get the context
            if (!GetThreadContext(hThread, ref context))
                throw new Win32Exception();

            // Change the context

            context.Dr0 = (uint)address;
            context.Dr7 = 0x00000001;

            // Set the changed context back
            if (!SetThreadContext(hThread, ref context))
                throw new Win32Exception();

            // Check if setting the context give any errors
            var error = Marshal.GetLastWin32Error();
            if (error != 0)
            {
                throw new ApplicationException("Error is setting context.");
            }

            // Resume the thread
            if (ResumeThread(hThread) == -1) throw new ApplicationException("Cannot resume thread.");
        }

        while (true)
        {
            if (!WaitForDebugEvent(out evt, -1))
                throw new Win32Exception();

            // Multiple if's for easier debugging at this moment
            if (evt.dwDebugEventCode == (uint)DebugEventType.EXCEPTION_DEBUG_EVENT)
            {
                if (evt.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP)
                {
                    if (evt.Exception.ExceptionRecord.ExceptionAddress == address)
                    {
                        context = new CONTEXT
                        {
                            ContextFlags = (uint)CONTEXT_FLAGS.CONTEXT_DEBUG_REGISTERS |
                                           (uint)CONTEXT_FLAGS.CONTEXT_INTEGER
                        };
                        GetThreadContext((IntPtr)evt.dwThreadId, ref context);
                        return (IntPtr)context.Ebx; // ebx get
                    }
                }
            }

            ContinueDebugEvent(evt.dwProcessId, evt.dwThreadId, DBG_CONTINUE);//DBG_EXCEPTION_NOT_HANDLED);
        }
    }

有很多 Kernel32 方法:

[DllImport("kernel32.dll")]
    static extern int ResumeThread(IntPtr hThread);
    [DllImport("kernel32.dll")]
    static extern uint SuspendThread(IntPtr hThread);
    [DllImport("kernel32.dll")]
    public static extern IntPtr OpenThread(ThreadAccessFlags dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
    [DllImport("Kernel32.dll", SetLastError = true)]
    static extern bool DebugActiveProcess(int dwProcessId);
    [DllImport("Kernel32.dll", SetLastError = true)]
    static extern bool WaitForDebugEvent([Out] out DEBUG_EVENT lpDebugEvent, int dwMilliseconds);
    [DllImport("Kernel32.dll", SetLastError = true)]
    static extern bool ContinueDebugEvent(int dwProcessId, int dwThreadId, uint dwContinueStatus);
    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool IsDebuggerPresent();
    [DllImport("kernel32.dll")]
    private static extern bool GetThreadContext(IntPtr hThread, ref CONTEXT lpContext);
    [DllImport("kernel32.dll")]
    public static extern bool SetThreadContext(IntPtr hThread, ref CONTEXT lpContext);

    [StructLayout(LayoutKind.Sequential)]
    public unsafe struct DEBUG_EVENT
    {
        public readonly uint dwDebugEventCode;
        public readonly int dwProcessId;
        public readonly int dwThreadId;


        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 86, ArraySubType = UnmanagedType.U1)]
        private readonly byte[] debugInfo;


        public EXCEPTION_DEBUG_INFO Exception
        {
            get
            {
                if (debugInfo == null)
                    return new EXCEPTION_DEBUG_INFO();


                fixed (byte* ptr = debugInfo)
                {
                    return *(EXCEPTION_DEBUG_INFO*)ptr;
                }
            }
        }


        public LOAD_DLL_DEBUG_INFO LoadDll
        {
            get
            {
                if (debugInfo == null)
                    return new LOAD_DLL_DEBUG_INFO();


                fixed (byte* ptr = debugInfo)
                {
                    return *(LOAD_DLL_DEBUG_INFO*)ptr;
                }
            }
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct LOAD_DLL_DEBUG_INFO
    {
        public readonly IntPtr hFile;
        public readonly IntPtr lpBaseOfDll;
        public readonly uint dwDebugInfoFileOffset;
        public readonly uint nDebugInfoSize;
        public readonly IntPtr lpImageName;
        public readonly ushort fUnicode;
    }


    [StructLayout(LayoutKind.Sequential)]
    public struct EXCEPTION_DEBUG_INFO
    {
        public EXCEPTION_RECORD ExceptionRecord;
        public readonly uint dwFirstChance;
    }


    [StructLayout(LayoutKind.Sequential)]
    public struct EXCEPTION_RECORD
    {
        public readonly uint ExceptionCode;
        public readonly uint ExceptionFlags;
        public readonly IntPtr ExceptionRecord;
        public readonly IntPtr ExceptionAddress;
        public readonly uint NumberParameters;


        //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 15, ArraySubType = UnmanagedType.U4)]
        //public readonly uint[] ExceptionInformation;


        public unsafe fixed uint ExceptionInformation[15];
    }


    public enum DebugEventType : int
    {
        CREATE_PROCESS_DEBUG_EVENT = 3, //Reports a create-process debugging event. The value of u.CreateProcessInfo specifies a CREATE_PROCESS_DEBUG_INFO structure.
        CREATE_THREAD_DEBUG_EVENT = 2, //Reports a create-thread debugging event. The value of u.CreateThread specifies a CREATE_THREAD_DEBUG_INFO structure.
        EXCEPTION_DEBUG_EVENT = 1, //Reports an exception debugging event. The value of u.Exception specifies an EXCEPTION_DEBUG_INFO structure.
        EXIT_PROCESS_DEBUG_EVENT = 5, //Reports an exit-process debugging event. The value of u.ExitProcess specifies an EXIT_PROCESS_DEBUG_INFO structure.
        EXIT_THREAD_DEBUG_EVENT = 4, //Reports an exit-thread debugging event. The value of u.ExitThread specifies an EXIT_THREAD_DEBUG_INFO structure.
        LOAD_DLL_DEBUG_EVENT = 6, //Reports a load-dynamic-link-library (DLL) debugging event. The value of u.LoadDll specifies a LOAD_DLL_DEBUG_INFO structure.
        OUTPUT_DEBUG_STRING_EVENT = 8, //Reports an output-debugging-string debugging event. The value of u.DebugString specifies an OUTPUT_DEBUG_STRING_INFO structure.
        RIP_EVENT = 9, //Reports a RIP-debugging event (system debugging error). The value of u.RipInfo specifies a RIP_INFO structure.
        UNLOAD_DLL_DEBUG_EVENT = 7, //Reports an unload-DLL debugging event. The value of u.UnloadDll specifies an UNLOAD_DLL_DEBUG_INFO structure.
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct CONTEXT
    {
        public uint ContextFlags;
        public uint Dr0;
        public uint Dr1;
        public uint Dr2;
        public uint Dr3;
        public uint Dr6;
        public uint Dr7;
        public FLOATING_SAVE_AREA FloatSave;
        public uint SegGs;
        public uint SegFs;
        public uint SegEs;
        public uint SegDs;
        public uint Edi;
        public uint Esi;
        public uint Ebx;
        public uint Edx;
        public uint Ecx;
        public uint Eax;
        public uint Ebp;
        public uint Eip;
        public uint SegCs;
        public uint EFlags;
        public uint Esp;
        public uint SegSs;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)]
        public byte[] ExtendedRegisters;
    }

    public enum CONTEXT_FLAGS : uint
    {
        CONTEXT_i386 = 0x10000,
        CONTEXT_i486 = 0x10000,
        CONTEXT_CONTROL = CONTEXT_i386 | 0x01,
        CONTEXT_INTEGER = CONTEXT_i386 | 0x02,
        CONTEXT_SEGMENTS = CONTEXT_i386 | 0x04,
        CONTEXT_FLOATING_POINT = CONTEXT_i386 | 0x08,
        CONTEXT_DEBUG_REGISTERS = CONTEXT_i386 | 0x10,
        CONTEXT_EXTENDED_REGISTERS = CONTEXT_i386 | 0x20,
        CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS,
        CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | CONTEXT_EXTENDED_REGISTERS
    }

    [Flags]
    public enum ThreadAccessFlags : int
    {
        TERMINATE = 0x0001,
        SUSPEND_RESUME = 0x0002,
        GET_CONTEXT = 0x0008,
        SET_CONTEXT = 0x0010,
        SET_INFORMATION = 0x0020,
        QUERY_INFORMATION = 0x0040,
        SET_THREAD_TOKEN = 0x0080,
        IMPERSONATE = 0x0100,
        DIRECT_IMPERSONATION = 0x0200
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct FLOATING_SAVE_AREA
    {
        public uint ControlWord;
        public uint StatusWord;
        public uint TagWord;
        public uint ErrorOffset;
        public uint ErrorSelector;
        public uint DataOffset;
        public uint DataSelector;

        // missing some stuff
        public uint Cr0NpxState;
    }

    [DllImport("kernel32.dll")]
    private static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, int dwLength);

    [DllImport("kernel32.dll")]
    public static extern bool ReadProcessMemory(IntPtr hProcess, int lpBaseAddress, byte[] buffer, int size,
        int lpNumberOfBytesRead);

但由于某种原因,它从未达到 evt.Exception.ExceptionRecord.ExceptionAddress == address

我对内存读取还很陌生,很难弄清楚上面的代码有什么问题。

编辑:另外,如果我取消注释 context.Dr7 = 0x00000001; 我正在尝试读取的应用程序会崩溃。

你的答案似乎隐藏在你用其他代码移植的评论之一中......设置和获取线程上下文的函数需要线程的句柄(可能使用适当的权限使用 OpenThread 打开,包括在至少 get/set 上下文)。相反,您显然传递了一个强制转换的进程 ID。您应该考虑检查 return 值是否成功,它可能有助于确定您的问题。

理论:

您想附加到一个进程,在其中放置一个断点,在调试器应用程序中注册一个事件并等待该事件。您需要将断点放在您作为参数提供的地址处,并且通过阅读上下文您应该能够看到 EDX 的内容。这看起来很合理,就像人类开发人员会做的那样。

实现:

查看您的实现,您尝试将断点置于该地址的方法似乎很可疑。阅读 here 我了解到尝试在 运行 线程上设置上下文可能会产生不可预知的结果。您也可能缺少足够的权限。假设您有权限,尝试在设置上下文之前停止线程

我预测的另一个问题是你想停止和调试的线程有一个需要尽可能少改变的上下文。我认为您应该首先停止线程,读取它的上下文,更改 Dr0 标志( 假设这是您设置断点的地方),然后设置所有其他寄存器信息不变的上下文。

否则,我认为您基本上改变了程序的执行,我强烈感觉其中一些寄存器是只读的

您需要考虑以下两点。希望对你有帮助。

调试中:

如果这不起作用,您需要添加一个使用 GetLastError() 的函数来查看函数失败的原因(我怀疑 SetThreadContext() 就是那个在第一时间造成问题)。

您还必须检查上下文结构是否已正确定义,并且所有成员都具有相同的顺序并定义了顺序 here。该结构必须由 C# 代码完全对齐,就像在非托管代码中一样。

另外请检查您是否 运行 在 64 位 OS 上。 64 位 OS 中线程的上下文与 32 位不同。寄存器扩展为 64b 等等。 如果使用 64 位 OS,需要重新定义上下文结构。如果你使用 ARM 机器也是如此。