security/리버싱핵심원리

SetWindowsHookEx()를 이용한 DLL Injection

민사민서 2023. 3. 19. 17:28

사실 Windows 메시지 후킹 방법이랑 동일하다.

1) 마우스 이벤트에 훅을 걸고

2) calc.exe에서 우클릭 발생 시 "https://web2.0calc.com/" 가 크롬으로 열리는 식으로 구현

// ClickHook.dll

#include "stdio.h"
#include "windows.h"

#define DEF_PROC_NAME "calc.exe"
#define CHROME_PATH "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
#define CALC_SITE "https://web2.0calc.com/"

HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;

BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved) {
	switch(fdwReason) {
	case DLL_PROCESS_ATTACH:
		g_hInstance = hinstDll;
	}
	// dll load success
	return TRUE;
}

LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
	if(nCode>=0) { // mouse message occurs
		if(wParam == WM_RBUTTONDOWN) { // mouse right click
			char szPath[MAX_PATH] = {0,};
			char* p = NULL;

			GetModuleFileNameA(NULL, szPath, MAX_PATH); // get current process' path
			p = strrchr(szPath, '\\'); // szPath에서 '\'의 마지막 표시에 대한 포인터 반환
			if( p!=NULL && !_stricmp(p+1,DEF_PROC_NAME)) {
				char szCmd[MAX_PATH] = {0,};
				sprintf(szCmd, "%s %s", CHROME_PATH, CALC_SITE);
				STARTUPINFOA si = {0,};
				PROCESS_INFORMATION pi = {0,};
				si.cb = sizeof(STARTUPINFO);

				// CreateProcess는 두번째인자 LPTSTR이므로
				// CreateProcessA 사용, 두번째 인자 LPSTR, 9번째 인자 STARTUPINFOA
				if(!CreateProcessA(NULL, szCmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
					MessageBoxA(NULL, "Error in Opening Chrome", "ERROR", MB_ICONERROR | MB_OK);
				} else {
					CloseHandle(pi.hThread);
					CloseHandle(pi.hProcess);
				}
			}
		}
	}
	return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

extern "C" __declspec(dllexport) void HookStart() {
	g_hHook = SetWindowsHookEx(WH_MOUSE, MouseProc, g_hInstance, 0);
}
extern "C" __declspec(dllexport) void HookStop() {
	if(g_hHook) {
		UnhookWindowsHookEx(g_hHook);
		g_hHook = NULL;
	}
}

szCmd로 "%s --incognito %s" 하면 시크릿모드로 열림 

// MouseHook.exe

#include "windows.h"
#include "stdio.h"
#include "conio.h"

#define DFN_DLL_NAME "ClickHook.dll"
#define DFN_HOOK_START "HookStart"
#define DFN_HOOK_STOP "HookStop"

typedef void(*PFN_HOOK_START)();
typedef void(*PFN_HOOK_STOP)();

void main() {
	HMODULE hDll = NULL;
	PFN_HOOK_START HookStart = NULL;
	PFN_HOOK_STOP HookStop= NULL;

	// LoadLibraryA는 인자로 LPCSTR, LoadLibraryW는 인자로 LPCWSTR
	hDll = LoadLibraryA(DFN_DLL_NAME);

	// GetProcAddress 두번째 인자는 LPCSTR(char *)이다, LPCTSTR(w_char *) 아님
	// 함수명은 항상 영어,숫자,일부 특수문자로 구성되므로
	HookStart = (PFN_HOOK_START)GetProcAddress(hDll, DFN_HOOK_START);
	HookStop = (PFN_HOOK_STOP)GetProcAddress(hDll, DFN_HOOK_STOP);

	HookStart();

	printf("press 'q' to quit!\n");
	while(_getch() != 'q');

	HookStop();

	FreeLibrary(hDll);
}

실행 결과 마우스 이벤트가 발생하는 모든 프로세스들에 ClickHook.dll이 inject 됨을 확인할 수 있었고,

그 중 calc.exe에서 우클릭이 발생할 때마다 새로운 탭이 열림을 확인했다.

 

1. WH_MOUSE vs. WH_MOUSE_LL 후크

=> WH_MOUSE_LL로 바꾸면 MouseHook.exe에만 dll inject되고 나머지 프로세스들에는 inject 안 됨


WH_MOUSE

- used when you want to monitor mouse events within the context of a specific process

- 후킹을 설치한 process의 context 내에서 마우스 이벤트를 모니터링합니다.
- 후킹 프로시저(MouseProc)가 DLL에 있어야 하며 프로세스에 주입되어야 합니다.
- 목표 프로세스("calc.exe"인 경우)에 주입되어야 올바르게 작동합니다.

 

WH_MOUSE_LL (LOW LEVEL)

- used when you want to monitor mouse events globally (across all processes)

- 모든 프로세스에서 전역적으로 마우스 이벤트를 모니터링합니다.
- 후킹 프로시저(MouseProc)가 DLL에 있거나 프로세스에 주입될 필요가 없습니다.
- 어떤 프로세스에서도 사용할 수 있으며 모든 프로세스의 마우스 이벤트를 수신합니다.

 

=> ClickHook.dll이 MouseHook.exe에 주입되고 훅이 셋팅된 이후에는 전역적으로 모든 이벤트에서 마우스 이벤트를 수신하기 때문에 마우스 이벤트가 발생하는 프로세스에 DLL이 주입될 필요가 없다

=> 특정 프로세스에서 마우스 이벤트가 발생하더라도 GetModuleFileNameA()의 반환값은 MouseHook.exe의 경로일 것이므로 이후 코드가 실행되지 않는다. 

=> MouseProc() 콜백함수는 후크를 설치한 프로세스의 컨텍스트에서 실행된다 (다른 프로세스의 컨텍스트에선 x)

 

2. MAX_PATH vs. _MAX_PATH

_MAX_PATH는 C 런타임 헤더인 "stdlib.h"에 정의되어 있다, "stdio.h" include했을 때 사용할 수 있다

#define _MAX_PATH 260

MAX_PATH는 윈도우 헤더 파일인 "WinDef.h"에 정의되어 있다, "windows.h" include 했을 때 사용 가능하다

#define MAX_PATH 260

 

3. WH_MOUSE에 대한 콜백함수 MouseProc()

LRESULT CALLBACK MouseProc(
  _In_ int    nCode,
  _In_ WPARAM wParam,
  _In_ LPARAM lParam
);

- nCode: ncode가 0보다 작으면 hook procedure는 CallNextHook을 이용해 메시지를 넘겨야 함

0 [HC_ACTION] The wParam and lParam parameters contain information about a mouse message
3 [HC_NOREMOVE] The wParam and lParam parameters contain information about a mouse message,  and the mouse message has not been removed from the message queue

 

- wParam: 마우스 메시지를 나타냄

WM_LBUTTONDOWN[UP], WM_RBUTTONDOWN[UP], WM_MOUSEWHEEL 등

https://learn.microsoft.com/ko-kr/windows/win32/inputdev/mouse-input-notifications

 

마우스 입력 알림 - Win32 apps

마우스 입력 알림

learn.microsoft.com

- lParam: MOUSEHOOKSTRUCT 구조체 포인터

https://learn.microsoft.com/ko-kr/windows/win32/api/winuser/ns-winuser-mousehookstruct?redirectedfrom=MSDN 

 

MOUSEHOOKSTRUCT(winuser.h) - Win32 apps

WH_MOUSE 후크 프로시저 MouseProc에 전달된 마우스 이벤트에 대한 정보를 포함합니다.

learn.microsoft.com

 

4. _stricmp vs. strcmp

_stricmp와 stricmp는 이름의 차이만 있을 뿐 기능은 동일한 함수이다 (대소문자를 구분하지 않는 문자열 비교를 수행)

* _stricmp는 Microsoft Visual C++ 런타임 라이브러리에 속한 함수

* stricmp는 표준화된 이름이 아님

 

=> 표준화된 이름이 아닌 함수(예: stricmp)를 사용하면 이식성 문제가 발생할 수 있으므로 _stricmp와 같은 표준화된 이름을 사용하는 것이 좋습니다.

=> strcmp(대소문자 구분하여 비교), strcasecmp(_stricmp과 동인한 기능), _wcsicmp(대소문자 구분하지 않는 유니코드 문자열 비교)

=> strrchr 버전이 없는 이유는 strrchr이 이미 표준 C 라이브러리에서 제공하는 함수이므로

5. char *strstr(const char *, const char *); 사용

- 표준 C 라이브러리에서 제공하는 함수

- str1에서 str2 첫 번째 표시 시작 위치에 대한 포인터를 반환, str2가 나타나지 않으면 NULL 리턴

- DLL inject된 프로세스의 이름 가져올 때 유용하겠다

LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
	if(nCode>=0) { // mouse message occurs
		if(wParam == WM_RBUTTONDOWN) { // mouse right click
			char szPath[MAX_PATH] = {0,};
			GetModuleFileNameA(NULL, szPath, MAX_PATH); // get current process' path
			if(strstr(szPath, DEF_PROC_NAME)!=NULL) {
   			// if current process is calc.exe

 

6. MessageBox()

사용자에게 아래 함수인 MessageBox 함수를 사용하여 메시지를 보여줄 수 있습니다.

int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);

hWnd: MessageBox를 소유할 윈도우 핸들, NULL이면 owner handle 없음

lpText : 사용자에게 보여줄 메시지 문자열

lpCaption : MessageBox 창의 타이틀 문자열

uType : MessageBox의 type을 설정 (아이콘/버튼 종류)

https://rudalskim.tistory.com/307

 

[Win32] MessageBox 사용하기

사용자에게 아래 함수인 MessageBox 함수를 사용하여 메시지를 보여줄 수 있습니다. int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType); lpText : 사용자에게 보여줄 메시지 문자열 lpCaption : Message

rudalskim.tistory.com

 

7. Unicode를 사용하도록 ClickHook.dll 구현해보면?

Windows API 호환성, 문자 인코딩 일관성 등의 이유로 유니코드 문자열이 멀티바이트 문자열보다 안전

// ClickHook.dll
LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
	if(nCode>=0) { // mouse message occurs
		if(wParam == WM_RBUTTONDOWN) { // mouse right click
			wchar_t szPath[MAX_PATH] = {0,};
			GetModuleFileName(NULL, szPath, MAX_PATH); // get current process' path
			if(wcsstr(szPath, DEF_PROC_NAME)!=NULL) {
				wchar_t szCmd[MAX_PATH] = {0,};
				wsprintf(szCmd, L"%s %s", CHROME_PATH, CALC_SITE);
				STARTUPINFO si = {0,};
				PROCESS_INFORMATION pi = {0,};
				si.cb = sizeof(STARTUPINFO);

				if(!CreateProcess(NULL, szCmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
					MessageBox(NULL, L"Error in Opening Chrome", L"ERROR", MB_ICONERROR | MB_OK);
				} else {
					CloseHandle(pi.hThread);
					CloseHandle(pi.hProcess);
				}
			}
		}
	}
	return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}