후킹

후킹()은 소프트웨어 공학 용어로, 운영 체제나 응용 소프트웨어 등의 각종 컴퓨터 프로그램에서 소프트웨어 구성 요소 간에 발생하는 함수 호출, 메시지, 이벤트 등을 중간에서 바꾸거나 가로채는 명령, 방법, 기술이나 행위를 말한다. 이때 이러한 간섭된 함수 호출, 이벤트 또는 메시지를 처리하는 코드를 훅()이라고 한다.

크래킹(불법적인 해킹)을 할 때 크래킹 대상 컴퓨터의 메모리 정보, 키보드 입력 정보 등을 빼돌리기 위해서 사용되기도 한다.

예를 들어 특정한 API를 후킹하게 되면 해당 API의 리턴값을 조작하는 등의 동작을 수행할 수 있다.

방법 
일반적으로 훅은 소프트웨어가 이미 실행중일 때 삽입되지만, 후킹은 응용 프로그램이 실행되기도 전에 쓰일 수 있는 전략이다. 이 두 기술은 아래에 더 자세히 설명되어 있다.

물리적 수정 
응용 프로그램이 실행되기 전에 리버스 엔지니어링 기법으로 실행 또는 라이브러리를 물리적 수정함으로써 후킹을 할 수 있다. 이러한 기법은 일반적으로 다른 모니터로 함수 호출을  가로채거나 그것들을 완전히 다른 함수로 대체하는데 사용된다.

예를 들어, 역어셈블러를 사용하여 모듈 내에서 함수의 시작점을 찾을 수 있다. 그 다음, 다른 라이브러리 모듈을 동적으로 로드하고, 로드된 라이브러리 내에서 원하는 함수를 실행하도록 변경할 수 있다. 해당되는 경우, 후킹으로 얻을 수 있는 관련된 다른 접근 방법은 실행 파일의 임포트 테이블을 변경하는 것이다. 이 테이블은 추가 라이브러리 모듈을 로드하도록 수정될 수 있을뿐만이 아니라 함수가 응용 프로그램에 의해 호출될 때 외부 코드가 호출되는 것으로 바뀔 수도 있다.

후킹을 하기 위한 대체 방법은 래퍼 라이브러리를 통해 함수 호출을 차단하는 것이다. 래퍼를 만들 때, 응용 프로그램을 로드하는 라이브러리는 원본 라이브러리와 동일한 기능을 가진 자신만의 라이브러리를 만든다. 즉, 접근할 수 있는 모든 기능은 원본과 대체된 라이브러리 모두 동일한 것이다. 이 래퍼 라이브러리는 원래 라이브러리의 기능 중 하나를 호출하거나, 논리의 완전히 새로운 설정으로 대체되도록 설계할 수 있다.

런타임 수정 
운영체제와 소프트웨어는 훅을 삽입하는 프로세스에 충분한 권한이 부여된 경우 런타임도중 이벤트 훅을 쉽게 삽입할 수 있다. 예를 들어, Microsoft Windows에서는 대화 상자, 스크롤바 및 메뉴뿐만 아니라 다른 항목에 대한 시스템 이벤트와 응용 프로그램 이벤트를 처리하거나 수정하는 데 사용될 수 있는 훅을 삽입할 수 있다. 또한 훅은 키보드 및 마우스 이벤트를 삽입, 제거, 프로세스 또는 수정할 수 있다. 리눅스에서는 훅이 넷필터를 통해 커널 내의 네트워크 이벤트를 처리하기 위해 비슷한 방식으로 사용될 수 있는 또 다른 예를 제공한다.

이러한 기능이 제공되지 않은 경우, 후킹의 특별한 형태는 프로세스에 의해 만들어진 라이브러리 함수 호출을 차단하는 방법을 사용한다. 후킹은 대상 함수의 첫 번째 몇몇 코드가 주입 코드로 이동하도록 지시를 변경하여 구현된다. 또는 공유 라이브러리의 개념을 사용하는 시스템에서는 인터럽트 벡터 테이블 또는 임포트 디스크립터 테이블이 메모리에서 수정될 수 있다. 기본적으로 이러한 전술은 물리적 수정과 같은 아이디어를 사용한다. 하지만 이미 실행되어 있는 상태라면 프로세스의 메모리에 있는 명령과 구조를 변경한다.

예제 코드

C# 키보드 이벤트 후킹 

다음 예제는 닷넷 프레임워크를 사용하여 마이크로소프트 윈도우에서 키보드 이벤트를 후킹하는 것이다.

using System.Runtime.InteropServices;

namespace Hooks
{
	public class KeyHook
	{
		/* Member variables */
		protected static int Hook;
		protected static LowLevelKeyboardDelegate Delegate;
		protected static readonly object Lock = new object();
		protected static bool IsRegistered = false;

