我已经为应用程序编写了C ++代码,其中有些变量必须具有不同的值,每个用户都将使用它(为简单起见,将其称为变量X)
X对于不同的用户具有不同的值。 (X)不应更改,也应嵌入exe本身(因此我无法从文件或任何其他类似的解决方案中读取它)
我不想分发源代码然后进行编译。相反,我想要一种无需编译即可直接编辑最终exe的方法(只是变量X的值不同!)这可能吗?
我这样做的想法是,如果我可以在恒定的内存位置强制使用(X),那么我可以轻松地从十六进制编辑器中编辑其值,例如。 (当黑客为某款游戏编写作弊工具时,我的意思是相同的。)
我希望我的问题很清楚
答案 0 :(得分:1)
在这个答案中,我将使用Visual Studio 2017 Community Edition,因为我想确保开发环境与Windows完全兼容。
我将介绍五种方法,从最容易维护的到较少维护的方法。当然,此答案的重点严格限于使用外部工具“共享” C ++变量的目标。
此类操作的安全性是一个不同的话题,无论如何最终都是徒劳的尝试。
Windows APIs 1 和PE 2 支持将资源嵌入可执行文件 3 中。
资源通常是图像,图标或本地化字符串,但它们可以是任何东西-包括原始二进制数据。
使用Visual Studio添加资源非常容易:在解决方案资源管理器>资源文件>添加>新建项目>资源>资源文件(.rc)
这将打开资源视图,右键单击 Resource.rc ,然后选择添加资源... 。
可以创建标准资源,但是我们需要一个可以称为RAW
的 Custom ... 类型。
这将创建一个新的二进制资源,为其提供ID,并在解决方案中创建一些文件。
回到 Solution Explorer ,我们可以看到这些新文件,并最终使用比VS集成的更好的十六进制编辑器来编辑 .bin 文件。
特别令人感兴趣的是resource.h
文件,我们可以包括该文件以定义资源ID,在我的情况下为IDR_RAW1
。
在制作完bin文件之后,我们准备在应用程序中读取它,使用的模式是通常的模式-我不希望再遍历这些API以获得新的答案,所以我将链接Official documentation并提供示例代码:
#include <Windows.h>
#include "resource.h"
int WINAPI WinMain(HMODULE hModule, HMODULE hPrevModule, LPSTR lpCmdLine, int showCmd)
{
//Get an handle to our resource
HRSRC hRes = FindResource(hModule, MAKEINTRESOURCE(IDR_RAW1), "RAW");
//Load the resource (Compatibility reasons make this use two APIs)
HGLOBAL hResData = LoadResource(hModule, hRes);
LPVOID ptrData = LockResource(hResData);
/*
ptrData is out binary content. Here is assumed it was a ASCIIZ string
*/
MessageBox(NULL, (LPCSTR)ptrData, "Title", MB_ICONINFORMATION);
return 0;
}
资源之所以好,是因为它们允许与其他自动构建工具轻松集成:在编译资源以动态生成资源之前,很容易添加构建步骤。
生成exe文件后更改也非常容易-CFF Explorer III是编辑PE模块资源的简单有效的工具。
甚至有可能完全替换资源,从而不限制我们自己将新资源的大小保持为与旧资源相同的大小。
只需在CFF中打开模块,选择资源编辑器,浏览至原始资源并进行编辑/替换即可。然后保存。
可执行文件是普通的PE模块,就像Dlls一样,区别确实是一点点。
就像Dlls可以导出函数和变量 4 一样,exe也可以。
使用VC ++,将符号标记为已导出的方法是__declspec(dllexport)
:
#include <Windows.h>
__declspec(dllexport) char var[30] = "Hello, cruel world!";
int WINAPI WinMain(HMODULE hModule, HMODULE hPrevModule, LPSTR lpCmdLine, int showCmd)
{
MessageBox(NULL, var, "Title 2", MB_ICONINFORMATION);
return 0;
}
此问题的C ++方面几乎没有受到影响。
PE模块的编辑不那么用户友好,但每个人仍然很容易遵循。
在CFF打开导出目录的情况下,将列出所有导出。
由于其支持的C ++功能,C ++编译器必须共享变量名称时,它们才能 mangle 共享-因此,在导出文件中找不到var
之类的好名字,而{{ 1}}。
在这种情况下,导出名称并没有真正实现任何目标,但是您必须能够识别正确的导出。
这应该很容易,因为很可能只有一个。
CFF将向您显示RVA函数,这是变量的RVA(相对于图像基数),您可以轻松地将其转换为文件偏移量,也可以简单地使用CFF中集成的地址转换。 br />
这将打开一个十六进制编辑器,并指向正确的字节。
如果您不想让PE导出指向您的变量,您可以告诉VS to generate a MAP file。
地图文件将列出目标文件导出的所有符号(注意:目标文件,而不是PE模块)。
因此,在这种情况下,您必须确保翻译单元导出了一个变量-这是“全局”变量的默认情况,但请务必记住不要将修改后的?var@@3PADA
链接附加到该变量上, static
,以防止编译器在常量折叠步骤中将其删除。
volatile
一个 MAP 文件将与exe一起在输出目录中生成,在其中出现这样的一行:
#include "Windows.h"
//extern is redundant, I use it only for documenting the intention
//volatile is a hack to prevent constant folding in this simple case
extern volatile int var2 = 3;
int WINAPI WinMain(HMODULE hModule, HMODULE hPrevModule, LPSTR lpCmdLine, int showCmd)
{
//A simple use of an int
return var2;
}
这为您提供了可以在CFF 地址转换器中使用的变量( 0003:00000018 ?var2@@3HC 00403018 Source.obj
)的VA。
您可以使用唯一值初始化变量。
为此,变量大小必须足够大,以使得大小相等的随机位序列最终具有相同值的概率可以忽略不计。
例如,如果var是QWORD,则在PE模块中找到另一个具有相同值的QWORD的概率非常低(2 64 中的一个),但是如果var是一个字节,则概率只有256分之一。
最终,在该变量之前添加一个标记变量(我将使用16个字节的随机数组)对其进行标记(即,作为唯一值)。
要修改PE,请使用十六进制编辑器查找该唯一值,这将为您提供要编辑的var的偏移量。
每个发行版之后,对应用程序进行反向引擎处理(这很容易,因为您可以使用VS和源代码对其进行调试),并查看编译器在何处分配了变量。
记下RVA(注意:RVA不是VA,VA是可变的),然后使用CFF编辑exe。
每次构建新版本时,都需要进行反向工程分析。
1 正确的是“ Win32” API。
2 我强烈建议读者至少习惯于PE文件格式,因为我必须假设这样做才能使答案始终简短。不了解PE文件格式可能会导致整个问题不了解。
3 实际上,在任何PE模块中。
一般 4 符号。