How To Manipulate The Execution Flow Of TOCTOU Attacks
Execution flow is the order in which instructions are executed in a system. Manipulating the execution flow of a legitimate process to execute malicious code through a “Time-of-Check to Time-of-Use” (TOCTOU) attack involves exploiting the time gap between when a system checks a condition and when it acts on that condition. Put simply, a TOCTOU attack takes advantage of the delay between when a system checks something (like a condition) and when it actually acts on that check. During this delay, I can manipulate the process so that, instead of performing the intended task, the system ends up executing malicious code.
This blog post documents some of my workflows to achieve this, along with a sample execution example. Each H2 header represents a workflow.
File or resource substitution
If a process checks for the existence or integrity of a file or resource (like a configuration file or library) and then uses it shortly afterward, there might be a window of opportunity to swap out the legitimate file with a malicious one.
Execution steps
- Monitor the process to identify when it checks the file/resource.
- Quickly replace the legitimate file with a malicious one during the TOCTOU window before the process uses it.
- The process then unwittingly executes the malicious file, believing it to be the legitimate one.
Example
Monitor the process
First, I must monitor the target process ID (PID) to detect when it checks the file/resource. Monitoring can be done using tools like strace
(Linux) or dtruss
(macOS).
# Linux - strace to monitor sys calls of specific process
strace -p <PID> -e trace=open,read,stat # macOS - dtruss for similar monitoring
sudo dtruss -p <PID>
Wait for the check
Next, I watch for the specific system calls (like open
, read
, or stat
) that indicate the process is checking the file/resource. I need to time my action between this check and when the file/resource is actually used.
Once I observe the process a few times, I’ll start to get an idea of the typical delay between the check and the use. This number will inform how quickly I need to act to replace the file.
Other times, I will script a replacement using a tool that monitors the system call or a loop in a script to attempt the replacement repeatedly. I do this often because the timing is critical and usually too fast for manual intervention.
Replace legitimate file with malicious one
During the TOCTOU window (between the check and the use), I will quickly replace the legitimate file with my malicious version. Here, I use standard file manipulation commands like mv
, cp
, or ln
.
mv /path/malicious/file /path/legitimate/file
Or, if I want to keep the legitimate file around and just link the malicious one:
ln -sf /path/malicious/file /path/legitimate/file
I must execute this command during the small window of time after the process checks the file and before the process uses it.
Wait for execution
After replacing the file, the process will execute the malicious file, thinking it’s the legitimate one. This step is where the TOCTOU race condition is exploited.
Other tools that can help
inotifywait
(Linux)
Monitor the file system for access, modification, or deletion events.
inotifywait -m /path/file
auditd
(Linux) / fs_usage
(macOS)
To audit file access in real-time.
sudo auditctl -w /path/file -p war -k file_watch ausearch -k file_watch
or
sudo fs_usage -w | grep "/path/file"
Symbolic link (symlink) attack
If a process checks the target of a symbolic link (symlink) and then uses the symlink to access a file or directory, I usually try to replace the symlink’s target with a malicious one between the check and use.
Execution
- Create a symlink pointing to a legitimate file or directory.
- The system checks the symlink and validates the target as safe.
- Before the system actually uses the file or directory, I change the symlink to point to a malicious file.
- The system then executes or interacts with the malicious file, believing it is the originally checked file.
Example
Create a symlink to a legitimate file or directory
On Linux or macOS, I create a symbolic link using the ln -s
command. Suppose I have a legitimate file called legit_file.txt
:
ln -s legit_file.txt symlink_file.txt
This creates a symbolic link called symlink_file.txt
pointing to legit_file.txt
.
Wait for system to validate symlink
At this point, I need to wait for the system to check or validate the file. For example, a program might check the symlink target for permissions, file type, or other attributes.
Change symlink to point to a malicious file
Once the system has validated the original symlink target, but before it uses it, I will change the symlink to point to a different, malicious file. For instance, suppose my malicious file is malicious_file.txt
:
rm symlink_file.txt ln -s malicious_file.txt symlink_file.txt
These commands remove the original symlink and create a new one with the same name, now pointing to malicious_file.txt
.
System uses malicious file
After the symlink has been changed, when the system attempts to use symlink_file.txt
, it will interact with malicious_file.txt
instead of the original legit_file.txt
. If the system only validates the symlink once and does not recheck it before use, this can lead to executing or interacting with the malicious file.
Environment variable manipulation
Some processes check environment variables before executing a command or loading a library. If there’s a delay between the check and the execution, I can try to modify the environment variables to point to malicious code.
Execution
- The legitimate process starts and checks an environment variable, like
PATH
orLD_LIBRARY_PATH
, to determine where to find executable files or libraries. - Changes the environment variable to point to a malicious executable or library during the TOCTOU window.
- When the process continues execution, it loads and executes the malicious code instead of the legitimate one.
Example
Identify the target process
First, I will identify a legitimate process that checks environment variables like PATH
or LD_LIBRARY_PATH
before executing files or loading libraries.
Create malicious code
Next, I will create a malicious executable or shared library that the target process will execute or load.
Example 1: Malicious executable
If I am in a testing (i.e., not executing on the target system) setting, I will create a simple C program that prints a message when executed.
Then, compile the program to create an executable:
gcc -o malicious_executable malicious.c
Example 2: Malicious shared library
Here, I will create a malicious shared library that prints a message when loaded. Again, this is only for testing settings; this would not be good practice during an engagement.
Then, I will compile:
gcc -shared -o malicious.so -fPIC malicious.c
Prepare the environment
Next, I would place the malicious executable or library in a directory that I control. I ensure that the directory has appropriate permissions so that it is accessible by the target process.
Manipulate the environment variables
Manipulate environment variables so the target process uses my malicious code instead of the legitimate one.
Option A: Exploiting the PATH
environment variable
In this case, I would prepend my directory containing the malicious executable to the PATH
environment variable.
export PATH=/path/to/malicious/dir:$PATH
Now, the target process will use my malicious executable instead of the legitimate one when it executes.
Option B: Exploiting the LD_LIBRARY_PATH
environment variable
In this case, I would prepend my directory containing the malicious library to the LD_LIBRARY_PATH
environment variable.
export LD_LIBRARY_PATH=/path/to/malicious/lib:$LD_LIBRARY_PATH
Now, when it loads a library, the target process loads my malicious library instead of the legitimate one.
Trigger the target process
At this point, I trigger the target process to run. Triggering could involve starting the process directly or waiting for a scheduled execution.
Observe the execution
Lastly, I verify the execution by observing the output if I am doing this process in a test setting. If successful, I will see my malicious message (e.g., “malicious library loaded”) printed, indicating that the target process executed my code.
Shared memory manipulation
If a process checks shared memory for data integrity and then uses the data, I might try to modify the shared memory contents between the check and use.
Execution
- Identify the shared memory segment used by the legitimate process.
- Wait for the process to perform its integrity check.
- Quickly alter the contents of the shared memory during the TOCTOU window.
- Acting on the altered shared memory, the process may execute malicious code or behave unintendedly.
Example
Identify the shared memory segment
I will use ipcs
to list the shared memory segments on the system. I can identify the shared memory segment by observing the memory usage of the target process, usually by comparing the output before and after the process creates/uses shared memory.
ipcs -m
Output will be formatted roughly like this.
This command lists all shared memory segments. I will look for segments that correspond to my target process by matching the PID or by checking the size of the segments.
Attach to the shared memory segment
Once identified, I can attach to the shared memory segment using the shmat
system call or use a tool like gdb
to examine the process memory directly.
Note: When I “attach” to a shared memory segment using the shmat
system call, I am essentially mapping the shared memory segment into my process’s address space. Mapping allows my process to directly access the contents of the shared memory as if it were a part of my process’s own memory.
Replace shm_key
and size
with the segment’s key and size values.
shm_key
- The key for the shared memory segment I want to attach to. The key can be obtained from the
ipcs -m
command or by inspecting the target process.
- The key for the shared memory segment I want to attach to. The key can be obtained from the
size
- The size of the shared memory segment. The size is also obtainable from
ipcs -m
or by analyzing the process.
- The size of the shared memory segment. The size is also obtainable from
Monitor for the integrity check
I use strace
(on Linux) to monitor system calls made by the target process, specifically looking for read/write access to the shared memory.
# Linux
strace -p <PID> -e trace=shmctl,shmat,shmdt # macOS option 1 - dtruss
sudo dtruss -p <PID> 2>&1 | grep -E 'shmctl|shmat|shmdt' # macOS option 2 - dtrace
sudo dtrace -n 'syscall::shmctl:entry /pid == <PID>/ { printf("%s\n", execname); }'
These commands will show me when the process attaches to the shared memory segment and when the target process performs operations that might include an integrity check.
Modify the shared memory (TOCTOU exploit)
During the TOCTOU window (between when the process checks the memory and when it uses it), I modify the shared memory contents. This modification requires precise timing, often achievable via a script or a custom program that waits for a specific condition before making the change.
// c
// modify shared memory content
strcpy((char *)shmaddr, "malicious_data");
I replace "malicious_data"
with the data I intend to inject.
Observe the process behavior
The process should now act on the altered shared memory, which, depending on the nature of the alteration, could potentially lead to the execution of malicious code or an unintended operation.
Race condition exploitation
A race condition occurs when 2 >= processes or threads access shared resources concurrently, and the outcome depends on the timing of their execution. If a process checks a resource and another process can modify that resource before the first process uses it, I can exploit this to inject malicious code.
Execution
- Monitor the legitimate process for actions that involve shared resources.
- Introduce or exploit a race condition by having a malicious process race against the legitimate process to modify the resource after it’s checked but before it’s used.
- The legitimate process uses the maliciously modified resource, resulting in the execution of my code.
Example
Monitor the legitimate process
Again, I use tools like strace
(Linux) or dtruss
(macOS) to observe system calls and actions related to file access, memory, or other shared resources.
Identify the critical section
A critical section is the portion of the code where the legitimate process checks and then uses a shared resource. This step is typically where I can introduce the race condition.
To find such sections, I have to review the application’s source code (if available) or use the monitoring tools mentioned above to trace file access, memory mappings, or inter-process communication.
Create the malicious process
Here, I need to write a script or program that continuously tries to modify the shared resource during the critical section.
Now, I will compile and run this program as a background process.
gcc -o race_condition race_condition.c
./race_condition &
Exploit race condition
Here, I am attempting to change the resource between the time the legitimate process checks it, and the time when it is used.
Example: Symlink race condition
A common technique is to exploit symbolic links:
This script will continuously create a symbolic link from /tmp/targetfile
(the legitimate resource) to /tmp/malicious_file
(my malicious file). The idea is that when the legitimate process accesses /tmp/targetfile
, it might actually be using /tmp/malicious_file
instead, depending on the timing.
Observe
The goal is for the legitimate process to use the maliciously modified resource, thereby executing my code or resulting in other unexpected behavior.
Practical considerations
Timing
Success in these attacks requires precise timing. I must insert my malicious code during the narrow TOCTOU window. Techniques like process monitoring, race condition exploitation, and system call interposition can help identify and exploit these windows.
Permissions
These attacks often require elevated permissions or a deep understanding of the target system. For example, file substitution may require the ability to terminate processes or modify system files.
System defenses
Many modern systems employ defenses against TOCTOU attacks, such as atomic operations, secure system calls (e.g., openat()
), and stricter permissions and access controls.
Atomic operations
Atomic operations are “indivisible,” all-or-nothing actions that prevent race conditions by ensuring that a sequence of operations is completed without interruption. Thus, they defend against TOCTOU attacks by eliminating the window where an attacker could alter a resource between checks and usage.
Indivisibility in atomic operations refers to the guarantee that the operation is executed as a single, unbreakable step. No other processes can observe, interrupt, or modify the state of the operation while it is in progress, thus ensuring consistency and preventing race conditions.
Secure system calls
openat()
is a secure system call that mitigates TOCTOU attacks by allowing file access operations to be performed relative to a directory file descriptor, thereby reducing the risk of race conditions when accessing files.
Bypassing
Bypassing these defenses may require advanced techniques or exploiting other vulnerabilities to weaken them. For instance, there may be additional complexity, like bypassing security mechanisms like AppArmor, SELinux, or System Integrity Protection (SIP) on Linux or macOS.
If you are unfamiliar with these mechanisms, I included a brief description of each below. I might make some posts on how I have tried to bypass these in the future, but for now, I have not created any resources to help with this.
AppArmor
AppArmor is a Linux security module that enforces mandatory access control policies on programs, limiting their access to files and resources; it mitigates TOCTOU attacks by restricting what a compromised program can access, even if it attempts to exploit a race condition.
SELinux
Security-Enhanced Linux (SELinux) enforces fine-grained access control policies based on security contexts. By tightly controlling what actions processes can perform and on which resources, SELinux reduces the impact of TOCTOU attacks.
SIP
SIP in macOS restricts the modification of critical system files and directories. It minimizes the risk of TOCTOU attacks by preventing unauthorized changes to protected areas of the system, even if an attacker gains root access.
Conclusion
In conclusion, TOCTOU attacks exploit the time gap between when a system checks a resource and when it uses that resource, creating an opportunity for malicious intervention.
This post explored some of my workflows to achieve this, along with a sample execution example. Methods included file substitution, symlink manipulation, environment variable exploitation, shared memory modification, and race condition exploitation. Each method relies on timing and an understanding of the target system’s operations.
I hope you enjoyed this post. If you are interested in learning more about security, consider reading IDS Security Using Decision Trees and Neural Networks.
READ MORE HERE