7

精简版:

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

长版

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

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

好的,这是基本要求:

  • 编程语言:C++
  • 平台:Windows(7 专业版)
  • 使用的 IDE:Visual Studio 2012
  • 如果它们对于简化开发不是必需的,则无需额外的库

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

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

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

一些搜索确实产生了第一个答案:使用ReadProcessMemoryandWriteProcessMemory可以轻松更改另一个进程的内存中的值,而无需获得调试权限。

然而,我的导师想要的是能够定义指针(比如说 unsigned 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 所以我猜它确实 set SE_DEBUG_NAME,我认为这是我需要设置的标志。

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

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

我希望你能帮助我:) 我对应用程序的具体要求束手无策,如果你有想法使用整个不同的方法来实现我的目标,你可以自由地启发我,但我不能把它呈现给我的上级,这样只会增加我的知识:D

4

4 回答 4

2

根据您的描述,您似乎已经到了可以使用 SE_DEBUG 打开进程的地步。此时,您现在有了目标进程的句柄。

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

首先我们需要看一下ReadProcessMemory的定义:

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

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

因此,用于从目标进程中读取 unsigned 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 进行额外调用以查找指针引用的值。

于 2013-06-21T22:36:24.970 回答
2

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

当您在代码中取消引用指针时,您实际上是在尝试访问进程中的内存。您的导师的任何一厢情愿都无法使指针取消引用访问另一个进程中的内存。

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

我不认为您真的需要使用令牌和特权进行所有这些长度。如果我没记错的话,您添加了调试权限,请调用 OpenProcess 并直接使用它。而且我认为您通常可以跳过添加特权。

一些搜索确实产生了第一个答案:使用 ReadProcessMemory 和 WriteProcessMemory 可以轻松更改另一个进程的内存中的值,而无需获得调试权限。然而,我的导师想要的是能够定义指针(比如说 unsigned int),它应该指向被调试进程的内存空间,有效地保存我之前写的基地址。他们真的很想要这个,我什至不能和他们说这个,所以我最后还是坚持这样做......

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


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

我宁愿假设你的被调试者是一个不情愿的受害者,但如果它准备合作,那么共享内存可能是一种选择。

即使那样,您也需要小心该共享内存中的任何指针,因为通常该内存会映射到每个进程中的不同虚拟地址。

于 2013-06-21T22:37:17.710 回答
1

我认为您正在尝试访问内核域内存范围,因此是异常。

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

我假设您使用的是 32 位机器。

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

于 2013-06-21T22:15:43.663 回答
0

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

foreign_ptr<int> ptr{0xDE123F00};


int a = *ptr;
*ptr = 1;
于 2013-07-25T17:53:56.257 回答