먼저 DllMain() 함수를 살펴보겠습니다.
wininet!InternetConnectW() 의 후킹 함수인 NewInternetConnectW() 함수에 대한 설명입니다. 이 함수는 IE 의 접속 주소를 모니터링 하면서 특정 사이트에 접속을 시도할 때 원하는 사이트로 접속을 돌리는 역할을 수행합니다.
ntdll!ZwResumeThread() 의 후킹 함수인 NewZwResumeThread() 함수입니다.
위의 ntdll!ZwResumeThread() API 후킹 방법은 단순히 kernel32!CreateProcess() API 를 후킹하는 것보다 더 강력하고 편리한 방법입니다. 왜냐하면 CreateProcess() 는 내부적으로 CreateProcessInternal() 을 호출합니다. 만약 프로그램에서 CreateProcessInternal() 을 직접 호출한다면 정상적인 후킹이 되지 않습니다. (차라리 CreateProcessInternal() 을 후킹하는 것이 더 좋은 방법입니다. – 좋은 방법을 가르쳐 주신 iwillhackyou 님께 감사드립니다.)
Global API Hooking 예제 코드를 분석하면서 해당 기술에 대한 이해를 돕습니다.
본 내용을 읽기 전에 이전 포스트를 참고하세요. 같이 이어지는 내용입니다.
* 참고!
모든 소스 코드는 MS Visual C++ 2008 Express Edition 으로 개발 되었으며, Windows 7 32bit & IE 8 에서 테스트 되었습니다.
전체 소스 코드는 아래 첨부된 파일을 참고하시기 바랍니다.
주요 함수에 대해 설명 드리겠습니다. 설명의 편의를 위해서 에러 처리 관련 코드는 제거하였습니다. 에러 처리 코드가 포함된 원본 함수의 전체 코드는 첨부된 파일에서 확인하시기 바랍니다.
DllMain()
먼저 DllMain() 함수를 살펴보겠습니다.
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
char szCurProc[MAX_PATH] = {0,};
char *p = NULL;
switch( fdwReason )
{
case DLL_PROCESS_ATTACH :
GetModuleFileName(NULL, szCurProc, MAX_PATH);
p = strrchr(szCurProc, '\\');
if( (p != NULL) && !_stricmp(p+1, "iexplore.exe") )
{
// wininet!InternetConnectW() API 를 후킹 하기 전에
// 미리 wininet.dll 을 로딩 시킴
LoadLibrary("wininet.dll");
}
// hook
hook_by_code("ntdll.dll", "ZwResumeThread",
(PROC)NewZwResumeThread, g_pZWRT);
hook_by_code("wininet.dll", "InternetConnectW",
(PROC)NewInternetConnectW, g_pICW);
break;
case DLL_PROCESS_DETACH :
// unhook
unhook_by_code("ntdll.dll", "ZwResumeThread",
g_pZWRT);
unhook_by_code("wininet.dll", "InternetConnectW",
g_pICW);
break;
}
return TRUE;
}
DllMain() 함수의 핵심 기능은 ntdll!ZwResumeThread() 와 wininet!InternetConnectW() API 의 hook/unhook 입니다.
한가지 특이한 코드는 실행 프로세스 이름이 iexplorer.exe 인 경우 wininet.dll 을 로딩 시키는 코드입니다. iexplore.exe 프로세스가 정상적으로 실행되면 기본적으로 wininet.dll 을 로딩하고 있는데 왜 굳이 추가적으로 로딩을 시키는 걸까요? 그 이유는 Global API Hooking 때문입니다.
ntdll!ZwResumeThread() 를 이용한 API 후킹은 해당 프로세스의 메인 스레드가 시작되기 전에 제
어를 가로채기 때문에 우리가 후킹하려는 wininet.dll 모듈이 아직 로딩되어 있지 않는 상황이 발생할 수 있습니다. 이를 방지 하기 위해서 iexplore.exe 프로세스인 경우 wininet!InternetConnectW() API 를 후킹하기 전에 무조건 wininet.dll 을 로딩시키는 것입니다.
NewInternetConnectW()
wininet!InternetConnectW() 의 후킹 함수인 NewInternetConnectW() 함수에 대한 설명입니다. 이 함수는 IE 의 접속 주소를 모니터링 하면서 특정 사이트에 접속을 시도할 때 원하는 사이트로 접속을 돌리는 역할을 수행합니다.
HINTERNET WINAPI NewInternetConnectW
(
HINTERNET hInternet,
LPCWSTR lpszServerName,
INTERNET_PORT nServerPort,
LPCTSTR lpszUsername,
LPCTSTR lpszPassword,
DWORD dwService,
DWORD dwFlags,
DWORD_PTR dwContext
)
{
HINTERNET hInt = NULL;
FARPROC pFunc = NULL;
HMODULE hMod = NULL;
// unhook
unhook_by_code("wininet.dll", "InternetConnectW", g_pICW);
// call original API
hMod = GetModuleHandle("wininet.dll");
pFunc = GetProcAddress(hMod, "InternetConnectW");
if( !_wcsicmp(lpszServerName, L"www.naver.com") ||
!_wcsicmp(lpszServerName, L"www.daum.net") ||
!_wcsicmp(lpszServerName, L"www.nate.com") ||
!_wcsicmp(lpszServerName, L"www.yahoo.com") )
{
hInt = ((PFINTERNETCONNECTW)pFunc)(hInternet,
L"www.reversecore.com",
nServerPort,
lpszUsername,
lpszPassword,
dwService,
dwFlags,
dwContext);
}
else
{
hInt = ((PFINTERNETCONNECTW)pFunc)(hInternet,
lpszServerName,
nServerPort,
lpszUsername,
lpszPassword,
dwService,
dwFlags,
dwContext);
}
// hook
!hook_by_code("wininet.dll", "InternetConnectW",
(PROC)NewInternetConnectW, g_pICW;
return hInt;
}
위 함수의 코드는 매우 단순합니다. 함수의 2 번째 파라미터인 lpszServerName 문자열이 바로 접속 주소입니다. 이 접속주소를 모니터링 하여 우리나라 4 대 포탈 사이트(Naver, Daum, Nate, Yahoo)인 경우, 제 블로그(ReverseCore)로 연결을 바꿔버립니다.
hook_by_code() / unhook_by_code() 에 대한 설명은 아래 포스트를 참고하시기 바랍니다.
NewZwResumeThread()
ntdll!ZwResumeThread() 의 후킹 함수인 NewZwResumeThread() 함수입니다.
NTSTATUS WINAPI NewZwResumeThread(HANDLE ThreadHandle, PULONG SuspendCount)
{
NTSTATUS status, statusThread;
FARPROC pFunc = NULL, pFuncThread = NULL;
DWORD dwPID = 0;
static DWORD dwPrevPID = 0;
THREAD_BASIC_INFORMATION tbi;
HMODULE hMod = NULL;
char szModPath[MAX_PATH] = {0,};
// call ntdll!ZwQueryInformationThread()
hMod = GetModuleHandle("ntdll.dll");
pFuncThread = GetProcAddress(hMod, "ZwQueryInformationThread");
statusThread = ((PFZWQUERYINFORMATIONTHREAD)pFuncThread)
(ThreadHandle, 0, &tbi, sizeof(tbi), NULL);
// Dll Injection to the new child process
dwPID = (DWORD)tbi.ClientId.UniqueProcess;
if ( (dwPID != GetCurrentProcessId()) && (dwPID != dwPrevPID) )
{
dwPrevPID = dwPID;
// change privilege
SetPrivilege(SE_DEBUG_NAME, TRUE);
// get injection dll path
GetModuleFileName(GetModuleHandle(STR_MODULE_NAME),
szModPath,
MAX_PATH);
// call InjectDll()
InjectDll(dwPID, szModPath);
}
// unhook
unhook_by_code("ntdll.dll", "ZwResumeThread", g_pZWRT);
// call ntdll!ZwResumeThread()
pFunc = GetProcAddress(hMod, "ZwResumeThread");
status = ((PFZWRESUMETHREAD)pFunc)(ThreadHandle, SuspendCount);
// hook
hook_by_code("ntdll.dll", "ZwResumeThread",
(PROC)NewZwResumeThread, g_pZWRT);
return status;
}
NewResumeThread() 함수의 첫 번째 파라미터는 resume 시킬 스레드의 ThreadHandle 입니다. 지난번 설명에서 이 스레드는 바로 자식 프로세스의 메인 스레드라고 설명 드렸습니다.
따라서 NewResumeThread() 함수 초반부의 ZwQueryInformationThread() API 를 호출하는 이유는 바로 ThreadHandle 이 가리키는 스레드(자식 프로세스의 스레드) 가 소속된 자식 프로세스의 PID 를 얻기 위함입니다.
이렇게 ThreadHandle 파라미터를 이용하여 (지금 막 생성된) 자식 프로세스의 PID 를 얻어 내었습니다. 이 PID 를 이용하여 redirect.dll (후킹 DLL) 을 인젝션 시켜 줍니다. 해당 자식 프로세스는 메인 스레드가 실행되기도 전에 이미 redirect.dll 이 인젝션 되면서 자동으로 API 후킹이 걸리게 됩니다.
마지막으로 ntdll!ZwResumeThread() API 를 정상적으로 호출하여 자식 프로세스의 메인 스레드를 resume 시킵니다. 이제 자식 프로세스는 API 가 후킹된 채로 정상 실행됩니다.
High-Level API Hooking vs Low-Level API Hooking
위의 ntdll!ZwResumeThread() API 후킹 방법은 단순히 kernel32!CreateProcess() API 를 후킹하는 것보다 더 강력하고 편리한 방법입니다. 왜냐하면 CreateProcess() 는 내부적으로 CreateProcessInternal() 을 호출합니다. 만약 프로그램에서 CreateProcessInternal() 을 직접 호출한다면 정상적인 후킹이 되지 않습니다. (차라리 CreateProcessInternal() 을 후킹하는 것이 더 좋은 방법입니다. – 좋은 방법을 가르쳐 주신 iwillhackyou 님께 감사드립니다.)
이런 식으로 Low-Level API (ntdll.dll 에서 제공되는 API) 를 후킹할수록 더 강력합니다. 하지만 대부분의 Low-Level API 들은 undocumented 되어 있으며, OS 버전에 따라 변경될 가능성이 존재합니다. 반면에 High-Level API (kernel32.dll 레벨 - documented) 들은 변경될 가능성이 없고 잘 문서화 되어 있기 때문에 안정적인 후킹이 가능합니다. 대신에 후킹 성능이 좀 떨어집니다.
따라서 High-Level API 후킹과 Low-Level API 후킹은 서로 일장 일단이 있기 때문에 상황에 맞게 적절히 선택하여 구현하시는 것이 현명한 방법입니다.
+---+
다음 강좌는 Code Injection 을 통한 API 후킹에 대한 내용입니다. 지금까지는 DLL 을 인젝션 시켜서 API 후킹을 진행하였지만, 짤막한 코드를 삽입시켜 동일한 기능을 구현하는 것입니다. DLL Injection 방법과 많은 차이점이 있으며, 이 또한 흥미로운 주제가 될 것입니다.
ReverseCore
위 글이 도움이 되셨다면 추천(VIEW ON) 부탁 드려요~
'study' 카테고리의 다른 글
| 어셈블리 언어를 이용한 Code Injection (2) (2) | 2010/06/28 |
|---|---|
| 어셈블리 언어를 이용한 Code Injection (1) (0) | 2010/06/27 |
| Code Injection 기법 (3) (7) | 2010/06/24 |
| Code Injection 기법 (2) (14) | 2010/06/23 |
| Code Injection 기법 (1) (8) | 2010/06/22 |
| Advanced Global API Hooking – IE 접속 제어 (4) (27) | 2010/05/17 |
| Advanced Global API Hooking – IE 접속 제어 (3) (10) | 2010/05/07 |
| Advanced Global API Hooking – IE 접속 제어 (2) (10) | 2010/04/25 |
| Advanced Global API Hooking – IE 접속 제어 (1) (21) | 2010/03/29 |
| DLL Injection in Windows 7 (3) (32) | 2010/02/23 |
| DLL Injection in Windows 7 (2) (15) | 2010/02/21 |
API,
CreateProcess,
CreateProcessInternal,
Global API Hooking,
hook_by_code,
IE,
Internet Explorer,
InternetConnect,
it,
kernel32.dll,
NtCreateUserProcess,
NtResumeThread,
redirect,
redirection,
Reverse Code Engineering,
Reverse Engineering,
ReverseCore,
Reversing,
unhook_by_code,
wininet.dll,
ZwCreateUserProcess,
ZwResumeThread,
리버스 엔지니어링,
리버싱,
소프트웨어 역공학,
우회,
후킹
redirect.cpp
