72. Anti-Debugging - Self-Deletion

Anti-Debugging - Self-Deletion

Introduction

During the previous module, multiple techniques were discussed to obstruct researchers and malware analysts from inspecting the malware and prevent them from understanding the functionality or creating signatures. This module will cover an advanced anti-debugging technique that works by making the malware to self-delete.

The NTFS file system

Before diving into self-deletion, it's important to understand how New Technology File System (NTFS) works. NTFS is a proprietary file system implemented as the primary file system for the Windows operating system. It surpasses its predecessors, FAT and exFAT, by offering features such as file and folder permissions, compression, encryption, hard links, symbolic links, and transactional operations. NTFS also offers enhanced reliability, performance, and scalability.

NTFS file system also supports alternate data streams. Files in NTFS file systems can have multiple streams of data in addition to the default stream, :$DATA:$DATA exists for every file, providing an alternative means of accessing them.

Deleting A Running Binary

It is not possible to delete the current running process's binary on Windows since deleting a file normally requires that no other process is using it. The image below shows an unsuccessful attempt to delete the "Release" folder while having a file opened within that folder open.

Another example is shown using the DeleteFile WinAPI which deletes an existing file. The DeleteFile WinAPI fails with an ERROR_ACCESS_DENIED error.

One way to get around this is by renaming the default data stream :$DATA to another random name that represents a new data stream. After that, deleting the newly renamed data stream will result in the binary being erased from the disk, even while it's still running.

Retrieve File Handle

The first step of the process is to retrieve a handle of the target file, which is the local implementation's file. The file handle can be retrieved using the CreateFile WinAPI. The access flag must be set to DELETE to provide file deletion permissions.

Renaming The Data Stream

The next step to delete a running binary file is to rename the :$DATA data stream. This can be achieved by using the SetFileInformationByHandle WinAPI with the FileRenameInfo flag.

The SetFileInformationByHandle WinAPI function is shown below.

BOOL SetFileInformationByHandle(
  [in] HANDLE                    hFile,                       // Handle to the file for which to change information.
  [in] FILE_INFO_BY_HANDLE_CLASS FileInformationClass,        // Flag value that specifies the type of information to be changed
  [in] LPVOID                    lpFileInformation,           // Pointer to the buffer that contains the information to change for
  [in] DWORD                     dwBufferSize                 // The size of 'lpFileInformation' buffer in bytes
);

The FileInformationClass parameter should be a FILE_INFO_BY_HANDLE_CLASS enumeration value.

When the FileInformationClass parameter is set to FileRenameInfo, then lpFileInformation must be a pointer to the FILE_RENAME_INFO structure, this is mentioned by Microsoft as shown in the following image

FILE_RENAME_INFO Structure

The FILE_RENAME_INFO structure is shown below.

typedef struct _FILE_RENAME_INFO {
  union {
    BOOLEAN ReplaceIfExists;
    DWORD   Flags;
  } DUMMYUNIONNAME;
  BOOLEAN ReplaceIfExists;
  HANDLE  RootDirectory;
  DWORD   FileNameLength;   // The size of 'FileName' in bytes
  WCHAR   FileName[1];      // The new name
} FILE_RENAME_INFO, *PFILE_RENAME_INFO;

The two members that need to be set are FileNameLength and FileName. Microsoft's documentation explains how to define a new NTFS file stream name.

Therefore, FileName should be a wide-character string that starts with a colon (:).

Deleting The Data Stream

The last step is to delete the :$DATA stream to erase the file from the disk. To do so, the same SetFileInformationByHandle WinAPI will be used, with a different flag, FileDispositionInfo. This flag marks the file for deletion when its handle is closed. This is the flag Microsoft uses in the example section.

When the FileDispositionInfo flag is used, lpFileInformation must be a pointer to the FILE_DISPOSITION_INFO structure, this is mentioned by Microsoft as shown in the following image

The FILE_DISPOSITION_INFO structure is shown below.

