第五十一个SetWindowsHookEx安装一个钩子

WINDOWS是基于消息的系统,鼠标移动,单击,键盘按键,窗口关闭等都会产生相应的消息,那么钩子是什么意思呢,它可以监控一个消息,比如在一个窗口里单击了一下,首先获得这个消息的,不是应用程序,而是系统,系统获取这个消息后,就去查看这个消息是在哪个窗口产生的,找到窗口后,再把消息投递到相应程序里的消息队列里,这之间有一个传递过程,那么钩子的作用就是在消息到达应用程序之前截获它,钩子可以关联一个函数(钩子处理函数),也就是说,如果对一个进程安装了一个钩子,进程再接收到相应在消息之前,会先去执行钩子所关联的函数,

先来看一下这个函数定义:

HHOOK WINAPI SetWindowsHookEx(int idHook,HOOKPROC lpfn,HINSTANCE hmod,DWORD dwThreadId)

第一个参数idHook指明要安装的钩子类型,如WH_KEYBOARD(键盘钩子),WH_MOUSE(鼠标钩子),第二个参数是钩子处理函数的地址,该函数必须是这种固定的格式:LRESULT WINAPI HookProc(int nCode,WPARAM wParam,LPARAM lParam)

第三个参数hmod是钩子函数所在模块的句柄,第四个参数dwThreadId是线程ID,待监视消息的ID,如果为0,则为全局钩子,监视所有消息

好,接下来我们举一个例子,钩子类型为WH_KEYBOARD,全局钩子。截获键盘按键消息,并扔掉该消息,让键盘失灵。

由于是装的是全局钩子,所以钩子处理函数必须放在动态链接库里。那么我们就设计一个动态链接库吧。

现给出动态链接库的所有代码:(KeyDll.dll)

include "stdafx.h"

include<windows.h>

BOOL APIENTRY DllMain( HANDLE hModule,

DWORD  ul_reason_for_call,

LPVOID lpReserved

)

{

return TRUE;

}

HMODULE WINAPI ModuleFromAddress(PVOID pv)//该函数根据内存地址,获得其所在的模块句柄

{

MEMORY_BASIC_INFORMATION mbi;

VirtualQuery(pv,&mbi,sizeof(mbi));

return (HMODULE)mbi.AllocationBase;

}

LRESULT CALLBACK HookKey(int nCode,WPARAM wParam,LPARAM lParam)

{

return TRUE;//返回真,扔掉该消息

}

extern "C" __declspec(dllexport) void SetHook(void)

{

SetWindowsHookEx(WH_KEYBOARD,HookKey,ModuleFromAddress(HookKey),0);

}

生成dll文件后,把它复制到相应的目录下去。

再新建一个工程,调用用动态链接库里的函数,代码如下:

include<windows.h>

int main()

{

HMODULE hMod=LoadLibrary("KeyDll.dll");

typedef void(*pSetHook)(void);

pSetHook SetHook=(pSetHook)GetProcAddress(hMod,"SetHook");

SetHook();

while(1)

{

Sleep(1000);//避免程序结束,自动释放动态链接库

}

return 0;

}

这样当按下了一个键后,接收该按键消息的进程,会先去执行钩子处理函数,然后再处理消息,而钩子处理函数的几个参数说明了按键的详细信息,如按了哪个键,是按下(KEYDOWN)还是松开(KEYUP)。如果有兴趣的话,把上面那钩子处理函数的代码换成下面这个

LRESULT CALLBACK HookKey(int nCode,WPARAM wParam,LPARAM lParam)

{

char sz[25];

sprintf(sz,"%c",wParam);//这个函数头文件#include

MessageBox(NULL,sz,sz,MB_OK);

return FALSE;

}

每按下一个键,就会弹出一个提示框,并输出所按下的键,只对字符键有用。

第五十二个SHGetFileInfo获取一个文件的各项信息(文件关联图标,属性等)

函数定义: DWORD SHGetFileInfo(LPCSTR pszPath, DWORD dwFileAttributes, SHFILEINFOA FAR *psfi, UINT cbFileInfo, UINT uFlags);

