Self Deleting Executables

Introduction

In this post, we delve into the concept of “Self-Deleting Executables,” exploring their functionality, utility, and implementation. We’ll cover various applications of this concept and provide a detailed explanation, including a proof of concept (POC). As always, I’ll walk you through how it works and provide code examples. Additionally, we’ll explore the implementation of self-deleting executables on both Win32API and Linux platforms. We’ll examine existing examples for Windows and develop our implementation for Linux/Unix systems, focusing on deleting locked files or the current running executable from disk.

Overview

Why would someone want a program to delete itself? Well, evasion and stealth are common reasons, but there’s also the consideration of operational security (OPSEC) tactics. If a program handles sensitive information and its discovery could compromise security, then self-deletion makes sense, or perhaps just an uninstallation. So, I’ve gathered information on how to enable an executable to delete itself from disk while running. However, there’s very little information available, and what does exist is obscure. but there are techniques that have been around for years, yet there’s been little discussion on this topic. Let’s begin.

Example 001

Normally it is impossible to delete an executable file whilst it is running. Just run program.exe, then try to delete the executable - it won’t work, same thing with linux/mac the executable need to be closed or when the process terminates in order to preform a deletion of the program, However there’s a work around this problem, Let’s look at this example below

    // Get the path of the current executable
    char szPath[MAX_PATH];
    GetModuleFileName(NULL, szPath, MAX_PATH);
    DeleteFile(szPath)

The code above retrieves the full path to the current executable, and then tries to delete the file whilst running. This code will fail because of this thing called memory-mapped files to load an executable image into memory.

When the Windows loader runs an executable, it opens the executable’s disk file and maps that region of disk into memory, effectively loading the executable into memory. This disk file is kept open during the lifetime of the process, and is only closed when the process terminates. Because of this lock on the file.

Instead of deleting the executable from within itself, we can rely on a parent process. After the executable finishes its task, it can signal the parent process to delete it. However, this approach sidesteps the idea of self-deletion.

Another method involves designing a routine where the executable first terminates itself and then deletes its own file. This essentially brings us back to the initial technique, which ends with the termination of the executable process. However, implementing this requires careful coding because the program needs to handle both tasks. but it gets the job done.

int CommitSuicide(char * szCmdLine) {
    HANDLE hTemp;
    char szPath[MAX_PATH];
    char szTemp[MAX_PATH];

    static BYTE buf[1024];

    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    UINT ret;

    //open a temporary file
    GetTempPath(MAX_PATH, szTemp);
    lstrcat(szTemp, "suicide.exe");

    GetModuleFileName(0, szPath, MAX_PATH);

    CopyFile(szPath, szTemp, FALSE);

    hTemp = CreateFile(szTemp, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, 0,
      OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, 0);

    //Create a process using the temporary executable. This will cause
    //the file's handle count to increase, so we can close it.
    ZeroMemory( & si, sizeof(STARTUPINFO));
    ZeroMemory( & pi, sizeof(PROCESS_INFORMATION));

    ZeroMemory( & si, sizeof(STARTUPINFO));
    si.cb = sizeof(STARTUPINFO);

    lstrcat(szTemp, " ");
    lstrcat(szTemp, szCmdLine);

    ret = CreateProcess(0, szTemp, 0, 0, FALSE, NORMAL_PRIORITY_CLASS, 0, 0, & si, & pi);

    //Close our handle to the new process. Because the process is
    //memory-mapped, there will still be a handle held by the O/S, so
    //it won't get deleted. Give the other process a chance to run..
    Sleep(100);
    CloseHandle(hTemp);

    return 0;

When it’s time to delete ourselves, we first spawn a new process using the current executable file as the input parameter, creating a temporary file in the system’s temporary directory (tmp) and copying the current executable to that temporary file. We then create a new process using the temporary file and wait for a brief period using Sleep(). Afterward, we close the handle to the temporary file.

If any command-line parameters have been passed, we wait again using Sleep() and then proceed to delete the file specified by the command-line parameter. If no command-line parameters are passed, we respawn the path of the current executable file as a command-line parameter.

char szPath[MAX_PATH];
// Parse the command line
// Normally, we should not be run with any paramters
// We re-spawn ourselves with the current module's path

if(szCmdLine[0] == '\0')

{

// Spawn a duplicate process, and tell it to delete US
HMODULE hModule = GetModuleHandle(0);
GetModuleFileName(hModule, szPath, MAX_PATH);
CommitSuicide(szPath);
// Exit normally
return 0;

}

// This is where we pick up execution second time we run,
// When a filename has been specified on the command line

else

{

// Give the calling process time to exit...

Sleep(200);

// Delete the file specified on command line.

DeleteFile(szCmdLine);
// DeleteFile(szTemp);

return 0;

Pretty explanatory right, While this approach does get the job done. Still , the fact that our deletion code executes in the remote process even before Windows has had a chance to initialize it fully places some restrictions on the kind of APIs that we can invoke. It so turns out that APIs like DeleteFile and ExitProcess do work while the process is in this half-baked state.

To Workaround this There’s actually a proof of concept (POC) out there that shows how this works. Let’s break it down, To address our first problem, the code opens a handle to the current executable file early in the process execution. This means that deletion is initiated even before Windows fully initializes the process.

The ds_rename_handle function renames the program to a stream name, making it invisible in standard file system operations.

Additionally, the code marks the file for deletion on close (ds_deposite_handle), ensuring that it will be deleted automatically by the operating system once all handles to it are closed. This achieves self-deletion without relying on external processes or scheduling deletions for the next system startup.

static HANDLE ds_open_handle(PWCHAR pwPath) {
    return CreateFileW(pwPath, DELETE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
}

static BOOL ds_rename_handle(HANDLE hHandle) {
    FILE_RENAME_INFO fRename;
    RtlSecureZeroMemory(&fRename, sizeof(fRename));

    // Set our FileNameLength and FileName to DS_STREAM_RENAME
    LPWSTR lpwStream = DS_STREAM_RENAME;
    fRename.FileNameLength = sizeof(lpwStream);
    RtlCopyMemory(fRename.FileName, lpwStream, sizeof(lpwStream));

    return SetFileInformationByHandle(hHandle, FileRenameInfo, &fRename, sizeof(fRename) + sizeof(lpwStream));
}

static BOOL ds_deposite_handle(HANDLE hHandle) {
    // Set FILE_DISPOSITION_INFO::DeleteFile to TRUE
    FILE_DISPOSITION_INFO fDelete;
    RtlSecureZeroMemory(&fDelete, sizeof(fDelete));

    fDelete.DeleteFile = TRUE;

    return SetFileInformationByHandle(hHandle, FileDispositionInfo, &fDelete, sizeof(fDelete));
}

int main(int argc, char** argv) {
    WCHAR wcPath[MAX_PATH + 1];
    RtlSecureZeroMemory(wcPath, sizeof(wcPath));

    // Get the path to the current running process
    if (GetModuleFileNameW(NULL, wcPath, MAX_PATH) == 0) {
        // Error handling
    }

    // Open a handle to the current executable file
    HANDLE hCurrent = ds_open_handle(wcPath);
    if (hCurrent == INVALID_HANDLE_VALUE) {
        // Error handling
    }

    // Rename the associated handle's file name
    if (!ds_rename_handle(hCurrent)) {
        // Error handling
    }

    // Close the initial handle to allow for file deletion on close
    CloseHandle(hCurrent);

    // Reopen another handle to the current executable file and set it for deletion on close
    hCurrent = ds_open_handle(wcPath);
    if (hCurrent == INVALID_HANDLE_VALUE) {
        // Error handling
    }

    if (!ds_deposite_handle(hCurrent)) {
        // Error handling
    }

    // Close the handle, triggering the deletion deposition
    CloseHandle(hCurrent);

    // Verify that the file has been deleted
    if (PathFileExistsW(wcPath)) {
        // Error handling
    }

    return 0;
}

Simple check to check if running (VM) environment. If it detects that the program is running within a VM, it triggers a self-deletion mechanism. This can be a basic example of how such a technique might be used in the context of anti-analysis,

bool isVm = false; isVm = DetectVM::IsVboxVM(); isVm |= DetectVM::IsVMwareVM(); isVm |= DetectVM::IsMsHyperV(); if (isVm) { DetectVM::foo();

Other way we can also use already built in tools like SDelete SDelete - Sysinternals Simply invoke SDelete from within your routine using system calls or by spawning a new process. This is a better approach for ensuring operational security (OPSEC) compared to our first example.

SDelete securely deletes files by overwriting them with random patterns multiple times, making it virtually impossible to recover the file contents. However, this deletion process occurs only after the application terminates, which is a suitable approach when dealing with sensitive information.

However, there are some nuances to consider. For example, SDelete does not have a mechanism to allocate free space for secure overwriting. To overwrite the file names of a deleted file, SDelete renames the file 26 times, replacing each character each time. For instance, the first rename of “foo.txt” would be to “AAA.AAA”.

Cool sources inspired the article, Take a peek when you get a chance, it’s some pretty neat stuff. Here’s the links: http://www.catch22.net/tuts/self-deleting-executables https://blogorama.nerdworks.in/selfdeletingexecutables/

Example 002

Well, for Linux, it’s actually quite simple to achieve a self-deleting executable similar to Windows. You can start by spawning a child process that executes a separate thread responsible for attempting to delete the executable file. This approach makes sense because it ensures that the deletion process is carried out independently.

The difference lies in the use of the fork() system call on Linux, which is akin to Windows’ CreateProcess function. When you invoke fork(), it creates a child process that is essentially a copy of the parent process, inheriting its memory and resources. Additionally, threading on Linux is achieved through POSIX threads (pthreads), which are similar to the threads used in the example above. While the core concept remains consistent across operating systems, the main difference lies in the specific system calls, functions, and routines used to trigger the self-deletion process.

int SelfDelete(const char * executableName) {
  SELFDEL local = {
    0
  };
  pid_t pid = fork();

  if (pid == 0) {
    memcpy(local.opCodes, & remote_thread, CODESIZE);

    local.fnUnlink = unlink;
    local.fnExit = exit;

    getcwd(local.FileName, MAXPATHLEN);
    strcat(local.FileName, "/");
    strcat(local.FileName, executableName);

    pthread_attr_t attr;
    pthread_attr_init( & attr);
    pthread_attr_setdetachstate( & attr, PTHREAD_CREATE_DETACHED);
    pthread_t tid;

    pthread_create( & tid, & attr, (void * ( * )(void * )) & remote_thread, & local);
    pthread_attr_destroy( & attr);

    sleep(1);
  }
  return 0;
}

Pretty simple, I used this example in Dark Morph the self-del trigger can be activated either by checking a condition or by making a simple function call, Of course, this used in a malware context, illustrating how to achieve self-modification concepts, including anti-analysis techniques like self-deletion if a debugger exists. However, this is still a simple example.

But just like SDelete in Windows, Linux offers its own tool called shred. We can use it in an operational security (OPSEC) case to securely delete files beyond recovery by overwriting their contents multiple times with random data. Another approach is using the magic variable rm -- "$0", which deletes the currently running script or executable file.

script="$0"

# Function that is called when the script exits:
function finish {
    echo "Securely shredding ${script}"; shred -u ${script};
}
# When your script is finished, exit with a call to the function, "finish":
trap finish EXIT

Relies on Unix Shred (https://en.wikipedia.org/wiki/Shred_(Unix)) after successful execution. This can reduce the chance of leaking sensitive information contained within. shred -u "${0}"

That’s all for now. I hope you found this information useful. I’ve covered various methods to delete an executable while it’s running, Until next time.