		/* DLL imports */
		[DllImport("user32")]
		private static extern int SetWindowsHookEx(int idHook, LowLevelKeyboardDelegate lpfn,
			int hmod, int dwThreadId);

		[DllImport("user32")]
		private static extern int CallNextHookEx(int hHook, int nCode, int wParam, KBDLLHOOKSTRUCT lParam);

		[DllImport("user32")]
		private static extern int UnhookWindowsHookEx(int hHook);

		/* Types & constants */
		protected delegate int LowLevelKeyboardDelegate(int nCode, int wParam, ref KBDLLHOOKSTRUCT lParam);
		private const int HC_ACTION = 0;
		private const int WM_KEYDOWN = 0x0100;
		private const int WM_KEYUP = 0x0101;
		private const int WH_KEYBOARD_LL = 13;

		[StructLayout(LayoutKind.Sequential)]
		public struct KBDLLHOOKSTRUCT
		{
			public int vkCode;
			public int scanCode;
			public int flags;
			public int time;
			public int dwExtraInfo;
		}

		/* Methods */
		static private int LowLevelKeyboardHandler(int nCode, int wParam, ref KBDLLHOOKSTRUCT lParam)
		{
			if (nCode == HC_ACTION)
			{
				if (wParam == WM_KEYDOWN)
					System.Console.Out.WriteLine("Key Down: " + lParam.vkCode);
				else if (wParam == WM_KEYUP)
					System.Console.Out.WriteLine("Key Up: " + lParam.vkCode);
			}
			return CallNextHookEx(Hook, nCode, wParam, lParam);
		}

		public static bool RegisterHook()
		{
			lock (Lock)
			{
				if (IsRegistered)
					return true;
				Delegate = LowLevelKeyboardHandler;
				Hook = SetWindowsHookEx(
					WH_KEYBOARD_LL, Delegate,
					Marshal.GetHINSTANCE(
						System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0]
					).ToInt32(), 0
				);

				if (Hook != 0)
					return IsRegistered = true;
				Delegate = null;
				return false;
			}
		}

		public static bool UnregisterHook()
		{
			lock (Lock)
			{
				return IsRegistered = (UnhookWindowsHookEx(Hook) != 0);
			}
		}
	}
}

DirectDraw 후킹 
다음은 윈도에서 DirectDraw(다이렉트드로) 함수 호출을 후킹한 예이다. 이것은 APIHijack(에이피아이 하이잭)이라는 후킹 라이브러리를 활용한 것이고, 소스는 DLL(동적 링크 라이브러리)로 컴파일되었다. 인스톨훅(InstallHook)을 호출할 추가적인 응용 프로그램도 필요하다.

#include <windows.h>
#include <shlwapi.h>
#include <ddraw.h>
#include "testdll.h"
#include "..\apihijack.h"

char temp[256];
HINSTANCE hDLL;

// type defs
typedef HRESULT (WINAPI *DirectDrawCreateEx_Type)( GUID FAR *lpGUID, LPVOID *lplpDD, REFIID iid, IUnknown FAR *pUnkOuter );

// function prototypes
HRESULT WINAPI MyDirectDrawCreateEx( GUID FAR * lpGuid, LPVOID  *lplpDD, REFIID  iid,IUnknown FAR *pUnkOuter );

// hook structure
enum
{
    D3DFN_DirectDrawCreateEx = 0
};

SDLLHook D3DHook = 
{
    "DDRAW.DLL",
    false, NULL,
    {
        { "DirectDrawCreateEx", MyDirectDrawCreateEx },
        { NULL, NULL }
    }
};

// Hook function.
HRESULT WINAPI MyDirectDrawCreateEx(GUID FAR* lpGuid, LPVOID  *lplpDD, REFIID  iid,IUnknown FAR *pUnkOuter )
{
    DirectDrawCreateEx_Type OldFn = (DirectDrawCreateEx_Type)D3DHook.Functions[D3DFN_DirectDrawCreateEx].OrigFn;
    return OldFn( lpGuid, lplpDD, iid, pUnkOuter );
}

// CBT Hook-style injection.
BOOL APIENTRY DllMain( HINSTANCE hModule, DWORD fdwReason, LPVOID lpReserved )
{
    if ( fdwReason == DLL_PROCESS_ATTACH )  // When initializing....
    {
        hDLL = hModule;

        // Only hook the APIs if this is the fsim proess.
        GetModuleFileName(GetModuleHandle(NULL), temp, sizeof(temp));
        PathStripPath(temp);

        if(stricmp(temp, "fsim.exe" ) == 0)
            HookAPICalls( &D3DHook );
    }

    return TRUE;
}

// This segment must be defined as SHARED in the .DEF
#pragma data_seg (".HookSection")
// Shared instance for all processes.
HHOOK hHook = NULL;
#pragma data_seg ()

