Payload Execution Control

The more actions malware performs, the more likely it is to be picked up by monitoring systems. Limiting the actions performed by malware and focusing on essential tasks is called "Execution Control".

Synchronization Objects

A synchronization object is an object whose handle can be specified in one of the wait functions to coordinate the execution of multiple threads.

The following object types are provided exclusively for synchronization.

Type
Description

Event

Notifies one or more waiting threads that an event has occurred. For more information, see Event Objects.

Mutex

Can be owned by only one thread at a time, enabling threads to coordinate mutually exclusive access to a shared resource. For more information, see Mutex Objects.

Semaphore

Maintains a count between zero and some maximum value, limiting the number of threads that are simultaneously accessing a shared resource. For more information, see Semaphore Objects.

Waitable timer

Notifies one or more waiting threads that a specified time has arrived. For more information, see Waitable Timer Objects.

Events

Event Objects notify one or more waiting threads that an event has occurred. They can be used to cooridinate the execution of multiple threads or processes. They can be either manual or automatic.

To use events in a program, the CreateEventA WinAPI can be employed. The usage of the function is demonstrated below:

HANDLE hEvent = CreateEventA(NULL, FALSE, FALSE, "ControlString");

if (hEvent != NULL && GetLastError() == ERROR_ALREADY_EXISTS)
	// Payload is already running
else
	// Payload is not running

Mutex

Mutex Objects can be owned by only one thread at a time, enabling threads to coordinate mutually exclusive access to a shared resource. Short for "mutual exclusion".

CreateMutexA is used to create a named mutex as follows:

HANDLE hMutex = CreateMutexA(NULL, FALSE, "ControlString");

if (hMutex != NULL && GetLastError() == ERROR_ALREADY_EXISTS)
	// Payload is already running
else
	// Payload is not running

Semaphore

Semaphore Objects maintain a count between zero and some maximum value, limiting the number of threads that are simultaneously accessing a shared resource. There are two types of semaphores: binary and counting. A binary semaphore has a value of 1 or 0, indicating whether the resource is available or unavailable, respectively. A counting semaphore, on the other hand, has a value greater than 1, representing the number of available resources or the number of processes that can access the resource concurrently.

To control execution of a payload, a named semaphore object will be created each time the payload is executed. If the binary is executed multiple times, the first execution will create the named semaphore and the payload will be executed as intended. On subsequent executions, the semaphore creation will fail as the semaphore with the same name is already running. This indicates that the payload is currently being executed from a previous run and therefore should not be run again to avoid duplication.

CreateSemaphoreA will be used to create a semaphore object. It is important to create it as a named semaphore to prevent executions after the initial binary run. If the named semaphore is already running, CreateSemaphoreA will return a handle to the existing object and GetLastError will return ERROR_ALREADY_EXISTS. In the code below, if a "ControlString" semaphore is already running, GetLastError will return ERROR_ALREADY_EXISTS.

HANDLE hSemaphore = CreateSemaphoreA(NULL, 10, 10, "ControlString");

if (hSemaphore != NULL && GetLastError() == ERROR_ALREADY_EXISTS)
	// Payload is already running
else
	// Payload is not running

\

Other Forms of Synchronization Objects

In some circumstances, you can also use a file, named pipe, or communications device as a synchronization object.

CreateNamedPipeA

Example of setting up a named pipe and using it for inter-process communication (IPC).

Using CreateNamedPipeA:

HANDLE hNamedPipe;
char buffer[1024];
DWORD bytesRead;

// Create a named pipe
hNamedPipe = CreateNamedPipeA(
    "\\\\.\\pipe\\MyNamedPipe",  // Pipe name
    PIPE_ACCESS_DUPLEX,         // Pipe open mode
    PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, // Pipe mode
    1,                          // Maximum instances
    1024,                       // Output buffer size
    1024,                       // Input buffer size
    0,                          // Default timeout (0 means blocking)
    NULL                        // Security attributes
);

if (hNamedPipe == INVALID_HANDLE_VALUE) {
    fprintf(stderr, "CreateNamedPipe failed with error %d\n", GetLastError());
    return 1;
}

printf("Waiting for a client to connect...\n");

// Wait for a client to connect to the named pipe
if (!ConnectNamedPipe(hNamedPipe, NULL)) {
    fprintf(stderr, "ConnectNamedPipe failed with error %d\n", GetLastError());
    CloseHandle(hNamedPipe);
    return 1;
}

printf("Client connected. Waiting for data...\n");

// Read data from the client
if (ReadFile(hNamedPipe, buffer, sizeof(buffer), &bytesRead, NULL)) {
    printf("Received data from client: %s\n", buffer);
} else {
    fprintf(stderr, "ReadFile failed with error %d\n", GetLastError());
}

// Clean up
CloseHandle(hNamedPipe);

return 0;

Others

Taken from microsoft docs:

Object
Description

Change notification

Created by the FindFirstChangeNotification function, its state is set to signaled when a specified type of change occurs within a specified directory or directory tree. For more information, see Obtaining Directory Change Notifications.

Console input

Created when a console is created. The handle to console input is returned by the CreateFile function when CONIN$ is specified, or by the GetStdHandle function. Its state is set to signaled when there is unread input in the console's input buffer, and set to nonsignaled when the input buffer is empty. For more information about consoles, see Character-Mode Applications

Job

Created by calling the CreateJobObject function. The state of a job object is set to signaled when all its processes are terminated because the specified end-of-job time limit has been exceeded. For more information about job objects, see Job Objects.

Memory resource notification

Created by the CreateMemoryResourceNotification function. Its state is set to signaled when a specified type of change occurs within physical memory. For more information about memory, see Memory Management.

Process

Created by calling the CreateProcess function. Its state is set to nonsignaled while the process is running, and set to signaled when the process terminates. For more information about processes, see Processes and Threads.

Thread

Created when a new thread is created by calling the CreateProcess, CreateThread, or CreateRemoteThread function. Its state is set to nonsignaled while the thread is running, and set to signaled when the thread terminates. For more information about threads, see Processes and Threads.

\

Last updated