0x00 前言
之前写了CRC Check Bypass篇,以LUA脚本的方式过检测
而本文则以C++ HOOK的方式来过掉CRC检测,本文后续的所有代码均是为了写成DLL
之前那一篇用的CE自带的gtutorial-x86_64
本文依然使用CE自带的gtutorial
,不过是gtutorial-i386
0x01 Go to do it !
浅析
具体思路和原理已经写在了CRC Check Bypass
篇,这里便不再重复叙述。
由于由64位(x86_64)换成了32位(i386),地址也会有所变化,依然按照之前那篇的方式去寻找,找得三个扫描地址。
对于004332C2
处
可以看到此处代码将[eax+ecx*2]
的值赋值给eax,从而进行后续的操作。
由此可以知道两条信息:
[eax+ecx*2]
即为需要校验的字节,即将被存放到eax中。eax+ecx*2
(注意没有加[])即为存放该字节的地址。
接下来要做的就是“移花接木”,具体细节在CRC Check Bypass
篇也有写,概括就是,复制全部代码段到一段自己申请的内存,让这个CRC扫描程序去扫描复制过去的代码段,然后自己就可以随意修改真正的代码段了。
但是问题的难点,也就是本文的重点也就来了:如何用C++去实现这些?
难点一 内联汇编如何编写?
可以在一个函数中采用__asm{}
代码块的方式来编写汇编代码
基本格式为
1 | static __declspec(naked) void foo() { |
static关键字
为了规避LNK2005
的报错
naked关键字
告诉编译器,以下的汇编不需要编译器再优化什么指令
难点二 相关参数如何传递?
接下来就是边界比较了,那么该如何将上边界、下边界、基址、复制的代码段的基址等传递进来?
我曾想过将函数写成static __declspec(naked) void foo(int param1, int param2)
这种形式,但是这种形式的函数,在HOOK的时候,也是需要传参的,这样就极大的加大了HOOK框架的编写难度。
翻阅了许多现有的代码,我发现大部分人都采用了硬编码的方式,例如要push程序基址,因为32位程序基址几乎都是0x400000,他们可以在确认目标程序的基址是0x400000以后硬编码进去,会有类似于如下的代码
1 | DWORD base = 0x400000 |
基址还好,基本是个固定值,但是如果涉及到程序大小,而我还想动态获取它,那该如何操作?
由于算是第一次接触内联汇编,也是不了解具体的优先顺序,我一直担心一个问题。
如果我创建一个全局变量temp,然后利用一个函数动态地在HOOK之前就给这个全局变量temp赋上相应的值,那么我的内联汇编中类似于push temp
的这个语句,到底是push了动态赋值前的temp还是动态赋值后的temp?
经过实验,答案是后者!
这也就意味着,我可以大胆的将全局变量应用到内联汇编中,尽管在编写代码的时候它们还未被真正的赋值!
而且后续的测试证明,即便是封装成一个类,然后创建一个类对象,去传递这个对象的成员变量,也是可行的!
但是也一定要记住,在HOOK之前,一定保证全局变量被动态赋值了!!
优化点一 封装成类
涉及到这么变量,不妨封装成一个类,然后在构造函数中,执行各种初始化(动态赋值)操作。
1 | //CRC_Bypass.h |
难点三 内联汇编函数如何设计?
对于0x4332C2
处,编写得到如下代码:其中test
是上述封装的类的一个对象实例。
1 | //为了避免“naked只能应用到非成员函数的定义”的报错,后续所有这类内联汇编函数均写在CRC_Bypass.cpp中 |
首先,由于会用到eax寄存器,故需要保存一下原有的值,可使用
1 | push eax |
但是记得最后再进行一个pop eax
在浅析
中我们分析得知,
[eax+ecx*2]
即为需要校验的字节,即将被存放到eax中。eax+ecx*2
(注意没有加[])即为存放该字节的地址。
第2点可以作为突破口
我们需要得到这个地址,显然可以使用lea指令
,具体如下
1 | lea eax, [eax + ecx * 2] |
从而使得eax中存放的是当前正在扫描的地址
接下来比较这个地址是否超过上下界,如果越界均跳转到originalcode1
标签处,然后执行程序原本的代码,不再进行“移花接木”。
1 | cmp eax, [test.pProcessImageBase] |
接下来借助如下三个关系
当前语句在原始代码段中的偏移=当前地址(还在原始代码段中)-原始代码段的基址
语句在原始代码段中的偏移=语句在新代码段(复制过去的)中的偏移
当前语句在新代码段中的地址=新代码段的基址+偏移语句在原始代码段中的偏移
可以得当前地址-原始代码段基址+新代码段基址=目标扫描地址(我们想让程序去扫的那个地址)
1 | sub eax, [test.pProcessImageBase] |
然后由于程序原本后续的各种操作都是利用eax寄存器
而目前eax中是地址,地址里面存的是等待进行后续操作的值
故需要将eax中的值再传递给eax寄存器
1 | movzx eax, word ptr[eax] |
然后需要直接跳转到exit1
标签处,以避免执行originalcode1
标签处的原始代码。
1 | jmp exit1 |
至此,我们完成了“移花接木”操作。
但是并没有完全结束,我们还需要收尾,由于我采用的是HOOK技术学习笔记-其二篇的HOOK小模板,固定至少需要修改8个字节
明显能看出来,这8个字节会占用3条指令,第三条指令虽然占了但是没占满。
所以可以得出来如下结论:
- 我们需要在内联汇编函数中,补全被占掉的指令,其中第一条已经在前面补完了,包括两种处理情况,第一种是地址在上下界之间,则“移花接木”然后补;第二种是地址越界了,直接补(originalcode1)。
- 补完以后要跳转回第四条指令,关于这个跳转,有很多办法,可以jmp,也可以push 地址/ret
而且,还记得前面的push eax
吗,在收尾的时候,也要进行一次pop eax
综上所述,可以有
1 | exit1 : |
至此,内联汇编函数设计完毕,其他两处均可以仿照这个思路来进行设计!
难点四 如何进行HOOK?
我采用的是HOOK技术学习笔记-其二篇的HOOK小模板,能够保证HOOK的原子性
1 | //CRC_Bypass.cpp |
1 | //main.h |
1 | // dllmain.cpp : 定义 DLL 应用程序的入口点。 |