使用调试权限和读/写内存打开进程

Open process with debug privileges and read/write memory

本文关键字:写内存 进程 调试 权限      更新时间:2023-10-16

短版本:

我试图用调试权限打开一个进程句柄,并定义一个指向被调试对象内存中对象的指针。

长版本

我是一名计算机科学大学的学生,在毕业的最后一年,我的任务是构建一个应用程序,用于下一代学生的教育目的。

你可能会问,我为什么在这里寻求帮助?嗯,目标平台是Windows,不幸的是,我对WinAPI一无所知。。。

好的,这是基本要求:

  • 编程语言:C++
  • 平台:Windows(7 Professional)
  • 使用的IDE:Visual Studio 2012
  • 没有额外的库,如果它们不是简化开发所必需的

该应用程序将用于什么用途

使用这个应用程序,学生将学会处理地址,在这种情况下是静态地址:被调试进程将有一些静态指针,这些指针会导致其他指针本身形成多维指针。学生们必须使用一些调试技术来找到这些基地址(这不是我工作的一部分!),并尝试在这些指针的末尾找到值。

导师将使用我的应用程序来随机更改被调试过程中的值和/或结构。

一些搜索确实得到了第一个答案:使用ReadProcessMemoryWriteProcessMemory可以很容易地更改另一个进程的内存中的值,而无需获得调试权限。

然而,我的导师们想要的是能够定义指针(比如说无符号int),指针应该指向被调试进程的内存空间,有效地保存我之前写过的基地址。他们真的很想要这个,而我甚至无法说服他们,所以我最终不得不这么做。。。

究竟什么应该起作用

好吧,如果以下(伪)代码有效,我就完成了我的任务:

grantThisProcessDebugPrivileges();
openAnotherProcessWhileItsRunning("targetProcess.exe");
unsigned int * targetValue = (unsigned int*) 0xDE123F00;
// or even
myCustomClass * targetClass = (myCustomClass*) 0xDE123F00;

其中地址0xDE123F00位于targetProcess.exe的内存空间中。

我知道这是可能的,否则就不会有调试器显示这些信息。

到目前为止我做了什么(或尝试了什么…)

好吧,问题是:我真的很困惑,我是否必须在打开目标进程之前激活应用程序的调试权限,在行,或者更确切地说,给目标进程这些权限。

所以我在MSDN中找到了一个例子,并尝试实现它:

    BOOL SetPrivilege(
    HANDLE hToken,          // token handle
    LPCTSTR Privilege,      // Privilege to enable/disable
    BOOL bEnablePrivilege   // TRUE to enable.  FALSE to disable
    )
{
    TOKEN_PRIVILEGES tp;
    LUID luid;
    TOKEN_PRIVILEGES tpPrevious;
    DWORD cbPrevious=sizeof(TOKEN_PRIVILEGES);
    if(!LookupPrivilegeValue( NULL, Privilege, &luid )) return FALSE;
    // 
    // first pass.  get current privilege setting
    // 
    tp.PrivilegeCount           = 1;
    tp.Privileges[0].Luid       = luid;
    tp.Privileges[0].Attributes = 0;
    AdjustTokenPrivileges(
            hToken,
            FALSE,
            &tp,
            sizeof(TOKEN_PRIVILEGES),
            &tpPrevious,
            &cbPrevious
            );
    if (GetLastError() != ERROR_SUCCESS) return FALSE;
    // 
    // second pass.  set privilege based on previous setting
    // 
    tpPrevious.PrivilegeCount       = 1;
    tpPrevious.Privileges[0].Luid   = luid;
    if(bEnablePrivilege) {
        tpPrevious.Privileges[0].Attributes |= (SE_PRIVILEGE_ENABLED);
    }
    else {
        tpPrevious.Privileges[0].Attributes ^= (SE_PRIVILEGE_ENABLED &
            tpPrevious.Privileges[0].Attributes);
    }
    AdjustTokenPrivileges(
            hToken,
            FALSE,
            &tpPrevious,
            cbPrevious,
            NULL,
            NULL
            );
    if (GetLastError() != ERROR_SUCCESS) return FALSE;
    return TRUE;
};

我的主要观点是:

HANDLE mainToken;
// I really don't know what this block of code does :<
if(!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, FALSE, &mainToken))
{
    if (GetLastError() == ERROR_NO_TOKEN)
    {
        if (!ImpersonateSelf(SecurityImpersonation))
        return 1;
        if(!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, FALSE, &mainToken)){
            cout << GetLastError();
        return 1;
        }
     }
    else
        return 1;
}
if (!SetPrivilege(mainToken, SE_DEBUG_NAME, true))
{
    CloseHandle(mainToken);
    cout << "Couldn't set DEBUG MODE: " << GetLastError() << endl;
    return 1;
};
unsigned int processID = getPID("targetProcess.exe");
HANDLE hproc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processID);
if (hproc == NULL)
{
    cout << "Couldn't open the process" << endl;
    return 1;
};
    unsigned int * theValue = (unsigned int*) 0xDE123F;

