0% found this document useful (0 votes)
44 views54 pages

Code Injection and Hooking

Uploaded by

Shivam Pandey
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
44 views54 pages

Code Injection and Hooking

Uploaded by

Shivam Pandey
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 54

Code Injection and

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.

By providing private address space for each process, the operating


system ensures that processes do not overwrite each other's data.
The virtual memory space need not always be divided into 2 GB
halves; that is just the default setup.

For example, you can enable a 3 GB boot switch, which increases


the process memory to 3 GB, ranging from 0x00000000 -
0xBFFFFFFF; the kernel memory gets the remaining 1 GB,
from 0xC0000000 - 0xFFFFFFFF:
User Mode And Kernel Mode
• The user-space contains code (such as executable and DLL) that runs with
restricted access, known as the user mode.
• In other words, the executable or DLL code that runs in the user space cannot
access anything in the kernel space or directly interact with the hardware.
• The kernel space contains the kernel itself (ntoskrnl.exe) and the device drivers.
The code running in the kernel space executes with a high privilege, known as
kernel mode, and it can access both the user-space and the kernel space.
• By providing the kernel with a high privilege level, the operating system ensures
that a user-mode application cannot cause system instability by accessing protected
memory or I/O ports.
• Third-party drivers can get their code to run in kernel mode by implementing and
installing signed drivers.
The difference between the space (user space/kernel space) and the
mode (user mode/kernel mode) is that space specifies the location
where the contents (data/code) are stored, and mode refers to the
execution mode, which specifies how an application's instructions
are allowed to execute.
• If the user-mode applications cannot directly interact with the
hardware, then the question is, how can a malware binary running in
user-mode write content to a file on the disk by calling the WriteFile
API?.
• In fact, most of the APIs called by user-mode applications, end up
calling the system service routines (functions) implemented in the
kernel executive (ntoskrnl.exe), which in turn interacts with the
hardware (such as, for writing to a file on the disk). I
• In the same manner, any user-mode application that calls a GUI-
related API ends up calling the functions exposed by win32k.sys in the
kernel space.
The ntdll.dll (residing in the user-space) acts as the gateway between the
user-space and the kernel space. In the same way, user32.dll acts as a
gateway for the GUI applications.
Windows API
Call Flow
• When a process is invoked by double-clicking a program, the process
executable image and all its associated DLLs are loaded into the
process memory by the Windows loader.
• When a process starts, the main thread gets created, which reads the
executable code from the memory and starts executing it.

• 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

As mentioned earlier, the objective of a


code injection technique is to inject code
into the remote process memory and
execute the injected code within the
context of a remote process. The injected
code could be a module such as an
executable, DLL, or even shellcode. Code
injection techniques provide many
benefits for attackers
Once the code is injected into the remote process,
an adversary can do the following things:
• Force the remote process to execute the injected code to perform
malicious actions (such as downloading additional files or stealing
keystrokes).
• Inject a malicious module (such as a DLL) and redirect the API call
made by the remote process to a malicious function in the injected
module. The malicious function can then intercept the input
parameters of the API call, and also filter the output parameters
For example,
• Internet Explorer uses HttpSendRequest() to send a request containing an
optional POST payload to the web server, and it uses InternetReadFile() to
fetch the bytes from the server's response to display it in the browser.
• An attacker can inject a module into Internet Explorer's process memory and
redirect the HttpSendRequest() to the malicious function within the injected
module to extract credentials from the POST payload.
• In the same manner, it can intercept the data received from the
InternetReadFile() API to read the data or modify the data received from the
web server.
• This enables an attacker to intercept the data (such as banking credentials)
before it reaches the web server, and it also allows an attacker to replace or
insert additional data into the server’s response (such as inserting an extra
field into the HTML content) before it reaches the victim's browser.
Once the code is injected into the remote process,
an adversary can do the following things:

• Injecting code into an already running process allows an adversary to