typedef struct _FILE_DISPOSITION_INFO {
  BOOLEAN DeleteFile;       // Set to 'TRUE' to mark the file for deletion
} FILE_DISPOSITION_INFO, *PFILE_DISPOSITION_INFO;

The DeleteFile member must simply be set to TRUE to delete the file.

Refreshing File Data Stream

After calling SetFileInformationByHandle for the first time to rename the file's NTFS file stream, the file handle should be closed and re-opened with another CreateFile call. This is done to refresh the file data stream so that the new handle contains the new data stream.

Self-Deletion Final Code

The DeleteSelf function shown below uses the described process to delete a file from the disk while it's running.

Everything in the code snippet below has been previously explained except for the GetModuleFileNameW WinAPI. This function is used to retrieve the path for the file that contains the specified module. If the first parameter is set to NULL (as in the code snippet below), then it retrieves the path of the executable file for the current process.

// The new data stream name
#define NEW_STREAM L":Maldev"


BOOL DeleteSelf() {


	WCHAR                       szPath [MAX_PATH * 2] = { 0 };
	FILE_DISPOSITION_INFO       Delete                = { 0 };
	HANDLE                      hFile                 = INVALID_HANDLE_VALUE;
	PFILE_RENAME_INFO           pRename               = NULL;
	const wchar_t*              NewStream             = (const wchar_t*)NEW_STREAM;
	SIZE_T                      sRename               = sizeof(FILE_RENAME_INFO) + sizeof(NewStream);


    // Allocating enough buffer for the 'FILE_RENAME_INFO' structure
	pRename = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sRename);
	if (!pRename) {
		printf("[!] HeapAlloc Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

    // Cleaning up some structures
	ZeroMemory(szPath, sizeof(szPath));
	ZeroMemory(&Delete, sizeof(FILE_DISPOSITION_INFO));

	//----------------------------------------------------------------------------------------
    // Marking the file for deletion (used in the 2nd SetFileInformationByHandle call)
	Delete.DeleteFile = TRUE;

    // Setting the new data stream name buffer and size in the 'FILE_RENAME_INFO' structure
	pRename->FileNameLength = sizeof(NewStream);
	RtlCopyMemory(pRename->FileName, NewStream, sizeof(NewStream));

	//----------------------------------------------------------------------------------------

    // Used to get the current file name
	if (GetModuleFileNameW(NULL, szPath, MAX_PATH * 2) == 0) {
		printf("[!] GetModuleFileNameW Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	//----------------------------------------------------------------------------------------
    // RENAMING

    // Opening a handle to the current file
	hFile = CreateFileW(szPath, DELETE | SYNCHRONIZE, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
	if (hFile == INVALID_HANDLE_VALUE) {
		printf("[!] CreateFileW [R] Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	wprintf(L"[i] Renaming :$DATA to %s  ...", NEW_STREAM);

    // Renaming the data stream
	if (!SetFileInformationByHandle(hFile, FileRenameInfo, pRename, sRename)) {
		printf("[!] SetFileInformationByHandle [R] Failed With Error : %d \n", GetLastError());
		return FALSE;
	}
	wprintf(L"[+] DONE \n");

	CloseHandle(hFile);

	//----------------------------------------------------------------------------------------
    // DELEING

    // Opening a new handle to the current file
	hFile = CreateFileW(szPath, DELETE | SYNCHRONIZE, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
	if (hFile == INVALID_HANDLE_VALUE) {
		printf("[!] CreateFileW [D] Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	wprintf(L"[i] DELETING ...");

    // Marking for deletion after the file's handle is closed
	if (!SetFileInformationByHandle(hFile, FileDispositionInfo, &Delete, sizeof(Delete))) {
		printf("[!] SetFileInformationByHandle [D] Failed With Error : %d \n", GetLastError());
		return FALSE;
	}
	wprintf(L"[+] DONE \n");

	CloseHandle(hFile);

	//----------------------------------------------------------------------------------------

    // Freeing the allocated buffer
	HeapFree(GetProcessHeap(), 0, pRename);

	return TRUE;
}

Demo

The image below shows the SelfDeletion.exe process running although the binary file was erased from disk.