<hookiat!MySetWindowTextW() 함수>
본 내용은 이전 포스트에서 이어지는 내용입니다.
API Hooking – 계산기, 한글을 배우다. (1)
API Hooking – 계산기, 한글을 배우다. (2)
소스 코드 분석
* 참고!
모든 소스 코드는 MS Visual C++ 2008 Express Edition 으로 개발 되었으며, Windows XP SP3 환경에서 테스트 되었습니다.
InjectDll.cpp 소스는 예전에 설명 드렸던 내용과 기본적인 구조는 비슷합니다. 대신 약간의 업그레이드를 하였는데요.
실행 파라미터를 늘려서 Injection 과 Ejection 을 동시에 지원하도록 수정하였습니다. 또한 권한 상승 함수를 추가시켜 (간혹 발생할 수 있는) OpenProcess() 에러를 방지 하였습니다. (관련 설명은 생략하겠습니다. 더 자세한 내용은 MSDN 을 찾아보시기 바랍니다.)
InjectDll.cpp 에 대한 더 자세한 설명은 아래 링크를 참고해 주세요.
DLL Injection – 프로세스에 침투하기
DLL Ejection - 침투시킨 DLL 빼내기
# DllMain()
{
switch( fdwReason )
{
case DLL_PROCESS_ATTACH :
// original API 주소저장
g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"),
"SetWindowTextW");
// # hook
// user32!SetWindowTextW() 를 hookiat!MySetWindowText() 로 후킹
hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);
break;
case DLL_PROCESS_DETACH :
// # unhook
// calc.exe 의IAT 를원래대로복원
hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc);
break;
}
return TRUE;
}
늘 그렇듯이 예제 코드의 메인 함수는 간단합니다.
중요 코드들을 한 라인씩 살펴보겠습니다.
case DLL_PROCESS_ATTACH :
g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"), "SetWindowTextW");
먼저 DLL_PROCESS_ATTACH 이벤트에서 user32!SetWindowTextW() API 주소를 구합니다. 이는 나중에 unhook 작업에서 사용되기 때문에 전역변수(g_pOrgFunc)에 잘 저장해 놓습니다.
hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);
그리고 hook_iat() 함수를 호출하여 IAT 를 후킹(hooking)합니다. (user32!SetWindowTextW() 주소를 hookiat!MySetWindowTextW() 주소로 변경)
case DLL_PROCESS_DETACH :
hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc);
DLL_PROCESS_DETACH 이벤트에서 IAT 후킹을 풀어(unhook)줍니다. (hookiat!MySetWindowTextW() 주소를 user32!SetWindowTextW() 주소로 변경)
# MySetWindowTextW()
{
wchar_t* pNum = L"영일이삼사오육칠팔구";
wchar_t temp[2] = {0,};
int i = 0, nLen = 0, nIndex = 0;
nLen = wcslen(lpString);
for(i = 0; i < nLen; i++)
{
// '수'문자를 '한글'문자로 변환
// lpString 은 wide-character (2 byte) 문자열
if( L'0' <= lpString[i] && lpString[i] <= L'9' )
{
temp[0] = lpString[i];
nIndex = _wtoi(temp);
lpString[i] = pNum[nIndex];
}
}
// user32!SetWindowTextW() API 호출
// (위에서 lpString 버퍼 내용을 변경하였음)
return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);
}
IAT 가 후킹된 계산기(calc.exe) 프로세스는 코드 내에서 user32!SetWindowTextW() 함수를 호출할 때마다 사실은 위의 hookiat!MySetWindowTextW() 함수가 호출됩니다.
함수의 파라미터 lpString 은 출력할 (wide-character) 문자열 버퍼입니다. 따라서 이 lpString 을 조작하면 사용자가 원하는 문자열을 출력시킬 수 있습니다.
nLen = wcslen(lpString);
for(i = 0; i < nLen; i++)
{
if( L'0' <= lpString[i] && lpString[i] <= L'9' )
{
temp[0] = lpString[i];
nIndex = _wtoi(temp);
lpString[i] = pNum[nIndex];
}
}
위의 for 루프에서 lpString 에 저장된 ‘수’ 문자열을 ‘한글’ 문자열로 교체합니다.
아래는 lpString 버퍼의 변경 전/후의 그림입니다.
<Fig. 1>
숫자 "123" 에 대해서 한글은 "일이삼" 으로 표시할 수 있습니다. 즉, 숫자 1 글자당 한글 1 글자로 1:1 대응이 가능하다는 것이죠. 이런 특성은 기존 버퍼를 그대로 사용할 수 있다는 장점이 있습니다. 위 코드를 보시면 lpString 문자열 버퍼에 직접 변환된 문자열(한글)을 기록합니다.
참고로 만약 영어라면 "ONETWOTHREE" 와 같이 길게 표시해야 하기 때문에 기존 버퍼("123")에 덮어쓸 수 없고, 새롭게 버퍼를 할당 받아서 사용한 후 그 버퍼 주소를 original API 에 넘겨야 합니다.
return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);
for 루프가 종료하면 마지막으로 함수 포인터 g_pOrgFunc 를 호출합니다.
g_pOrgFunc 는 DllMain() 에서 미리 구해놓은 user32!SetWindowTextW() API 시작 주소입니다.
즉, 원본 함수를 호출해서 계산기의 에디트 윈도우에 (변환된) ‘한글’ 문자열을 출력시킵니다.
# hook_iat()
{
HMODULE hMod;
LPCSTR szLibName;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
PIMAGE_THUNK_DATA pThunk;
DWORD dwOldProtect, dwRVA;
PBYTE pAddr;
// hMod, pAddr = ImageBase of calc.exe
// = VA to MZ signature (IMAGE_DOS_HEADER)
hMod = GetModuleHandle(NULL);
pAddr = (PBYTE)hMod;
// pAddr = VA to PE signature (IMAGE_NT_HEADERS)
pAddr += *((DWORD*)&pAddr[0x3C]);
// dwRVA = RVA to IMAGE_IMPORT_DESCRIPTOR Table
dwRVA = *((DWORD*)&pAddr[0x80]);
// pImportDesc = VA to IMAGE_IMPORT_DESCRIPTOR Table
pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);
for( ; pImportDesc->Name; pImportDesc++ )
{
// szLibName = VA to IMAGE_IMPORT_DESCRIPTOR.Name
szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);
if( !stricmp(szLibName, szDllName) )
{
// pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk
// = VA to IAT(Import Address Table)
pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod +
pImportDesc->FirstThunk);
// pThunk->u1.Function = VA to API
for( ; pThunk->u1.Function; pThunk++ )
{
if( pThunk->u1.Function == (DWORD)pfnOrg )
{
// 메모리 속성을 E/R/W 로변경
VirtualProtect((LPVOID)&pThunk->u1.Function,
4,
PAGE_EXECUTE_READWRITE,
&dwOldProtect);
// IAT 값을 변경 (후킹)
pThunk->u1.Function = (DWORD)pfnNew;
// 메모리 속성 복원
VirtualProtect((LPVOID)&pThunk->u1.Function,
4,
dwOldProtect,
&dwOldProtect);
return TRUE;
}
}
}
}
return FALSE;
}
IAT Hooking 을 수행하는 함수입니다.
코드 자체는 짧은데 주석이 많아서 전체적으로 길어 보입니다.
hook_iat() 함수의 초반부는 PE Header 정보를 읽어서 IAT 를 따라가는 내용입니다. 위 코드를 이해하시려면 꼭 IAT 구조를 알고 계셔야 합니다.
IAT 에 대한 상세한 정보는 아래 링크를 참고해 주세요.
PE(Portable Executable) File Format (6) - PE Header
코드를 보면 PE Header 를 따라가다가 아래 코드에서 우리가 원하는 Import Directory Table 의 시작 주소를 구합니다.
pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);
Import Directory Table 은 IMAGE_IMPORT_DESCRIPTOR 구조체 배열로 이루어져 있습니다. IAT 를 구하려면 먼저 이곳을 찾아야 합니다.
위의 코드에서 pImportDesc 에는 01012B80 값이 들어옵니다. 이 주소를 PEView 로 보면 아래 그림과 같습니다.
<Fig. 2>
위 그림이 계산기 프로세스의 IMAGE_IMPORT_DESCRIPTOR 테이블(배열) 입니다. PEView 에서는 Import Directory Table 라고 표시하는 곳이죠. 우리가 목표로 하는 user32.dll 은 <Fig. 2> 의 아래쪽에 위치하는군요. 저곳을 찾아가 봅시다.
for( ; pImportDesc->Name; pImportDesc++ )
{
// szLibName = VA to IMAGE_IMPORT_DESCRIPTOR.Name
szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);
if( !stricmp(szLibName, szDllName) )
{
위의 for 루프 내에서 pImportDesc->Name 과 szDllName("user32.dll") 을 비교해서 user32.dll 의 IMAGE_IMPORT_DESCRIPTOR 구조체 주소를 찾아냅니다.
결국 pImportDesc = 01012BE4 가 됩니다. (<Fig. 2> 참고)
이제 user32.dll 에 대한 IAT 로 가야 합니다. pImportDesc->FirstThunk 멤버가 바로 IAT 를 가리키는 멤버입니다.
// pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk
// = VA to IAT(Import Address Table)
pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod +
pImportDesc->FirstThunk);
위 코드에서 pThunk 가 바로 user32.dll 에 대한 IAT (010010A4) 입니다. (<Fig. 2> 참고)
이 주소를 PEView 로 보면 아래 그림과 같습니다.
<Fig. 3>
User32.dll 의 IAT 에 상당히 많은 함수들이 import 되어 있는걸 보실 수 있습니다. 우리가 찾는 SetWindowTextW 는 01001110 주소에 있으며 값은 77CF61C9 입니다.
// pThunk->u1.Function = VA to API
for( ; pThunk->u1.Function; pThunk++ )
{
if( pThunk->u1.Function == (DWORD)pfnOrg )
{
위의 for 루프에서 pThunk->u1.Function 과 pfnOrg (77CF61C9 <– SetWindowTextW 시작주소) 를 비교하여 정확히 SetWindowTextW 의 IAT 주소(01001110)를 찾아 냅니다. (이제 pThunk = 01001110 이고 pThunk->u1.Function = 77CF61C9 입니다.)
여기까지의 코드가 계산기 프로세스의 ImageBase 에서부터 user32!SetWindowTextW 의 IAT 주소를 찾아가는 과정입니다.
이후부터는 위에서 구한 IAT 주소의 값을 바꿔 치는 (후킹) 코드가 이어집니다.
// IAT 값을 변경
pThunk->u1.Function = (DWORD)pfnNew;
pThunk->u1.Funcion 에는 원래 77CF61C9 (SetWindowTextW 주소) 값이 있었지만, pfnNew (10001000 <- hookiat!MySetWindowTextW 주소) 값으로 변경합니다.
이제 계산기 코드에서 user32!SetWindowTextW() API 를 호출하면 hook_iat!MySetWindowTextW() 가 호출되는 것입니다.
* 참고!
저 위쪽의 hook_iat() 함수 코드를 보시면 후킹 하기 전에 VirtualProtect() 함수를 이용해서 해당 IAT 메모리 영역 을 "읽기|쓰기" 모드로 변경하고, 후킹 한 후에 다시 원래 모드로 되돌리는 코드가 존재합니다. 계산기 프로세스의 IAT 메모리 영역이 "읽기" 전용이기 때문에 미리 이러한 작업을 해주는 것입니다.
이상으로 hook_iat.cpp 의 코드 설명을 마치겠습니다.
다음 포스트에서는 OllyDbg 를 이용하여 후킹 코드를 디버깅하면서 후킹된 IAT 메모리 영역을 확인해 보겠습니다.
많이 기대해 주세요~
API Hooking – 계산기, 한글을 배우다. (4)
+---+
ReverseCore
위 글이 도움이 되셨다면 추천(VIEW ON) 부탁드려요~
'study' 카테고리의 다른 글
| API Hooking - '스텔스' 프로세스 (4) (42) | 2010/01/17 |
|---|---|
| API Hooking - '스텔스' 프로세스 (3) (39) | 2009/12/30 |
| API Hooking - '스텔스' 프로세스 (2) (32) | 2009/12/16 |
| API Hooking – '스텔스' 프로세스 (1) (18) | 2009/12/13 |
| API Hooking - 계산기, 한글을 배우다. (4) (10) | 2009/11/27 |
| API Hooking – 계산기, 한글을 배우다. (3) (35) | 2009/11/20 |
| API Hooking - 계산기, 한글을 배우다. (2) (14) | 2009/11/13 |
| API Hooking - 계산기, 한글을 배우다. (1) (30) | 2009/11/10 |
| API Hooking - 메모장 WriteFile() 후킹 (3) (26) | 2009/11/04 |
| API Hooking - 메모장 WriteFile() 후킹 (2) (3) | 2009/11/03 |
| API Hooking - 메모장 WriteFile() 후킹 (1) (22) | 2009/10/08 |
InjectDll.cpp
