Post

IAT-Camoflage

IAT-Camoflage

What is it?

Floods the IAT with legitimate-looking Windows API imports by calling them inside a dead code path. A compile-time-derived value ensures the condition guarding those calls is provably false at runtime, so the APIs are never actually called — but they appear in the binary’s import table, burying any genuinely suspicious imports in noise.

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
RandomCompileTimeSeed():
  Computes a value from __TIME__ macro (hours/minutes/seconds of compilation):
    '0' * -40271
    + seconds_ones  * 1
    + seconds_tens  * 10
    + minutes_ones  * 60
    + minutes_tens  * 600
    + hours_ones    * 3600
    + hours_tens    * 36000
  → returns an int derived from when the binary was compiled


HelperFunc(&pAddr):
  HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0xFF)
  *(int*)pAddr = RandomCompileTimeSeed() % 0xFF
  → *pAddr is always in range [0, 254]
  return pAddr


IATCamo():
  pAddr = NULL
  int* a = HelperFunc(&pAddr)

  if (*a > 350) {               ← ALWAYS FALSE: *a is 0..254, never > 350
    MessageBoxA(NULL, NULL, NULL, NULL);   ← dead code
    GetLastError();
    RegisterClassW(NULL);
    IsWindowVisible(NULL);
    ConvertDefaultLocale(NULL);
    MultiByteToWideChar(NULL, NULL, NULL, NULL, NULL, NULL);
    IsDialogMessageW(NULL, NULL);
  }
  HeapFree(GetProcessHeap(), 0, pAddr);


What appears in the binary's IAT:
  kernel32.dll:
    HeapAlloc, HeapFree, GetLastError, MultiByteToWideChar
  user32.dll:
    MessageBoxA, RegisterClassW, IsWindowVisible,
    ConvertDefaultLocale, IsDialogMessageW

Static scanner sees:
  MessageBoxA, IsWindowVisible, ConvertDefaultLocale → GUI app?
  Nothing that screams injection or malware on first glance


The trick:
  Compiler cannot optimize away the dead block because the condition
  depends on a heap-allocated value at runtime — it can't prove at
  compile time that *a > 350 is unreachable (even though it is).
  So the linker keeps all the imports to satisfy potential execution paths.

The RandomCompileTimeSeed() serves two purposes: it makes the fake imports slightly different per build (since the seed changes with compilation time, the exact value of *a varies), and it makes the unreachable condition look more like a legitimate runtime check rather than if (FALSE). A static analyst has to trace through the math to prove the condition is always false rather than it being obviously dead code.

The weak point: the guarding APIs (HeapAlloc, HeapFree) reveal that something is being allocated — a careful analyst will notice the allocation and recognize the pattern. Also all NULL parameter calls would immediately crash or return errors if somehow triggered, which is a tell.

iat-camoflage.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
/*
	- Its important to make the malware appear to be normal so to instead hiding WinAPI's, its more effective to create fake imported functions.
	- This can be done by calling the WinAPI with NULL parameters.
*/

#include <Windows.h>
#include <stdio.h>

// Generate a compile-time-derived seed based on __TIME__
int RandomCompileTimeSeed(void)
{
	return '0' * -40271 +
		__TIME__[7] * 1 +			// seconds ones
		__TIME__[6] * 10 +			// seconds tens
		__TIME__[4] * 60 +			// minutes ones
		__TIME__[3] * 600 +			// minutes tens
		__TIME__[1] * 3600 +		// hours ones
		__TIME__[0] * 36000;		// hours tens
}

// Dummy helper intended to discourage compiler optimization.
PVOID HelperFunc(_Out_ PVOID* ppAddr) {
	PVOID pAddr = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0xFF);
	
	if (!pAddr)
		return NULL;

	// Store a compile-time-derived value (0-254) in the buffer
	*(int*)pAddr = RandomCompileTimeSeed() % 0xFF;

	// Return the allocated address to the caller
	*ppAddr = pAddr;

	return pAddr;

}

// Fill important the fake WinAPI to cameflage the IAT
VOID IATCamo() {
	PVOID pAddr = NULL;
	
	int* a = (int*)HelperFunc(&pAddr);

	// The generated value is always in the range [0, 254], making this condition impossible
	if (*a > 350) {
		unsigned __int64 i = MessageBoxA(NULL, NULL, NULL, NULL);
		i = GetLastError();
		i = RegisterClassW(NULL);
		i = IsWindowVisible(NULL);
		i = ConvertDefaultLocale(NULL);
		i = MultiByteToWideChar(NULL, NULL, NULL, NULL, NULL, NULL);
		i = IsDialogMessageW(NULL, NULL);
	}

	// Cleaning up
	HeapFree(GetProcessHeap(), 0, pAddr);
}

int main(void) {
	IATCamo();

	return 0;
}

This post is licensed under CC BY 4.0 by the author.
Source code: IAT-Camoflage