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;
}