A not so well known feature of the New Technology File System (NTFS) is the support for multiple data streams. Alternate data streams allow files to contain more than one stream of data. It's purpose was primarily to allow compatibility with Macintosh systems.
Every file has at least one data stream: In Windows, the default data stream is:$DATA.
Data Streams
Since :$DATA exists on every file, it can be an alternate way to access any file. Any applications that creates, looks at, or depends on the end of a file name (or extension) should be aware of alternate data streams. Unsanitized user input could use the :$DATA stream to change the behavior of the program.
Creating Alternate Data Streams
C:\> type C:\windows\system32\notepad.exe > c:\windows\system32\calc.exe:notepad.exe
typedef enum _FILE_INFO_BY_HANDLE_CLASS {
FileBasicInfo,
FileStandardInfo,
FileNameInfo,
FileRenameInfo, // This is the flag we will use to change the filename.
...
}
SetFileInformationByHandle WinAPI function:
if (!SetFileInformationByHandle(
hFile,
FileRenameInfo,
&pRename,
sRename
)) {
wprintf(L"SetFileInformationByHandle Failed with Error %d", GetLastError());
return -1;
};
Delete The Data Stream
We then delete the :$DATA stream to erase the file from the disk.
We will close and reopen the previous handle.
Final Code
// The new data stream name
#define NEW_STREAM L":Foobar"
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 StreamLength = wcslen(NewStream) * sizeof(wchar_t);
SIZE_T sRename = sizeof(FILE_RENAME_INFO) + StreamLength;
// 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 = StreamLength;
RtlCopyMemory(pRename->FileName, NewStream, StreamLength);
//----------------------------------------------------------------------------------------
// 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);
//----------------------------------------------------------------------------------------
// DELETING
// 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;
}
Hidden Files Misc
We can confirm that the files are in fact hidden when viewing the explorer
The file handle can be retrieved using the CreateFile WinAPI. The must be set to DELETE to provide file deletion permissions.
We can rename the default data stream of the running binary with WinAPI function with the flag. There is a list of enum values that we can choose from.
To do so, the same SetFileInformationByHandle WinAPI will be used, with a different flag, This flag marks the file for deletion when its handle is closed.