更改屏幕显示数据的作弊程序
——再谈 Win2K 下的 API 拦截
作者:欧胜军
前言
本文(其实是随笔)和实例代码描述的内容主要包括:API拦截,消息钩子,枚举子窗体,注册系统热键。其中消息钩子、API拦截、枚举子窗体实现代码在dll中,注册系统热键代码实现在测试exe中,另外exe代码段还涉及自定义消息和系统栏图标。希望对你有用。
一年过去了,仍然是老样子——为生活而奔波、为money而忙碌。好在身体还蛮健康,吃得好,睡得香:-)但愿新的一年里腰包会鼓一些。今天整理硬盘,发现了这些代码,回想当初开始写的时候好像还在
VCKBASE 论坛里提出过一些问题,也得到了论坛很多朋友的热心帮助,在此感谢。本来想好好的写一下心得,特别是能让VC初学者学到一些东西。但自己语文功底太差,就只能想到哪写到哪了。希望你边品茶,边上网,一手聊天,一手回帖,左眼看PP,右眼能坚持看完下面的这些方块字。
写这个程序的来由:最初由于客户的需求不是很明显,只是说把他的一个数据库程序的数据作一个临时性的欺骗,以应付检查(谁检查就不知道了,哈,让我写作弊程序呢,考我呐),唯一的要求是不更改真实的数据。我其实比较头疼数据库,就想不研究他的数据库结构了,心想采用一个比较懒的办法实现就行了。9月份的时候我也挺闲的,在网上狂搜了一通之后,基本就决定用屏幕取词的方式来做了。就是截获显示到屏幕上的数据,只要是数字类型的,就乘以一个基数,然后把算好的数据回写到屏幕上。其实这个屏幕取词的技术网上早就公开了,我也刚好想顺便练练手。写得差不多的时候,吐血的时候也就到了,客户要求除了临时欺骗之外,还要把显示的数据当场用原来程序上的打印按钮打印出来。我傻眼了,所以也就废弃了这种方法,吐了一通血、憋了一通气、老老实实的研究了原程序的数据库结构,还好是 SQL
Server 2K,以前弄过,终于赶在检查之前交了活。
一不小心就说了这么多废话。好了,转到正题。下面分别道来:
拦截API(或者说截获API 反正一个意思)
先说说截获API了,在HOOKAPI.h和cpp文件中,这个是繁体版本。网上流行很多个版本,我觉得还是这个比较简洁一点(简单万岁嘛)。原作者不知道是台湾还是香港的,注释用的BIG5码。截获API一般就几个步骤:
获得函数地址(GetProcAddress),取得被拦截的函数地址和要替代这个被拦截的函数的地址(也就是拦截后要处理的函数);
形成JMP指令(__asm),准备避开被拦截的函数跳转到处理函数直接处理;
设置内存可写(VirtualProtect),跳转后的函数执行;
记得恢复内存。
网上关于API拦截的教程很多,反正我也说不清楚就不多说了。其实我的理解很简单,就是想办法打开老板办公室的们、潜入、然后替换奖金单、然后关门、走人……就这么简单。注意点有三个:一个就是原函数的地址,在哪个dll中,别查错了;一个就是替换函数的参数,必须遵照游戏规则,该什么类型就什么类型;另外一个就是内存的读写顺序,不能漏也不能反。具体可以参考HookAPI的代码段,这里就不帖了。
钩子(Hook)
关于消息钩子,其实就两句话:SetWindowsHookEx ,UnhookWindowsHookEx
,装载钩子和卸载钩子,不过也就这两句话,学写钩子的时候,折腾了好一阵子。给初学者一个建议:理解瘟到死的消息机制,暂时放下MFC,尝试写至少一个SDK程序,动手写之前,好好看看MSDN,很多问题都可以在这里得到答案。SetWindowsHookEx有四个参数,主要就是注意1和4这两个参数,第一个参数建议少用WH_CALLWNDPROC
除非必须这么做。一般情况下,有消息,键盘和鼠标钩子就够用了,当然特殊情况除外。
枚举子窗体
至于枚举子窗体,一般采用两种方法:
1、EnumChildWindows回调函数
//例子
调用:EnumChildWindows(g_hWndTag, EnumChildWindowsProc,0);
函数:
//-------------------------------------------------------------------------
//枚举子窗体回调函数
BOOL CALLBACK EnumChildWindowsProc( HWND hWnd, LPARAM lParam )
{
char buff[256]={''\0''};
if(::GetWindowLong(hWnd,GWL_STYLE)& WS_VISIBLE)
{
::GetWindowText(hWnd,buff,256);
//注销这些按钮或窗体
if( NULL != strstr(buff,"打印") ||
NULL != strstr(buff,"Excel") ||
NULL != strstr(buff,"导出") )
{
//这种只是屏蔽,界面上仍可看见
//EnableWindow(hWnd,FALSE);
//彻底注销,一劳永逸:-)
DestroyWindow(hWnd);
}
}
return TRUE;
}
2、GetWindow的方法
//例子:百试不爽的while大法,小心while一去不复还
HWND hWndChild=NULL;
hWndChild = ::GetWindow(g_hWnd,GW_CHILD);
while( NULL != hWndChild )
{
//styleXP.CreateClassXP(hWndChild);
hWndChild = ::GetWindow(hWndChild,GW_HWNDNEXT);
}
这里多说一句,一些换肤程序所采用的窗体子类化,大多就是采用的这两种方法之一。不过我始终觉得那是一项庞大费时且容易使人崩溃的工作,我曾经尝试写了一个小小的类,仅仅只是对按钮用GDI重画了一次,就已经接近忍耐极限,最终决定放弃了。
注册系统热键
要用到RegisterHotKey函数,三步曲:
1、要注册哪些键,先申明一下
//注册系统热键
#define ID_A1501
#define ID_B1502
#define ID_C1503
#define ID_D1504
#define ID_E1505
2、程序初始化的时候,调用注册热键函数,当然你直接写在初始化函数中我也不能反驳,这是你的自由:P
//RegSysHotkey();
//-------------------------------------------------------------------------
//注册系统热键
void CTestDlg::RegSysHotkey()
{
HWND hWnd = this->m_hWnd;
RegisterHotKey(hWnd, ID_A, MOD_ALT, 65);//Alt + A
RegisterHotKey(hWnd, ID_B, MOD_ALT, 66);//Alt + B
RegisterHotKey(hWnd, ID_C, MOD_ALT, 67);
RegisterHotKey(hWnd, ID_D, MOD_ALT, 68);
RegisterHotKey(hWnd, ID_E, MOD_ALT, 69);
}
3、重载PreTranslateMessage,在你程序运行的时候来截获热键的输入。
BOOL CTestDlg::PreTranslateMessage(MSG* pMsg)
{
switch (pMsg->message)
{
//处理系统热键WM_HOTKEY消息
case WM_HOTKEY:
switch(pMsg->wParam)
{
case ID_A:
OnAppShow();
break;
case ID_B://彻底隐藏
HideMe();
break;
case ID_C:…….具体请参照exe代码片断
自定义消息和系统栏图标
就不详叙了,请参照代码。
好了,就说这么多了,新年新气象,希望大家都有一个好心情。给出的代码没有什么版权,想用就用吧,代码也没怎么整理,只是重新编译了一下,在demo路径下,好像用的是静态链接,希望在你的机器上能运行。运行不起来可别怪我。不明白的地方请看目录下的readme文件和代码的注释,或者去VCKBASE论坛提出吧,有很多大侠会帮忙解决的。