進程動態攔截注入API HOOK


最近工作中遇到一個問題,需要通過程序界面進行判斷程序的運行狀態,剛開始認為很簡單,不就是一個窗體控件獲取,獲取Button的狀態和Text。剛好去年干過該事情,就沒太在意,就把優先級排到后面了,隨着項目交付時間的臨近,就准備開始解決問題,一下懵逼了,這次軟件作者也聰明了,居然換了花樣。

從圖中可以發現,此處的按鈕上的文字等信息不是通過Button信息進行操作。靜想猜測是通過Windows API直接將信息進行寫入,那么哪個API可以進行寫入呢,想想不難發現,其實就是窗口程序中在窗口中寫入文字的方法DrawText,同時隱約記得SetWindowsText也具備該功能。既然有猜想了那就實踐看看,驗證下猜想。

開干之前,還需要理理思路:

  1. 既然程序會調用DrawText方法,那我要獲取文本,就必須截獲到該方法
  2. 截獲方法不就是使用Windows上大名鼎鼎的鈎子(Hook)函數。
  3. 說到鈎子函數,這又分鈎窗口消息鈎API函數。對於本文來說當然就是后者了。實現API HOOK主要有兩個重要環節:
    • 如何把代碼注入到目標地址空間
    • 如何讓自己的代碼被調用
  4. 稍稍查詢下資料,發現鈎窗口函數貌似就復雜了,如果要研究細節請參考該文
  5. 進一步資料查詢,我發現以牛逼函數庫(居然還是微軟自己開發的):Detours(當然我會提供下載鏈接)Detours它用於實現攔截Win32二進制代碼中的API函數。它使用一個JMP指令替換了目標函數的前面幾個字節,使得控制直接調用實現的Detours函數。並通過一個trampoline函數保留了原來函數的功能調用。
  6. 到目前為止就是對該庫的使用了。從文檔上來看應該沒什么問題了。但是文檔上是通過創建進程進行注入DetourCreateProcessWithDll,對目前我的應用場景就不太匹配了。客戶程序一直處於運行狀態,我需要獲取動態注入,那么問題來了,如何解決了?
BOOL WINAPI DetourCreateProcessWithDll(LPCSTR lpApplicationName,
                                        LPSTR lpCommandLine,
                                        ...);
  1. 能提出問題,基本上問題就解決了一般。果不然,通過百度和Google的不懈努力,終於發現原來Detours1.5版本中有個方法DetourContinueProcessWithDll該方法就是進行動態注入的,這下就可以開干了(至於Detours的原理本文就不再贅述,請大家自行查詢資料,本文以解決實際問題問題主)
BOOL WINAPI DetourContinueProcessWithDll(HANDLE hProcess, LPCSTR lpDllName);

經過上面幾個步驟下來,已經有了完整的思路,下文主要結合實踐,進行代碼實踐。本文主要從如下幾個方面進行時間:

  • 首先,居然是要截取函數DrawTextSetWindowText那么首先的先實現自己的函數(通過DLL封裝)
  • 然后,就是動態注入我們的DLL文件指定進程
  • 最后,拿出來溜溜(本文為了簡便,僅將相關信息打印到DebugView中)

實現HookWindowTextDll

首先,按照Detours的編程規范,需要在加載HookWindowTextDll時通過方法DetourFunctionWithTrampoline進行注冊。在卸載的時候通過DetourRemove卸載。

該處主要分以下幾步:

  1. 需要Hook的方法聲明和實現
  2. 安裝和卸載注入方法

Hook方法聲明和實現

首先需要聲明我們的方法

BOOL WINAPI MySetWindowTextA( HWND hWnd, LPCTSTR lpString ); 
BOOL WINAPI MySetWindowTextW( HWND hWnd, LPCWSTR lpString ); 

int WINAPI MyDrawTextA( HDC hDC, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat );
int WINAPI MyDrawTextW( HDC hDC, LPCWSTR lpString, int nCount, LPRECT lpRect, UINT uFormat );

//該方法主要是用Real_SetWindowTextA保存原來函數SetWindowTextA地址,方便后面調用
DETOUR_TRAMPOLINE( BOOL WINAPI Real_SetWindowTextA( HWND a0, LPCTSTR a1 ), SetWindowTextA );
DETOUR_TRAMPOLINE( BOOL WINAPI Real_SetWindowTextW( HWND a0, LPCWSTR a1 ), SetWindowTextW );

DETOUR_TRAMPOLINE( int WINAPI Real_DrawTextA( HDC a0, LPCTSTR a1, int a2, LPRECT a3, UINT a4 ), DrawTextA );
DETOUR_TRAMPOLINE( int WINAPI Real_DrawTextW( HDC a0, LPCWSTR a1, int a2, LPRECT a3, UINT a4 ), DrawTextW );

//代碼實現,限於篇幅,僅列出MySetWindowTextA
BOOL WINAPI MySetWindowTextA( HWND hWnd, LPCTSTR lpString )
{
#ifdef _DEBUG
	char strMsg[ 1024 ]={0};
	wsprintf( strMsg, "SetWindowTextA : %s. size = %ld\n", lpString, strlen(lpString) );
	OutputDebugString( strMsg );
#endif

	return Real_SetWindowTextA( hWnd, lpString );
}

Hook方法的安裝和卸載

BOOL APIENTRY DllMain( HANDLE hModule, 
	DWORD  ul_reason_for_call, 
	LPVOID lpReserved )
{
	if( DLL_PROCESS_ATTACH == ul_reason_for_call )//dll加載
	{
		InstallProbes();
	}
	else if( DLL_PROCESS_DETACH == ul_reason_for_call )//dll卸載
	{
		UninstallProbes();
	}
	else;

	return TRUE;
}

