我正在尝试使用调试权限打开一个进程句柄,并定义一个指向调试对象内存中对象的指针。
我是毕业最后一年的计算机科学大学生,他的任务是建立一个应用程序,用于下一代学生的教育目的。
为什么我在这里寻求帮助,你可能会问?好吧,目标平台是Windows,遗憾的是我不知道WinAPI ......
好的,这是基本要求:
使用这个应用程序,学生应学会处理地址,在这种情况下是静态的:调试对象进程将有一些静态指针,这导致其他指针自己形成一个多维指针。 学生必须使用一些调试技术找到这些基地址(这不是我工作的一部分!)并尝试在这些指针的末尾找到这些值。
导师将使用我的应用程序随机更改调试对象过程中的值和/或结构。
有些搜索确实产生了第一个答案:使用ReadProcessMemory
和WriteProcessMemory
,可以轻松更改另一个进程的内存中的值,而无需获得调试权限。
然而,我的导师想要的是能够定义指针(比如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所以我猜它确实设置了SE_DEBUG_NAME
,我认为这是我需要设置的标志。
但是 - 例如 - 在输出theValue
的解除引用值后,应用程序崩溃并显示访问冲突消息,这表明我的方法不起作用。我特别注意使用管理员权限启动VisualStudio调试器(否则SetPrivilege失败)。
我在这里真的很无能为力,我不知道设置SE_DEBUG_NAME
是否是正确的做法会增加我的整体困惑。
我希望你能帮助我:) 关于应用程序的具体要求,我的双手是束缚的,如果你有想法用一种完全不同的方法实现我的目标,你可以自由地给我,但我无法将它呈现给我的上级所以它会只是加入我的知识:D
答案 0 :(得分: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进行额外调用以查找指针引用的值。
答案 1 :(得分:2)
每个进程都有自己的虚拟地址空间。一个进程中的地址仅在该进程中具有意义。在C ++代码中取消引用指针将访问正在执行的进程的虚拟地址空间。
当您取消引用代码中的指针时,实际上是在尝试访问进程中的内存。您的导师不会有任何一厢情愿的想法,可以在另一个过程中使指针取消引用访问内存。
如果您希望从其他进程读取和写入内存,则必须使用ReadProcessMemory和WriteProcessMemory。
我认为你真的不需要用令牌和特权来满足所有这些长度。如果我没记错,你添加了调试权限,调用OpenProcess并直接进入它。我认为您通常可以跳过添加权限。
有些搜索确实产生了第一个答案:使用ReadProcessMemory和WriteProcessMemory可以轻松地更改另一个进程的内存中的值 任何需要获得调试权限。然而,我的导师想要的是能够定义指针(比如unsigned int),它应该指向调试对象进程的内存空间,有效地保存我之前写的基地址。他们真的想要这个,我甚至不能说出来,所以我最后坚持这样做......
他们想要的是不可能的。我建议你告诉他们在做出不可能的要求之前更好地了解虚拟内存!
@Cody Gray帮助提到了内存映射文件。如果调试对象和调试器合作,那么他们可以使用内存映射文件来共享公共的内存区域。在这种情况下,两个进程都可以将内存映射到其虚拟地址空间并以正常方式访问它。
我宁愿假设您的调试对象是一个不情愿的受害者,但如果它准备合作,那么共享内存可能是一种选择。
即使这样,您也需要小心共享内存中的任何指针,因为通常情况下,内存会映射到每个进程中的不同虚拟地址。
答案 2 :(得分:1)
我认为您正在尝试访问内核区域内存范围,因此也是例外。
用户范围是0x00000000 - 7FFFFFFF,因此请尝试在此范围内访问,因为以上任何内容都是内核空间。
我假设您使用的是32位计算机。
检查 User Space and System Space (Microsoft Docs)。
答案 3 :(得分:0)
您可以通过实现适当的运算符来创建一个行为类似于指针的类型,就像shared_ptr
一样:
foreign_ptr<int> ptr{0xDE123F00};
int a = *ptr;
*ptr = 1;