achieve persistence.
• Injecting code into trusted processes allows an attacker to bypass
security products (such as whitelisting software) and hide from the
user.
• In the following code injection techniques, there is a malware process
(launcher or loader) that injects code, and a legitimate process (such as
explorer.exe) into which the code will be injected.
• Before performing code injection, the launcher needs to first identify
the process to inject the code.
• This is typically done by enumerating the processes running on the
system; it uses three API calls:

• CreateToolhelp32Snapshot(), Process32First(), and Process32Next()


• CreateToolhelp32Snapshot() is used to obtain the snapshot of all of the
running processes;
• Process32First() gets the information about the first process in the
snapshot;
• Process32Next() is used in a loop to iterate through all of the
processes.
• The Process32First() and Process32Next() APIs get information about
the process, such as the executable name, the process ID, and the
parent process ID; this information can be used by malware to
determine whether it is the target process or not.
• Sometimes, instead of injecting code into an already running process,
malicious programs launch a new process (such as notepad.exe) and
then inject code into it.
• Whether the malware injects code into an already running process or
launches a new process to inject code, the objective in all the code
injection techniques (covered next) is to inject malicious code (either
DLL, executable, or Shellcode) into the address space of the target
(legitimate) process and force the legitimate process to execute the
injected code.
• Depending on the code injection technique, the malicious component
to be injected can reside on the disk or in the memory.
Remote DLL Injection
• In this technique, the target (remote) process is forced to load a malicious DLL
into its process memory space via the LoadLibrary() API.
• The kernel32.dll exports LoadLibrary(), and this function takes a single
argument, which is the path to the DLL on the disk, and loads that DLL into the
address space of the calling process.
• In this injection technique, the malware process creates a thread in the target
process, and the thread is made to call LoadLibrary() by passing a malicious
DLL path as the argument.
• Since the thread gets created in the target process, the target process loads the
malicious DLL into its address space. Once the target process loads the malicious
DLL, the operating system automatically calls the DLL's DllMain() function,
thus executing the malicious code.
• The following steps describe in detail how this technique is performed,
with an example of a malware named nps.exe (loader or launcher) that
injects a DLL via LoadLibrary() into the legitimate explorer.exe
process.
• Before injecting the malicious DLL component, it is dropped onto the
disk, and then the following steps are performed:
• The malware process (nps.exe) identifies the target process
(explorer.exe, in this case) and gets its process ID (pid). The idea of
getting the pid is to open a handle to the target process so that the
malware process can interact with it.
• To open a handle, the OpenProcess() API is used, and one of the
parameters it accepts is the pid of the process.
• In the following screenshot, the malware calls OpenProcess() by
passing the pid of explorer.exe (0x624, which is 1572) as the third
parameter. The return value of OpenProcess() is the handle to the
explorer.exe process:
• The malware process then allocates memory in the target process
using the VirutualAllocEx() API.
• In the following screenshot, the 1st argument (0x30) is the handle to
explorer.exe (the target process), which it acquired from the previous
step.
• The 3rd argument, 0x27 (39), represents the number of bytes to be
allocated in the target process, and the 5th argument (0x4) is a constant
value that represents the memory protection of PAGE_READWRITE.
• The return value of VirtualAllocEx() is the address of the allocated
memory in explorer.exe:
• The reason for allocating the memory in the target process is to copy a
string that identifies the full path of the malicious DLL on the disk.
• The malware uses WriteProcessMemory() to copy the DLL pathname
to the allocated memory in the target process. \
• In the following screenshot, the 2nd argument, 0x01E30000, is the
address of the allocated memory in the target process, and the 3rd
argument is the full path to the DLL that will be written to the
allocated memory address 0x01E30000 in explorer.exe:
• The idea of copying the DLL pathname to the target process memory
is that, later, when the remote thread is created in the target process
and when LoadLibrary() is called via a remote thread, the DLL path
will be passed as the argument to LoadLibrary().
• Before creating a remote thread, malware must determine the address
of LoadLibrary() in kernel32.dll; to do that, it calls the
GetModuleHandleA() API and passes kernel32.dll as the argument,
which will return the base address of Kernel32.dll.
• Once it gets the base address of kernel32.dll, it determines the address
of LoadLibrary() by calling GetProcessAddress().
• At this point, the malware has copied the DLL pathname in the target
process memory, and it has determined the address of LoadLibrary().
• Now, the malware needs to create a thread in the target process
(explorer.exe), and this thread must be made to execute LoadLibrary()
by passing the copied DLL pathname so that the malicious DLL will
be loaded by explorer.exe.
• To do that, the malware calls CreateRemoteThread() (or the
undocumented API NtCreateThreadEx()), which creates a thread in the
target process.
• Once the injection is complete, the malware calls the VirtualFree() API
to free the memory containing the DLL path and closes the handle to
the target process (explorer.exe) by using the CloseHandle() API.
DLL Injection Using APC (APC Injection)
• In the previous technique, after writing the DLL pathname,
CreateRemoteThread() was invoked to create a thread in the target
process, which in turn called LoadLibrary() to load the malicious
DLL.
• The APC injection technique is similar to remote DLL injection, but
instead of using CreateRemoteThread(), a malware makes use of
Asynchronous Procedure Calls (APCs) to force the thread of a target
process to load the malicious DLL.
• The way the APC injection technique works is, a malware process
identifies the thread in the target process (the process into which the
code will be injected) that is in an alertable state, or likely to go into
an alertable state.
• It then places the custom code in that thread's APC queue by using the
QueueUserAPC() function.
• The idea of queuing the custom code is that, when the thread enters
the alertable state, the custom code gets picked up from the APC
queue, and it gets executed by the thread of the target process.
DLL Injection Using SetWindowsHookEx()
• DLL injection using SetWindowsHookEx() is a technique employed in
Windows programming to inject custom code (a DLL) into another
process's address space and intercept certain events or messages.
• This method is commonly used for various purposes, including
logging keystrokes, monitoring system events, and implementing
global hooks for applications like hotkeys or mouse events.
• Here's a basic overview of how this technique works:
• Create a DLL: First, you need to create a DLL that contains the code
you want to inject into the target process. This DLL typically contains
a function that will be called whenever the desired event occurs.
• Choose a Hook Type: SetWindowsHookEx() is a function in the
Windows API that installs an application-defined hook procedure into
a hook chain. You need to specify the type of hook you want to install,
such as keyboard (WH_KEYBOARD_LL or WH_KEYBOARD) or
mouse (WH_MOUSE_LL or WH_MOUSE).
• Hook Procedure: You'll also need to define a hook procedure within
your DLL. This procedure will be called whenever the specified event
occurs. The hook procedure receives information about the event and
can perform custom actions or modify the behavior of the target
process.
• Inject the DLL: Once the DLL and hook procedure are ready, you need
to inject the DLL into the target process. This can be done using
various techniques, such as creating a remote thread in the target
process and loading the DLL, or using functions like
CreateRemoteThread() or SetWindowsHookEx() itself.
Remote Executable/Shellcode Injection
• In this technique, the malicious code is injected into the target process
memory directly, without dropping the component on the disk.
• The malicious code can be a shellcode or an executable whose import
address table is configured for the target process.
• The injected malicious code is forced to execute by creating a remote
thread via CreateRemoteThread(), and the start of the thread is made to
point to the code/function within the injected block of code.
• The advantage of this method is that the malware process does not have
to drop the malicious DLL on the disk; it can extract the code to inject
from the resource section of the binary, or get it over the network and
perform code injection directly.
Hooking Techniques
• Another reason an attacker injects code (mostly DLL, but it can also
be an executable or shellcode) into the legitimate (target) process is to
hook the API calls made by the target process.
• Once a code is injected into the target process, it has full access to the
process memory and can modify its components.
• The ability to alter the process memory components allows an attacker
to replace the entries in the Import Address Table(IAT) or modify
the API function itself; this technique is referred to as hooking
By hooking an API, an attacker can control the execution path of the
program and re route it to the malicious code of his choice. The
malicious function can then:

• Block calls made to the API by legitimate applications (such as security


products).
• Monitor and intercept input parameters passed to the API.
• Filter the output parameters returned from the API.
IAT Hooking
• As mentioned earlier, the IAT contains the addresses of functions that
an application imports from DLLs.
• In this technique, after a DLL is injected into the target (legitimate)
process, the code in the injected DLL (Dllmain() function) hooks the
IAT entries in the target process.
The following gives a high-level overview of the steps used to perform
this type of hooking:
• Locate the IAT by parsing the executable image in memory.
• Identify the entry of the function to hook.
• Replace the address of the function with the address of the malicious
function.
• Let's look at an example of a legitimate program deleting a file by
calling the DeleteFileA() API.
• The DeleteFileA() object accepts a single parameter, which is the
name of the file to be deleted.
• The following screenshot displays the legitimate process (before
hooking), consulting the IAT normally to determine the address of
DeleteFileA(), and then calling DeleteFileA() in the kernel32.dll
• When the program's IAT is hooked, the address of DeleteFileA() in the
IAT is replaced with the address of the malicious function, as follows.
Now, when the legitimate program calls DeleteFileA(), the call is
redirected to the malicious function in the malware module.
• The malicious function then invokes the original DeleteFileA() function, to
make it seem like everything is normal. The malicious function sitting in
between can either prevent the legitimate program from deleting the file, or
monitor the parameter (the file that is being deleted), and then take some
action
• In addition to blocking and monitoring, which typically happens
before invoking the original function, the malicious function can also
filter the output parameters, which occurs after the re-invocation.
• This way, the malware can hook APIs that display lists of processes,
files, drivers, network ports, and so on, and filter the output to hide
from the tools that use these API functions.
Inline Hooking (Inline Patching)
• IAT hooking relies on swapping the function pointers, whereas, in inline
hooking, the API function itself is modified (patched) to redirect the
API to the malicious code.
• As in IAT hooking, this technique allows the attacker to intercept,
monitor, and block calls made by a specific application, and filter output
parameters. In inline hooking, the target API function's first few
bytes (instructions) are usually overwritten with a jump statement
that re routes the program control to the malicious code.
• The malicious code can then intercept the input parameters, filter output,
and redirect the control back to the original function
• To help you understand, let's suppose that an attacker wants to hook
the DeleteFileA() function call made by a legitimate application.
• Normally, when the legitimate application's thread encounters the call
to DeleteFileA(), the thread starts executing from the start of the
DeleteFileA() function, as shown here:
• To replace the first few instructions of a function with a jump, the
malware needs to choose which instructions to replace. The jmp
instruction requires at least 5 bytes, so the malware needs to choose
instructions that occupy 5 bytes or more.
• In the preceding diagram, it is safe to replace the first three
instructions (highlighted using a different color), because they take up
exactly 5 bytes, and also, these instructions do not do much, apart
from setting up the stack frame.
• The three instructions to be replaced in DeleteFileA() are copied, and
then replaced with a jump statement of some sort, which transfers
control to the malicious function.
• The malicious function does what it wants to do, and then executes the
original three instructions of DeleteFileA() and jumps back to the
address that lies below the patch (below the jump instruction), as
shown in the following diagram. The replaced instructions, along with
the jump statement that returns to the target function, are known as the
trampoline:
• This technique can be detected by looking for unexpected jump
instructions at the start of the API function, but be aware that malware
can make detection difficult by inserting the jump deeper in the API
function, rather than at the start of the function.
• Instead of using a jmp instruction, malware may use a call instruction,
or a combination of push and ret instructions, to redirect control; this
technique bypasses the security tools, which only look for jmp
instructions.

You might also like