我试图打开一个具有debugging权限的进程句柄,并定义一个指针指向debugging对象的内存中的一个对象。
在gradle的最后一年,我是一名计算机科学的大学生,他的任务是为下一代学生build立一个应用于教育目的的应用程序。
为什么我在这里寻求帮助,你可能会问? 那么,目标平台是Windows,我不幸的是没有任何有关WinAPI的知识…
好的,这是基本要求:
使用这个应用程序,学生将学习处理地址,在这种情况下是静态地址:debugging对象进程将有一些静态指针,这导致其他指针自己形成一个多维指针。 学生必须使用一些debugging技术(这不是我工作的一部分)find这些基地址,并尝试在这些指针的最后find这些值。
我的应用程序将由导师用来随机更改debugging对象过程中的值和/或结构。
一些search没有产生第一个答案:使用ReadProcessMemory
和WriteProcessMemory
可以轻松地更改另一个进程的内存中的值,而不需要获取debugging权限。
然而,我的导师想要的是能够定义指向debugging对象进程的内存空间的指针(比如unsigned int),并有效地保存我之前写的基址。 他们真的想要这个,我甚至不能说出来,所以我坚持最后这样做…
那么,如果以下(伪)代码工作,我会完成我的任务:
grantThisProcessDebugPrivileges(); openAnotherProcessWhileItsRunning("targetProcess.exe"); unsigned int * targetValue = (unsigned int*) 0xDE123F00; // or even myCustomClass * targetClass = (myCustomClass*) 0xDE123F00;
地址0xDE123F00位于targetProcess.exe的内存空间中。
我知道这是可能的,否则将不会有debugging器可以显示此信息。
好的,问题是:我很困惑我是否必须在打开目标进程之前激活我的应用程序的debugging权限, 在打开目标进程之后执行,或者给目标进程赋予这些权限。
所以我在MSDN中find一个例子并试图实现它:
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
,应用程序崩溃,并显示访问冲突消息,这表明我的方法无效。 我特别注意用pipe理员权限启动VisualStudio Debugger(否则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,可以轻松地更改另一个进程的内存中的值,而不需要获取调试权限。 然而,我的导师想要的是能够定义指向调试对象进程的内存空间的指针(比如unsigned int),并有效地保存我之前写的基址。 他们真的想要这个,我甚至不能说出来,所以我坚持最后这样做。
他们想要的是不可能的。 我建议你在做出不可能的要求之前告诉他们更好地理解虚拟内存。
@Cody Gray有用地提到了内存映射文件。 如果调试对象和调试器协同工作,那么他们可以使用内存映射文件来共享一个共同的内存区域。 在这种情况下,这两个进程可以将内存映射到其虚拟地址空间并以正常方式访问它。
我宁愿假设你的被调试者是一个不愿意的受害者,但是如果它愿意合作,那么共享内存可能是一个选择。
即使这样你也需要小心这个共享内存中的任何指针,因为内存通常会被映射到每个进程的不同虚拟地址上。
你可以通过实现合适的操作符来创建一个类似于指针的类型,就像shared_ptr
一样:
foreign_ptr<int> ptr{0xDE123F00}; int a = *ptr; *ptr = 1;
我想你正在尝试访问内核的陆地记忆范围,因此是例外。
用户的地址范围是从0x00000000 – 7FFFFFFF,所以请尝试在这个范围内访问,因为以上任何内核空间。
我假设你在一台32位机器上。
检查用户空间和系统空间 (Microsoft Docs)。