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

Ahn*_*uhs 6 c++ memory windows debugging process

精简版:

我正在尝试打开具有调试权限的进程句柄,并定义一个指向调试对象内存中的对象的指针.

长版

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

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

好的,这是基本要求:

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

该应用程序将用于什么?

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

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

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

然而,我的导师想要的是能够定义指针(比如unsigned int),它应该指向调试对象进程的内存空间,有效地保存我之前写的基地址.他们真的想要这个,我甚至无法说出这些,所以我最终坚持这样做......

究竟应该起作用的是什么?

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

grantThisProcessDebugPrivileges();
openAnotherProcessWhileItsRunning("targetProcess.exe");

unsigned int * targetValue = (unsigned int*) 0xDE123F00;
// or even
myCustomClass * targetClass = (myCustomClass*) 0xDE123F00;
Run Code Online (Sandbox Code Playgroud)

其中地址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;
};
Run Code Online (Sandbox Code Playgroud)

在我的主要:

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;
Run Code Online (Sandbox Code Playgroud)

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

但是在 - 例如 - 输出解除引用的值之后theValue,应用程序崩溃了一个访问冲突消息,这表明我的方法不起作用.我特别注意启动具有管理员权限的VisualStudio调试器(否则SetPrivilege失败).

我在这里真的很无能,我不知道设置是否SE_DEBUG_NAME是正确的方法,这增加了我的整体困惑.

我希望你可以帮助我:)我的双手是关于应用程序的具体要求,如果你有想法使用一个完整的不同方法实现我的目标,你可以自由地使我,但我将无法把它呈现给我的上级所以它只会增加我的知识:D

小智 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);
Run Code Online (Sandbox Code Playgroud)

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

因此,从目标进程读取 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);
}
Run Code Online (Sandbox Code Playgroud)

而不是使用这一行:

unsigned int * theValue = (unsigned int*) 0xDE123F00;
Run Code Online (Sandbox Code Playgroud)

你会这样做:

unsigned int theValue = ReadUInt(hproc, 0xDE123F00);
Run Code Online (Sandbox Code Playgroud)

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