驱动练习:R0变速

驱动练习:驱动级变速以及两种键盘过滤钩子~

由于一直没发博客,有很多存货打算发一下,首先来看驱动级变速,一般来说,r3层的变速钩子并不难写,主要是hook GetTickCount()的返回值,对于r0来说,也是hook函数,只不过hook的是Ke头的函数,一个是KeUpdateSystemTime,这个函数顾名思义都知道,是用来更新系统时间的,hook它肯定是必不可少的,还有一个函数是KeQueryPerformanceCounter,这个函数原型如下
LARGE_INTEGER KeQueryPerformanceCounter( IN PLARGE_INTEGER PerformanceFrequency OPTIONAL );
其中PerformanceFrequency 表示CPU的频率,此函数返回系统从启动到此刻的时间,类型为LARGE_INTEGER,这个函数可以在r0层获得精确到us级的时间(因为是在硬件上支持的高精度计时器),当然如果你需要的是计算时间的话,需要使用两次这个函数去获得时间差。现在咱们就上windbg看看这个两个函数,方便hook

ok,下面进行hook吧,首先,需要获得这两个函数在内存中的地址,由于是导出函数,直接上MmGetSystemRoutineAddress,

NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path) {
driver->DriverUnload = Unload;//注册吊销
UNICODE_STRING hookname1 = RTL_CONSTANT_STRING(L"KeUpdateSystemTime");
oriUpdateTime = MmGetSystemRoutineAddress(&hookname1);//获取内核导出函数的地址
UNICODE_STRING hookname2 = RTL_CONSTANT_STRING(L"KeQueryPerformanceCounter");
oriQuery = MmGetSystemRoutineAddress(&hookname2);//获取内核导出函数的地址

然后需要首先查询一下系统的时间,注册LARGE_INTEGER变量来保存这个时间

g_liPreOriginalCounter.QuadPart = 0;
g_liPreReturnCounter.QuadPart = 0;
g_liPreOriginalCounter = KeQueryPerformanceCounter(NULL);
g_liPreReturnCounter.QuadPart = g_liPreReturnCounter.QuadPart;//初始化两个时间变量,一个用来保存首次查询的

由于使用的是jmp钩子,大家知道了jmp后面的与当前地址的偏移,所以说,我们需要计算出原始函数地址与Fake函数的地址的offset

BYTE nJmpCode1[5] = { 0xE9,0x00,0x00,0x00,0x00 };
//e9是jmp,用来调转到自己的地址
BYTE nJmpCode2[5] = { 0xE9,0x00,0x00,0x00,0x00 };
*(DWORD*)(nJmpCode1 + 1) = (DWORD)Fake_KeUpdateSystemTime - ((DWORD)oriUpdateTime + 5);
//这里jmp指令后面跟的是地址偏移,所以要用新地址减去旧地址。+5是e9后面的长度
*(DWORD*)(nJmpCode2+ 1) = (DWORD)Fake_KeQueryPerformanceCounter - ((DWORD)oriQuery + 5);

获取了偏移并且保存了原始函数的地址之后,就可以进行hook了,也就是用自己的jmp指令去代替原始函数的指令

RtlCopyMemory(oriUpdateTimeCode, oriUpdateTime, 5);
RtlCopyMemory(JmpOriginal_KeUpdateSystemTime, oriUpdateTime, 5);
RtlCopyMemory((BYTE*)oriUpdateTime, nJmpCode1, 5);//这个把跳转的opcode保存到里
/////////////
RtlCopyMemory(OriQueryCode, oriQuery, 5);
RtlCopyMemory(JmpOriginal_KeQueryPerformanceCounter, oriQuery, 5);
RtlCopyMemory((BYTE*)oriQuery, nJmpCode2, 5);

最后看下Fake函数的编写

LARGE_INTEGER __stdcall Fake_KeQueryPerformanceCounter(OUT PLARGE_INTEGER PerformanceFrequency) {
LARGE_INTEGER liResult;
LARGE_INTEGER liCurrent;
liCurrent = JmpOriginal_KeQueryPerformanceCounter(PerformanceFrequency);
liResult.QuadPart = g_liPreReturnCounter.QuadPart + (liCurrent.QuadPart - g_liPreOriginalCounter.QuadPart)*g_dwSpeed_X / g_dwSpeedBase;
//上面一句实现的是变速
g_liPreOriginalCounter.QuadPart = liCurrent.QuadPart;
g_liPreReturnCounter.QuadPart = liResult.QuadPart;
return liResult;
}

这个fake的函数中主要是对KeQueryPerformanceCounter这个函数返回值的hook,主要将这个函数的返回值乘上倍数就可以实现返回了更慢的时间,至于KeUpdateSystemTime这个函数的fake函数,在下面可以看到只是几句简单的asm,同样是把时间放慢了

VOID __declspec(naked) __cdecl Fake_KeUpdateSystemTime() {
__asm {
    mul g_dwSpeed_X
    div g_dwSpeedBase
    jmp JmpOriginal_KeUpdateSystemTime
}//一开始的时间是一个长整型,用eax和edx联合保存的
}

至于JmpOriginal_KeUpdateSystemTime这个函数,同样是用asm写的,也就是负责跳回原函数并且处理下堆栈

LARGE_INTEGER __declspec(naked) __stdcall JmpOriginal_KeQueryPerformanceCounter(OUT PLARGE_INTEGER PerformanceFrequency) {
__asm
{
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    mov eax, KeQueryPerformanceCounter
    add eax, 5
    jmp eax
}}

另一个同样,总结下,通过hook KeQueryPerformanceCounter和KeUpdateSystemTime的返回值实现了驱动级变速,也没啥特殊的,毕竟都是导出函数,算是驱动hook的一次练习