HOOK API(四) html
—— 進程防終止git
原文出處:www.cnblogs.com/fanling999/github
這算是一個實戰吧,作的一個應用須要實現進程的防終止保護,查了相關資料後決定用HOOK API的方式實現。起初學習HOOK API的原由是由於要實現對剪切板的監控,後來面對進程保護這樣一個需求時,綜合各方資料並本身動手實現HOOK OpenProcess() 和 TerminateProcess() 來從調用層實現進程的防終止。下面將進一步介紹實現的過程,也算是對學習的一個總結與實戰。框架
主要參考:http://www.cnblogs.com/delphi7456/archive/2010/10/31/1865729.htmlide
大致的HOOK API的實現思路在前面幾篇相關文章中已經講過。大體可分爲如下步驟:1.肯定要HOOK的API原型,並參照原型定義本身的API。2.在DLL中實現本身的API,並使用新的API入口地址替換原API地址實現HOOK,利用jmp xxxx 指令實現重定向。其中能夠利用GetProcAddress()獲取系統的API地址,經過WriteProcessMemory將本身寫的API地址替換掉原API地址。3.利用鼠標鉤子將本身的DLL注入到目標進程中。函數
咱們這裏要實現進程的防終止,設計到的API有兩個,分別是OpenProcess() 和 TerminateProcess(),這兩個API在Kernel32.dll中。若是隻HOOK 其中一個API是不可行的,若只HOOK OpenProcess(),那麼任務管理器將沒法獲取到受保護進程的信息,進而會出錯。若只HOOK TerminateProcess也是不可行的,由於一個進程的句柄在本進程與其餘進程中是不同的,所以如果你不知道本身進程在其餘進程中的句柄那將沒法HOOK TerminateProcess。學習
本事例採用的方案是,同時HOOK OpenProcess()和TerminateProcess(),在OpenProcess中獲取本身的受保護進程在其餘進程中的調用句柄,而後再TerminateProcess進程監控,若是發現有進程調用TerminateProcess而且所借宿的對象是本身要保護的進程,那麼就給出禁止關閉的提示窗口。測試
OpenProcess()是打開進程,而TerminateProcess()是結束進程,在調用TerminateProcess()結束進程時,必然會先調用OpenProcess()進程打開進程句柄。如下是這兩個API的原型: HANDLE OpenProcess( DWORD dwDesiredAccess, //渴望獲得的訪問權限(標誌) BOOL bInheritHandle, // 是否繼承句柄 DWORD dwProcessId // 進程標示符 ); BOOL TerminateProcess( HANDLE hProcess, //進程句柄 UINT uExitCode //進程終止碼 );
// MonitorDll.h : MonitorDll DLL 的主頭文件 // #pragma once #ifndef __AFXWIN_H__ #error "在包含此文件以前包含"stdafx.h"以生成 PCH 文件" #endif #include "resource.h" // 主符號 // CMonitorDllApp // 有關此類實現的信息,請參閱 MonitorDll.cpp // class CMonitorDllApp : public CWinApp { public: CMonitorDllApp(); // 重寫 public: virtual BOOL InitInstance(); int ExitInstance(); DECLARE_MESSAGE_MAP() };
// MonitorDll.cpp : 定義 DLL 的初始化例程。 // #include "stdafx.h" #include "MonitorDll.h" #ifdef _DEBUG #define new DEBUG_NEW #endif /* 全局變量 */
// 共享變量 #pragma data_seg("Share") HWND g_hwnd = NULL; // 主窗口句柄,加載HOOK時傳入 HINSTANCE hInstance = NULL; // 本DLL的實例句柄 HHOOK hhook = NULL; // 鼠標鉤子句柄 DWORD g_dwProcessId; // 進程id HANDLE g_hProcess = NULL; // 保存本進程在遠進程中的句柄 #pragma data_seg() #pragma comment(linker,"/section:Share,rws") // 其餘變量定義 HANDLE hProcess = NULL; // 當前進程句柄 bool bIsInjected = false; // 保證只注入一次 #define CODE_LENGTH 5 // 入口指令長度 // TerminateProcess typedef BOOL (WINAPI *TypeTerminateProcess)(_In_ HANDLE hProcess, _In_ UINT uExitCode); //Kernel32.dll TypeTerminateProcess oldTerminateProcess = NULL; FARPROC pfOldTerminateProcess = NULL; BOOL WINAPI MyTerminateProcess(_In_ HANDLE hProcess, _In_ UINT uExitCode); BYTE oldCodeTermPro[CODE_LENGTH]; // 原API入口 BYTE newCodeTermpro[CODE_LENGTH]; // 新API入口 // OpenProcess typedef HANDLE(WINAPI *TypeOpenProcess)( _In_ DWORD dwDesiredAccess,_In_ BOOL bInheritHandle,_In_ DWORD dwProcessId); TypeOpenProcess oldOpenProcess = NULL; FARPROC pfOldOpenProcess = NULL; HANDLE WINAPI MyOpenProcess(_In_ DWORD dwDesiredAccess,_In_ BOOL bInheritHandle,_In_ DWORD dwProcessId); BYTE oldCodeOpenPro[CODE_LENGTH]; BYTE newCodeOpenPro[CODE_LENGTH]; BOOL WINAPI HookLoad(HWND hwnd,DWORD dwProcessId); // 關於dll hook 操做 VOID WINAPI HookUnload(); VOID Inject(); VOID HookOn(); VOID HookOff();
BOOL SetPrivilege( HANDLE hToken, // access token handle LPCTSTR lpszPrivilege, // name of privilege to enable/disable BOOL bEnablePrivilege // to enable or disable privilege ) ; LRESULT CALLBACK MouseProc( // 鼠標鉤子子過程調用 int nCode, // hook code WPARAM wParam,// message identifier LPARAM lParam // mouse coordinates ); BOOL WriteMemory(LPVOID lpAddress,BYTE* pcode,size_t length); //將長度爲 length 的 pcode 寫入地址 lpAddress 的進程內存中 // //TODO: 若是此 DLL 相對於 MFC DLL 是動態連接的, // 則今後 DLL 導出的任何調入 // MFC 的函數必須將 AFX_MANAGE_STATE 宏添加到 // 該函數的最前面。 // // 例如: // // extern "C" BOOL PASCAL EXPORT ExportedFunction() // { // AFX_MANAGE_STATE(AfxGetStaticModuleState()); // // 此處爲普通函數體 // } // // 此宏先於任何 MFC 調用 // 出如今每一個函數中十分重要。這意味着 // 它必須做爲函數中的第一個語句 // 出現,甚至先於全部對象變量聲明, // 這是由於它們的構造函數可能生成 MFC // DLL 調用。 // // 有關其餘詳細信息, // 請參閱 MFC 技術說明 33 和 58。 // // CMonitorDllApp BEGIN_MESSAGE_MAP(CMonitorDllApp, CWinApp) END_MESSAGE_MAP() // CMonitorDllApp 構造 CMonitorDllApp::CMonitorDllApp(){ // TODO: 在此處添加構造代碼, // 將全部重要的初始化放置在 InitInstance 中 } // 惟一的一個 CMonitorDllApp 對象 CMonitorDllApp theApp; // CMonitorDllApp 初始化 BOOL CMonitorDllApp::InitInstance(){ CWinApp::InitInstance(); hInstance = AfxGetInstanceHandle(); // 獲取本dll句柄 /* 先提升權限,再獲取進程句柄。 由於只有權限足夠,才能獲取到當前進程的句柄。 */ HANDLE hToken; BOOL bRet = OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,&hToken); if (bRet == FALSE){ AfxMessageBox(_T("權限提高失敗")); } SetPrivilege(hToken,SE_DEBUG_NAME,TRUE); DWORD dwPid = ::GetCurrentProcessId(); hProcess = ::OpenProcess(PROCESS_ALL_ACCESS,0,dwPid); if (hProcess == NULL){ CString str; str.Format(_T("OpenProcess fail!!, error code = [%d]"),GetLastError()); AfxMessageBox(str); return FALSE; } Inject(); // 開始注入 return TRUE; } // // 實例退出函數。退出時,必定要記得恢復原函數地址!!! // int CMonitorDllApp::ExitInstance() { HookOff(); //要記得恢復原函數地址 return CWinApp::ExitInstance(); } /* 鼠標鉤子子過程,目的是加載本dll到使用鼠標的程序. 鼠標鉤子的做用:當鼠標在某程序窗口中時,就會加載咱們這個dll。 即便本DLL隨着鼠標鉤子注入到目標進程中。 */ LRESULT CALLBACK MouseProc( int nCode, // hook code WPARAM wParam, // message identifier LPARAM lParam // mouse coordinates ) { return CallNextHookEx(hhook,nCode,wParam,lParam); } /* 安裝鉤子。 主調程序傳入窗口句柄和進程id。 */ BOOL WINAPI HookLoad(HWND hwnd,DWORD dwProcessId){ BOOL ret = FALSE; g_hwnd = hwnd; g_dwProcessId = dwProcessId; hhook = ::SetWindowsHookEx(WH_MOUSE,MouseProc,hInstance,0);
if (hhook == NULL){ return FALSE; } else{ return TRUE; } } /* 卸載鉤子。 注:卸載鉤子以前,必定要記得恢復原函數地址!!! */ VOID WINAPI HookUnload(){ HookOff(); // 恢復原函數地址 if (hhook != NULL){ UnhookWindowsHookEx(hhook); } if (hInstance != NULL){ FreeLibrary(hInstance); } } /* 注入函數。 主要完成原函數地址的保存,保存到 oldCode_[]中; 新入口地址的計算,保存到newCode_[]中,即 jmp xxxx 指令。 新入口地址 = 新函數地址 - 原函數地址 - 指令長度 最後必定要記得HookOn!! */ VOID Inject(){ if (bIsInjected == TRUE){ return; } bIsInjected = TRUE;// 保證只注入一次
// TerminateProcess HMODULE hmodleKernel32; hmodleKernel32 = ::LoadLibrary(_T("Kernel32.dll")); if (NULL == hmodleKernel32){ AfxMessageBox(_T("加載Kernel32.dll失敗")); return; } // 獲取原函數地址 oldTerminateProcess = (TypeTerminateProcess)GetProcAddress(hmodleKernel32,"TerminateProcess"); if (NULL == oldTerminateProcess){ AfxMessageBox(_T("獲取TerminateProcess函數失敗")); return; } pfOldTerminateProcess = (FARPROC)oldTerminateProcess; // 保存原函數入口 _asm { lea edi,oldCodeTermPro mov esi,pfOldTerminateProcess cld mov ecx,CODE_LENGTH rep movsb } // 替換新函數入口 newCodeTermpro[0] = 0xe9; _asm { lea eax,MyTerminateProcess mov ebx,pfOldTerminateProcess sub eax,ebx sub eax,CODE_LENGTH mov dword ptr [newCodeTermpro+1],eax } // OpenProcess oldOpenProcess = (TypeOpenProcess)GetProcAddress(hmodleKernel32,"OpenProcess"); if (NULL == oldOpenProcess){ AfxMessageBox(_T("獲取OpenProcess地址失敗")); return; } pfOldOpenProcess = (FARPROC)oldOpenProcess;
_asm { lea edi,oldCodeOpenPro mov esi,pfOldOpenProcess cld mov ecx,CODE_LENGTH rep movsb }
newCodeOpenPro[0] = 0xe9; _asm { lea eax,MyOpenProcess mov ebx,pfOldOpenProcess sub eax,ebx sub eax,CODE_LENGTH mov dword ptr [newCodeOpenPro+1],eax } HookOn(); //填充完畢,開始HOOK } /* 將長度爲 length 的 pcode 寫入地址 lpAddress 的進程內存中 */ BOOL WriteMemory(LPVOID lpAddress,BYTE* pcode,size_t length){ ASSERT(hProcess != NULL); DWORD dwtemp,dwOldProtect,dwRet,dwWrited; dwRet = VirtualProtectEx(hProcess,lpAddress,length,PAGE_READWRITE,&dwOldProtect); CString logInfo; if ( 0 == dwRet){ logInfo.Format(_T("WriteMemory :: Call VirtualProtectEx fail, eror code = [%d]\n\n"),GetLastError()); AfxMessageBox(logInfo); return FALSE; } dwRet = WriteProcessMemory(hProcess,lpAddress,pcode,length,&dwWrited); if ( 0 == dwRet || 0 == dwWrited){ logInfo.Format(_T("WriteMemory :: Call WriteProcessMomory fail, error code = [%d]\n\n"),GetLastError()); AfxMessageBox(logInfo); return FALSE; } dwRet = VirtualProtectEx(hProcess,lpAddress,length,dwOldProtect,&dwtemp); if ( 0 == dwRet ){ logInfo.Format(_T("WriteMemory :: Recover Protect fail, error code = [%d]\n\n"),GetLastError()); AfxMessageBox(logInfo); return FALSE; } return TRUE; } /* 開始HOOK。 即,將Inject 初始化好的入口地址進行寫入進程內存中。 這裏,將新函數入口 newCode_[],寫入內存中。 這樣一來,在原函數被調用的時候,就會跳轉到咱們新函數的位置。 注: 這裏處理的函數,是當前須要替換的全部函數,因此只在Inject()函數中調用, 即進行初始化的時候用到該函數。 */ VOID HookOn(){ BOOL ret; ret = WriteMemory(pfOldTerminateProcess,newCodeTermpro,CODE_LENGTH); if (FALSE == ret){ AfxMessageBox(_T("HookOn :: Fail to write pfOldTerminateProcess")); } ret = WriteMemory(pfOldOpenProcess,newCodeOpenPro,CODE_LENGTH); if (FALSE == ret){ AfxMessageBox(_T("HookOn :: Fail to write pfOldOpenProcess")); } } /* 中止HOOK。 恢復原函數地址。 注:這裏處理的是全部替換的函數,因此通常狀況下只有在卸載HOOK函數中調用 */ VOID HookOff(){ ASSERT(hProcess != NULL); BOOL ret; ret = WriteMemory(pfOldTerminateProcess,oldCodeTermPro,CODE_LENGTH); if (FALSE == ret){ AfxMessageBox(_T("HookOff :: fail to recover pfOldTerminateProcess \n\n")); } ret = WriteMemory(pfOldOpenProcess,oldCodeOpenPro,CODE_LENGTH); if (FALSE == ret){ AfxMessageBox(_T("HookOff :: fail to recover pfOldOpenProcess")); } } /* 提高進程權限。 */ BOOL SetPrivilege( HANDLE hToken, // access token handle LPCTSTR lpszPrivilege, // name of privilege to enable/disable BOOL bEnablePrivilege // to enable or disable privilege ){
TOKEN_PRIVILEGES tp; LUID luid; CString info; if ( !LookupPrivilegeValue( NULL, // lookup privilege on local system lpszPrivilege, // privilege to lookup &luid ) ) // receives LUID of privilege { info.Format(_T("LookupPrivilegeValue error: %u\n"), GetLastError() ); AfxMessageBox(info); return FALSE; } tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; if (bEnablePrivilege) tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; else tp.Privileges[0].Attributes = 0; // Enable the privilege or disable all privileges. if ( !AdjustTokenPrivileges( hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES) NULL, (PDWORD) NULL) ){ info.Format(_T("AdjustTokenPrivileges error: %u\n"), GetLastError() ); AfxMessageBox(info); return FALSE; } if (GetLastError() == ERROR_NOT_ALL_ASSIGNED){ info.Format(_T("The token does not have the specified privilege. \n")); AfxMessageBox(info); return FALSE; } return TRUE; } // // 本身從新定義的進程終止函數。 // 檢查當前要終止的進程是不是受保護進程,如果則禁止關閉。 // BOOL WINAPI MyTerminateProcess(_In_ HANDLE hProcess, _In_ UINT uExitCode){ BOOL ret; if (g_hProcess == hProcess){ AfxMessageBox(_T("不能關閉受保護進程哦!!")); ret = TRUE; } else{ WriteMemory(pfOldTerminateProcess,oldCodeTermPro,CODE_LENGTH); ret = oldTerminateProcess(hProcess,uExitCode); WriteMemory(pfOldTerminateProcess,newCodeTermpro,CODE_LENGTH); } return ret; } // // 本身定義的打開進程函數。 // 若當前打開進程爲受保護進程,則記錄下該遠程調用句柄。 // HANDLE WINAPI MyOpenProcess(_In_ DWORD dwDesiredAccess,_In_ BOOL bInheritHandle,_In_ DWORD dwProcessId){ HANDLE hProcess = NULL; WriteMemory(pfOldOpenProcess,oldCodeOpenPro,CODE_LENGTH); hProcess = oldOpenProcess(dwDesiredAccess,bInheritHandle,dwProcessId); if ( dwProcessId == g_dwProcessId){ g_hProcess = hProcess; } WriteMemory(pfOldOpenProcess,newCodeOpenPro,CODE_LENGTH); return hProcess; }
MonitorDll中的MonitorDll.defui
; MonitorDll.def : 聲明 DLL 的模塊參數。
LIBRARY
EXPORTS
; 此處能夠是顯式導出
HookLoad
HookUnload
// MyWindowDlg.h : 頭文件 // #pragma once // CMyWindowDlg 對話框 class CMyWindowDlg : public CDialogEx{ // 構造 public: CMyWindowDlg(CWnd* pParent = NULL); // 標準構造函數 // 對話框數據 enum { IDD = IDD_MYWINDOW_DIALOG }; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持 // 實現 protected: HICON m_hIcon; HINSTANCE m_hinstHookDll; // MonitorDll的實例句柄 void HookLoad(); // 加載HOOK void HookUnload(); // 卸載HOOK // 生成的消息映射函數 virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnClose(); // 關閉程序的時候卸載DLL !!!!! DECLARE_MESSAGE_MAP() };
// MyWindowDlg.cpp : 實現文件 // #include "stdafx.h" #include "MyWindow.h" #include "MyWindowDlg.h" #include "afxdialogex.h" #ifdef _DEBUG #define new DEBUG_NEW #endif // 用於應用程序"關於"菜單項的 CAboutDlg 對話框 class CAboutDlg : public CDialogEx { public: CAboutDlg(); // 對話框數據 enum { IDD = IDD_ABOUTBOX }; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持 // 實現 protected: DECLARE_MESSAGE_MAP() }; CAboutDlg::CAboutDlg() : CDialogEx(CAboutDlg::IDD) { } void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); } BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx) END_MESSAGE_MAP() // CMyWindowDlg 對話框 CMyWindowDlg::CMyWindowDlg(CWnd* pParent /*=NULL*/) : CDialogEx(CMyWindowDlg::IDD, pParent) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } void CMyWindowDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); } BEGIN_MESSAGE_MAP(CMyWindowDlg, CDialogEx) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() END_MESSAGE_MAP() // CMyWindowDlg 消息處理程序 BOOL CMyWindowDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 將"關於..."菜單項添加到系統菜單中。 // IDM_ABOUTBOX 必須在系統命令範圍內。 ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != NULL) { BOOL bNameValid; CString strAboutMenu; bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX); ASSERT(bNameValid); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } }
// 設置此對話框的圖標。當應用程序主窗口不是對話框時,框架將自動 // 執行此操做 SetIcon(m_hIcon, TRUE); // 設置大圖標 SetIcon(m_hIcon, FALSE); // 設置小圖標 // TODO: 在此添加額外的初始化代碼 HookLoad(); // 加載HOOK return TRUE; // 除非將焦點設置到控件,不然返回 TRUE } void CMyWindowDlg::OnClose() { // TODO: 在此添加消息處理程序代碼和/或調用默認值 HookUnload(); // 退出窗口,要卸載HOOK CDialogEx::OnClose(); } void CMyWindowDlg::OnSysCommand(UINT nID, LPARAM lParam) { if ((nID & 0xFFF0) == IDM_ABOUTBOX){ CAboutDlg dlgAbout; dlgAbout.DoModal(); } else{ CDialogEx::OnSysCommand(nID, lParam); } } // 若是向對話框添加最小化按鈕,則須要下面的代碼 // 來繪製該圖標。對於使用文檔/視圖模型的 MFC 應用程序, // 這將由框架自動完成。 void CMyWindowDlg::OnPaint() { if (IsIconic()){ CPaintDC dc(this); // 用於繪製的設備上下文 SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0); // 使圖標在工做區矩形中居中 int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // 繪製圖標 dc.DrawIcon(x, y, m_hIcon); } else{ CDialogEx::OnPaint(); } } //當用戶拖動最小化窗口時系統調用此函數取得光標 //顯示。 HCURSOR CMyWindowDlg::OnQueryDragIcon(){ return static_cast<HCURSOR>(m_hIcon); } void CMyWindowDlg::HookLoad(){ m_hinstHookDll = ::LoadLibrary(_T("C:\\testProject\\MonitorDll.dll")); CString loginfo; if ( NULL == m_hinstHookDll){ loginfo.Format(_T("加載 MonitorDll.dll失敗,錯誤代碼 = [%d] "),GetLastError()); AfxMessageBox(loginfo); return; }
typedef BOOL (WINAPI* LoadMonitor)(HWND hwnd,DWORD dwProcessId); LoadMonitor loadMonitor = NULL; loadMonitor = (LoadMonitor)::GetProcAddress(m_hinstHookDll,"HookLoad"); if (NULL == loadMonitor) { loginfo.Format(_T("獲取函數 HookLoad 失敗,錯誤代碼 = [%d]"),GetLastError()); AfxMessageBox(loginfo); } if (loadMonitor(m_hWnd,GetCurrentProcessId())){ loginfo.Format(_T("HOOK加載成功")); AfxMessageBox(loginfo); } else{ loginfo.Format(_T("HOOK加載失敗")); AfxMessageBox(loginfo); } } /* 卸載HOOKDLL */ void CMyWindowDlg::HookUnload(){ CString logInfo; if (m_hinstHookDll == NULL){ m_hinstHookDll = LoadLibrary(_T("MonitorDll.dll")); if ( NULL == m_hinstHookDll){ logInfo.Format(_T("加載 MonitorDll.dll失敗,錯誤代碼 = [%d]"),GetLastError()); AfxMessageBox(logInfo); return; } } typedef VOID (WINAPI* UnloadHook)(); UnloadHook unloadHook = NULL; unloadHook = (UnloadHook)::GetProcAddress(m_hinstHookDll,"HookUnload"); if (NULL == unloadHook){ logInfo.Format(_T("獲取函數 HookUnload 失敗,錯誤代碼 = [%d]"),GetLastError()); AfxMessageBox(logInfo); return; } unloadHook(); }
本實例說明:有兩個輸出文件,一個是MonitorDll.dll,這是編譯MFC DLL工程獲得的,用來實現HOOK API的功能,由主窗口調用,注入到目標進程中。主窗口程序MyWindow,在窗口初始化時加載HOOK,在窗口進程正常退出時卸載HOOK。實例測試效果以下:
窗口初始化過程,打印受保護進程id:
窗口初始化過程當中自動加載HOOK,成功:
點擊肯定,出現如下對話框:
打開任務管理器,找到咱們的窗口進程MyWindow.exe:
試圖強制關閉咱們的進程MyWindow.exe:
彈出不能關閉對話框,這樣也就防止了進程被強制關閉: