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

  1. Monitor the process to identify when it checks the file/resource.
  2. Quickly replace the legitimate file with a malicious one during the TOCTOU window before the process uses it.
  3. 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

  1. Create a symlink pointing to a legitimate file or directory.
  2. The system checks the symlink and validates the target as safe.
  3. Before the system actually uses the file or directory, I change the symlink to point to a malicious file.
  4. 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

  1. The legitimate process starts and checks an environment variable, like PATH or LD_LIBRARY_PATH, to determine where to find executable files or libraries.
  2. Changes the environment variable to point to a malicious executable or library during the TOCTOU window.
  3. 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

  1. Identify the shared memory segment used by the legitimate process.
  2. Wait for the process to perform its integrity check.
  3. Quickly alter the contents of the shared memory during the TOCTOU window.
  4. 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
TOCTOU - pics output with n o shared memory

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.
  • size
    • The size of the shared memory segment. The size is also obtainable from ipcs -m or by analyzing the process.
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

  1. Monitor the legitimate process for actions that involve shared resources.
  2. 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.
  3. 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