pszPath是文件的路径,dwFileAttributes一般取0,如果想要获取文件夹信息的话,则取值为FILE_ATTRIBUTE_DIRECTORY,psfi是一个SHFILEINFO结构的指针,该结构存储文件信息,定义如下:

typedef struct _SHFILEINFOA

{

HICON       hIcon;                      // 文件关联图标句柄

int         iIcon;                      // 系统图标列表索引

DWORD       dwAttributes;               // 文件的属性

CHAR        szDisplayName[MAX_PATH];    // 文件的路径名

CHAR        szTypeName[80];             // 文件的类型名,如是bmp文件,还是执行文件exe,或者其它

} SHFILEINFO;

第四个参数cbFileInfo指明SHFILEINFO结构的大小,填sizoef(SHFILEINFO);

最后一个参数uFlags指定获取文件的什么信息,可选取值如下:(对应着SHFILEINFO里的成员)

SHGFI_ICON; //获得图标

SHGFI_DISPLAYNAME; //获得显示名

SHGFI_TYPENAME; //获得类型名

SHGFI_USEFILEATTRIBUTES; //获得属性

SHGFI_LARGEICON; //获得大图标

SHGFI_SMALLICON; //获得小图标

SHGFI_PIDL; // pszPath是一个标识符

比如,我只要获取文件图标,那么参数填SHGFI_LARGEICON就行了。如果又想获取文件关联的图标,又想获取文件类型名,那么就是

SHGFI_LARGEICON|SHGFI_TYPENAME;

函数例子:

SHFILEINFO   sfi;

SHGetFileInfo("e:\aa.bmp",0,&sfi,sizeof(sfi),

SHGFI_ICON|SHGFI_LARGEICON|SHGFI_USEFILEATTRIBUTES|SHGFI_TYPENAME);

接着可以用DrawIcon函数画出文件关联图标:该函数定义:BOOL DrawIcon(HDC hDC,int X,int Y, HICON hlcon );

第五十三个RegCreateKeyEx在注册表里创建一个子键,或获取一个子键的句柄

在这里我们先来了解一下注册表的基本概念,打开运行对话框,输入regedit,然后回车,便打开了注册表编辑器,首先映入眼前的,便是五个根键

HKEY_CLASSES_ROOT

HKEY_CURRENT_USER

HKEY_LOCAL_MACHINE

HKEY_USER

HKEY_CURRENT_CONFIG

在根键下面便是主键了,如HKEY_CURRENT_CONFIG根键下有两个主键,分别是Software和System(可能会不一样),那么主键下面是什么呢,对了,就是跟 RegCreateKeyEx函数相关的子键,子键下面就是具体的键值项了,但也可以又是子键。键值有五种可选类型,分别是:字符串值(REG_SZ),二进制值(REG_BINARY),DWORD值(REG_DWORD),多字符串值(REG_MULTI_SZ)和可扩充字符值(REG_EXPAND_SZ)。键值项还有其它信息,它的名称,数据。

了解了上面这些东西,接着就来了解下RegCreateKeyEx函数的各个参数吧,先来看一下函数定义:

LONG RegCreateKeyEx (

HKEY hKey,//根键句柄,指明要在哪个根键下创建子键,填根键名既可

LPCSTR lpSubKey,//子键名,包含完整路径名

DWORD Reserved,.//一般取0

LPSTR lpClass,//一般取NULL

DWORD dwOptions,//创建子键时的选项,可选值REG_OPTION_NON_VOLATILE,REG_OPTION_VOLATILE,这里取0既可

REGSAM samDesired,//打开方式,填KEY_ALL_ACCESS,在任何情况都行。

LPSECURITY_ATTRIBUTES lpSecurityAttributes,//指定继承性,还是取0

PHKEY phkResult,//子键对应句柄,待创建或打开的子键句柄将存储在该句柄里

LPDWORD lpdwDisposition//打开还是创建子键,对应REG_CREATED_NEW_KEY和REG_OPENED_EXISTING_KEY

);

在这里举一个例子,以便我们能更好的理解该函数。

在HKEY_CURRENT_CONFIG根键下的Software主键里创建一个名为MySelf的子键。

include<windows.h>

int main()

