본문 바로가기
Programming/Win_API

API Hooking - [1] Debugger Attach + 0xCC

by bbolmin 2013. 11. 23.



API Hooking 실습 첫 번째로 Debugger Attach해서 0xCC로 제어권을 받아서 후킹하는 방법을 해보자.


후킹할 프로그램은 아래와 같이 자신의 pid를 출력하고 아무키나 누르면 MessageBox를 띄워주는 프로그램이다.





MsgBox.exe







- MessageBox APi 원형


int WINAPI MessageBox(

  __in_opt  HWND hWnd,
  __in_opt  LPCTSTR lpText,
  __in_opt  LPCTSTR lpCaption,
  __in      UINT uType
);

MessageBox의 파라미터는 위와 같은데 후킹을 통해 lpText에 있는 "Hello World ~~" 부분을 "Hello Hooking !"으로 바꿔보도록 하겠다.





[ 실습 소스 ]


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

LPVOID g_pfMessageBox = NULL;
LPVOID g_lpChangeStr = NULL;
CREATE_PROCESS_DEBUG_INFO g_cpdi;
BYTE g_INT3Byte = 0xCC, g_OriginByte = 0;

BOOL InstallHook(LPDEBUG_EVENT pde)
{
	g_pfMessageBox = GetProcAddress(LoadLibraryA("User32.dll"), "MessageBoxA");
	memcpy(&g_cpdi, &pde->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO));
	ReadProcessMemory(g_cpdi.hProcess, g_pfMessageBox, &g_OriginByte, sizeof(BYTE), NULL);	//원래 byte값 백업
	WriteProcessMemory(g_cpdi.hProcess, g_pfMessageBox, &g_INT3Byte, sizeof(BYTE), NULL);	//0xcc로 write함으로써 Hook설치

	printf("\n[ Install Hook ]  0x%p (MessageBoxA) : 0x%2x -> 0xCC Patched\n", g_pfMessageBox, g_OriginByte);

	g_lpChangeStr = VirtualAllocEx(g_cpdi.hProcess, NULL, strlen("Hello Hooking ~!"+1, MEM_COMMIT, PAGE_READWRITE); 	// 후킹대상 프로세스에 메모리 할당
	WriteProcessMemory(g_cpdi.hProcess, (LPVOID)g_lpChangeStr, "Hello Hooking ~!", strlen("Hello Hooking ~!"), NULL);	// 변조할 문자열 복사

	return TRUE;
}

BOOL HookProc(LPDEBUG_EVENT pde)
{
	CONTEXT ctx;
	PBYTE lpBuffer = NULL;
	PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;
	


	if( EXCEPTION_BREAKPOINT == per->ExceptionCode ) //ExceptionCode가 INT3인 경우
	{
		if( g_pfMessageBox == per->ExceptionAddress ) //Exception 발생 주소가 후킹된 API함수의 주소인 경우
		{
			//Unhook
			WriteProcessMemory(g_cpdi.hProcess, g_pfMessageBox, &g_OriginByte, sizeof(BYTE), NULL);

			ctx.ContextFlags = CONTEXT_CONTROL;
			GetThreadContext(g_cpdi.hThread, &ctx);
			WriteProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8), &g_lpChangeStr, sizeof(LPVOID), NULL);		//lpText의 포인터를 바꿔줌

			//현재 eip는 0xcc+1에 있음.
			ctx.Eip = (DWORD)g_pfMessageBox; //eip를 0xcc부분으로 되돌려줌
			SetThreadContext(g_cpdi.hThread, &ctx);

			ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId	, DBG_CONTINUE);
			
			Sleep(0);	//훅이 해제된 상태의 MessageBox가 실행되도록 아래의 WriteProcessMemory를 연기시킴
			WriteProcessMemory(g_cpdi.hProcess, g_pfMessageBox, &g_INT3Byte, sizeof(BYTE), NULL); //다시 Hook설치함

			return TRUE;
		}
	}

	return FALSE;
}


void DebugLoop()
{
	DEBUG_EVENT de;
	DWORD dwContinueStatus;

	while(WaitForDebugEvent(&de, INFINITE))
	{
		dwContinueStatus = DBG_CONTINUE;

		if( CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode)
		{
			InstallHook(&de);						 //후킹 시작
		} 
		else if( EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode )	 //EXCEPTION_BREAKPOINT
		{
			if(HookProc(&de))					 //후킹을 수행하는 함수
				continue;
		}
		else if( EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
		{
			printf("[ Terminate Hook ]\n");
			break;												//디버거 종료
		}

		ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
		//DBG_CONTINUE or DBG_EXCEPTION_NOT_HANDLED
	}
}

int main(int argc, char *argv[])
{
	DWORD dwPID;
	
	if(argc != 2)
	{
		printf("\nUSAGE : HookPractice.exe [pid] \n");
		return 1;
	}

	dwPID = atoi(argv[1]);
	if(!DebugActiveProcess(dwPID))
	{
		printf("DebugActiveProcess(%d) failed!!! \n"
				"Error code = %d \n", dwPID, GetLastError());
		return 1;
	}

	DebugLoop();
}





위의 동작은 후킹할 프로세스의 MessageBoxA API함수의 시작 byte를 0xCC로 바꿔서 디버거에 제어권을 가져온 후

디버거에서 원하는 동작을 수행한 후 다시 원래 실행코드로 돌아가는 방식으로 동작한다.


위에서 만든 후킹 프로그램으로 테스트한 결과 "Hello World"에서 "Hello Hooking"으로 메시지 박스의 lpText 파라미터를 변조된 것을 볼 수 있다.