攔截注入通過方法DetourFunctionWithTrampoline進行,該方法原型如下:

BOOL  WINAPI DetourFunctionWithTrampoline(PBYTE pbTrampoline,
                                          PBYTE pbDetour);

這個函數有兩個參數,pbTrampoline和一個指向pbDetour函數的指針。目標函數Target之所以沒有作
為一個參數,是因為它已經編碼到pbTrampoline函數之中(上文中進行編碼DETOUR_TRAMPOLINE)。

BOOL InstallProbes()
{
	DetourFunctionWithTrampoline( (PBYTE)Real_SetWindowTextA, (PBYTE)MySetWindowTextA );
	DetourFunctionWithTrampoline( (PBYTE)Real_SetWindowTextW, (PBYTE)MySetWindowTextW );

	DetourFunctionWithTrampoline( (PBYTE)Real_DrawTextA, (PBYTE)MyDrawTextA );
	DetourFunctionWithTrampoline( (PBYTE)Real_DrawTextW, (PBYTE)MyDrawTextW );

	OutputDebugString("InstallProbesA ok.\n");
	return TRUE;
}

BOOL UninstallProbes()
{
	DetourRemove( (PBYTE)Real_SetWindowTextA, (PBYTE)MySetWindowTextA );
	DetourRemove( (PBYTE)Real_SetWindowTextW, (PBYTE)MySetWindowTextW );

	OutputDebugString("UNInstallProbesB ok.\n");
	return TRUE;
}

至此攔截注入的方法就完成。下一小節就是如何進行動態注入了。

動態注入HookWindowTextDll

既然要動態注入,那么就先要看看動態注入方法DetourContinueProcessWithDll方法的使用方法

//把一個動態鏈接庫注入到一個新的進程中
BOOL WINAPI DetourContinueProcessWithDllA(HANDLE hProcess, LPCSTR lpDllName)

該方法有兩個參數,一看看就明白了

  • hProcess:需要注入的原進程句柄
  • lpDllName:需要注入的Dll路徑,本文即HookWindowTextDll.dll

那么此時應該先獲取進程句柄,獲取進程句柄通過如下方法即可:

通過進程名稱獲取進程ID

DWORD	GetProcessIdFromProcessName(std::string processname)
{
	DWORD dwRet = 0;
	PROCESSENTRY32 pe32;
	pe32.dwSize = sizeof(pe32);
	HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (hProcessSnap != INVALID_HANDLE_VALUE)
	{
		BOOL bMore = ::Process32First(hProcessSnap, &pe32);
		while (bMore)
		{
			if (boost::iequals(pe32.szExeFile, processname))
			{
				dwRet = pe32.th32ProcessID;
              	 break;
			}
			bMore = ::Process32Next(hProcessSnap, &pe32);
		}
		::CloseHandle(hProcessSnap);
	}
	return dwRet;
}

調用測試:

std::string str1 = "WireCut.EXE";
DWORD dwProcessId = GetProcessIdFromProcessName(str1);
std::cout << dwProcessId << std::endl; //

獲取到了進程,那就進入下一節,獲取句柄。

通過進程ID獲取進程句柄

OpenProcess 函數用來打開一個已存在的進程對象,並返回進程的句柄

HANDLE OpenProcess(
		DWORD dwDesiredAccess, 
		BOOL bInheritHandle, 
		DWORD dwProcessId
);

參數:

  • dwDesiredAccess:想擁有的該進程訪問權限
    • PROCESS_ALL_ACCESS:所有能獲得的權限
    • PROCESS_CREATE_PROCESS:需要創建一個進程
    • PROCESS_CREATE_THREAD:需要創建一個線程
    • PROCESS_DUP_HANDLE:重復使用DuplicateHandle句柄
    • PROCESS_QUERY_INFORMATION:獲得進程信息的權限,如它的退出代碼、優先級
    • PROCESS_SET_INFORMATION :設置某些信息的權限,如進程優先級
    • PROCESS_SET_QUOTA :設置內存限制的權限,使用SetProcessWorkingSetSize
    • PROCESS_SUSPEND_RESUME :暫停或恢復進程的權限
    • PROCES_TERMINATE :終止一個進程的權限,使用TerminateProcess
    • PROCESS_VM_OPERATION :操作進程內存空間的權限(可用VirtualProtectEx和WriteProcessMemory)
    • PROCESS_VM_READ :讀取進程內存空間的權限,可使用ReadProcessMemory
    • PROCESS_VM_WRITE :讀取進程內存空間的權限,可使用WriteProcessMemory
    • SYNCHRONIZE :等待進程終止
  • bInheritHandle:表示所得到的進程句柄是否可以被繼承
  • dwProcessId:被打開進程的PID

返回類型:

  • 如成功,返回值為指定進程的句柄。
  • 如失敗,返回值為NULL,可調用GetLastError()獲得錯誤代碼。
HANDLE GetProcessHandle(DWORD nID)
{
	//PROCESS_ALL_ACCESS 所有能獲得的權限
	return OpenProcess(PROCESS_ALL_ACCESS, FALSE, nID);
}
DWORD dwProcessId = GetProcessIdFromProcessName("WireCut.EXE");
if (dwProcessId != 0)
{
	bRet = DetourContinueProcessWithDllW(GetProcessHandle(dwProcessId), szDllFilePath);	
}

到此就完成了所有工作,后面提供項目代碼和庫文件

鏈接:https://pan.baidu.com/s/1c09LWg9zo5NIVwR2htJYZA
提取碼:f0kt

歡迎關注交流共同進步
奔跑阿甘
博客地址:wqliceman.top


免責聲明!

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



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