글로벌 API 후킹
1. 현재 실행중인 모든 프로세스에 대해 API 후킹 - (1)에서 ZwQuerySystemInformation API 이용
2. 앞으로 실행될 모든 프로세스에 대해 API 후킹 - CreateProcess API 이용
2-1. CreateProcessA / CreateProcessW 모두 후킹해야하는 번거로움, 자식 프로세스가 후킹되지 않은 채로 실행될 가능성존재 - ZwResumeThread API 이용
HideProc2.exe 코드 구현
- HideProc.exe랑 동일, 은폐 프로세스 이름이 "notepad.exe"로 하드코딩
- "stealth2.dll"은 모든 프로세스가 인식할 수 있는 경로인 %system32%(C:\Windows\System32)에 위치함. 따라서 dll name을 full path로 변환해주는 루틴 제거
stealth2.dll 코드 구현 (5바이트 패치, CreateProcess 후킹)
// stealth2.dll
#include "stdio.h"
#include "windows.h"
#include "tchar.h"
#define STR_MODULE_NAME (L"stealth2.dll")
#define STR_HIDE_PROCESS_NAME (L"notepad.exe")
#define STATUS_SUCCESS (0x00000000L)
typedef LONG NTSTATUS;
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation = 0,
SystemPerformanceInformation = 2,
SystemTimeOfDayInformation = 3,
SystemProcessInformation = 5,
SystemProcessorPerformanceInformation = 8,
SystemInterruptInformation = 23,
SystemExceptionInformation = 33,
SystemRegistryQuotaInformation = 37,
SystemLookasideInformation = 45
} SYSTEM_INFORMATION_CLASS;
typedef struct _SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryOffset;
BYTE Reserved1[52];
PVOID Reserved2[3];
HANDLE UniqueProcessId;
PVOID Reserved3;
ULONG HandleCount;
BYTE Reserved4[4];
PVOID Reserved5[11];
SIZE_T PeakPagefileUsage;
SIZE_T PrivatePageCount;
LARGE_INTEGER Reserved6[6];
} SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;
typedef NTSTATUS (WINAPI *PFZWQUERYSYSTEMINFORMATION)(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength);
typedef BOOL (WINAPI *PFCREATEPROCESSA)(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
typedef BOOL (WINAPI *PFCREATEPROCESSW)(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
BYTE g_pOrgCPA[5] = {0,};
BYTE g_pOrgCPW[5] = {0,};
BYTE g_pOrgZwQSI[5] = {0,};
BOOL hook_by_code(LPCSTR szDllName, LPCSTR szFuncName, FARPROC pfnNew, PBYTE pOrgBytes)
{
FARPROC pFunc;
DWORD dwOldProtect, dwAddress;
BYTE pBuf[5] = {0xE9, 0, };
pFunc = GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
if( *((PBYTE)pFunc) == 0xE9 )
return FALSE;
VirtualProtect((LPVOID)pFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
memcpy(pOrgBytes, pFunc, 5);
dwAddress = (DWORD)pfnNew - (DWORD)pFunc - 5;
memcpy(&pBuf[1], &dwAddress, 4);
memcpy(pFunc, pBuf, 5);
VirtualProtect((LPVOID)pFunc, 5, dwOldProtect, &dwOldProtect);
return TRUE;
}
BOOL unhook_by_code(LPCSTR szDllName, LPCSTR szFuncName, PBYTE pOrgBytes)
{
FARPROC pFunc;
DWORD dwOldProtect;
pFunc = (FARPROC)GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
if( *((PBYTE)pFunc) != 0xE9 )
return FALSE;
VirtualProtect((LPVOID)pFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
memcpy(pFunc, pOrgBytes, 5);
VirtualProtect((LPVOID)pFunc, 5, dwOldProtect, &dwOldProtect);
return TRUE;
}
BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) {
TOKEN_PRIVILEGES tp;
HANDLE hToken;
LUID luid;
if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &hToken)) {
_tprintf(L"OpenProcessToken error: %u\n", GetLastError());
return FALSE;
}
if(!LookupPrivilegeValue(NULL, lpszPrivilege, &luid)) {
_tprintf(L"LookupPrivilegeValue error: %u\n", GetLastError());
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if(bEnablePrivilege)
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
tp.Privileges[0].Attributes = 0;
if(!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) {
_tprintf(L"AdjuestTokenPrivileges error: %u\n", GetLastError());
return FALSE;
}
if(GetLastError()==ERROR_NOT_ALL_ASSIGNED) {
_tprintf(L"The token does not have the specified privilege.\n");
return FALSE;
}
return TRUE;
}
NTSTATUS WINAPI NewZwQuerySystemInformation(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength)
{
NTSTATUS status;
FARPROC pFunc;
PSYSTEM_PROCESS_INFORMATION pCur, pPrev;
char szProcName[MAX_PATH] = {0,};
unhook_by_code("ntdll.dll", "ZwQuerySystemInformation", g_pOrgZwQSI);
pFunc = GetProcAddress(GetModuleHandleA("ntdll.dll"),
"ZwQuerySystemInformation");
status = ((PFZWQUERYSYSTEMINFORMATION)pFunc)
(SystemInformationClass, SystemInformation,
SystemInformationLength, ReturnLength);
if( status != STATUS_SUCCESS )
goto __NTQUERYSYSTEMINFORMATION_END;
if( SystemInformationClass == SystemProcessInformation )
{
pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;
while(TRUE)
{
if(pCur->Reserved2[1] != NULL)
{
if(!_tcsicmp((PWSTR)pCur->Reserved2[1], STR_HIDE_PROCESS_NAME))
{
if(pCur->NextEntryOffset == 0)
pPrev->NextEntryOffset = 0;
else
pPrev->NextEntryOffset += pCur->NextEntryOffset;
}
else
pPrev = pCur; // 원하는 프로세스를 못 찾은 경우만 pPrev 세팅
}
if(pCur->NextEntryOffset == 0)
break;
pCur = (PSYSTEM_PROCESS_INFORMATION)((ULONG)pCur + pCur->NextEntryOffset);
}
}
__NTQUERYSYSTEMINFORMATION_END:
hook_by_code("ntdll.dll", "ZwQuerySystemInformation",
(PROC)NewZwQuerySystemInformation, g_pOrgZwQSI);
return status;
}
BOOL InjectDll2(HANDLE hProcess, LPCTSTR szDllName) {
HANDLE hThread;
LPVOID pRemoteBuf;
DWORD bufSize = (DWORD)(_tcslen(szDllName)+1) * sizeof(TCHAR);
FARPROC pThreadProc;
pRemoteBuf = VirtualAllocEx(hProcess, NULL, bufSize, MEM_COMMIT, PAGE_READWRITE);
if(!pRemoteBuf)
return FALSE;
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllName, bufSize, NULL);
pThreadProc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryW");
hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pThreadProc, pRemoteBuf, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE);
CloseHandle(hThread);
return TRUE;
}
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(GetModuleHandleA("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 again
hook_by_code("kernel32.dll", "CreateProcessA", (FARPROC)NewCreateProcessA, g_pOrgCPA);
return bRet;
}
BOOL WINAPI NewCreateProcessW(
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", "CreateProcessW", g_pOrgCPW);
// original API 호출
pFunc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateProcessW");
bRet = ((PFCREATEPROCESSW)pFunc)(lpApplicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttributes,
bInheritHandles,
dwCreationFlags,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
lpProcessInformation);
// 생성된 자식 프로세스에 stealth2.dll 인젝션
if( bRet )
InjectDll2(lpProcessInformation->hProcess, STR_MODULE_NAME);
// hook again
hook_by_code("kernel32.dll", "CreateProcessW",
(PROC)NewCreateProcessW, g_pOrgCPW);
return bRet;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {
char szCurProc[MAX_PATH] = {0,};
char *p = NULL;
// HideProc2.exe에는 inject 되지 않도록
GetModuleFileNameA(NULL, szCurProc, MAX_PATH);
p = strrchr(szCurProc, '\\');
if( p!=NULL && !_stricmp(p+1, "HideProc2.exe") )
return TRUE;
// Privilege 변경
SetPrivilege(SE_DEBUG_NAME, TRUE);
switch(fdwReason) {
case DLL_PROCESS_ATTACH:
hook_by_code("kernel32.dll", "CreateProcessA", (FARPROC)NewCreateProcessA, g_pOrgCPA);
hook_by_code("kernel32.dll", "CreateProcessW", (FARPROC)NewCreateProcessW, g_pOrgCPW);
hook_by_code("ntdll.dll", "ZwQuerySystemInformation", (FARPROC)NewZwQuerySystemInformation, g_pOrgZwQSI);
break;
case DLL_PROCESS_DETACH:
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;
}
}
- NewCreateProcessA, NewCreateProcessW에서 원 API를 호출한 후, 생성된 자식 프로세스에 InjectDll2()를 이용해 stealth2.dll을 인젝션한다
- InjectDll2()는 자식 프로세스의 핸들을 받아서(hProcess) 인젝션 진행
* 기존 InjectDll()은 PID를 받아 OpenProcess로 프로세스의 핸들을 얻어 인젝션 진행하는 방식이었음
문제점?
- 후킹 함수가 호출될 때마다 반복적인 언훅/훅으로 인해 성능 저하
- 멀티스레드 환경에서 Run Time Error 발생 가능성 (한 스레드는 실행, 다른 스레드는 쓰기)
- 후킹 함수에서 자식 프로세스를 생성(원 API 호출)하고 다시 후킹하기까지 후킹 안 된 채로 실행된다
stealth3.dll 코드 구현 (핫 패치 방식)
- 위와 같이 NOP 5바이트 + MOV EDI,EDI 로 시작하는 API에 한해 핫 패치(7바이트 패치)가 가능하다
BOOL hook_by_hotpatch(LPCSTR szDllName, LPCSTR szFuncName, FARPROC pfnNew) {
FARPROC pFunc;
DWORD dwOldProtect, dwAddress;
BYTE pBuf[5] = {0xE9, 0, }; // JMP to pfnNew
BYTE pBuf2[2] = {0xEB, 0xF9}; // short JMP -7
pFunc = GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
// if already hot-patched
if(*((PBYTE)pFunc) == 0xEB)
return FALSE;
VirtualProtect((LPVOID)((DWORD)pFunc-5), 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);
dwAddress = (DWORD)pfnNew - (DWORD)pFunc; // pFunc-5(명령어 시작위치) + 5(명령어 길이)
memcpy(pBuf+1, &dwAddress, 4);
// JMP
memcpy((LPVOID)((DWORD)pFunc-5), &pBuf, 5);
// Short JMP
memcpy((LPVOID)pFunc, &pBuf2, 2);
VirtualProtect((LPVOID)((DWORD)pFunc-5), 7, dwOldProtect, NULL);
return TRUE;
}
BOOL unhook_by_hotpatch(LPCSTR szDllName, LPCSTR szFuncName) {
FARPROC pFunc;
DWORD dwOldProtect;
PBYTE pByte;
byte pBuf[5] = {0x90, 0x90, 0x90, 0x90, 0x90}; // NOP*5
byte pBuf2[2] = {0x8B, 0xFF}; // MOV EDI, EDI
pFunc = GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
// if already unhooked
if(*((PBYTE)pFunc) == 0x8B)
return FALSE;
VirtualProtect((LPVOID)((DWORD)pFunc-5), 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);
memcpy((LPVOID)((DWORD)pFunc-5), pBuf, 5);
memcpy(pFunc, pBuf2, 2);
VirtualProtect((LPVOID)((DWORD)pFunc-5), 7, dwOldProtect, NULL);
return TRUE;
}
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;
// original API 호출
pFunc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateProcessA");
pFunc = (FARPROC)((DWORD)pFunc+2);
bRet = ((PFCREATEPROCESSA)pFunc)(lpApplicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttributes,
bInheritHandles,
dwCreationFlags,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
lpProcessInformation);
// 생성된 자식 프로세스에 stealth2.dll 인젝션
if(bRet)
InjectDll2(lpProcessInformation->hProcess, STR_MODULE_NAME);
return bRet;
}
BOOL WINAPI NewCreateProcessW(
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;
// original API 호출
pFunc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateProcessW");
pFunc = (FARPROC)((DWORD)pFunc+2);
bRet = ((PFCREATEPROCESSW)pFunc)(lpApplicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttributes,
bInheritHandles,
dwCreationFlags,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
lpProcessInformation);
// 생성된 자식 프로세스에 stealth2.dll 인젝션
if( bRet )
InjectDll2(lpProcessInformation->hProcess, STR_MODULE_NAME);
return bRet;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {
char szCurProc[MAX_PATH] = {0,};
char *p = NULL;
// HideProc2.exe에는 inject 되지 않도록
GetModuleFileNameA(NULL, szCurProc, MAX_PATH);
p = strrchr(szCurProc, '\\');
if( p!=NULL && !_stricmp(p+1, "HideProc2.exe") )
return TRUE;
// Privilege 변경
SetPrivilege(SE_DEBUG_NAME, TRUE);
switch(fdwReason) {
case DLL_PROCESS_ATTACH:
hook_by_hotpatch("kernel32.dll", "CreateProcessA", (FARPROC)NewCreateProcessA);
hook_by_hotpatch("kernel32.dll", "CreateProcessW", (FARPROC)NewCreateProcessW);
hook_by_code("ntdll.dll", "ZwQuerySystemInformation", (FARPROC)NewZwQuerySystemInformation, g_pOrgZwQSI);
break;
case DLL_PROCESS_DETACH:
unhook_by_hotpatch("kernel32.dll", "CreateProcessA");
unhook_by_hotpatch("kernel32.dll", "CreateProcessW");
unhook_by_code("ntdll.dll", "ZwQuerySystemInformation", g_pOrgZwQSI);
break;
}
}
- ZwQuerySystemInformation은 5바이트 패치 방식의 API 후킹
- CreateProcessW, CreateProcessA는 핫 패치 방식의 API 후킹
- hook_by_hotpatch, unhook_by_hotpatch, 그리고 후킹 함수 내부의 구현 방식, DllMain이 달라졌다
(장점)
- DllMain에서 한 번만 후킹하면 최종적으로 언훅하기 전까지 언훅/훅 과정 불필요
- 진입점에 따라 분기가 달라진다
1) 프로세스 실행 중 API 호출 => 이중 JMP에 의해 사용자 정의 후킹 함수 실행
2) 사용자 정의 후킹 함수 내에서 (API+2) 호출 => 이중 JMP를 건너뛰고 원 API 함수 실행
(단점)
- ntdll.dll에서 제공하는 native API들은 7바이트 후킹 자체가 불가능
CreateProcess API 후킹의 단점 극복 - ZwResumeThread API 후킹
- kernel32!CreateProcessInternalW 안에서 ntdll!ZwCreateUserProcess API에 의해 자식 프로세스가 생성되고(메인 스레드는 suspend 상태), ntdll!ZwResumeThread에 의해 자식 프로세스 실행시킴
- ntdll!ZwResumeThread 후킹하면 자식 프로세스의 EP 코드 실행 전에 제어 가로챌 수 있음
- ntdll.dll에서 제공하는 native API라 핫 패치 방식 불가능, 고전적인 5바이트 패치 방식의 후킹 사용
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;
TCHAR szModPath[MAX_PATH] = {0,};
hMod = GetModuleHandle(L"ntdll.dll");
// call ntdll!ZwQueryInformationThread()
// ThreadHandle이 가리키는 스레드가 소속된 자식 프로세스의 PID 획득
pFuncThread = GetProcAddress(hMod, "ZwQueryInformationThread");
statusThread = ((PFZWQUERYINFORMATIONTHREAD)pFuncThread)
(ThreadHandle, 0, &tbi, sizeof(tbi), NULL);
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);
InjectDll(dwPID, szModPath);
}
unhook_by_code("ntdll.dll", "ZwResumeThread", g_pZWRT);
pFunc = GetProcAddress(hMod, "ZwResumeThread");
status = ((PFZWRESUMETHREAD)pFunc)(ThreadHandle, SuspendCount);
hook_by_code("ntdll.dll", "ZwResumeThread", (PROC)NewZwResumeThread, g_pZWRT);
return status;
}
'security > 리버싱핵심원리' 카테고리의 다른 글
CreateRemoteThread()를 이용한 DLL Injetcion의 한계 (0) | 2023.04.04 |
---|---|
ASLR 제거 (0) | 2023.04.04 |
API 코드 패치를 이용한 API hooking - notepad.exe 프로세스 은폐하기 (1) (0) | 2023.04.01 |
[API 후킹] IAT 조작을 이용한 메모장 암호화/복호화 (완) (0) | 2023.04.01 |
IAT 조작하여 API 후킹 - 한글이 출력되는 계산기 (0) | 2023.03.30 |