好吧,这段代码运行时没有任何错误,SetPrivilege返回TRUE,所以我想它确实设置了SE_DEBUG_NAME,我认为这是我需要设置的标志。

但是,例如,在输出theValue的取消引用值之后,应用程序崩溃,并显示访问违规消息,这表明我的方法不起作用。我特别注意用管理员权限启动VisualStudioDebugger(否则SetPrivilege失败)。

我真的对此一无所知,事实上,我不知道设置SE_DEBUG_NAME是否是正确的方法,这增加了我的整体困惑。

我希望你能帮我:)关于应用程序的具体请求,我无能为力,如果你有想法使用完全不同的方法来实现我的目标,你可以自由地启发我,但我无法将其提交给我的上级,所以这只会增加我的知识:D

根据你的描述,看来您已经到了可以使用SE_DEBUG打开进程的地步。在这一点上,您现在有了目标进程的句柄。

您的代码似乎缺少ReadProcessMemory的使用。

首先,我们需要了解ReadProcessMemory的定义:

BOOL WINAPI ReadProcessMemory(
          _In_   HANDLE hProcess,
          _In_   LPCVOID lpBaseAddress,
          _Out_  LPVOID lpBuffer,
          _In_   SIZE_T nSize,
          _Out_  SIZE_T *lpNumberOfBytesRead);

该函数本质上使您能够将一块内存从一个进程空间复制到您的进程空间中。因此,您需要使用此方法将一块内存读取到您希望读取的数据结构的大小,然后您可以将内存块重新解释为该数据类型。

因此,从目标进程读取无符号int的半伪代码如下所示:

unsigned int ReadUInt(HANDLE process, const void * address)
{
    // Add parameter validation
    unsigned char buffer[sizeof(unsigned int)] = {};
    size_t bytesRead = 0;
    BOOL res = ::ReadProcessMemory(process,  // The handle you opened with SE_DEBUG privs
                                   address,  // The location in the other process
                                   buffer,   // Where to transfer the memory to
                                   sizeof(unsigned int), // The number of bytes to read
                                   &bytesRead); // The number of bytes actually read
    if (!res)
    {
        // Deal with the error
    }
    if (bytesRead != sizeof(unsigned int))
    {
        // Deal with error where we didn't get enough memory
    }
   return *reinterpret_cast<unsigned int *>(buffer);
}

不使用此行:

unsigned int * theValue = (unsigned int*) 0xDE123F00;

你可以这样做:

unsigned int theValue = ReadUInt(hproc, 0xDE123F00);

请记住,这需要您知道要读取的类型的大小和内存布局。包含在连续内存中的简单类型可以在单个ReadProcessMemory调用中检索。包含指针和值的类型将要求您对ReadProcessMemory进行额外调用,以查找指针引用的值。

每个进程都有自己的虚拟地址空间。一个进程中的地址只在该进程中具有意义。取消引用C++代码中的指针将访问执行进程的虚拟地址空间。

当您在代码中取消引用指针时,您实际上是在试图访问进程中的内存。导师再多的一厢情愿也无法在另一个过程中使指针去引用访问内存。

如果要从其他进程读取和写入内存,则必须使用ReadProcessMemory和WriteProcessMemory。

我认为你真的不需要为了代币和特权而竭尽全力。如果我没有记错的话,你可以添加调试权限,调用OpenProcess并直接进入它。我认为你通常可以跳过添加权限。

一些搜索确实得到了第一个答案:使用ReadProcessMemory和WriteProcessMemory可以很容易地更改另一个进程的内存中的值,而无需任何获得调试权限的需要。然而,我的导师们想要的是能够定义指针(比如说无符号int),指针应该指向被调试进程的内存空间,有效地保存我之前写过的基地址。他们真的很想要这个,而我甚至无法说服他们,所以我最终不得不这么做。。。

他们想要什么是不可能的。我建议你告诉他们,在提出不可能的要求之前,要更好地了解虚拟内存!


@Cody Gray提到了内存映射文件。如果调试器和调试器协作,那么它们就可以使用内存映射文件来共享一个公共内存区域。在这种情况下,两个进程都可以将内存映射到它们的虚拟地址空间,并以正常方式访问它。

我认为您的被调试对象是一个不情愿的受害者,但如果它准备合作,那么共享内存可能是一种选择。

即便如此,您也需要小心共享内存中的任何指针,因为通常情况下,内存会在每个进程中映射到不同的虚拟地址。

我认为您正在尝试访问内核陆地内存范围,因此出现了异常。

用户land的范围是0x00000000-7FFFFFFF,所以尝试在这个范围内访问,因为上面的任何内容都是内核空间。

我假设你在一台32位的机器上。

检查用户空间和系统空间(Microsoft文档)。

您可以通过实现适当的运算符来创建一个行为类似指针的类型,就像shared_ptr所做的那样:

foreign_ptr<int> ptr{0xDE123F00};

int a = *ptr;
*ptr = 1;