Timestomp

Created the Friday 13 November 2020. Updated 1 year, 10 months ago.

Adversaries may modify file time attributes to hide new or changes to existing files. Timestomping is a technique that modifies the timestamps of a file (the modify, access, create, and change times), often to mimic files that are in the same folder. This is done, for example, on files that have been modified or created by the adversary so that they do not appear conspicuous to forensic investigators or file analysis tools.


Technique Identifiers

U0303 T1070.006


Code Snippets

Unprotect

Description

Original source and author: https://github.com/limbenjamin/nTimetools

// #######################################################################
// ############ HEADER FILES
// #######################################################################
#include <windows.h>
#include <stdio.h>
#include <inttypes.h>
#include <math.h>

typedef LONG NTSTATUS;
char* VERSION_NO = "1.1";
HANDLE file = NULL;


typedef struct _IO_STATUS_BLOCK {
	union {
		NTSTATUS Status;
		PVOID Pointer;
	};
	ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

typedef enum _FILE_INFORMATION_CLASS {
	FileBasicInformation = 4,
	FileStandardInformation = 5,
	FilePositionInformation = 14,
	FileEndOfFileInformation = 20,
} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS;

typedef struct _FILE_BASIC_INFORMATION {
	LARGE_INTEGER CreationTime;							// Created             
	LARGE_INTEGER LastAccessTime;                       // Accessed    
	LARGE_INTEGER LastWriteTime;                        // Modifed
	LARGE_INTEGER ChangeTime;                           // Entry Modified
	ULONG FileAttributes;
} FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION;

typedef NTSTATUS(WINAPI *pNtSetInformationFile)(HANDLE, PIO_STATUS_BLOCK, PVOID, ULONG, FILE_INFORMATION_CLASS);
typedef NTSTATUS(WINAPI *pNtQueryInformationFile)(HANDLE, PIO_STATUS_BLOCK, PVOID, ULONG, FILE_INFORMATION_CLASS);

HANDLE LoadFile(char *filename, FILE_BASIC_INFORMATION *fbi);
VOID RetrieveFileBasicInformation(char *filename, FILE_BASIC_INFORMATION *fbi);
DWORD SetFileMACE(HANDLE file, DWORD fileAttributes, char *mtimestamp, char *atimestamp, char *ctimestamp, char *btimestamp);
LARGE_INTEGER ParseDateTimeInput(char *inputstring);
VOID About();
VOID Usage();

// #######################################################################
// ############ FUNCTIONS
// #######################################################################

VOID About() {
	printf("nTimestomp, Version %s\r\n", VERSION_NO);
	printf("Copyright (C) 2019 Benjamin Lim\r\n");
	printf("Available for free from https://limbenjamin.com/pages/ntimetools\r\n");
	printf("\r\n");
}

VOID Usage() {
	printf("\r\n");
	printf("Usage: .\\nTimestomp.exe [Modified Date] [Last Access Date] [Last Write Date] [Creation Date]\r\n");
	printf("Date Format: yyyy-mm-dd hh:mm:ss.ddddddd\r\n");
	printf("\r\n");
}

HANDLE LoadFile(char *filename, FILE_BASIC_INFORMATION *fbi) {

	HANDLE file = NULL;
	HMODULE ntdll = NULL;

	file = CreateFile(filename, GENERIC_READ | GENERIC_WRITE | FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, 0, NULL);
	if (file == INVALID_HANDLE_VALUE) {
		printf("Cannot open file: %S\r\n", filename);
		Usage();
		exit(1);
	}

	/* load ntdll and retrieve function pointer */
	ntdll = GetModuleHandle(TEXT("ntdll.dll"));
	if (ntdll == NULL) {
		printf("Cannot load ntdll\r\n");
		CloseHandle(file);
		exit(1);
	}
	FreeLibrary(ntdll);

	return file;
}

/* returns the handle on success or NULL on failure. this function opens a file and returns
the FILE_BASIC_INFORMATION on it. */
VOID RetrieveFileBasicInformation(HANDLE file, FILE_BASIC_INFORMATION *fbi) {

	HMODULE ntdll = NULL;
	pNtQueryInformationFile NtQueryInformationFile = NULL;
	IO_STATUS_BLOCK iostatus;

	/* load ntdll and retrieve function pointer */
	ntdll = GetModuleHandle(TEXT("ntdll.dll"));
	if (ntdll == NULL) {
		printf("Cannot load ntdll\r\n");
		CloseHandle(file);
		exit(1);
	}

	/* retrieve current timestamps including file attributes which we want to preserve */
	NtQueryInformationFile = (pNtQueryInformationFile)GetProcAddress(ntdll, "NtQueryInformationFile");
	if (NtQueryInformationFile == NULL) {
		CloseHandle(file);
		exit(1);
	}

	/* obtain the current file information including attributes */
	if (NtQueryInformationFile(file, &iostatus, fbi, sizeof(FILE_BASIC_INFORMATION), 4) < 0) {
		CloseHandle(file);
		exit(1);
	}

	/* clean up */
	FreeLibrary(ntdll);

}

DWORD SetFileMACE(HANDLE file, DWORD fileAttributes, char *mtimestamp, char *atimestamp, char *ctimestamp, char *btimestamp) {

	HMODULE ntdll = NULL;
	pNtSetInformationFile NtSetInformationFile = NULL;
	IO_STATUS_BLOCK iostatus;

	FILE_BASIC_INFORMATION fbi;
	fbi.LastWriteTime = ParseDateTimeInput(mtimestamp);
	fbi.LastAccessTime = ParseDateTimeInput(atimestamp);
	fbi.ChangeTime = ParseDateTimeInput(ctimestamp);
	fbi.CreationTime = ParseDateTimeInput(btimestamp);
	
	fbi.FileAttributes = fileAttributes;

	/* load ntdll and retrieve function pointer */
	ntdll = GetModuleHandle(TEXT("ntdll.dll"));
	if (ntdll == NULL) {
		printf("Cannot load ntdll\r\n");
		CloseHandle(file);
		exit(1);
	}

	NtSetInformationFile = (pNtSetInformationFile)GetProcAddress(ntdll, "NtSetInformationFile");
	if (NtSetInformationFile == NULL) {
		CloseHandle(file);
		exit(1);
	}

	if (NtSetInformationFile(file, &iostatus, &fbi, sizeof(FILE_BASIC_INFORMATION), FileBasicInformation) < 0) {
		CloseHandle(file);
		exit(1);
	}

	/* clean up */
	printf("File timestamp successfully set\r\n");
	FreeLibrary(ntdll);

	return 0;
}

LARGE_INTEGER ParseDateTimeInput(char *inputstring) {

	SYSTEMTIME systemtime = { 0 };
	LARGE_INTEGER nanoTime = { 0 };
	FILETIME filetime;
	LARGE_INTEGER dec = { 0 };
	LARGE_INTEGER res = { 0 };

	if (sscanf_s(inputstring, "%hu-%hu-%hu %hu:%hu:%hu.%7d", &systemtime.wYear, &systemtime.wMonth, &systemtime.wDay, &systemtime.wHour, &systemtime.wMinute, &systemtime.wSecond, &dec.QuadPart) == 0) {
		printf("Wrong Date Format");
		CloseHandle(file);
		exit(1);
	}

	/* sanitize input */

	if (systemtime.wMonth < 1 || systemtime.wMonth > 12) {
		printf("Wrong Date Format");
		CloseHandle(file);
		exit(1);
	}
	if (systemtime.wDay < 1 || systemtime.wDay > 31) {
		printf("Wrong Date Format");
		CloseHandle(file);
		exit(1);
	}
	if (systemtime.wYear < 1601 || systemtime.wYear > 30827) {
		printf("Wrong Date Format");
		CloseHandle(file);
		exit(1);
	}

	if (systemtime.wMinute < 0 || systemtime.wMinute > 59) {
		printf("Wrong Date Format");
		CloseHandle(file);
		exit(1);
	}
	if (systemtime.wSecond < 0 || systemtime.wSecond > 59) {
		printf("Wrong Date Format");
		CloseHandle(file);
		exit(1);
	}

	systemtime.wMilliseconds = 0;
	if (SystemTimeToFileTime(&systemtime, &filetime) == 0) {
		printf("Invalid filetime\r\n");
		CloseHandle(file);
		exit(1);
	}

	nanoTime.LowPart = filetime.dwLowDateTime;
	nanoTime.HighPart = filetime.dwHighDateTime;

	res.QuadPart = nanoTime.QuadPart + dec.QuadPart;

	return res;
}

/* returns 0 on error, 1 on success. this function converts a LARGE_INTEGER to a SYSTEMTIME structure */
DWORD ConvertLargeIntegerToLocalTime(SYSTEMTIME *localsystemtime, LARGE_INTEGER largeinteger) {

	FILETIME filetime;
	FILETIME localfiletime;
	DWORD result = 0;

	filetime.dwLowDateTime = largeinteger.LowPart;
	filetime.dwHighDateTime = largeinteger.HighPart;

	if (FileTimeToSystemTime(&filetime, localsystemtime) == 0) {
		printf("Invalid filetime\r\n");
		exit(1);
	}

	return 1;
}

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

	if (argc < 5) {
		Usage();
		exit(1);
	}

	FILE_BASIC_INFORMATION fbi; 
	struct _SYSTEMTIME time = { 0 };
	wchar_t filename[4096] = { 0 };
	char str[256];
	CHAR lpVolumeNameBuffer[MAX_PATH + 1] = { 0 };
	CHAR lpFileSystemNameBuffer[MAX_PATH + 1] = { 0 };
	LARGE_INTEGER lpFileSizeHigh = { 0 };

	About();
	MultiByteToWideChar(CP_ACP, 0, argv[1], -1, filename, 4096);
	file = LoadFile(filename, &fbi);
	GetVolumeInformationByHandleW(file, &lpVolumeNameBuffer, ARRAYSIZE(lpVolumeNameBuffer), 0, 0, 0, &lpFileSystemNameBuffer, ARRAYSIZE(lpVolumeNameBuffer));
	GetFileSizeEx(file, &lpFileSizeHigh);

	printf("Filesystem type:		%S\r\n", lpFileSystemNameBuffer);
	printf("Filename:			%S\r\n", filename);
	printf("File size:			%d\r\n", lpFileSizeHigh.QuadPart);
	printf("\r\n");

	SetFileMACE(file, fbi.FileAttributes, argv[2], argv[3], argv[4], argv[5]);
	RetrieveFileBasicInformation(file, &fbi);

	printf("\r\n");
	ConvertLargeIntegerToLocalTime(&time, fbi.LastWriteTime);
	if (fbi.LastWriteTime.QuadPart != 0) {
		sprintf_s(str, 256, "%lld", fbi.LastWriteTime.QuadPart);
		printf("[M] Last Write Time:		%04d-%02d-%02d %02d:%02d:%02d.%7s UTC\r\n", time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond, &str[11]);
	}
	else {
		printf("[M] Last Write Time:		%04d-%02d-%02d %02d:%02d:%02d.0000000 UTC\r\n", time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond);
	}
	memset(&time, 0, sizeof(time));

	ConvertLargeIntegerToLocalTime(&time, fbi.LastAccessTime);
	if (fbi.LastAccessTime.QuadPart != 0) {
		sprintf_s(str, 256, "%lld", fbi.LastAccessTime.QuadPart);
		printf("[A] Last Access Time:		%04d-%02d-%02d %02d:%02d:%02d.%7s UTC\r\n", time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond, &str[11]);
	}
	else {
		printf("[A] Last Access Time:		%04d-%02d-%02d %02d:%02d:%02d.0000000 UTC\r\n", time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond);
	}
	memset(&time, 0, sizeof(time));

	ConvertLargeIntegerToLocalTime(&time, fbi.ChangeTime);
	if (fbi.ChangeTime.QuadPart != 0) {
		sprintf_s(str, 256, "%lld", fbi.ChangeTime.QuadPart);
		printf("[C] Metadata Change Time:	%04d-%02d-%02d %02d:%02d:%02d.%7s UTC\r\n", time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond, &str[11]);
	}
	else {
		printf("[C] Metadata Change Time:	%04d-%02d-%02d %02d:%02d:%02d.0000000 UTC\r\n", time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond);
	}
	memset(&time, 0, sizeof(time));

	ConvertLargeIntegerToLocalTime(&time, fbi.CreationTime);
	if (fbi.CreationTime.QuadPart != 0) {
		sprintf_s(str, 256, "%lld", fbi.CreationTime.QuadPart);
		printf("[B] Creation Time:		%04d-%02d-%02d %02d:%02d:%02d.%7s UTC\n", time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond, &str[11]);
	}
	else {
		printf("[B] Creation Time:		%04d-%02d-%02d %02d:%02d:%02d.0000000 UTC\n", time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond);
	}
	printf("\r\n");
	CloseHandle(file);
}

