改造聯想Y480的快捷鍵(跨進程替換窗口過程(子類化)的實現——遠程線程注入)


前段時間入手了聯想Y480N-IFI,在C面頂部有幾個快捷按鍵。一鍵恢復、一鍵影音,這兩個按鍵本身的功能對於本人是毫無作用,

我便想着能否改成像多媒體鍵盤那樣有一些快捷鍵可以打開一些軟件。正好這段時間有個9天的假期。我便開始研究。

對windows msg敏感同學應該就會想到,按鍵按下時就會有windows msg,那么可能是哪些消息呢?

我首先想到既然這些按鍵不是標准按鍵,那應該也不是標准的msg了,馬上想到非常有可能是自定義消息。於是,便打開spyxx進行分析。如下:

打開spyxx,Spy菜單→Log Messages...彈出message Options。選中Additional Windows里的All Window in system,

然后切換到Messages選項卡,先clear all,然后選中WM_USER,確定之后就開始記錄消息了。

可以看到。當按下這兩個按鍵時都會有一條WM_USER + 1002的消息,只是消息參數不同,一鍵恢復的消息參數是0x0b,而一鍵影音的則是0x05

繼續分析,可以看到。收到這條消息的窗口是哪個。

還可以得到所屬的進程

那么,接下來的要做的事,就是攔截這條消息,自己進行處理。

攔截的方式有很多,例如

1.全局hook這個消息

2.注入目標進程hook這個消息

3.注入目標進程替換窗口過程

最終,我確定下來,采用第三種方案。

程序最終界面,采用了WTL。

程序實現的功能:

一鍵影音和一鍵恢復兩個按鍵可以設置成如下功能:

打開我的電腦、庫、我的文檔、計算器、記事本、任務管理器、cmd、默認音樂播放器、默認視頻播放器、默認瀏覽器、默認郵件客戶端、指定的程序。

接下來看看一些關鍵方法:

遠程線程注入Dll原理:通過CreateRemoteThread這個API在目標進程中創建遠程線程。

由於LoadLibrary這個API和ThreadProc的原型基本一致。所以可以直接用LoadLibrary作為線程函數,然后把Dll的路徑作為線程參數。

LoadLibrary(W/A)這個API在kernel32.dll里導出。系統中每一個進程都會將kernel32.dll映射到同一個地址,

所以我們可以在本地進程里GetProcAddress顯式取到LoadLibrary的地址可以作為線程函數地址,

而Dll的路徑則需要用VirtualAllocEx在目標進程里分配內存空間。然后通過WriteProcessMemory將dll的路徑寫入到目標進程。

將VirtualAllocEx返回的地址作為線程參數即可。詳細的可以看看《Windows vic C/C++》 第22章。

注入的代碼:①

 1 BOOL InjectLib( DWORD dwProcessId, LPCTSTR pszLibFileName )
 2 {
 3     // TODO: Inject dll to process
 4     BOOL bOk = FALSE;  // Assume that the function fails
 5     HANDLE hProcess = NULL;
 6     HANDLE hThread = NULL;
 7     LPVOID pszLibFileRemote = NULL;
 8     __try
 9     {
10         // Get a handle for the target process.
11         hProcess = ::OpenProcess(
12             PROCESS_QUERY_INFORMATION |   // Required by Alpha
13             PROCESS_CREATE_THREAD     |   // For CreateRemoteThread
14             PROCESS_VM_OPERATION      |   // For VirtualAllocEx/VirtualFreeEx
15             PROCESS_VM_WRITE,             // For WriteProcessMemory
16             FALSE, dwProcessId);
17         if (hProcess == NULL) __leave;
18 
19         // Calculate the number of bytes needed for the DLL's pathname
20         int cb  = (::lstrlen(pszLibFileName) + 1) * sizeof(TCHAR);
21 
22         // Allocate space in the remote process for the pathname
23         pszLibFileRemote = ::VirtualAllocEx(hProcess, NULL, cb, MEM_COMMIT, PAGE_READWRITE);
24         if (pszLibFileRemote == NULL) __leave;
25 
26         // Copy the DLL's pathname to the remote process' address space
27         if (!::WriteProcessMemory(hProcess, pszLibFileRemote, 
28             pszLibFileName, cb, NULL)) __leave;
29 
30         // Get the real address of LoadLibraryW in Kernel32.dll
31 #ifdef UNICODE
32         PTHREAD_START_ROUTINE pfnThreadRtn = reinterpret_cast<PTHREAD_START_ROUTINE>(
33             ::GetProcAddress(::GetModuleHandle(_T("Kernel32")), "LoadLibraryW"));
34 #else
35         PTHREAD_START_ROUTINE pfnThreadRtn = reinterpret_cast<PTHREAD_START_ROUTINE>(
36             ::GetProcAddress(::GetModuleHandle(_T("Kernel32")), "LoadLibraryA"));
37 #endif
38         if (pfnThreadRtn == NULL) __leave;
39 
40         // Create a remote thread that calls LoadLibraryW(DLLPathname)
41         hThread = ::CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, pszLibFileRemote, 0, NULL);
42         if (hThread == NULL) __leave;
43 
44         // Wait for the remote thread to terminate
45         ::WaitForSingleObject(hThread, INFINITE);
46         bOk = TRUE; // Everything executed successfully
47     }
48     __finally
49     { // Now, we can clean everything up
50 
51         // Free the remote memory that contained the DLL's pathname
52         if (pszLibFileRemote != NULL) ::VirtualFreeEx(hProcess, pszLibFileRemote, 0, MEM_RELEASE);
53 
54         CLOSE_HANDLE(hThread);
55         CLOSE_HANDLE(hProcess);
56     }
57     return bOk;
58 }

查找指定進程的指定模塊基址的代碼:

 1 BOOL FindProcessModule( DWORD dwProcessId, LPCTSTR pszModuleName, PMODULEENTRY32 pMe )
 2 {
 3     BOOL bOk = FALSE; // Assume that the function fails
 4     HANDLE hthSnapshot = NULL;
 5     __try
 6     {
 7         // Grab a new snapshot of the process
 8         hthSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId);
 9         if (hthSnapshot == INVALID_HANDLE_VALUE) __leave;
10         
11         MODULEENTRY32 me = {sizeof(MODULEENTRY32)};
12         if (NULL == pMe) pMe = &me;
13         
14         // Get the HMODULE of the desired library
15         BOOL bFound = FALSE;
16         BOOL bMoreMods = ::Module32First(hthSnapshot, pMe);
17 
18         for (; bMoreMods; bMoreMods = ::Module32Next(hthSnapshot, pMe))
19         {
20             bFound = (::lstrcmpi(pMe->szModule,  pszModuleName) == 0) || 
21                 (::lstrcmpi(pMe->szExePath, pszModuleName) == 0);
22             if (bFound) break;
23         }
24         if (!bFound) __leave;
25 
26         bOk = TRUE; // Everything executed successfully
27     }
28     __finally 
29     { // Now we can clean everything up
30         CLOSE_HANDLE(hthSnapshot);
31     }
32     return bOk;
33 }

程序里判斷當前注入狀態也是通過上面這段代碼來實現。

取消注入的代碼:

 1 BOOL EjectLib( DWORD dwProcessId, LPCTSTR pszLibFileName )
 2 {
 3     // TODO: Eject dll to process
 4     BOOL bOk = FALSE; // Assume that the function fails
 5     HANDLE hProcess = NULL;
 6     HANDLE hThread = NULL;
 7     MODULEENTRY32 me = {sizeof(MODULEENTRY32)};
 8 
 9     if (!::FindProcessModule(dwProcessId, pszLibFileName, &me)) return FALSE;
10 
11     __try 
12     {
13         // Get a handle for the target process.
14         hProcess = OpenProcess(
15             PROCESS_QUERY_INFORMATION |   
16             PROCESS_CREATE_THREAD     | 
17             PROCESS_VM_OPERATION,  // For CreateRemoteThread
18             FALSE, dwProcessId);
19         if (hProcess == NULL) __leave;
20 
21         // Get the real address of FreeLibrary in Kernel32.dll
22         PTHREAD_START_ROUTINE pfnThreadRtn = reinterpret_cast<PTHREAD_START_ROUTINE>(
23             ::GetProcAddress(::GetModuleHandle(_T("Kernel32")), "FreeLibrary"));
24         if (pfnThreadRtn == NULL) __leave;
25 
26         // Create a remote thread that calls FreeLibrary()
27         hThread = ::CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, me.modBaseAddr, 0, NULL);
28         if (hThread == NULL) __leave;
29 
30         // Wait for the remote thread to terminate
31         ::WaitForSingleObject(hThread, INFINITE);
32 
33         bOk = TRUE; // Everything executed successfully
34     }
35     __finally 
36     { // Now we can clean everything up
37         CLOSE_HANDLE(hThread);
38         CLOSE_HANDLE(hProcess);
39     }
40     return TRUE;
41 }

程序開機自啟的實現:

寫入注冊表,加上命令行參數,通過參數來判斷是手動啟動還是自啟。

注冊表操作用了CReg類。代碼如下:

 1 BOOL CMainDlg::SetAutoRun(BOOL bAuto)
 2 {
 3     CRegKey reg;
 4     LONG lRet = reg.Open(HKEY_LOCAL_MACHINE,
 5         _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"), KEY_WRITE);
 6     if (ERROR_SUCCESS == lRet)
 7     {
 8         if (bAuto)
 9         {
10             TCHAR szTmp[MAX_PATH];
11             wsprintf(szTmp, _T("%s -auto"), m_szAppPath);
12             lRet = reg.SetStringValue(_T("OnKeyMgr"), szTmp);
13         }
14         else
15         {
16             lRet = reg.DeleteValue(_T("OnKeyMgr"));
17         }
18     }
19     else
20     {
21         MessageBox(_T("Access is denied. Plz Run as administrator to retry."), _T("Error"), MB_OK | MB_ICONERROR);
22     }
23     reg.Close();
24     return lRet == ERROR_SUCCESS;
25 }

為了方便,將dll作為資源嵌入到exe里。程序運行時會判斷是否存在dll。如果不存在則釋放出dll。代碼如下

 1 BOOL CMainDlg::ExpandLib(void)
 2 {
 3     // Determine whether the file already exists
 4     if (::PathFileExists(m_szLibPath)) return TRUE;
 5 
 6     //Expand res
 7     HANDLE hLib = NULL;
 8     __try
 9     {
10         HRSRC hRes = ::FindResource(NULL, MAKEINTRESOURCE(IDR_BIN_LIB), _T("BIN"));
11         if (hRes == NULL) __leave;
12 
13         HGLOBAL hData = ::LoadResource(NULL, hRes);
14         if (hData == NULL) __leave;
15 
16         LPVOID lpData = ::LockResource(hData);
17         if (NULL == lpData) __leave;
18 
19         DWORD dwResSize = ::SizeofResource(NULL, hRes);
20 
21         hLib = ::CreateFile(m_szLibPath, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
22         if (hLib == INVALID_HANDLE_VALUE) __leave;
23 
24         DWORD dwWritten;
25         if (!::WriteFile(hLib, lpData, dwResSize, &dwWritten, NULL))    __leave;
26 
27         return TRUE;
28     }
29     __finally
30     {
31         CLOSE_HANDLE(hLib);
32     }
33     return FALSE;
34 }

程序可以把快捷鍵自定義為打開某個程序,在使用文件選擇對話框時可以選中快捷方式,而程序則需要解析出快捷方式真正的路徑。代碼如下②

 1 HRESULT ResolveShortcut( LPCWSTR lpszShortcutPath, LPWSTR lpszFilePath)
 2 {
 3     HRESULT hRes = E_FAIL;
 4     CComPtr<IShellLink> ipShellLink;
 5     // buffer that receives the null-terminated string 
 6     // for the drive and path
 7     WCHAR szPath[MAX_PATH];     
 8     // buffer that receives the null-terminated 
 9     // structure that receives the information about the shortcut
10     WIN32_FIND_DATA wfd;    
11     WCHAR wszTemp[MAX_PATH];
12 
13     lpszFilePath[0] = L'\0';
14 
15     // Get a pointer to the IShellLink interface
16     hRes = ::CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink,
17                             reinterpret_cast<LPVOID*>(&ipShellLink)); 
18 
19     if (SUCCEEDED(hRes)) 
20     { 
21         // Get a pointer to the IPersistFile interface
22         CComQIPtr<IPersistFile> ipPersistFile(ipShellLink);
23 
24         // IPersistFile is using LPCOLESTR, 
25         ::lstrcpynW(wszTemp, lpszShortcutPath, MAX_PATH);
26 
27         // Open the shortcut file and initialize it from its contents
28         hRes = ipPersistFile->Load(wszTemp, STGM_READ); 
29         if (SUCCEEDED(hRes)) 
30         {
31             // Try to find the target of a shortcut, 
32             // even if it has been moved or renamed
33             hRes = ipShellLink->Resolve(NULL, SLR_UPDATE); 
34             if (SUCCEEDED(hRes)) 
35             {
36                 // Get the path to the shortcut target
37                 hRes = ipShellLink->GetPath(szPath, MAX_PATH, &wfd, SLGP_RAWPATH); 
38                 if (FAILED(hRes))   return hRes;
39 
40                 ::lstrcpynW(lpszFilePath, szPath, MAX_PATH); 
41             } 
42         } 
43     } 
44     return hRes;
45 }

打開某個程序這里都是通過ShellExecute來實現。

接下來說說如何獲取一些默認程序。

ShellExcute支持用CLSID作為參數來打開程序,打開我的電腦,我的文檔、庫、是通過CLSID來打開。

對應的CLSID分別是:

::{20D04FE0-3AEA-1069-A2D8-08002B30309D}

::{031E4825-7B94-4dc3-B131-E946B44C8DD5}

::{450D8FBA-AD25-11D0-98A8-0800361B1103}

這些CLSID是從注冊表翻出來的。

打開email是ShellExecute支持mailto協議

其他程序都是通過讀取注冊表解析關聯的程序。

讀取的代碼如下

第一個參數是擴展名,如.mp3,第二個參數傳出參數,需要一個TCHAR sz[MAX_PATH]。第三個參數是一個標志位。

如果是要解析默認瀏覽器。則將第二個參數設置為"http"第三個參數設置為FALSE,

因為解析默認瀏覽器比其他的少了一個步驟。

 1 BOOL CMgr::GetAssociatedApp( LPCTSTR pszExt, LPTSTR pszPath, BOOL bIsExt /*= TRUE*/ )
 2 {
 3     HKEY hKey;
 4     LONG lRet;
 5     DWORD dwData = MAX_PATH;
 6     if (bIsExt)
 7     {
 8         lRet = ::RegOpenKeyEx(HKEY_CLASSES_ROOT, pszExt, 0, KEY_QUERY_VALUE, &hKey);
 9         if (ERROR_SUCCESS != lRet) return FALSE;
10 
11         lRet = ::RegQueryValueEx(hKey, NULL, 0, NULL, reinterpret_cast<LPBYTE>(pszPath), &dwData);
12         if (ERROR_SUCCESS != lRet) return FALSE;
13 
14         lRet = ::RegCloseKey(hKey);
15         if (ERROR_SUCCESS != lRet) return FALSE;
16     }
17 
18     TCHAR szSubKey[MAX_PATH];
19     ::wsprintf(szSubKey, TEXT("%s\\Shell\\Open\\Command"), pszPath);
20 
21     lRet = ::RegOpenKeyEx(HKEY_CLASSES_ROOT, szSubKey, 0, KEY_QUERY_VALUE, &hKey);
22     if (ERROR_SUCCESS != lRet) return FALSE;
23 
24     dwData = MAX_PATH;
25     lRet = ::RegQueryValueEx(hKey, NULL, 0, NULL, reinterpret_cast<LPBYTE>(pszPath), &dwData);
26     if (ERROR_SUCCESS != lRet) return FALSE;
27 
28     lRet = ::RegCloseKey(hKey);
29     if (ERROR_SUCCESS != lRet) return FALSE;
30 
31     ::PathRemoveArgs(pszPath);
32     return TRUE;
33 }

以上是主要代碼。詳細的可以看附件提供的源碼

另外寫的過程中有一些暫時用不到的代碼,在這里分享下:

一段用來顯式加載Dll的宏:

 1 /*********************************************************************************** 
 2  * ex for decl API : void PFNPathRemoveArgs(PTSTR pszPath)                         
 3  *                                                                                 
 4  *   typedef PTSTR (WINAPI *PROCTYPE(PathFindExtension))(PTSTR); // proc type decl 
 5  *   PROCTYPE(PathFindExtension) PathFindExtension; // variable decl               
 6  *   GETPROCADDR(hMod, PathFindExtension); 
 7  *
 8  * Expand:                                                                         
 9  *   typedef PTSTR (WINAPI *PFNPathFindExtension)(PTSTR);                          
10  *   PFNPathFindExtension PathFindExtension;                                       
11  *   PathFindExtension = reinterpret_cast<PFNPathFindExtension>(                   
12  *                           ::GetProcAddress(hMod, "PathFindExtensionW"));        
13 ***********************************************************************************/
14 
15 #define PROCNAME(x) #x
16 #define PROCTYPE(x) PFN##x
17 
18 #ifdef UNICODE
19 #define GETPROCADDR(hMod, proc) \
20     proc = reinterpret_cast<PROCTYPE(proc)> (::GetProcAddress(hMod, PROCNAME(proc##W)))
21 #else
22 #define GETPROCADDR(hMod, proc) \
23     proc = reinterpret_cast<PROCTYPE(proc)> (::GetProcAddress(hMod, PROCNAME(proc##A)))
24 #endif

如果這個宏有更好的寫法,歡迎指教。

下面代碼是用來判斷dll是否是64位的

 1 BOOL CMainDlg::IsWow64Lib(LPCTSTR pszDll)
 2 {
 3     HANDLE hFile = NULL;
 4     __try
 5     {
 6         //Open dll file
 7         hFile = ::CreateFile(pszDll, GENERIC_READ, 0, NULL, OPEN_EXISTING, NULL, NULL);
 8         if (hFile == INVALID_HANDLE_VALUE) __leave;
 9 
10         IMAGE_DOS_HEADER imgDosHdr;
11         DWORD dwRead;
12         // Read the IMAGE_DOS_HEADER
13         if (!::ReadFile(hFile, &imgDosHdr, sizeof(IMAGE_DOS_HEADER), &dwRead, NULL)) __leave;
14 
15         // MZ header
16         if (imgDosHdr.e_magic != IMAGE_DOS_SIGNATURE) __leave;
17 
18         IMAGE_NT_HEADERS imgNtHdr;
19         // Offset file pointer to the pe header
20         ::SetFilePointer(hFile, imgDosHdr.e_lfanew, 0, FILE_BEGIN);
21         // Read the IMAGE_NT_HEADER
22         ::ReadFile(hFile, &imgNtHdr, sizeof(IMAGE_NT_HEADERS), &dwRead, NULL);
23 
24         // PE header
25         if (imgNtHdr.Signature != IMAGE_NT_SIGNATURE) __leave;
26 
27         return imgNtHdr.FileHeader.Machine != IMAGE_FILE_MACHINE_I386;
28     }
29     __finally
30     {
31         CLOSE_HANDLE(hFile);
32     }
33     return FALSE;
34 }

代碼里經常出現用來關閉句柄的宏:

1 #define CLOSE_HANDLE(handle) \
2     do \
3 { \
4     CloseHandle(handle); \
5     handle = NULL; \
6 } while (FALSE)

本來還打算。用一個32位的程序,嵌入兩個dll一個32位一個64.在不同平台選擇不同dll注入。

一開始是這么寫。后來發現。沒法直接用32位程序注入64位dll到64位進程,會出現拒絕訪問。

得換思路實現。不過沒時間寫,就把思路分享如下:

思路一:再寫一個64位的injector嵌入到exe。然后在64位系統上就釋放這個Injector來注入。

思路二:用native API。得到ntdll.dll的baseaddr。然后定位到LdrLoadDll的addr。然后定位到RtlCreateUserThread,通過RtlCreateUserThread來創建遠程線程並用LdrLoadDll來加載dll。這個需要內聯匯編實現。比較麻煩。

另外,寫這個程序時。一開始是在DLL_PROCESS_ATTACH時處理了很多操作。。。這就出現了有時DllMain里的代碼沒執行的情況。。。絞盡腦汁。。唯一能想到的原因就是Dllloader出現deadlock。。不過我沒去驗證。。在微軟上看到一份文檔,有興趣可以看看。《Best Practices for Creating DLLs》

 

最后我把一些操作封裝成類,在構造函數里操作,然后再Dll里定義了一個全局對象。這樣就沒出現問題了。

=================================華麗的分割線======================================

源碼下載:OneKeyMgr_src.7z (IDE采用VS2012)

可執行文件:OneKeyMgr.7z

mark:

①此段代碼借鑒了Jeffrey Richter的InjectLib。

②此處借鑒codeproject上的一篇文章

 

轉載請標明出處,原文地址:http://www.cnblogs.com/hwangbae/archive/2013/01/10/2855210.html

如果覺得本文對您有幫助,請支持一下,您的支持是我寫作最大的動力,謝謝。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM