5 byte, API, API Code Patch, API Hook, API Hooking, API 후킹, code patch, CreateToolhelp32Snapshot, EnumProcess, global hooking, HideProc.exe, it, ntdll.dll, procexp.exe, Reverse Code Engineering, Reverse Engineering, Reversing, Stealth, Stealth.dll, taskmgr.exe, ZwQuerySystemInformation, 글로벌 후킹, 리버싱, 소프트웨어 역공학, 스텔스, 은폐, 코드 패치, 후킹
반응형
Global API Hooking 개념과 구현 방법에 대해서 알아보도록 하겠습니다.
<Fig - Global API Hooking>
본 내용은 이전 포스트에서 이어지는 내용입니다.
* 참고!
모든 소스 코드는 MS Visual C++ 2008 Express Edition 으로 개발 되었으며, Windows XP SP3 & Windows 7 환경에서 테스트 되었습니다.
Global API Hooking
Global API Hooking 이란 1) 현재 실행중인 모든 프로세스와 2) 앞으로 실행될 모든 프로세스에 대해서 API Hooking 을 시키는 것입니다.
지난번에 설명한 예제 프로그램(HideProc.exe, stealth.dll)은 global hooking 이 아닙니다. 위에서 설명한 2) 번 조건이 만족되지 않기 때문입니다.
HideProc.exe 를 실행 하여 notepad.exe 프로세스를 은폐시켜도 이후에 Process Explorer (혹은 task manager) 를 실행시키면 이들 프로세스에서는 notepad.exe 프로세스를 볼 수 있습니다.
그 이유는 HideProc.exe 실행 이후에 생성된 프로세스들에게는 stealth.dll 파일이 (자동으로) 인젝션 되지 않기 때문입니다.
이러한 문제를 해결하기 위한 다양한 방법이 있을 수 있습니다.
그 중에서 또 다른 API 를 Hooking 하여 Global API Hooking 을 구현하는 방법에 대해서 설명 드리겠습니다.
Kernel32!CreateProcess() API
새로운 프로세스가 생성되려면 kernel32!CreateProcess() API 를 사용해야 합니다. WinExec(), ShellExecute(), system() 등의 API 도 내부적으로는 CreateProcess() 를 호출합니다.
따라서 현재 실행중인 모든 프로세스에 stealth.dll 을 인젝션 하고, stealth.dll 에서 CreateProcess() API 를 후킹하면 이후 실행되는 프로세스에게도 자동으로 stealth.dll 을 인젝션 시키도록 만들 수 있습니다.
새로운 프로세스가 생성되려면 kernel32!CreateProcess() API 를 사용해야 합니다. WinExec(), ShellExecute(), system() 등의 API 도 내부적으로는 CreateProcess() 를 호출합니다.
BOOL WINAPI CreateProcess(
__in_opt LPCTSTR lpApplicationName,
__inout_opt LPTSTR lpCommandLine,
__in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in BOOL bInheritHandles,
__in DWORD dwCreationFlags,
__in_opt LPVOID lpEnvironment,
__in_opt LPCTSTR lpCurrentDirectory,
__in LPSTARTUPINFO lpStartupInfo,
__out LPPROCESS_INFORMATION lpProcessInformation
);
* 출처 : http://msdn.microsoft.com/en-us/library/ms682425(VS.85).aspx
__in_opt LPCTSTR lpApplicationName,
__inout_opt LPTSTR lpCommandLine,
__in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in BOOL bInheritHandles,
__in DWORD dwCreationFlags,
__in_opt LPVOID lpEnvironment,
__in_opt LPCTSTR lpCurrentDirectory,
__in LPSTARTUPINFO lpStartupInfo,
__out LPPROCESS_INFORMATION lpProcessInformation
);
* 출처 : http://msdn.microsoft.com/en-us/library/ms682425(VS.85).aspx
따라서 현재 실행중인 모든 프로세스에 stealth.dll 을 인젝션 하고, stealth.dll 에서 CreateProcess() API 를 후킹하면 이후 실행되는 프로세스에게도 자동으로 stealth.dll 을 인젝션 시키도록 만들 수 있습니다.
다시 설명 드리면 모든 프로세스는 부모 프로세스에서 (CreateProcess() 를 이용하여) 생성시켜주기 때문에 부모 프로세스의 CreateProcess() API 를 후킹하여 자식 프로세스에게 stealth.dll 을 인젝션 하도록 만들면 됩니다. (보통 부모 프로세스는 explorer.exe 가 될 것입니다.)
어떤가요? 좋은 아이디어 이지요?
이와 같이 Global API Hooking 의 개념은 어렵지 않습니다.
하지만 CreateProcess() API 를 후킹하면 아래와 같이 고려해야 할 사항들이 있습니다.
1) CreateProcess() API 를 후킹할 때는 kernel32!CreateProcessA(), kernel32!CreateProcessW() 두 개의 API 를 각각 후킹해야 합니다. (ASCII 버전과 UniCode 버전)
2) CreateProcessA(), CreateProcessW() 는 각각 내부적으로 CreateProcessInternalA(), CreateProcessInternalW() 를 호출합니다. 실제 MS 제품들 중에서 일부는 CreateProcessInternalA/W 를 직접 호출하기도 하지요. 따라서 좀 더 정확히 Global API Hooking 을 구현하기 위해서는 이 두 함수를 더 후킹해줘야 합니다.
3) 후킹 함수(NewCreateProcess) 는 원본 함수(CreateProcess) 를 호출 한 후 생성된 자식 프로세스에 대해서 API 를 후킹 해야 합니다. 따라서 아주 짧은 시간동안 자식 프로세스가 후킹 되지 않은 채로 실행될 수 있습니다.
2) CreateProcessA(), CreateProcessW() 는 각각 내부적으로 CreateProcessInternalA(), CreateProcessInternalW() 를 호출합니다. 실제 MS 제품들 중에서 일부는 CreateProcessInternalA/W 를 직접 호출하기도 하지요. 따라서 좀 더 정확히 Global API Hooking 을 구현하기 위해서는 이 두 함수를 더 후킹해줘야 합니다.
3) 후킹 함수(NewCreateProcess) 는 원본 함수(CreateProcess) 를 호출 한 후 생성된 자식 프로세스에 대해서 API 를 후킹 해야 합니다. 따라서 아주 짧은 시간동안 자식 프로세스가 후킹 되지 않은 채로 실행될 수 있습니다.
많은 리버싱 선배님들에 의하여 kernel32!CreateProcess() 보다 더 후킹하기 좋은 함수가 발견 되었습니다.
바로 ntdll!ZwResumeThread() API 입니다.
NtResumeThread(
IN HANDLE ThreadHandle,
OUT PULONG SuspendCount OPTIONAL
);
* 유저 모드에서는 NtXXX 계열과 ZwXXX 계열은 동일합니다.
* 출처 : http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Thread/NtResumeThread.html
IN HANDLE ThreadHandle,
OUT PULONG SuspendCount OPTIONAL
);
* 유저 모드에서는 NtXXX 계열과 ZwXXX 계열은 동일합니다.
* 출처 : http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Thread/NtResumeThread.html
ZwResumeThread() 는 프로세스가 생성 된 후 메인 스레드 실행 직전에 호출되는 함수입니다. 따라서 이 함수 하나만 후킹하면 자식 프로세스의 코드가 하나도 실행되지 않은 상태에서 API 를 후킹시킬 수 있습니다.
단점은 ZwResumeThread() 는 undocumented API 라서 언제 바뀔지 알 수 없으며, 그만큼 안정성을 보장 할 수 없습니다. 따라서 ZwResumeThread() 같은 undocumented API 를 후킹 할 때는 OS 가 패치되면서 변경될 수 있다는 것을 항상 염두에 두어야 합니다. 하위 버전에서는 잘 되던 후킹이 최신 버전에서는 갑자기 안 되는 일이 많기 때문입니다.
실습
* 참고
위 stealth2.dll 는 CreateProcess 후킹 버전입니다.
ZwResumeThread 후킹 버전을 원하시는 분께서는 따로 요청해 주시면 보내드리겠습니다.
실습을 간단히 하기 위해서 은폐 프로세스를 notepad.exe 로 고정하였습니다. 참고하시기 바랍니다.
#1. stealth2.dll 파일 -> %SYSTEM% 폴더에 복사
<Fig. 1>
실행 중인 모든 프로세스에 stealth2.dll 파일을 인젝션 시킬 예정입니다. 따라서 모든 프로세스에서 공통적으로 인식할 수 있는 path 인 %SYSTEM% 폴더에 stealth2.dll 파일을 복사합니다.
#2. HideProc2.exe –hide 실행
<Fig. 2>
기존 HideProc.exe 와 비교해서 실행 파라미터가 변경되었습니다. 은폐 프로세스 이름이notepad.exe 로 하드코딩 되어있습니다.
HideProc2.exe 를 –hide 옵션으로 실행시키면 이제부터 글로벌 후킹이 시작됩니다.
#3. ProcExp.exe & notepad.exe 실행
Process Explorer(혹은 작업관리자) 와 notepad 를 여러 개 실행 해 주세요.
<Fig. 3>
위 그림을 보시면 ProcExp.exe 와 notepad.exe 프로세스가 각각 2개씩 실행되고 있습니다.
하지만 ProcExp.exe 에서는 notepad.exe 프로세스가 은폐되어 있습니다.
추가로 ProcExp.exe 를 몇 개 더 실행해 보시기 바랍니다. 마찬가지로 새로 생성된 ProcExp.exe 프로세스에서도 notepad.exe 프로세스가 은폐되어서 보이지 않을 것입니다.
이것이 바로 Global API Hooking 의 효과입니다.
#4. HideProc2.exe –show 실행
Global API Hooking 을 해제 합니다.
<Fig. 4>
이제 Process Explorer(혹은 작업관리자) 에서 notepad.exe 프로세스가 보일 것입니다.
소스 코드
# HideProc2.cpp
HideProc2.cpp 는 기존 HideProc.cpp 에서 실행 파라미터를 줄인 것뿐이므로, 기존 설명을 참고하시면 되겠습니다.
# stealth2.cpp
stealth2.cpp 는 기존 stealth.cpp 에서 은폐 프로세스 이름을 "notepad.exe" 로 하드 코딩 하였고, global hooking 을 위해서 CreateProcessA() API 와 CreateProcessW() API 를 후킹 하는 코드가 추가 되었습니다.
DllMain()
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
char szCurProc[MAX_PATH] = {0,};
char *p = NULL;
// HideProc2.exe 프로세스에는 인젝션 되지 않도록 예외처리
GetModuleFileName(NULL, szCurProc, MAX_PATH);
p = strrchr(szCurProc, '\\');
if( (p != NULL) && !_stricmp(p+1, "HideProc2.exe") )
return TRUE;
return TRUE;
}
{
char szCurProc[MAX_PATH] = {0,};
char *p = NULL;
// HideProc2.exe 프로세스에는 인젝션 되지 않도록 예외처리
GetModuleFileName(NULL, szCurProc, MAX_PATH);
p = strrchr(szCurProc, '\\');
if( (p != NULL) && !_stricmp(p+1, "HideProc2.exe") )
return TRUE;
// change privilege
SetPrivilege(SE_DEBUG_NAME, TRUE);
SetPrivilege(SE_DEBUG_NAME, TRUE);
switch( fdwReason )
{
case DLL_PROCESS_ATTACH :
// hook
hook_by_code("kernel32.dll", "CreateProcessA",
(PROC)NewCreateProcessA, g_pOrgCPA);
hook_by_code("kernel32.dll", "CreateProcessW",
(PROC)NewCreateProcessW, g_pOrgCPW);
hook_by_code("ntdll.dll", "ZwQuerySystemInformation",
(PROC)NewZwQuerySystemInformation, g_pOrgZwQSI);
break;
{
case DLL_PROCESS_ATTACH :
// hook
hook_by_code("kernel32.dll", "CreateProcessA",
(PROC)NewCreateProcessA, g_pOrgCPA);
hook_by_code("kernel32.dll", "CreateProcessW",
(PROC)NewCreateProcessW, g_pOrgCPW);
hook_by_code("ntdll.dll", "ZwQuerySystemInformation",
(PROC)NewZwQuerySystemInformation, g_pOrgZwQSI);
break;
case DLL_PROCESS_DETACH :
// unhook
unhook_by_code("kernel32.dll", "CreateProcessA",
g_pOrgCPA);
unhook_by_code("kernel32.dll", "CreateProcessW",
g_pOrgCPW);
unhook_by_code("ntdll.dll", "ZwQuerySystemInformation",
g_pOrgZwQSI);
break;
}
// unhook
unhook_by_code("kernel32.dll", "CreateProcessA",
g_pOrgCPA);
unhook_by_code("kernel32.dll", "CreateProcessW",
g_pOrgCPW);
unhook_by_code("ntdll.dll", "ZwQuerySystemInformation",
g_pOrgZwQSI);
break;
}
return TRUE;
}
위 DllMain() 함수를 보시면 CreateProcessA, CreateProcessW 를 후킹하는 코드가 추가되었습니다.
NewCreateProcessA()
CreateProcessA() API 의 후킹 함수인 NewCreateProcessA() 코드를 살펴 보겠습니다. (NewCreateProcessW() 코드도 거의 동일합니다.)
BOOL WINAPI NewCreateProcessA(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
)
{
BOOL bRet;
FARPROC pFunc;
// unhook
unhook_by_code("kernel32.dll", "CreateProcessA", g_pOrgCPA);
// original API 호출
pFunc = GetProcAddress(GetModuleHandle("kernel32.dll"), "CreateProcessA");
bRet = ((PFCREATEPROCESSA)pFunc)(lpApplicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttributes,
bInheritHandles,
dwCreationFlags,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
lpProcessInformation);
// 생성된 자식 프로세스에 stealth2.dll 을 인젝션 시킴
if( bRet )
InjectDll2(lpProcessInformation->hProcess, STR_MODULE_NAME);
// hook
hook_by_code("kernel32.dll", "CreateProcessA",
(PROC)NewCreateProcessA, g_pOrgCPA);
return bRet;
}
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
)
{
BOOL bRet;
FARPROC pFunc;
// unhook
unhook_by_code("kernel32.dll", "CreateProcessA", g_pOrgCPA);
// original API 호출
pFunc = GetProcAddress(GetModuleHandle("kernel32.dll"), "CreateProcessA");
bRet = ((PFCREATEPROCESSA)pFunc)(lpApplicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttributes,
bInheritHandles,
dwCreationFlags,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
lpProcessInformation);
// 생성된 자식 프로세스에 stealth2.dll 을 인젝션 시킴
if( bRet )
InjectDll2(lpProcessInformation->hProcess, STR_MODULE_NAME);
// hook
hook_by_code("kernel32.dll", "CreateProcessA",
(PROC)NewCreateProcessA, g_pOrgCPA);
return bRet;
}
코드는 매우 단순합니다.
일단 후킹을 풀고 원본 함수를 실행해서 생성된 자식 프로세스에 stealth2.dll 을 인젝션 시킵니다.
그 후 다음 실행을 위해서 다시 후킹해 줍니다.
제 글을 꾸준히 읽어오셨다면 쉽게 이해하실 수 있으실 겁니다.
한가지 눈여겨 볼 사항은 인젝션 함수인 InjectDll2() 입니다.
기존 InjectDll() 함수는 프로세스 ID (PID) 를 이용하여 프로세스 핸들을 얻어 인젝션 시키는 방법이었습니다. (OpenProcess() API 이용)
하지만 위의 경우는 CreateProcessA() API 를 호출하면서 자연스럽게 자식 프로세스의 핸들(lpProcessInformaiton->hProcess)을 얻을 수 있습니다. 이 내용도 같이 참고하시면 좋을 것 같습니다.
+---+
지금까지 Global API Hooking 에 대해서 알아보았습니다.
시스템 전체 프로세스에 대해서 후킹을 하는 기술이기 때문에 예상치 못한 에러가 발생할 수 있습니다. 따라서 사전에 꼼꼼한 테스트가 필요합니다. 그리고 undocumented API 를 후킹할 때는 현재 OS 버전에서 예상대로 동작하는지 반드시 확인하셔야 합니다.
다음번에는 API Hooking 시리즈의 마지막 테마인 Code Injection 기법에 대해서 설명드리도록 하겠습니다.
많이 기대해 주세요.
ReverseCore
위 글이 도움이 되셨다면 추천(VIEW ON) 부탁 드려요~
반응형
'study' 카테고리의 다른 글
진법 변환 (2진수, 10진수, 16진수) (23) | 2012.03.06 |
---|---|
API Hooking - '스텔스' 프로세스 (3) (50) | 2009.12.30 |
API Hooking - '스텔스' 프로세스 (2) (32) | 2009.12.16 |
API Hooking – '스텔스' 프로세스 (1) (23) | 2009.12.13 |
API Hooking - Tech Map (14) | 2009.09.29 |
API Hooking - 리버싱의 '꽃' (25) | 2009.09.22 |
DLL Injection - 다른 프로세스에 침투하기 (4) (33) | 2009.07.30 |
DLL Injection - 다른 프로세스에 침투하기 (3) (10) | 2009.07.17 |
DLL Injection - 다른 프로세스에 침투하기 (2) (117) | 2009.07.17 |
DLL Injection - 다른 프로세스에 침투하기 (1) (17) | 2009.07.06 |
- 이전 댓글 더보기