Jean-Pierre LESUEUR

Description

This tiny code snippet demonstrate the principle of file time stomping.

Steps:

  • Enumerate files in current directory (excluding the target file).
  • Sort enumerated files by modification date.
  • Takes the most recent file and apply its File Creation Date, File Last Modification and File Last Access to our target file.

Additional information:

  • Supports relative target file.
  • If no files lives inside the current directory, then current directory (parent folder) date information are used.
  • If no files lives inside the current directory and current directory is a root path, then timestomp procedure fails.

using System;
using System.IO;

void timeStomp(String targetFile)
{
    targetFile = Path.GetFullPath(targetFile);

    if (!File.Exists(targetFile))
    {
        throw new FileNotFoundException(String.Format("File \"{0}\" does not exists.", targetFile));
    }

    string? parentDirectory = Path.GetDirectoryName(targetFile);
    bool isInRoot = false;

    if (parentDirectory == null)
    {
        parentDirectory = Directory.GetDirectoryRoot(targetFile);
        isInRoot = true;
    }

    var options = new EnumerationOptions()
    {
        IgnoreInaccessible = true,
        RecurseSubdirectories = true,
        AttributesToSkip = FileAttributes.System | FileAttributes.Hidden,
    };

    var candidates = new DirectoryInfo(parentDirectory)
        .GetFiles("*.*", options)
        .Where(file => !file.FullName.Equals(targetFile, StringComparison.OrdinalIgnoreCase))
        .OrderByDescending(file => file.LastWriteTime)
        .ToList();

    FileInfo? candidate = null;    
    
    if (candidates.Count > 0)
    {
        candidate = candidates.First();
    }   
    else if (!isInRoot)
    {
        candidate = new FileInfo(parentDirectory);
    }

    if (candidate != null)
    {
        Console.WriteLine(string.Format("Using \"{0}\" file for timeStomping...", candidate));

        File.SetCreationTime(targetFile, candidate.CreationTime);
        File.SetLastAccessTime(targetFile, candidate.LastAccessTime);
        File.SetLastWriteTime(targetFile, candidate.LastWriteTime);

        Console.WriteLine("Done.");
    }
    else
    {
       throw new Exception("Could not find suitable existing file for timeStomping...");
    }
}

timeStomp("G:\\test\\sub7.exe");

Detection Rules

rule:
  meta:
    name: timestomp file
    namespace: anti-analysis/anti-forensic/timestomp
    authors:
      - moritz.raabe@mandiant.com
    scope: function
    att&ck:
      - Defense Evasion::Indicator Removal on Host::Timestomp [T1070.006]
    examples:
      - Practical Malware Analysis Lab 03-04.exe_:0x4014e0
  features:
    - and:
      - or:
        - api: kernel32.GetSystemTime
        - api: kernel32.FileTimeToLocalFileTime
        - api: kernel32.GetSystemTimeAsFileTime
        - api: kernel32.SystemTimeToFileTime
        - api: kernel32.GetFileTime
      - api: kernel32.SetFileTime

Additional Resources

External Links

Subscribe to our Newsletter


The information entered into this form is mandatory. It will be subjected to computer processing. It is processed by computer in order to support our users and readers. The recipients of the data will be : contact@unprotect.it.

According to the Data Protection Act of January 6th, 1978, you have at any time, a right of access to and rectification of all of your personal data. If you wish to exercise this right and gain access to your personal data, please write to Thomas Roccia at contact@unprotect.it.

You may also oppose, for legitimate reasons, the processing of your personal data.