{

HKEY hroot;//子键句柄

DWORD dwDisposition;//对应着最后一个参数

RegCreateKeyEx(HKEY_CURRENT_CONFIG,"Software\MySelf",0,NULL,0,KEY_ALL_ACCESS,NULL,&hroot,&dwDisposition);

return 0;

}

第五十四个RegSetValueEx根据子键句柄在其下创建或修改一个键值

函数定义:LONG RegSetValueEx(

HKEY hKey,           // 子键句柄

LPCTSTR lpValueName, // 键值名称,如果提供的子键下没有该名称,则创建

DWORD Reserved,      // 保留,填0

DWORD dwType,        // 键值类型,

CONST BYTE *lpData,  // 键值的数据

DWORD cbData         // 键值的数据的大小

);

接着我们以增加开机自启动为例,来看一下函数是如何创建一个键值的,我们知道,像程序添加开机自启动一般都在

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run下添加一个键值,键值类型为二进制(REG_SZ),而键值的数据就为要自启动程序的路径。

假设e盘下有一个AutoRun.exe的应用程序,让电脑开机时自动运行它。

include<windows.h>

int main()

{

HKEY hroot;//子键句柄

DWORD dwDisposition;

RegCreateKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE\Microsoft\Windows\CurrentVersion\Run",0,

NULL,0,KEY_ALL_ACCESS,NULL,&hroot,&dwDisposition);

RegSetValueEx(hroot,"AutoRun",0,REG_SZ,(BYTE *)"e:\AutoRun.exe",sizeof("e:\AutoRun.exe"));

return 0;

}

第五十五个RegDeleteValue根据子键句柄删除其下的一个键值

这里直接举一个例子,删除RegSetValueEx函数创建的键值

include<windows.h>

int main()

{

HKEY hroot;//子键句柄

DWORD dwDisposition;

RegCreateKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE\Microsoft\Windows\CurrentVersion\Run",0,

NULL,0,KEY_ALL_ACCESS,NULL,&hroot,&dwDisposition);

RegDeleteValue(hroot,"AutoRun");//删除子键下名为AutoRun的键值

return 0;

}

第五十六个RegQueryValueEx根据子键句柄获取一个键值数据,类型。

函数定义:LONG

RegQueryValueEx (

HKEY hKey,//根键句柄

LPCWSTR lpValueName,//键值名称

LPDWORD lpReserved,//预留,填0

LPDWORD lpType,//接收键值类型

LPBYTE lpData,//接收键值数据

LPDWORD lpcbData//接收数据的大小

);

例子,获取RegSetValueEx函数创建的键值的类型,数据

include<windows.h>

include<stdio.h>

int main()

{

char Data[52];

DWORD Size,Type;

HKEY hroot;//子键句柄

DWORD dwDisposition;

RegCreateKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE\Microsoft\Windows\CurrentVersion\Run",0,

NULL,0,KEY_ALL_ACCESS,NULL,&hroot,&dwDisposition);//获取根键句柄

RegQueryValueEx(hroot,"AutoRun",0,&Type,(BYTE *)Data,&Size);//获取AutoRun的信息

printf("键值名称:AutoRun ");

switch(Type)

{

case REG_SZ:printf("键值类型:REG_SZ");break;

case REG_BINARY:printf("键值类型:REG_BINARY");break;

case REG_DWORD:printf("键值类型:REG_DWORD");break;

case REG_MULTI_SZ:printf("键值类型:REG_MULTI_SZ");break;

case REG_EXPAND_SZ:printf("键值类型:REG_EXPAND");break;

}

printf(" 键值数据:%s  %d\n",Data,Size);

return 0;

}

第五十七个RegEnumValue根据子键句柄返回对应索引的键值信息(名称,数据,类型,子键下第一个键值索引为0,以此类推,函数成功执行返回ERROR_SUCCESS)

函数定义:LONG

RegEnumValue (

HKEY hKey,//子键句柄

DWORD dwIndex,//键值索引

LPWSTR lpValueName,//接收键值名称,字符数组

LPDWORD lpcbValueName,//指明数组大小

LPDWORD lpReserved,//预留,0

LPDWORD lpType,//键值类型,填NULL,不获取

LPBYTE lpData,//键值数据,填NULL,不获取

LPDWORD lpcbData//接收数据的大小,如果键值数据那项参数为NULL,则该项也为NULL

);

