第五十一個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<stdio.h>
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再創建一個線程,就行了。