MFC hook钩子学习
先用spy++(VC自带工具)看看各个窗口控件属性和序号,有利于明确目标和提供参考。表格,在MFC里面很大可能是CListCtrl,还有可能是CListBox、Chat。至于你猜的那个概率很低,具体可以SPY++参考。用钩子跨进程获取数据效率是很低的,如果数据量很大,不建议这么搞。一般数据都是在数据库文件或者资源中,这两种不用钩子效率更高些。如果只是为了学习钩子,先从编辑框、窗口标题之类的开始。要进行钩子拦截,首先还是要知道目标窗口属性和信息比较方便。
如何实现这样的api hook
api hook技术的难点,并不在于hook技术,初学者借助于资料“照葫芦画瓢”能够很容易就掌握hook的基本使用技术。但是如何修改api函数的入口地址?这就需要学习pe可执行文件(.exe,.dll等)如何被系统映射到进程空间中,这就需要学习pe格式的基本知识。windows已经提供了很多数据结构struct帮助我们访问pe格式,借助它们,我们就不要自己计算格式的具体字节位置这些繁琐的细节。但是从api hook的实现来看,pe格式的访问部分仍然是整个编程实现中最复杂的一部分,对于经常crack的朋友不在此列。
假设我们已经了解了pe格式,那么我们在哪里修改api的函数入口点比较合适呢?这个就是输入符号表imported symbols table(间接)指向的输入符号地址。
下面对于pe格式的介绍这一部分,对于没有接触过pe格式学习的朋友应该是看不太明白的,但我已经把精华部分提取出来了,学习了pe格式后再看这些就很容易了。
pe格式的基本组成
+-------------------+
| DOS-stub | --DOS-头
+-------------------+
| file-header | --文件头
+-------------------+
| optional header | --可选头
|- - - - - - - - - -|
| |
| data directories | --(可选头尾的)数据目录
| |
+-------------------+
| |
| section headers | --节头
| |
+-------------------+
| |
| section 1 | --节1
| |
+-------------------+
| |
| section 2 | --节2
| |
+-------------------+
| |
| ... |
| |
+-------------------+
| |
| section n | --节n
| |
+-------------------+
在上图中,我们需要从“可选头”尾的“数据目录”数组中的第二个元素——输入符号表的位置,它是一个IMAGE_DATA_DIRECTORY结构,从它中的VirtualAddress地址,“顺藤摸瓜”找到api函数的入口地点。
下图的简单说明如下:
OriginalFirstThunk 指向IMAGE_THUNK_DATA结构数组,为方便只画了数组的一个元素,AddressOfData 指向IMAGE_IMPORT_BY_NAME结构。
IMAGE_IMPORT_DESCRIPTOR数组:每个引入的dll文件都对应数组中的一个元素,以全0的元素(20个bytes的0)表示数组的结束
IMAGE_THUNK_DATA32数组:同一组的以全0的元素(4个bytes的0)表示数组的结束,每个元素对应一个IMAGE_IMPORT_BY_NAME结构
IMAGE_IMPORT_BY_NAME:如..@Consts@initialization$qqrv. 表示
Unmangled Borland C++ Function: qualified function __fastcall Consts::initialization()
为了减少这个图的大小,不得已将汇编和c++的结构都用上了。这个图是输入符号表初始化的情形,此时两个IMAGE_THUNK_DATA结构数组的对应元素都指向同一个IMAGE_IMPORT_BY_NAME结构。
程序加载到进程空间后,两个IMAGE_THUNK_DATA结构数组指向有所不同了。看下图:
// 本文转自 C++Builder研究 - http://www.ccrun.com/article.asp?i=1036&d=cf6de2
始化的,“两个结构都指向同一个IMAGE_IMPORT_BY_NAME”,此时还没有api函数地址
当PE文件准备执行时,前图已转换成上图。一个结构指向不变,另一个出现api函数地址
如果PE文件从kernel32.dll中引入10个函数,那么IMAGE_IMPORT_DESCRIPTOR 结构的 Name1域包含指向字符串"kernel32.dll"的RVA,同时每个IMAGE_THUNK_DATA 数组有10个元素。(RVA是指相对地址,每一个可执行文件在加载到内存空间前,都以一个基址作为起点,其他地址以基址为准,均以相对地址表示。这样系统加载程序到不同的内存空间时,都可以方便的算出地址)
上述这些结构可以在winnt.h头文件里查到。
具体编程实现
我将手上的vc示例代码进行了适当修正,修改了一些资源泄漏的小问题,移植到c++builder6 & update4上,经过测试已经可以完成基本的api hook功能。有几个知识点说明一下:
1、 dll中共享内存变量的实现
正常编译下的dll,它的变量使用到的内存是独立的。比如你同时运行两个调用了某个dll的用户程序,试图对某一个在dll中定义的全局变量修改赋值的时候,两个程序里的变量值仍然是不同的。
共享的方法为:在.cpp文件(.h文件里如此设置会提示编译错误)的头部写上如上两行:
#pragma option -zRSHSEG // 改变缺省数据段名
#pragma option -zTSHCLASS // 改变缺省数据类名
HINSTANCE hdll = NULL; // 用来保存该动态连接库的句柄
HHOOK hApiHook = NULL; // 钩子句柄
HHOOK hWndProc = NULL; // 窗口过程钩子用来拦截SendMessage
int threadId = 0;
另外建立一个与dll同名,不同后缀的def文件,如HookDll.def文件,写上:
LIBRARY HookDll.dll
EXPORTS
;...
SEGMENTS
SHSEG CLASS 'SHCLASS' SHARED
;end
这样设置后在.cpp文件中定义的变量,如果进行了初始化,将进入“SHCLASS”共享内存段(如果不初始化,将不改变其默认段属性)。
上述的共享对于本示例代码并不是必须的,只是稍微演示了一下。
2、 api hook修改api函数入口点地址的时机
很显然,我们必须通过hook进入目标进程的地址空间后,再在位于该地址空间里的hook消息处理过程里修改输入符号表“指向”的api函数入口点地址,退出hook前也必须在这个消息处理过程里恢复原来的地址。只要我们牢记修改的过程发生在目标进程的地址空间中,就不会发生访问违例的错误了。
示例代码使用了WH_GETMESSAGE、WH_CALLWNDPROC两中hook来演示如何hook api,但WH_GETMESSAGE实际上并没有完成具体的功能。
为了让初学者尽快的掌握重点,我将代码进行了简化,是一个不健壮、不灵活的演示示例。
3、 函数的内外部表现形式
例如api函数MessageBox,这个形式是我们通常用到的,但到了dll里,它的名字很可能出现了两个形式,一个是MessageBoxA,另一个是MessageBoxW,这是因为系统需要适应Ansi和Unicode编码的两种形式,我们不在函数尾端添加“A”或“W”,是不能hook到需要的函数的。
4、 辅助pe格式查看工具
PE Explorer是一个非常好的查看pe资源的工具,通过它可以验证自己手工计算的pe地址,可以更快的掌握pe格式。
调试器ollydbg也是非常好的辅助工具,例如查看输入符号表中的api函数。
hook的基础学习,需要什么软件.
//部分代码: 留下E-Mail 我给你全套Delphi 钩子的实例
ibrary Key;
{ Important note about DLL memory management: ShareMem must be the
first unit in your library's USES clause AND your project's (select
Project-View Source) USES clause if your DLL exports any procedures or
functions that pass strings as parameters or function results. This
applies to all strings passed to and from your DLL--even those that
are nested in records and classes. ShareMem is the interface unit to
the BORLNDMM.DLL shared memory manager, which must be deployed along
with your DLL. To avoid using BORLNDMM.DLL, pass string information
using PChar or ShortString parameters. }
uses
SysUtils,
Classes,
windows,
messages,
HookKey_Unit in 'HookKey_Unit.pas';
{$R *.RES}
exports
HookOn,
HookOff;
begin
end.
//--------
unit TestHookKey_Unit;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
const
WM_HOOKKEY= WM_USER + $1000;
HookDLL = 'Key.dll';
type
THookProcedure=procedure; stdcall;
TForm1 = class(TForm)
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
FileMapHandle : THandle;
PMem : ^Integer;
HandleDLL : THandle;
HookOn,
HookOff : THookProcedure;
procedure HookKey(var message: TMessage); message WM_HOOKKEY;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
Memo1.ReadOnly:=TRUE;
Memo1.Clear;
HandleDLL:=LoadLibrary( PChar(ExtractFilePath(Application.Exename)+
HookDll) );
if HandleDLL = 0 then raise Exception.Create('未发现键盘钩子DLL');
@HookOn :=GetProcAddress(HandleDLL, 'HookOn');
@HookOff:=GetProcAddress(HandleDLL, 'HookOff');
IF not assigned(HookOn) or
not assigned(HookOff) then
raise Exception.Create('在给定的 DLL中'+#13+
'未发现所需的函数');
FileMapHandle:=CreateFileMapping( $FFFFFFFF,
nil,
PAGE_READWRITE,
0,
SizeOf(Integer),
'TestHook');
if FileMapHandle=0 then
raise Exception.Create( '创建内存映射文件时出错');
PMem:=MapViewOfFile(FileMapHandle,FILE_MAP_WRITE,0,0,0);
PMem^:=Handle;
HookOn;
end;
procedure TForm1.HookKey(var message: TMessage);
var
KeyName : array[0..100] of char;
Accion : string;
begin
GetKeyNameText(Message.LParam,@KeyName,100);
if ((Message.lParam shr 31) and 1)=1
then Accion:='Key Up'
else
if ((Message.lParam shr 30) and 1)=1
then Accion:='ReKeyDown'
else Accion:='KeyDown';
Memo1.Lines.add( Accion+
': '+
String(KeyName)) ;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if Assigned(HookOff) then
HookOff;
if HandleDLL0 then
FreeLibrary(HandleDLL);
if FileMapHandle0 then
begin
UnmapViewOfFile(PMem);
CloseHandle(FileMapHandle);
end;
end;
end.
//-----------
unit HookKey_Unit;
interface
uses windows,messages;
const
WM_HOOKKEY = WM_USER + $1000;
procedure HookOn; stdcall;
procedure HookOff; stdcall;
implementation
var
HookDeTeclado : HHook;
FileMapHandle : THandle;
PViewInteger : ^Integer;
function CallBackDelHook( Code : Integer;
wParam : WPARAM;
lParam : LPARAM
) : LRESULT; stdcall;
begin
if code=HC_ACTION then
begin
FileMapHandle:=OpenFileMapping(FILE_MAP_READ,False,'TestHook');
if FileMapHandle0 then
begin
PViewInteger:=MapViewOfFile(FileMapHandle,FILE_MAP_READ,0,0,0);
PostMessage(PViewInteger^,WM_HOOKKEY,wParam,lParam);
UnmapViewOfFile(PViewInteger);
CloseHandle(FileMapHandle);
end;
end;
Result := CallNextHookEx(HookDeTeclado, Code, wParam, lParam)
end;
procedure HookOn; stdcall;
begin
HookDeTeclado:=SetWindowsHookEx(WH_KEYBOARD, CallBackDelHook, HInstance , 0);
end;
procedure HookOff; stdcall;
begin
UnhookWindowsHookEx(HookDeTeclado);
end;
end.