?? 29a-7.019
字號:
===========================[ Hooking Windows API ]==============================
Technics of hooking API functions on Windows
--------------------------------------------
Author: Holy_Father <holy_father@phreaker.net>
Version: 1.1 english
Date: 6.10.2002
=====[ 1. Contents ]============================================================
1. Contents
2. Introduction
3. Hooking methods
3.1 Hooking before running
3.2 Hooking during running
3.2.1 Own process hooking using IAT
3.2.2 Own process hooking using entry point rewriting
3.2.3 Original function saving
3.2.4 Other process hooking
3.2.4.1 DLL Injection
3.2.4.2 Independent code
3.2.4.3 Raw change
4. Ending
=====[ 2. Introduction ]========================================================
This text is about hooking API functions on OS Windows. All examples
here completely works on Windows systems based on NT technology version NT 4.0
and higher (Windows NT 4.0, Windows 2000, Windows XP). Probably will also work
on others Windows systems.
You should be familiar with processes on Windows, assembler, PE files
structure and some API functions to understand whole text.
When using term "Hooking API" here, I mean the full change of API.
So, when calling hooked API, our code is run immediately. I do not deal with
cases of API monitoring only. I will write about complete hooking.
=====[ 3. Hooking methods ]=====================================================
Our goal is generally to replace the code of some function with our
code. This problem can be sometimes solved before running the process. This can
be done mostly with user level process which are run by us and the goal is e.g.
to change the program behaviour. Example of this can be application crack.
E.g. program which wants original CD-ROM during startup (this was in the game
Atlantis) and we want to run it without CDs. If we change the function for
getting a drive type we would be able to run this program from the hard drive.
This can not be done or we do not want to do this when want to hook
system process (e.g. services) or in the case we do not know which process will
be the target. Then we will use hooking during running technic. Example of
using this can be rootkit or virus with anti-antivirus technics.
=====[ 3.1 Hooking before running ]=============================================
This is about physical module change (mostly .exe or .dll) when the
function, which we want to change, is. We've got three possibilities at least
here on how to do this.
The first is to find entry point of that function and basically to
rewrite its code. This is limited by the function size but we can load some
other modules dynamically (API LoadLibrary), so it could be enought.
Kernel functions (kernel32.dll) can be used in all cases because
each process in windows has its own copy of this module. Other advantage
is if we know on which OS will be changed module run. We can use direct
pointers in this case for e.g. API LoadLibraryA. This is because the address
of kernel module in memory is static in the scope of one OS Windows version.
We can also use behaviour of dynamically loaded module. In this case its
initialization part is run immediately after loading to the memory. We are not
limited in initialization part of new module.
Second possibility of replacing function in module is its extension.
Then we have to choose between replacing first 5 bytes by relative jump
or rewriting IAT. In the case of relative jump, this will redirect the code
execution to our code. When calling function which IAT record is changed,
our code will be executed directly after this call. But extension of the module
is not so easy because we have to care about DLL header.
Next one is replacing the whole module. That means we create own
version of the module which can load the original one and call original
functions which we are not interested in. But important functions will be
totally new. This method is not so good for big modules which can contain
hundreds of exports.
=====[ 3.2 Hooking during running ]=============================================
Hooking before running is mostly very special and intimately oriented
for concrete application (or module). If we replace function in kernel32.dll
or in ntdll.dll (only on NT OS) we will get perfect replace of this function
in all processes which will be run later, but it is so difficult to make it
because we have to take care about accuracy and code prefection of new
functions or whole new modules, but the main problem is that only process
which will be run later will be hooked (so for all process we have to reboot
system). Next problem could be access to these files because NT OS tries to
protect them. Much more pretty solution is to hook process during running. This
method require more knowledge but the result is perfect. Hooking during running
can be done only on process for which we have writing access to their memory.
For the writing in itself we will use API function WriteProcessMemory. We will
start from hooking our own process during running.
=====[ 3.2.1 Own process hooking using IAT ]====================================
There are many possibilities here. At first I will show you how to hook
function by rewriting IAT. Following picture shows structure of PE file:
+-------------------------------+ - offset 0
| MS DOS Header ("MZ") and stub |
+-------------------------------+
| PE signature ("PE") |
+-------------------------------+
| .text | - module code
| Program Code |
| |
+-------------------------------+
| .data | - initialized (global static) data
| Initialized Data |
| |
+-------------------------------+
| .idata | - information for imported functions
| Import Table | and data
| |
+-------------------------------+
| .edata | - information for exported functions
| Export Table | and data
| |
+-------------------------------+
| Debug symbols |
+-------------------------------+
Important part for us here is Import Address Table (IAT) in the .idata
part. This part contains description of imports and mainly imported functions
addresses. Now it is important to know how are PE files created. When calling
arbitrary API indirectly in programming language (that means we call it using
its name, no using its OS specific address) the compiler does not link direct
calls to the module but it links call to IAT on jmp instruction which will be
filled by process loader while OS is loading process to the memory. This is why
we can use the same binary on two different version os Windows where modules
can be loaded to another addresses. Process loader will fill out direct jmp
instructions in IAT which is used by our calls from the program code. So,
if we are able to find out specific function in IAT which we want to hook,
we can easily change jmp instruction there and redirect code to our address.
Every call after doing this will execute our code. Advantage of this method
is its perfection. Disadvantage is often amount of functions which should be
hooked (e.g. if we want to change program behaviour in the file searching APIs
we will have to change functions FindFirstFile and FindNextFile, but we have to
know that these functions have its ANSI and WIDE version, so we have to change
IAT address for FindFirstFileA, FindFirstFileW, FindNextFileA and also
FileNextFileW. But there still some others like FindFirstFileExA and its WIDE
version FindFirstFileExW which are called by previous mentioned functions.
We know that FindFirstFileW calls FindFirstFileExW but this is done directly
- not usinig IAT. And still some others to go. There are e.g. ShellAPI
functions like SHGetDesktopFolder which also directly calls FindFirstFileW or
FindFirstFileExW). But if we will get all of them, the result will be perfect.
We can use ImageDirectoryEntryToData from imagehlp.dll to find out IAT
easily.
PVOID ImageDirectoryEntryToData(
IN LPVOID Base,
IN BOOLEAN MappedAsImage,
IN USHORT DirectoryEntry,
OUT PULONG Size
);
We will use Instance of our application as Base (Instance can be get by calling
GetModuleHandle:
hInstance = GetModuleHandleA(NULL);
), and as DirectoryEntry we will use constant IMAGE_DIRECTORY_ENTRY_IMPORT.
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1
Result of this function is pointer to the first IAT record. IAT records are
structures which are defined by I IMAGE_IMPORT_DESCRIPTOR. So, the result
is a pointer on IMAGE_IMPORT_DESCRIPTOR.
typedef struct _IMAGE_THUNK_DATA {
union {
PBYTE ForwarderString;
PDWORD Function;
DWORD Ordinal;
PIMAGE_IMPORT_BY_NAME AddressOfData;
} ;
} IMAGE_THUNK_DATA,*PIMAGE_THUNK_DATA;
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
PIMAGE_THUNK_DATA OriginalFirstThunk;
} ;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
PIMAGE_THUNK_DATA FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;
The Name value in IMAGE_IMPORT_DESCRIPTOR is a relative reference to
the name of module. If we want to hook a function e.g. from kernel32.dll
we have to find out in imports which belongs to the descriptor with name
kernel32.dll. We will call ImageDirectoryEntryToData at first and than we will
try to find descriptor with name "kernel32.dll" (there can be more than one
descriptor with this name). Finally we will have to find our function in
the list of all functions in the record (address of our function can be get
by GetProcAddress function). If we find it we must use VirtualProtect to change
memory page protection and after then we can write to this part of memory.
After rewriting the address we have to change the protection back. Before
calling VirtualProtect we have to know some information about this memory page.
This is done by VirtualQuery. We can add some tests in case some calls will
fail (e.g. we will not continue if the first VirtualProtect call failed, etc)>
PCSTR pszHookModName = "kernel32.dll",pszSleepName = "Sleep";
HMODULE hKernel = GetModuleHandle(pszHookModName);
PROC pfnNew = (PROC)0x12345678, //new address will be here
pfnHookAPIAddr = GetProcAddress(hKernel,pszSleepName);
ULONG ulSize;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc =
(PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(
hInstance,
TRUE,
IMAGE_DIRECTORY_ENTRY_IMPORT,
&ulSize
);
while (pImportDesc->Name)
{
PSTR pszModName = (PSTR)((PBYTE) hInstance + pImportDesc->Name);
if (stricmp(pszModName, pszHookModName) == 0)
break;
pImportDesc++;
}
PIMAGE_THUNK_DATA pThunk =
(PIMAGE_THUNK_DATA)((PBYTE) hInstance + pImportDesc->FirstThunk);
while (pThunk->u1.Function)
{
PROC* ppfn = (PROC*) &pThunk->u1.Function;
BOOL bFound = (*ppfn == pfnHookAPIAddr);
if (bFound)
{
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(
ppfn,
&mbi,
sizeof(MEMORY_BASIC_INFORMATION)
);
VirtualProtect(
mbi.BaseAddress,
mbi.RegionSize,
PAGE_READWRITE,
&mbi.Protect)
)
*ppfn = *pfnNew;
DWORD dwOldProtect;
VirtualProtect(
mbi.BaseAddress,
mbi.RegionSize,
mbi.Protect,
&dwOldProtect
);
break;
}
pThunk++;
}
Result of calling Sleep(1000) can be for example this:
00407BD8: 68E8030000 push 0000003E8h
00407BDD: E812FAFFFF call Sleep
Sleep: ;this is jump on address in IAT
004075F4: FF25BCA14000 jmp dword ptr [00040A1BCh]
original table:
0040A1BC: 79 67 E8 77 00 00 00 00
new table:
0040A1BC: 78 56 34 12 00 00 00 00
So the final jump is to 0x12345678.
=====[ 3.2.2 Own process hooking using entry point rewriting ]==================
The method of rewriting first few instructions on the function entry
point is realy simple. As in the case of rewritng address in IAT we have to
change a page protection at first. Here it will be first 5 bytes of the given
function which we want to hook. For later usage we will use dynamical alocation
of MEMORY_BASIC_INFORMATION structure. The beginning of the function is get
by GetProcAddress again. On this address we will insert relative jump to our
code. Following program calls Sleep(5000) (so it will wait for 5 seconds), than
the Sleep functions is hooked and redirected to new_sleep, finally it calls
Sleep(5000) again. Because new function new_sleep does nothing and returns
immediately the whole program will take only 5 in place of 10 seconds.
.386p
.model flat, stdcall
includelib lib\kernel32.lib
Sleep PROTO :DWORD
GetModuleHandleA PROTO :DWORD
GetProcAddress PROTO :DWORD,:DWORD
VirtualQuery PROTO :DWORD,:DWORD,:DWORD
VirtualProtect PROTO :DWORD,:DWORD,:DWORD,:DWORD
VirtualAlloc PROTO :DWORD,:DWORD,:DWORD,:DWORD
VirtualFree PROTO :DWORD,:DWORD,:DWORD
FlushInstructionCache PROTO :DWORD,:DWORD,:DWORD
GetCurrentProcess PROTO
ExitProcess PROTO :DWORD
.data
kernel_name db "kernel32.dll",0
sleep_name db "Sleep",0
old_protect dd ?
MEMORY_BASIC_INFORMATION_SIZE equ 28
PAGE_READWRITE dd 000000004h
PAGE_EXECUTE_READWRITE dd 000000040h
MEM_COMMIT dd 000001000h
MEM_RELEASE dd 000008000h
.code
start:
push 5000
call Sleep
do_hook:
push offset kernel_name
call GetModuleHandleA
push offset sleep_name
push eax
call GetProcAddress
mov edi,eax ;finally got Sleep address
push PAGE_READWRITE
push MEM_COMMIT
push MEMORY_BASIC_INFORMATION_SIZE
push 0
call VirtualAlloc
test eax,eax
jz do_sleep
mov esi,eax ;alocation for MBI
push MEMORY_BASIC_INFORMATION_SIZE
push esi
push edi
call VirtualQuery ;inforamtion about the memory page
test eax,eax
jz free_mem
call GetCurrentProcess
push 5
push edi
push eax
call FlushInstructionCache ;just to be sure :)
lea eax,[esi+014h]
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -