API hooking is a technique by which we can instrument and modify the behavior and flow of API calls. API hooking can be done using various methods on Windows. Techniques include memory break point and .DEP and JMP instruction insertion. We will briefly discuss the trampoline insertion techniques.
Hooking can be used to introspect calls in a Windows application or can be used to capture some information related to the API Calls.
Let us consider the following application making some basic Win32 API calls.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| /************************************************* Simple WIN32 APP making some API calls ************************************************/ #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <stdio.h> int main( int argc, char **argv) { MessageBox(NULL, "Hello world" , "Hello World!" , MB_OK); return EXIT_SUCCESS; } |
Running this program will lead us to this message box:
Now let us consider the situation that we want to monitor the call to the message. The following diagram illustrates the procedure.
The code which is responsible for the jump to the hooked dll is known as trampoline. So the basic idea is to redirect the call at the base of the API function.
For injection related purposes, we will create an injector. Following is the code for the injector exe that will be used to create a process in suspended mode or will try to inject in a running process.
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
| int main( int argc, char **argv) { char * pName = 0; unsigned int type = 0, PID; unsigned char psDLLname [MAX_PATH] = {0}; LPVOID pvMem = NULL; LPDWORD rc; PROCESS_INFORMATION ProcessInfo; STARTUPINFO StartupInfo; HANDLE hProcess, hProcess2, hThread; if (argc < 3) { printf ( "...[] Usage %s <ProcessName> / <process ID> <type = 1 for injection , 2 for creation>..." , argv[0]); exit (0); } type = atoi (argv[2]); EnableDebugPriv(); printf ( "n [].......... Type = %d , Process Name = %sn" , type, argv[1]); ZeroMemory(&StartupInfo, sizeof (StartupInfo)); StartupInfo.cb = sizeof StartupInfo ; //Only compulsory field if (type == 1) // Injection { PID = atoi (argv[1]); hProcess = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION |PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, PID ); if (!hProcess ) { printf ( "Open Process Failed.." ); } } else // creation { pName = argv[1]; CreateProcess(pName, NULL, NULL,NULL,FALSE,CREATE_SUSPENDED,NULL,NULL,&StartupInfo,&ProcessInfo); hProcess = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION |PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, ProcessInfo.dwProcessId ); } GetModuleFileName(NULL, psDLLname, MAX_PATH); sprintf (psDLLname, "%s.dll" , psDLLname); pvMem = VirtualAllocEx( hProcess, 0, MAX_PATH, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); WriteProcessMemory( hProcess, pvMem, psDLLname, MAX_PATH, NULL ); hThread = CreateRemoteThread( hProcess, 0, 0, LoadLibrary, pvMem, 0, &rc ); ResumeThread(hThread); ResumeThreads(ProcessInfo.dwProcessId, 1); } |
In order to give the application SE_DEBUG privileges, we have to elevate the privileges to SE_DEBUG PRIVILEGES. For that purpose we can use the following API calls.
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
| void EnableDebugPriv( void ) { HANDLE hToken; LUID sedebugnameValue; TOKEN_PRIVILEGES tkp; if ( ! OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) ) { _debug(); return ; } if ( ! LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &sedebugnameValue ) ) { _debug(); CloseHandle( hToken ); return ; } tkp.PrivilegeCount = 1; tkp.Privileges[0].Luid = sedebugnameValue; tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if ( ! AdjustTokenPrivileges( hToken, FALSE, &tkp, sizeof tkp, NULL, NULL ) ) _debug(); CloseHandle( hToken ); } |
Similar code can be found here: http://msdn.microsoft.com/en-us/library/windows/desktop/aa446619(v=vs.85).aspx
Inside the dll we need to code a handler and JMP replacer for the API call MessageBoxA.
JMP instruction has the following OPCODE structure.
JMP = {0xe9} ADDRESS = {0×00, 0×00, 0×00, 0×00, 0×00}
where ADDRESS = Jump destination – (EIP + SIZEOF(OPCODE))
which can be simply calculated using the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| unsigned char * JMP_OPCODE(unsigned int addr, unsigned int Hook) { static unsigned char OPCODE[0x06] = {0xe9, 0x00, 0x00, 0x00, 0x00, 0x00}; unsigned int Addr_RESX = addr - (Hook + 5); memcpy (&OPCODE[1], &Addr_RESX, sizeof ( int )); return OPCODE; } A similar thing can be done for a call OPCODE: unsigned char * CALL_OPCODE(unsigned int addr, unsigned int Hook) { static unsigned char OPCODE[0x06] = {0xe8, 0x00, 0x00, 0x00, 0x00, 0x00}; unsigned int Addr_RESX = addr - (Hook + 5); memcpy (&OPCODE[1], &Addr_RESX, sizeof ( int )); return OPCODE; } |
For example if we want to get the JMP opcode MessageBoxA function we will use this code in the following ways:
1
2
3
4
5
6
7
| Void hook_function_MessageBoxA { ….. Hook Code …. Jump back } unisgned char * trampoline = JMP_OPCODE(MessageBoxA, hook_function_MessageBoxA); |
This function returns a chunk of five bytes of manipulated JMP op code which can be later on replaced at the function base.
1
2
3
4
5
6
| Unsigned int org_buffer[5] = {0}; // This will save the original bytes at the function buffer = getAddr( "MessageBoxA" , "user32.dll" ); VirtualProtect(buffer, 5, PAGE_EXECUTE_READWRITE, &x); MessageBox(NULL, "Hello word" !, "Hello world!" , MB_OK); |
We also need to save the original instructions and when our hook is called we need to replace them again.
1
| memcpy (org_buffer, API_CALL, 5); |
We also need to modify the Hooked function to replace the original bytes afterwards:
1
2
3
4
5
6
7
8
9
| Void hook_function_MessageBoxA { ….. Hook Code memcmy(API_CALL, org_buffer, 5); __asm { JMP [API_CALL];<br/> } |
No comments:
Post a Comment