前段時間入手了聯想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
如果覺得本文對您有幫助,請支持一下,您的支持是我寫作最大的動力,謝謝。