改造联想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
如果觉得本文对您有帮助,请支持一下,您的支持是我写作最大的动力,谢谢。