例子:输出Run下的所有键值名

include<windows.h>

include<stdio.h>

int main()

{

char Name[52];

int Index=0;

DWORD dwSize=52;

DWORD Size,Type;

HKEY hroot;//子键句柄

DWORD dwDisposition;

RegCreateKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE\Microsoft\Windows\CurrentVersion\Run",0,

NULL,0,KEY_ALL_ACCESS,NULL,&hroot,&dwDisposition);//获取根键句柄

while(RegEnumValue(hroot,Index,Name,&dwSize,NULL,NULL,NULL,NULL)==ERROR_SUCCESS)

{

printf("%s\n",Name);

Index++;//索引从0开始每次自增一,函数如果执行失败,则索引已到头

}

return 0;

}

其实也还可以扩充一下,可以像msconfig程序那样列出当前计算机的所有开机自启动程序,当然,注册表也不只就前面的那一个子键下可以添加自启动程序,在HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run下也可以添加,所以这些子键都需要去查看,更多添加自启动程序的子键可以到百度里去搜一下,大家如果掌握前面那几个注册表操作函数,可以结合起来试着做一个可以添加,查看,删除开机自启动程序的小程序。

第五十八个ExitWindowsEx关机,重启,注销计算机函数

这个函数只有两个参数,后一个参数为系统预留,填0就可以了,而第一个参数则,指明关机,还是重启,或注销,可选值如下:

EWX_LOGOFF//注销    EWX_REBOOT//重启 NT系统中需SE_SHUTDOWN_NAME 特权 EWX_SHUTDOWN//关机,需权限。

例子:关闭计算机,由于需要SE_SHUTDOWN_NAME权限,所以我们得先提升权限,代码如下:

include<windows.h>

int main()

{

HANDLE hToken;

TOKEN_PRIVILEGES tkp;

OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToken);

LookupPrivilegeValue(NULL,SE_SHUTDOWN_NAME,&tkp.Privileges[0].Luid);

tkp.PrivilegeCount=1;

tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;

AdjustTokenPrivileges(hToken,FALSE,&tkp,0,(PTOKEN_PRIVILEGES)NULL,0);

::ExitWindowsEx(EWX_SHUTDOWN,0);

return 0;

}

第五十九个VirtualAllocEx在其它的进程中分配内存空间

函数定义:LPVOID

VirtualAllocEx(

HANDLE hProcess,//进程句柄,将会在该进程句柄相关的进程分配空间

LPVOID lpAddress,//默认为系统指定,填NUL

DWORD dwSize,//分配多大的内存

DWORD flAllocationType,//填MEM_COMMIT

DWORD flProtect//指定分配的内存属性,为PAGE_READWRITE,内存可读写

);

函数返回分配的内存首地址,

第六十个CreateRemoteThread创建一个远程线程(在其它进程中创建线程)

函数定义:HANDLE

WINAPI

CreateRemoteThread(HANDLE hProcess,//进程句柄,函数将在这个进程句柄关联的进程创建线程

LPSECURITY_ATTRIBUTES lpThreadAttributes,

DWORD dwStackSize,

LPTHREAD_START_ROUTINE lpStartAddress,

LPVOID lpParameter,

DWORD dwCreationFlags,

LPDWORD lpThreadId

);

这个函数比CreateThread函数多了一个参数,就是这个函数的第一个hProcess(函数在该进程里创建线程),后面的六个参数跟第三十九个函数CreateThread的六个参数一样,这里就不再解释了。

例子:远程线程注入

创建一个远程线程,就必须得有一个线程函数供线程执行,而线程函数又不能在其它程序里。那要怎么办呢?大家看一下线程函数的定义,和LoadLibrary函数的定义,它们的定义相似,都是只有一个参数,而且每个程序都能调用LoadLibrary函数,这样我们便能把LoadLibrary函数作为线程函数。这样创建的线程就会去执行LoadLibrary函数。因而我们就有了一次让其它程序调用LoadLibrar函数的机会,并还可以指定LoadLibrary函数的参数(通过创建远程线程函数传递)。前面在动态链接库提到,一个程序如果调用LoadLibrary函数,它都会自动去执行相应动态链接库里的DllMain函数,所以我们自己可以编写一个动态链接库,在DllMain函数里写入想要其它程序执行的代码。再通过CreateRemoteThread函数在其它程序创建一个线程去执行LoadLibary加载我们已经编写好的动态链接库,这样就可以让其它程序执行我们的代码了。这里还有一个问题,CreateRemoteThread函数传递过去的参数,因为要供注入的那个程序访问,所以参数数据所存储的空间不能在调用CreateRemoteThread函数的程序里。必须调用VirtualAllocEx函数,在注入程序里分配一个空间,把数据(动态链接库的名称)存在里面,而新分配空间的首地址则作为CreateRemoteThread函数的参数传过去。这样注入程序访问的是自己的地址空间。

远程线程注入:

假设动态链接库为“ReCode.dll”它的代码如下:

include<windows.h>

BOOL APIENTRY DllMain( HANDLE hModule,

DWORD  ul_reason_for_call,

LPVOID lpReserved

)//DllMain函数,只要加载这个动态链接库的程序,都会跑来执行这个函数

{//在这里填让其它程序执行的代码

while(1)

{

MessageBox(NULL,"aaaa","aaaa",MB_OK);//简单的让其它程序每隔3秒弹出一个提示框

Sleep(3000);

}

return TRUE;

}

编译运行,然后把生成的“ReCode.dll”文件复制到c:\windows\system23下去。

注入线程的代码:

//选择ctfmon.exe(输入法管理)作为我们要注入进线程的程序

include<windows.h>

include<tlhelp32.h>

include<stdio.h>

int main()

{

char DllName[25]="ReCode.dll";

HANDLE hProcess;//用于存储ctfmon.exe的进程句柄

//先提升进程权限,使其能获取任何进程句柄,并对其进行操作

HANDLE hToken;

OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken );

TOKEN_PRIVILEGES tp;

LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid );

tp.PrivilegeCount = 1;

tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

AdjustTokenPrivileges( hToken, FALSE, &tp, sizeof( TOKEN_PRIVILEGES ), NULL, NULL );

////////////////////////////////////////////////////////////////////////////

//Process32First和Process32Next函数结合(寻找)获取ctfmon.exe进程ID号

//再调用OpenProcess函数根据进程ID获得进程句柄

PROCESSENTRY32 pe32;//进程相关信息存储这个结构里

pe32.dwSize=sizeof(pe32);

//给系统内的所有进程拍一个快照

HANDLE hProcessSnap=::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);

BOOL bMore=::Process32First(hProcessSnap,&pe32);

while(bMore)

{

if(strcmp("ctfmon.exe",pe32.szExeFile)==0)//如果找到进程名为ctfmon.exe

hProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pe32.th32ProcessID);//获取句柄

bMore=::Process32Next(hProcessSnap,&pe32);//寻找下一个

}

//在ctfmon进程中分配空间

LPVOID lpBuf=VirtualAllocEx(hProcess,NULL,strlen(DllName),MEM_COMMIT, PAGE_READWRITE );

DWORD WrSize;

//把DllName里的数据写入到分配的空间里

WriteProcessMemory(hProcess, lpBuf, (LPVOID)DllName, strlen(DllName), &WrSize);

//创建远程线程

CreateRemoteThread(hProcess,NULL,0,(LPTHREAD_START_ROUTINE)LoadLibraryA,lpBuf,0,NULL);

return 0;//程序使命完成,结束

}

当然,给一个程序安装钩子,也可以让指定的应用程序加载特定的动态链接库,但要了解,加载动态链接库的是是应用程序的主程序,你总不能让应用程序不干它自己的事,而来一直执行DllMain函数里的代码吧!而且即使这样,当安装钩子的程序退出或卸载钩子的时候,那么被系统强迫加载动态链接库的程序,也会自动释放动态链库,退出DllMain函数。如此,那就没有办法了吗?,办法肯定是有的,用CreateThread函数。当其它程序主线程执行DllMain函数的时候,使其调用CreateThread再创建一个线程,就行了。

Comments
Write a Comment