Code Injection and Hooking
Code Injection and Hooking
Hooking
Code Injection and
Hooking
• In this chapter, you will learn how malicious
programs inject code into another process
(called target process or remote process) to
perform malicious actions. The technique of
injecting malicious code into a target process's
memory and executing the malicious code
within the context of the target process is called
code injection (or process injection).
An attacker typically chooses a legitimate process (such as explorer.exe or svchost.exe)
as the target process.
Once the malicious code is injected into the target process, it can then perform
malicious actions, such as logging keystrokes, stealing passwords, and exfiltrating data,
within the context of the target process.
After injecting the code into the memory of the target process, the malware component
responsible for injecting code can either continue to persist on the system, thereby
injecting code into the target process every time the system reboots, or it can delete
itself from the filesystem, keeping the malicious code in memory only.
Virtual Memory
• Virtual memory is a memory management technique used by modern
operating systems to provide each process with the illusion of a large,
contiguous address space, even if physical memory (RAM) is limited.
It allows multiple processes to run concurrently while efficiently
utilizing available memory resources.
• Windows memory manager, with the help of hardware, translates the
virtual address into the physical address (in RAM) where the actual
data resides; to manage the memory, it pages some of the memory to
the disk.
• Virtual memory is segregated into process memory (process
space or user space) and kernel memory (kernel space or system
space).
• The size of the virtual memory address space depends on the
hardware platform.
• For example, on a 32-bit architecture, by default, the total virtual
address space (for both process and kernel memory) is a
maximum of 4 GB.
• On a 32-bit system, out of the 4 GB virtual address space, each
process thinks that it has 2 GB of process memory, ranging from
0x00000000 - 0x7FFFFFFF.
• Since each process thinks that it has its own private virtual address
space (which ultimately gets mapped to physical memory), the total
virtual address gets much larger than the available physical memory
(RAM).
• The Windows memory manager addresses this by paging some of
the memory to the disk; this frees the physical memory, which can
be used for other processes, or for the operating system itself.
• Even though each Windows process has its own private memory
space, the kernel memory is, for the most part, common, and is shared
by all the processes.
The following diagram shows the memory layout of 32-bit architecture.
You may notice a 64 KB gap between the user and kernel space; this
region is not accessible and ensures that the kernel does not accidentally
cross the boundary and corrupt the user-space.
Even though the virtual address range is the same for each process
(x00000000 - 0x7FFFFFFF), both the hardware and Windows make
sure that the physical addresses mapped to this range are different for
each process.
For instance, when two processes access the same virtual address, each
process will end up accessing a different address in the physical
memory.
• An important point to remember is that it is not the process that executes the
code, it is the thread that executes the code (a process is merely a container
for the threads).
• The thread that is created starts executing in the user-mode (with restricted
access). A process may explicitly create additional threads, as required.
• Let's suppose that an application needs to call the WriteFile() API,
which is exported by kernel32.dll.
• To transfer the execution control to WriteFile(), the thread has to know
the address of WriteFile() in the memory.
• If the application imports WriteFile(), then it can determine its address
by looking in a table of function pointers called the Import Address
Table (IAT), as shown in the preceding diagram.
• This table is located in an application's executable image in the
memory, and it is populated by the windows loader with the function
addresses when the DLLs are loaded.
An application can also load a DLL during runtime by calling the
LoadLibrary() API, and it can determine the address of a function within
the loaded DLL by using the GetProcessAddress() API. If an application
loads a DLL during runtime, then the IAT does not get populated.
• Once the thread determines the address of WriteFile() from the IAT or
during runtime, it calls WriteFile(), implemented in kernel32.dll. The
code in the WriteFile() function ends up calling a function,
NtWriteFile(), exported by the gateway DLL, ntdll.dll.
• The NtWriteFile() function in ntdll.dll is not a real implementation of
NtWriteFile(). The actual function, with the same name, NtWriteFile()
(system service routine), resides in ntoskrnl.exe (executive), which
contains the real implementation. T
• he NtWriteFile() in ntdll.dll is just a stub routine that executes either
SYSENTER (x86) or SYSCALL (x64) instructions. These instructions
transition the code to the kernel mode
• Now, the thread running in kernel mode (with unrestricted access)
needs to find the address of the actual function, NtWriteFile(),
implemented in ntoskrnl.exe.
• To do that, it consults a table in the kernel space called the System
Service Descriptor Table (SSDT) and determines the address of
NtWriteFile().
• It then calls the actual NtWriteFile() (system service routine) in the
Windows executive (in ntoskrnl.exe), which directs the request to the
I/O functions in the I/O manager.
• The I/O manager then directs the request to the appropriate kernel-
mode device driver.
Code Injection
Techniques