传统变速齿轮通过HOOK GetTickCount时间函数 之后修改返回值获得效果。

这样做的好处是方便,不好是容易被一些反的游戏检测出来.或者游戏自己仿写一个GetTickCount也不难.

因为分析GetTickCount的动作。其实是访问了进程内存地址$7FFE0000。

进程内存地址$7FFE0000上是一个不断更新的数值。其更新是由系统来完成。系统中$FFDF0000地址正是跟进程上映射到相同的物理地址。

那么我们可以在系统中再创建一个空间,作为一个假的时间记录空间,然后由系统更新$FFDF0000的时候顺便更新这个假的记录空间,当然这个是按照我们的意思来更新(即时间变快),然后将这要改变速度的目标进程的内存地址$7FFE0000映射改为我们这个假的空间。那么无论是 GetTickCount函数获得的都是只按我们意愿改变的时间。

原理说完了。来看下代码:

首先。我们在驱动入口时,此时位于系统上下文,我们应该做些什么。

1.获得系统更新时间函数的地址:

代码:
 RTlInitAnsiString(@g_KeUpdateSystemTime_Name_Ansi,'KeUpdateSystemTime');
 RtlAnsiStringToUnicodeString(@g_KeUpdateSystemTime_Name_Unicode,@g_KeUpdateSystemTime_Name_Ansi,true);   //字符
 g_KeUpdateSystemTime_Address:=MmGetSystemRoutineAddress(@g_KeUpdateSystemTime_Name_Unicode);
 g_KeUpdateSystemTime_Address_C:=cardinal(g_KeUpdateSystemTime_Address);
 RtlFreeUnicodeString(@g_KeUpdateSystemTime_Name_Unicode);
2.因为我们要HOOK这个函数(为的是系统更新时间时一并替我们更新假的空间上数据),所以要看下这个函数的版本是否适合(我用的是XP SP3),这个叫硬编码吧?我使用了....没办法..技术的确不高...
代码:
 if g_KeUpdateSystemTime_Address<>nil then
 begin
  g_pmdlSystemCall:=MmCreateMdl(nil,g_KeUpdateSystemTime_Address,$6D);
  if g_pmdlSystemCall<>nil then
  begin
   MmBuildMdlForNonPagedPool(g_pmdlSystemCall);
   g_pmdlSystemCall^.MdlFlags:=g_pmdlSystemCall^.MdlFlags or MDL_MAPPED_TO_SYSTEM_VA;
   MappedSystemCallTable:=MmMapLockedPages(g_pmdlSystemCall,KernelMode);
   Byte_KeUpdateSystemTime:=g_KeUpdateSystemTime_Address;
   if Byte_KeUpdateSystemTime^=$B9 then
   begin
    DbgPrint('版本匹配成功1!'#13#10);
    Cardinal_KeUpdateSystemTime:=PTR(g_KeUpdateSystemTime_Address_C+1);
    if Cardinal_KeUpdateSystemTime^=$FFDF0000 then
    begin
     DbgPrint('版本匹配成功2!'#13#10);
     SysBBOK:=true;
    end;
   end;
  end;
 end;
3.改写时间更新函数的代码,一个跳转跳到我们的自己的代码的地址上去。。
代码:
  HST1Next:=PTR(g_KeUpdateSystemTime_Address_C+5);
  HST1To:=cardinal(@HST1)-g_KeUpdateSystemTime_Address_C-5;
  Cardinal_KeUpdateSystemTime:=PTR(g_KeUpdateSystemTime_Address_C+1);
  Byte_KeUpdateSystemTime:=g_KeUpdateSystemTime_Address;
  Cardinal_KeUpdateSystemTime^:=HST1To;
  Byte_KeUpdateSystemTime^:=$E9;
4.我们自己的代码,看看做了什么。
代码:
procedure HST1;
asm
 push eax
 push ebx
 push ecx
 push edx
 call HST1EX    //调用顺便更新假空间的函数
 pop edx
 pop ecx
 pop ebx
 pop eax
 mov ecx,$FFDF0000   //恢复我们改写的内容,在系统更新时间函数上我们的跳转覆盖了这个指令
 jmp HST1Next           //跳回去下一个指令
end;
5.而HST1EX(调用顺便更新假空间的函数)所做的:
代码:
procedure HST1EX;
label L;
begin
 if HST1_OPEN=1 then
 begin
  HST1_GT_Temp1:=PTR($FFDF0004);
  HST1_GT1_INGG:=HST1_GT_Temp1^;
  HST1_GT_Temp1:=PTR($FFDF0000);                   //取系统上真实的时间.
  HST1_GT1:=HST1_GT_Temp1^;
  HST1_GT_Temp2:=PTR($FFDF0008);
  HST1_GT2:=HST1_GT_Temp2^;
  HST1_GT_Temp3:=PTR($FFDF0014);
  HST1_GT3:=HST1_GT_Temp3^;
  
  if HST1_GT_JiaShu=0 then
  begin
   HST1_GT1_Save:=HST1_GT1;
   HST1_GT2_Save:=HST1_GT2;
   HST1_GT3_Save:=HST1_GT3;
  end;

//加快的时间=真实系统时间+(真实系统时间-原记录系统时间)*要改变的速度倍数
  HST1_GT1_ING:=HST1_GT1+(HST1_GT1-HST1_GT1_Save)*(HST1_GT_JiaShu);

//  HST1_GT2_ING:=HST1_GT2+(HST1_GT2-HST1_GT2_Save)*(HST1_GT_JiaShu);
  HST1_GT_CC1:=HST1_GT_JiaShu;
  HST1_GT_CC2:=HST1_GT2-HST1_GT2_Save;
  //HST1_GT2_ING:=HST1_GT_CC1*HST1_GT_CC2;
  llmul(HST1_GT_CC1,HST1_GT_CC2,HST1_GT2_ING);
  HST1_GT2_ING:=HST1_GT2+HST1_GT2_ING;

  HST1_GT_TempC2:=@HST1_GT2_ING;
  inc(HST1_GT_TempC2);
  HST1_GT2_INGG:=HST1_GT_TempC2^;
  
//  HST1_GT3_ING:=HST1_GT3+(HST1_GT3-HST1_GT3_Save)*(HST1_GT_JiaShu);
  HST1_GT_CC1:=HST1_GT_JiaShu;
  HST1_GT_CC2:=HST1_GT3-HST1_GT3_Save;
  //HST1_GT3_ING:=HST1_GT_CC1*HST1_GT_CC2;
  llmul(HST1_GT_CC1,HST1_GT_CC2,HST1_GT3_ING);
  HST1_GT3_ING:=HST1_GT3+HST1_GT3_ING;

  HST1_GT_TempC3:=@HST1_GT3_ING;
  inc(HST1_GT_TempC3);
  HST1_GT3_INGG:=HST1_GT_TempC3^;


  HST1_GT_Temp1:=g_pSharedMemory;
  HST1_GT_Temp1^:=HST1_GT1_ING;
  Inc(HST1_GT_Temp1);
  HST1_GT_Temp1^:=HST1_GT1_INGG;

  HST1_GT_Temp2:=PTR(cardinal(g_pSharedMemory)+$8);
  HST1_GT_Temp2^:=HST1_GT2_ING;

  HST1_GT_Temp1:=PTR(cardinal(g_pSharedMemory)+$10);
  HST1_GT_Temp1^:=HST1_GT2_INGG;

  HST1_GT_Temp3:=PTR(cardinal(g_pSharedMemory)+$14);
  HST1_GT_Temp3^:=HST1_GT3_ING;

  HST1_GT_Temp1:=PTR(cardinal(g_pSharedMemory)+$1C);
  HST1_GT_Temp1^:=HST1_GT3_INGG;

  g_pSharedMemory_Temp:=PTR(cardinal(g_pSharedMemory)+$20);
  HST1_Memory_Temp:=PTR($FFDF0020);
  HST1_N:=0;
L:
  g_pSharedMemory_Temp^:=HST1_Memory_Temp^;
  inc(g_pSharedMemory_Temp);       //除了更新时间地址,还得把后面空间上的内容一并复制上去.
  inc(HST1_Memory_Temp);
  HST1_N:=HST1_N+1;
  if HST1_N<1016 then
  begin
   goto L;
  end;
 end;
end; 

好了,现在我们拥有了一个和$FFDF0000(一个页面)上面内容一模一样的页面,并且上面的时间记录数据是可以按我们的意思改变的.

然后我们该去改变目标进程了.找到关于内存映射到物理地址的表.

1.首先.我们要知道 分页机制开启 CR0中最高位PG位控制分页管理机制

代码:
    asm
     mov eax,cr0
     mov GetMyCR0,eax
    end;
2.如果GetMyCR0最高位为1,那么我们得知道 开启PAE情况

代码:
    asm
     mov eax,cr4
     mov GetMyCR4,eax
    end;
3.如果GetMyCR4第5位上为0.那么就为未开启PAE,否则开启PAE

4.对于两种情况的处理,我们这里还先得获得目标进程的CR3
代码:
    if PsLookupProcessByProcessId(PID,GetThePeProcess)=STATUS_SUCCESS then
    begin
     ObfDereferenceObject(GetThePeProcess);
     GetThePeProcessP:=GetThePeProcess;
     GETPIDCR3:=GetThePeProcessP^.Pcb.DirectoryTableBase;
    end;
5.未开启PAE情况
代码:
 PDEBA:=uCR3;
 dwPDEIndex:=GetCardinalBIT(VirtualAddr,22,10); //取线性地址的高2位作为选取页目录指针表项的索引
 dwToPTE:=PDEBA+dwPDEIndex*4;   //计算指针表项地址
 //读出地址数据
 CLow:=ReadPhyMem(dwToPTE);

 if GetCardinalBIT(CLow,7,1)=1 then  //使用的是4MB的页
 begin
  exit;
 end;

 CI:=GetCardinalBIT(CLow,12,20);
 CI:=CI shl 12;

 PTEBA:=CI;
 dwPTEIndex:=GetCardinalBIT(VirtualAddr,12,10);
 dwToP:=PTEBA+dwPTEIndex*4;   //计算指针表项地址

 result:=dwToP;
6.开启PAE情况
代码:
 PDPTEBA:=uCR3;
 dwPDPTEIndex:=GetCardinalBIT(VirtualAddr,30,2); //取线性地址的高2位作为选取页目录指针表项的索引
 dwToPDE:=PDPTEBA+dwPDPTEIndex*8;   //计算指针表项地址
 //读出地址数据
 CLow:=ReadPhyMem(dwToPDE);
 CHigh:=ReadPhyMem(dwToPDE+4);
 CI:=GetCardinalBIT(CLow,12,20)+GetCardinalBIT(CHigh,0,4) shl 20;
 CI:=CI shl 12;

 PDEBA:=CI;
 dwPDEIndex:=GetCardinalBIT(VirtualAddr,21,9);
 dwToPTE:=PDEBA+dwPDEIndex*8;   //计算指针表项地址

 //读出地址数据
 CLow:=ReadPhyMem(dwToPTE);
 CHigh:=ReadPhyMem(dwToPTE+4);

 if GetCardinalBIT(CLow,7,1)=1 then  //使用的是2MB的页
 begin
  exit;
 end;

 CI:=GetCardinalBIT(CLow,12,20)+GetCardinalBIT(CHigh,0,4) shl 20;
 CI:=CI shl 12;

 PTEBA:=CI;
 dwPTEIndex:=GetCardinalBIT(VirtualAddr,12,9);
 dwToP:=PTEBA+dwPTEIndex*8;   //计算指针表项地址

 result:=dwToP;
得到表记录的地址之后,剩下的事情就是将记录内容(原指向的物理地址),改为我们假空间的物理地址即可..

未开启
代码:
    CLow:=ReadPhyMem(Addr);
    CI:=GetCardinalBIT(CLow,12,20);
    CI:=CI shl 12;
    S1:=PNAME+';'+inttostr(PID)+';'+inttostr(Addr)+';'+inttostr(CLow)+';'+''+';'+inttostr(CI)+';';
    CLow:=MYPLYMEM+GetCardinalBIT(CLow,0,12);
    WritePhyMem(Addr,CLow);
    ListBox2.Items.Add(S1);
开启
代码:
    CLow:=ReadPhyMem(Addr);
    CHigh:=ReadPhyMem(Addr+4);
    CI:=GetCardinalBIT(CLow,12,20)+(GetCardinalBIT(CHigh,0,4) shl 20);
    CI:=CI shl 12;
    S1:=PNAME+';'+inttostr(PID)+';'+inttostr(Addr)+';'+inttostr(CLow)+';'+inttostr(CHigh)+';'+inttostr(CI)+';';
    CI:=MYPLYMEM;
    CLow:=GetCardinalBIT(CLow,0,12)+(GetCardinalBIT(CI,12,20) shl 12);
    CHigh:=(GetCardinalBIT(CHigh,4,28) shl 4)+GetCardinalBIT(CI,32,5);

最后,因为这个工具是很久之前写的了.写得比较乱,所以现在我自己也不能完全理解了!!只能从其中摘要一点发出来.全部详细请自行分析源代码.


最后上工程源代码:
编译环境:程序:Delphi 6 驱动:Delphi6 + KmdKit4D
上传的附件 BXCL.rar