TESTDLL_API LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    return CallNextHookEx( hHook, nCode, wParam, lParam);
}

TESTDLL_API void InstallHook()
{
    hHook = SetWindowsHookEx(WH_CBT, HookProc, hDLL, 0);
}

TESTDLL_API void RemoveHook()
{
    UnhookWindowsHookEx( hHook );
}

JMP 명령어를 이용한 API/함수 후킹(가로채기) 
다음 소스 코드는 오픈 소스인 Chrom(크롬) 라이브러리를 사용하는 API/함수 후킹의 예시이다. 이는 새로운 함수에 대한 JMP 명령으로 대상 함수의 처음 6바이트를 덮어써서 훅한다. 코드는 DLL로 컴파일 된 후, 코드 인젝션의 방법을 사용하여 대상 프로세스에 로드된다. 이 예제 코드는 "nspr4.dll"의 "PR_Write" 함수와 "c:/log.txt"에 기록된 모든 HTTP/HTTPS 요청을 훅한다.

/*
Firefox_hook (C) Raja Jamwal 2011
Distributed under GNU GPL License

Firefox_hook is a example code for Chrom Library, Firefox_hook log every
HTTP/HTTPS requests that firefox makes

Chrom, is API/Funtion interception/hijacking library for windows systems
Copyright (C) 2011  Raja Jamwal

This file is part of Chrom.
*/

#include "stdafx.h"
#include "chrom.h"

Hook Firefox; // Hook firefox
DWORD PR_Write_H (DWORD *fd,  void *buf,DWORD amount); // this is our overiding-function
typedef DWORD (*prWrite)(DWORD*,void*,DWORD); // defination of our original function

prWrite prw = NULL; // create a orginal function, we later point this to orginal function
		    // address

char log_file[]="c://log.txt"; // logfile

// this will log our data
int write_log(char * log, char * data)
{
ofstream fout(log, ios::app);
fout << data;
fout.close();
return TRUE;
}

// initialize hooking, this adds the jump instruction to orginal function address
int create_hooks()
{
	// Override PR_Write function in nspr4.dll with our PR_Write_H,
	// Note nspr4.dll must already be
	// loaded in process space
	Firefox.Initialize("PR_Write", "nspr4.dll", PR_Write_H);
	// Write jump instruction on orginal function address
	Firefox.Start();
	return TRUE;
}

// our overriding function
DWORD PR_Write_H (DWORD *fd,  void *buf,DWORD amount)
{
	// reset hooks, this will replace the jump instruction to original data
	Firefox.Reset();
        // You may skip the code shown below and call the original function directly
        // since after calling Firefox.Reset() the address of the original function,
        // now contains the original function's data
        // point prw(function) to original function (optional)
	prw = (prWrite)Firefox.original_function;
        // log the headers
	write_log(log_file, (char*) buf);
	// call the real PR_Write function
	DWORD ret = prw(fd, buf, amount);
	// again place the jump instruction on the original function
	Firefox.Place_Hook();
	return ret;
}

Netfilter 후킹 
이 예제에서는 Netfilter(넷필터)를 사용하여 리눅스 커널에서 네트워크 트래픽을 변경할 수 있는 훅를 사용하는 방법을 보여준다.
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/skbuff.h>

#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/in.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>

/* Port we want to drop packets on */
static const uint16_t port = 25;

/* This is the hook function itself */
static unsigned int hook_func(unsigned int hooknum,
                       struct sk_buff **pskb,
                       const struct net_device *in,
                       const struct net_device *out,
                       int (*okfn)(struct sk_buff *))
{
        struct iphdr *iph = ip_hdr(*pskb);
        struct tcphdr *tcph, tcpbuf;

        if (iph->protocol != IPPROTO_TCP)
                return NF_ACCEPT;

        tcph = skb_header_pointer(*pskb, ip_hdrlen(*pskb), sizeof(*tcph), &tcpbuf);
        if (tcph == NULL)
                return NF_ACCEPT;

        return (tcph->dest == port) ? NF_DROP : NF_ACCEPT;
}

/* Used to register our hook function */
static struct nf_hook_ops nfho = {
        .hook     = hook_func,
        .hooknum  = NF_IP_PRE_ROUTING,
        .pf       = NFPROTO_IPV4,
        .priority = NF_IP_PRI_FIRST,
};

static __init int my_init(void)
{
        return nf_register_hook(&nfho);
}

static __exit void my_exit(void)
{
    nf_unregister_hook(&nfho);
}

module_init(my_init);
module_exit(my_exit);

각주

후킹 프로그램의 목록 
 AGTH

외부 링크 
  - hook 세 번째 항목 참조
 

제어 흐름
컴퓨터 보안
DLL 인젝션