嘿,你还不是黑客么?
从HL1转到刚了HL2SDK的人通常会有相同的疑问,例如,如何改变速度,如何改变玩家外观颜色?这些都是小事,以及独立的MOD在HL2都不是问题。
取而代之的,你需要面对另一个噩梦:你必须使用MOD的 CBaseEntity 来实现一些功能,但是不同的mod有不同的虚拟表(virtual table layout)。调用 CBaseEntity::SetHealth 也许能在 HL2MP中使用,但不能在CS:S 或 DoD:S 中使用,调用有些 function 甚至会引起程序崩溃,有些简单的function没有接口提供你调用,你该怎么办?
你需要:
a. 找到这个function
b. 通过一些方法在程序运行时定位到这个function
c. 调用它
这篇文章,我会重点讲述后两个。查找function是很困难的,但也不是不可能完成的任务。这包含一些逆向工程的内容。我推荐你使用OllyDbg 或者 IDA 逆向包含你所需function的二进制文件,通常,搜索关键字符串附近你就能很快找到你需要的function。
例如:
我想要CS:S中能够结束本回合的一个function。根据经验,回合结束的时候会显示#Round_Draw 译文的消息,在IDA中搜索这个字符串附近,我找到这个function:(此处为一个图片,但图片链接已失效,凑合看)因为另外一个字符串在 0x2205A240 ,这个看起来更像是我需要的,所以我完成了最难的部分,查找function!
然而,0x2205a240这个地址很有可能在CS:S下次编译的时候改变,我们需要一个方法在程序运行时找到这个function,签名扫描(signature scanning),用非常原始的方法存储function的头并扫描它。每次插件加载时,你需要搜索全部的dll的内存,来寻找这个function的唯一字节集,如果你找到了它,你就找到了这个function的基址,这里有两个重要的问题:
- 字节集是唯一的么?
- 字节集在运行时会改变么?
两个问题的答案通常都是肯定的!不是因为代码会自我修改,只是因为代码是浮动的(relocatable)。首先我们来做简单的部分:存储function的头32个字节。
#define MKSIG(name) name##_Sig, name##_SigBytes
#define RoundEnd_Sig "\x83\xEC\x18\x53\x55\x8B\xE9\x8B\x4C\x24\x28\x56\x57\x33\xFF\x8D"
#define RoundEnd_SigBytes 16
我只完成了16个字节,但是你知道了方法。说明我们需要扩大这个签名到64字节,因为现在还不是唯一的,一个常见的问题出现:这段指令在运行有很大的可能会发生改变,这样我们就无法定位到它了。
jmp ds:off_2205A4A0[eax*4]
offsets表现形式类似与数组(array),跳转到数组的某个键?这肯定需要一个键值对照表。无论如何,function拆解成二进制看起来是这样的
FF 24 85 A0 A4 05 22
既然我们知道 0x2205A4A0 很容易因为重新编译而改变,那就让我们采用通配符 *(0x2A)来表示。地址在程序集中使用的是低字节序,所以我们的签名修改后应该像这样:
#define gaben "\xFF\x24\x85\x2A\x2A\x2A\x2A"
现在,我们完成了签名,明天:如何在内存中找到dll,以及如何扫描出基址!
相关文章
发现功能,第2部分(Finding Functions, Part 2)
发现功能,第3部分(Finding Functions, Part 3)
原文
Oh, you’ve not hacked yet?
Newcomers to the HL2SDK often have the same question. They come from the world of HL1 where simple things, such as changing velocities and making players glow, were trivial. Not only trivial, in fact, but mod independent. This isn’t the case in HL2.
Instead, you’re faced with a nightmare: you must use CBaseEntity from a mod, but that mod has changed the virtual table layout. Calling CBaseEntity::SetHealth might work on, say, HL2MP — but not on CS:S or DoD:S. Some functions will crash, others simply won’t work. The function you need can’t be done through datamaps. What do you do?
You need to a)Find the function, b)Make a way to find it at runtime, and c)Call it. In this article, I’ll concentrate on the latter two. Finding the function is often very difficult, but it can be done if you learn the tricks. Without going into details, it requires a bit of reverse engineering. I recommend getting a copy of OllyDbg (free) or IDA (not for free) and disassembling the binary containing the function. Using literal strings and catching flows of execution, you can often find what you’re looking for quite quickly.
For an example, I wanted the function in CS:S that terminates the round. I know from experience that this will display the translated message for “#Round_Draw”. After mucking around in IDA, I find this function:
AVOID LIKE THE PLAGUE
Because of the other strings at 0x2205A240, it looks like a good candidate! (In fact this is something CS:S DM uses). I’ve completed the hardest part: finding the function!
However, it’s quite likely that 0x2205a240 will change next time CS:S is recompiled. I need a way to find this function at runtime – something fysh and lance have called “signature scanning”. For today, I’ll cover only “stupid” signature scanning – a very primitive way of storing the function header and scanning for it. The idea is that every time your plugin loads, you will search the entirety of the DLL’s memory for a certain sequence of bytes (the first ~32 bytes of the function). If you find it, then you have found the address of the function. There are two important things to consider: Is the sequence unique, and could the sequence itself change at runtime?
The answer to these is usually both yes. Not because the code is self editing (although CS:S DM has a bug where it edits a function, doesn’t restore it, and then cannot find it again), but because of the way code is relocatable, offsets to other sections will definitely change. Linux is even more random in this regard: ELF libraries rarely load at the same address each time. First, lets’s do the simple part: store the first 32 bytes of the function.
#define MKSIG(name) name##_Sig, name##_SigBytes
#define RoundEnd_Sig "\x83\xEC\x18\x53\x55\x8B\xE9\x8B\x4C\x24\x28\x56\x57\x33\xFF\x8D"
#define RoundEnd_SigBytes 16
I’ve only done 16 bytes but you get the idea. Say we extend this signature to 64 bytes because it’s not unique. A common problem occurs: there is an instruction that has a high chance of changing at any time from relocatability:
jmp ds:off_2205A4A0[eax*4]
A simple investigation of this offset shows it looks like an array of addresses. Jumping to an array of address? This must be a case table for a switch statement. Anyway, this function in the disassembly looks like:
FF 24 85 A0 A4 05 22
Since we know 0x2205A4A0 could easily change from a recompile or base relocation*, let’s introduce a wildcard character: ‘*’ (0x2A). The address in the instruction is stored in little-endian order from the end. Our signature for this instruction would look like:
#define gaben "\xFF\x24\x85\x2A\x2A\x2A\x2A"
Now, I have a complete signature. Tomorrow: how to find the DLL in memory, and how to scan for the address!
*– For those paying attention, that address is actually with the code. It has no chance of being relocated. However it could definitely change with a recompile. A better example would be an instruction going to the .data section or an E8 eip-relative call, or one of those string offsets.

浙公网安备 33010602011771号