第一次在这个精彩的网站上发布了我的Google搜索结果后首次发布此消息。
基本上,我想找到存储在特定内存地址的变量名称。我有一个编写的内存编辑应用程序编辑单个值,问题是每次修补应用程序保存此值时,我必须将新内存地址硬编码到我的应用程序中,并重新编译,这需要花费很多时间维持它几乎不值得做。
我想要的是获取存储在某个内存地址的变量的名称,这样我就可以在运行时找到它的地址并将其用作要编辑的内存地址。
这都是用C ++编写的。
提前致谢!
编辑:
我已经决定要从.txt文件中流式传输数据,但我不确定如何将字符串转换为LPVOID以用作WriteProcessMemory()中的内存地址。这就是我尝试过的:
string fileContents;
ifstream memFile("mem_address.txt");
getline(memFile, fileContents);
memFile.close();
LPVOID memAddress = (LPVOID)fileContents.c_str();
//Lots of code..
WriteProcessMemory(WindowsProcessHandle, memAddress, &BytesToBeWrote, sizeof(BytesToBeWrote), &NumBytesWrote);
代码在语法方面都是正确的,它编译并运行,但WriteProcessMemory错误,我只能想象它与我的错误LPVOID变量有关。如果扩展我的问题的使用是违反规则,我会道歉,如果是,我将删除我的编辑。
答案 0 :(得分:7)
编译并生成所谓的map
文件。这可以使用Visual-C ++(/MAP
链接器选项)轻松完成。在那里你会看到符号(函数,......)及其起始地址。使用此映射文件(注意:每次重新编译时都必须更新),您可以将地址与名称匹配。
这实际上并不那么容易,因为地址是相对于首选加载地址的,并且可能(随机化)与实际加载地址不同。
可以在此处找到有关检索正确地址的一些旧提示:http://home.hiwaay.net/~georgech/WhitePapers/MapFiles/MapFiles.htm
答案 1 :(得分:3)
通常,编译程序时不会保留变量的名称。如果您控制编译过程,通常可以配置链接器和编译器以生成列出所有全局变量的内存中的位置的映射文件。但是,如果是这种情况,您可以通过不使用直接内存访问来更轻松地实现目标,而是创建外部程序可以调用的正确命令协议。
如果您无法控制其他程序的编译过程,那么您可能运气不好,除非程序附带了映射文件或调试符号,其中任何一个都可用于派生变量的名称来自他们的地址。
请注意,对于堆栈变量,派生它们的名称将需要完整的调试符号,这是一个非常重要的过程。堆变量没有名称,所以你自然没有运气。此外,正如@jdehaan的回答中所提到的,地图文件在最好的时候可能有点棘手。总而言之,最好有一个适当的控制协议,你可以使用它来避免任何依赖于其他程序内存的内容。
最后,如果您无法控制其他程序,那么我建议将变量位置放入单独的数据文件中。这样您就不再需要每次都重新编译,甚至可以支持多个版本的程序。如果您愿意,也可以使用某种自动更新服务从您的服务器中提取此数据文件的新版本。
答案 2 :(得分:1)
除非您确实拥有相关应用程序,否则没有标准方法可以执行此操作。如果您拥有该应用程序,则可以关注@jdehaan answer。
在任何情况下,为什么不将内存地址硬编码到应用程序中,为什么不在某个地方托管一个简单的feed,你可以随时更新每个版本的目标应用程序需要更改的内存地址?这样,您可以在需要能够操作新版本时更新该Feed,而不是每次都重新编译应用程序。
答案 3 :(得分:1)
你不能直接这样做;变量名实际上不存在于已编译的二进制文件中。如果程序是用Java或C#编写的,那么可能能够做到这一点,它会在已编译的二进制文件中存储有关变量的信息。
此外,这通常不可能,因为目标程序内的最新值的副本总是可能位于CPU寄存器内而不是内存中。如果有问题的程序是在发布模式下编译的,并且启用了优化,则更有可能发生这种情况。
如果可以确保目标程序是在调试模式下编译的,那么您应该能够使用编译器发出的调试符号(.pdb文件)来将地址映射到变量,但在这种情况下,您需要像正在调试一样启动目标进程 - 普通的读进程内存和写进程内存方法不起作用。
最后,您的问题忽略了一个非常重要的考虑因素 - 即使存储了此类信息,也不需要存在与特定地址相对应的变量。
答案 4 :(得分:0)
如果您有相关应用程序的源代码并且不考虑最佳内存使用情况,那么您可以在类似于以下内容的调试友好结构中声明有趣的变量:
typedef struct {
const char head_tag[15] = "VARIABLE_START";
char var_name[32];
int value;
const char tail_tag[13] = "VARIABLE_END";
} debuggable_int;
现在,您的应用应该能够在程序的内存空间中搜索并查找head和tail标记。找到您的一个可调试变量后,它可以使用var_name
和value
成员来识别和修改它。
但是,如果你要达到这个长度,你可能最好在启用调试符号和使用常规调试器的情况下进行构建。
答案 5 :(得分:0)
SymFromName
,它将允许您提供符号的名称,它将返回(除其他外)该符号的地址
当然,要做到这一点,你将必须在允许进行调试的帐户下运行。但是,至少对于全局变量,您不一定要停止目标进程来查找符号,地址等。事实上,如果它选择的话,它对于一个使用它们的进程来说就可以了。我早期的实验中很少有人知道这些功能的确如此。这里是我几年前写的一些演示代码,它至少给出了一个大致的想法(虽然它已经足够老了它使用SymGetSymbolFromName
,这是SymFromName
之后的几代人。用调试信息编译并退回 - 它产生了相当多的输出。
#define UNICODE
#define _UNICODE
#define DBGHELP_TRANSLATE_TCHAR
#include <windows.h>
#include <imagehlp.h>
#include <iostream>
#include <ctype.h>
#include <iomanip>
#pragma comment(lib, "dbghelp.lib")
int y;
int junk() {
return 0;
}
struct XXX {
int a;
int b;
} xxx;
BOOL CALLBACK
sym_handler(wchar_t const *name, ULONG_PTR address, ULONG size, void *) {
if (name[0] != L'_')
std::wcout << std::setw(40) << name
<< std::setw(15) << std::hex << address
<< std::setw(10) << std::dec << size << L"\n";
return TRUE;
}
int
main() {
char const *names[] = { "y", "xxx"};
IMAGEHLP_SYMBOL info;
SymInitializeW(GetCurrentProcess(), NULL, TRUE);
SymSetOptions(SYMOPT_UNDNAME);
SymEnumerateSymbolsW(GetCurrentProcess(),
(ULONG64)GetModuleHandle(NULL),
sym_handler,
NULL);
info.SizeOfStruct = sizeof(IMAGEHLP_SYMBOL);
for (int i=0; i<sizeof(names)/sizeof(names[0]); i++) {
if ( !SymGetSymFromName(GetCurrentProcess(), names[i], &info)) {
std::wcerr << L"Couldn't find symbol 'y'";
return 1;
}
std::wcout << names[i] << L" is at: " << std::hex << info.Address << L"\n";
}
SymCleanup(GetCurrentProcess());
return 0;
}
答案 6 :(得分:0)
WinDBG有一个特别有用的命令
LN
给定内存位置,它将在该位置给出符号的名称。使用正确的调试信息,它是一个调试器(我的意思是调试人员:))boon!。
以下是我系统上的示例输出(XP SP3)
0:000&GT;在7c90e514(7c90e514)
ntdll!KiFastSystemCallRet | (7c90e520)ntdll!KiIntSystemCall 完全匹配: ntdll!KiFastSystemCallRet()