使用 PKI 进行轻量级文件验证
我正在尝试扩展在线游戏的遗留代码,以合理保证与游戏关联的资源文件是最新版本,并且未被篡改。我想在没有 DRM 的情况下执行此操作,无需进入内核模式,也无需硬件帮助。理想情况下,我最终决定应该与用户空间一样安全。如果他们能够绕过这些机制,那么我想说他们已经获得了顽皮的权利——至少在一段时间内,然后我们会禁止他们。 :)
显而易见的事情是对每个文件进行加密哈希,并将哈希输出与预先计算的哈希进行比较。但必须注意确保用户不能轻易篡改用于与当前计算的哈希值进行比较的预先计算的哈希值。以下是我在体系结构层面考虑的措施:
- 使用操作系统工具在进程启动时锁定所有资源文件以进行写入,这样其他进程就无法在事后覆盖这些文件。
- 计算每个资源文件的MD5和。
- 连接到 https 服务器,需要根据客户端可执行文件中存储的特定签名证书进行验证。
- 通过 https 下载包含“正确”哈希值的文件,并将其存储在 RAM 中(而不是存储到磁盘)。
- 将计算出的哈希值与通过 https 接收到的哈希值进行比较。
- 如果哈希值不匹配则退出。
这比在客户端存储哈希更安全,因为只要它们存储在客户端,通常有人就可以修改它们。使用此方案,攻击者必须弄清楚如何以更改嵌入的公共证书的方式修改可执行文件,以便它实际上包含攻击者的证书,该证书针对攻击者的 https Web 服务器进行验证,该服务器包含与攻击者的中毒哈希值相匹配的中毒哈希值。资源文件被篡改。这当然是可行的,但它比程序使用存储在客户端上的预先计算的密钥要困难得多,对吧?
可执行文件是本机代码,因此可以将其包装在加壳器中,这使得编辑二进制文件和替换公钥变得更加困难。
现在,假设我刚刚在两段前描述了如何潜在地攻击我设计的方案:除了我所描述的方法之外,还有其他方法可以攻击该方案吗?
我的第二个问题是,什么是可以从 Win32 C++(Visual Studio 2010 编译器)调用来完成这些任务的免费(如免费软件或自由)库?它们可以通过内置的 Windows API 来完成还是需要第三方解决方案?
总而言之,问题是: 1. 我建议的方案是否足够强大,足以让大多数脚本小子感到困惑?和 2. 我将使用哪些依赖项或库来实现它?
由于这只是一个游戏,与国家安全甚至货币风险无关(游戏是100%免费软件,没有游戏内经济),所以我实施的解决方案应该相当不错,但不一定是工业性的-力量。
请随意回答我的问题的一个或两个部分,如果您认为合适,请纠正我,如果您认为我一开始就问了错误的问题。
谢谢!
I am trying to extend the legacy code of an online game to provide a reasonable assurance that the resource files associated with the game are the latest version, and not tampered with. I'd like to do this without DRM, without going into kernel mode, and without hardware assistance. What I ultimately settle on should ideally be about as secure as user-space gets. If they can get around these mechanisms then I'd say they've earned the right to be naughty -- for a while at least, then we ban them. :)
The obvious thing to do is take a cryptographic hash of each of the files, and compare the hash output to pre-computed hashes. But care must be taken to assure that the user can not easily tamper with the pre-computed hashes that are used for comparison against the currently-computed hashes. Here are the measures I had in mind, at an architectural level:
- Use OS facilities to lock all the resource files for writing when the process is started, so no other processes can overwrite the files after the fact.
- Calculate the MD5 sums of each resource file.
- Connect to an https server, requiring verification against a particular signed certificate stored in the client executable.
- Download a file over https containing the "correct" hashes, storing it in RAM (not to disk).
- Compare the computed hashes to the ones received over https.
- Exit if the hashes don't match.
This is more secure than storing the hashes client-side, because as long as they are stored client-side, it is usually possible for someone to modify them. Using this scheme, the attacker would have to figure out how to modify the executable in a way that changes the embedded public certificate so that it actually contains the attacker's certificate, which verifies against the attacker's https web server containing the poisoned hashes that match the attacker's tampered resource files. This is certainly feasible, but it's much more difficult than it would be if the program were using pre-computed keys stored on the client, right?
The executable is native code, so it may be possible to wrap it in a packer that makes it even more difficult to edit the binary and replace the public key.
Now, granted that I just described two paragraphs ago how to potentially attack the scheme I devised: is there any way other than what I described to attack this scheme?
My second question is, what is a free (as in freeware OR freedom) library that can be called from Win32 C++ (Visual Studio 2010 compiler) to accomplish these tasks? Can they be done with built-in Windows APIs or is a third party solution required?
So in summary, the questions:
1. Is the scheme I suggested robust enough to baffle most script kiddies; and
2. What dependencies or libraries would I use to implement it?
Since this is just a game and not a matter of national security or even monetary risk (the game is 100% freeware with no in-game economy), the solution I implement should be pretty good, but it doesn't have to be industrial-strength.
Feel free to answer one or both parts of my question as you see fit, and do correct me if you think I'm asking the wrong questions to begin with.
Thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我喜欢你的解决方案。这是经过深思熟虑的。您基本上回答了自己的问题。我有几点建议需要指出。
最适合您任务的 Win32 库是 用于散列的 Windows Crypto API。它还支持我推荐的 SHA-1(因为 MD5 如今被认为是脆弱且可破解的)。您可能还想考虑将 HMAC 与 SHA1 结合使用,以便计算出的哈希值包含密钥(嵌入在代码中)。
Wininet 是一个适合下载的库。我相信它支持带证书验证的 HTTPS。
用于打开文件的 Windows API CreateFile本质上可以锁定内容文件。只需打开所有文件而不指定任何 FILE_SHARE_* 标志。
让我谈谈我在游戏行业的短暂经历中学到的一些其他想法。
在作弊者转向下一个有趣的游戏之前,大多数游戏的生命周期很短(几个月到一年)。因此,鉴于确实没有完美的解决方案来阻止无事可做的坚定黑客,您本质上是在寻找可以赢得时间的解决方案。也就是说,您在游戏中投入的保护黑客越多,坚定的黑客破解您的解决方案所需的时间就越长。即使您的所有保护措施都不是加密安全的,它也会为您赢得更多时间。
如果您不担心黑客会尝试反汇编和修改您游戏的 EXE,那么根据您提出的解决方案,您可能已经完成了。但请考虑以下事项。
1)这是一个更简单的攻击。在代码中的某个位置,您可能会拥有一个函数,用于根据您描述的哈希验证解决方案来决定是否可以继续进行游戏。黑客所要做的就是找到最终验证结束的位置,并强制您的验证代码始终成功。也就是说,如果您的代码中有这样的函数:
那么对于坚定的黑客来说,修改自己的 EXE 以使该代码路径始终成功就变得异常容易。黑客只需等待代码进行最终检查并替换程序集即可将错误位翻转为真。
一个明显的解决方法是将此函数的不同变体链接到游戏代码的不同区域,并在游戏过程中定期调用。使用 #define C 宏而不是函数来实现此检查。将代码的 10 种不同变体实现为 10 个不同的宏。在代码的 50 个不同位置调用这些宏 - 这样黑客就必须找到所有这些宏(并修补它们)才能成功。如果内容被认为无效,游戏就会出现错误。例如,如果他的内容文件无效,玩家的生命值和弹药值会随机变为零,这样他就很容易被杀死!
2) 更频繁地修补游戏(每隔几个月),即使除了生成新的内容哈希值和 EXE 的新布局之外没有任何理由,这是另一种赢得时间的保护。想象一下,您每个月都会修补游戏并简单地更新加密哈希算法并对内容进行一些细微的调整。发布原始破解的黑客必须重新开始。如果他再次成功,这将花费他更多的时间来分发他的更新破解版。
3)我会同时进行内容的本地验证和网络验证。他们应该使用不同的哈希算法。
4) 另一个节省时间的奇葩黑客:验证 EXE 本身的哈希值以及内容(本地和网络上)。
5) 使用内容和 exe 的另一个哈希作为简单的加密密钥,用于对发送到服务器的所有网络消息进行数字签名。您可以简单地附加到网络消息:your_simple_fash_hash(content_hash, exe_hash, network_message)。服务器对所有收到的网络消息执行相同的操作。针对不同的网络消息类型实现不同版本的哈希/签名代码。查看 Murmur 哈希 作为快速哈希函数示例。现在黑客也必须破解网络代码!
6) 让黑客很难使用 Softice 或任何其他类型的内核调试器反汇编您的游戏代码。用于存储内容验证哈希(或任何加密密钥)的任何变量也应该加密。一种简单的保护 - 将所有重要变量存储在 RAM 中时,将它们与 0xAAAAAAAAA(或其他一些噪音)进行异或。当您需要使用这些值时,将它们异或回临时变量,适当地使用它们,然后在计算完成后立即将结果清零。除了内容文件哈希之外,玩家健康、燃料和弹药的变量也应该以这种方式处理。
7) 如果可能的话,找到一种巧妙的方法在 RAM 中移动重要变量,这样它们就不会在整个游戏中保留在同一内存位置。
好吧,那是一个大脑垃圾场。我更喜欢为在 stackoverflow 上只有 1 个声誉点的人写文章。我希望它有帮助。
I like your solution. It is well thought out. You essentially answer your own questions. I have a few suggestions to point out.
The Win32 libraries most appropriate for your task are the Windows Crypto API for hashing. It also supports SHA-1, which I'd recommend (as MD5 is considered vulnerable and crackable these days). You may also want to look at using HMAC with SHA1 such that the calculated hash includes a secret key (embedded in code).
Wininet is an appropriate library for downloading. I believe it supports HTTPS with certificate validation.
The Windows API CreateFile for opening the file can essentially lock the content files. Just open all the files without specifying any FILE_SHARE_* flags.
Let me throw out a couple of other ideas that I learned during my brief time in the games industry.
Most games only have a short lifetime (a few months to a year) before the cheaters move on to the next fun game. As such, given that there really isn't a perfect solution to stop a determined hacker with nothing better to do, you are essentially looking for solutions that can buy time. That is, the more protection hacks you put into the game, the longer it will take a determined hacker to break your solution. Even if your all your protections aren't cryptographically secure, it buys you more time.
If you are not concerned with hackers who will try to disassemble and modify your your game's EXE, you likely are done given your proposed solution. But consider the following.
1) Here's a simpler attack. Somewhere in your code, you will likely have a function that decides whether or not it is acceptable to proceed with the game based on the hash validation solution you described. All a hacker has to do is find where the end of the final validation occurs and force your validation code to always succeed. That is, if you have a function like this in your code:
Then it becomes INCREDIBLY EASY for a determined hacker to modify his own EXE to make this code path always succeed. The hacker simply waits for the code to get to the point where it does the final check and replaces the assembly to flip a false bit to true.
One obvious workaround is to have different variations of this function linked into different areas of the game code and are called periodically during game play. Implement this check with a #define C macro instead of a function. Implement 10 different variations of the code as 10 different macros. Call these macros at 50 different places of the code - such that the hacker will have to find them all (and patch them) in order to succeed. Make it such that if the content is deemed to be invalid, the game behaves buggy. For example, if his content files are invalid, the game player's health and ammo value randomly go to zero such that he's any easy kill!
2) Patching the game more often (every few months), even if there's isn't any reason to other than to generate new hashes of the content and a new layout of the EXE is another protection that buys time. Imagine every month you patch the game and simply update the crypto hashing algorithm and some slight tweaks to the content. The hacker who released the original crack has to start over again. This takes him time and more time to distribute his updated crack if he's successful again.
3) I would do both local validation of the content as well as network validation. They should use different hashing algorithms.
4) Another cheezy hack that buy time: Validate the HASH of the EXE itself as well as the content (both locally and on the network).
5) Use another hash of the content and exe to be a simple crypto key for digitally signing all network messages to the server. You can simply append to the network message: your_simple_fash_hash(content_hash, exe_hash, network_message). The server does the same thing for all received network messages. Implement different versions of this hash/signature code for different network message types. Look at the Murmur hash as a fast hash function example. Now the hacker has to hack the networking code too!
6) Make it hard for the hacker to disassemble your game code using Softice or any other sort of kernel debugger. Any variables used to store content validation hashes (or any crypto keys) should be encrypted as well. One cheezy protection - XOR all important variables with 0xAAAAAAAA (or some other noise) when storing them in RAM. When you need to use these values, XOR them back into a temp var, use them appropraite, then zero out the result as soon as you are done with the computation. In addition to content file hashes, variables for the players health, fuel, and ammo should be treated this way as well.
7) If possible, find a clever way to move important variables around in RAM such that they never persist in the same memory location for the entirety of the game.
Ok, that's a brain dump. More than I like to write for someone that only has 1 reputation point on stackoverflow. I hope it helps.