What an import table tells you about intent
The closest thing to a declaration of capability that a binary offers without being run, and the two ways it lies.
Before a Windows executable runs, it has already told you a great deal about
what it intends to do. The import directory lists every function it expects the
loader to resolve, and functions are capabilities. A program that never imports
anything from ws2_32.dll is not opening a socket through the normal path.
Reading the shape rather than the entries
Individual imports matter less than the profile. A handful of DLLs and their usual implications:
kernel32, universal, tells you nothing on its ownadvapi32, registry, services, tokens, privilege manipulationws2_32,wininet,winhttp, network capability, at descending levels of abstractioncrypt32,bcrypt, cryptography, which is as common in backup tooling as in ransomwarepsapi,tlhelp32, process enumerationuser32with hooking functions, input capturenetapi32, domain and share enumeration
A backup utility that imports file APIs, compression and crypto is coherent. A purported PDF viewer that imports process enumeration, token manipulation and raw sockets is describing something other than viewing PDFs. That mismatch, between what a file claims and what it equips itself to do, is where the signal lives.
The first way it lies: dynamic resolution
LoadLibrary and GetProcAddress resolve functions at runtime, and anything
resolved that way never appears in the import table. This is completely
ordinary: plugin systems, optional features and version-dependent APIs all use
it, and it is also the standard way to hide capability.
The tell is the shape of the absence. A large binary with a very short import
table has to be resolving imports somehow, and a near-empty table containing
little beyond LoadLibrary and GetProcAddress is a statement in itself.
Function name strings often survive in the binary even when the imports do not,
which is why strings and imports are worth reading together rather than
separately.
The second way it lies: delay loading
Delay-loaded imports live in a separate directory and are resolved on first call. Tooling that reads only the standard import directory misses them entirely. They are legitimate and common; they are simply somewhere else, and a reader who does not know to look finds a cleaner-looking binary than the one in front of them.
Where this leaves you
An import table is not a verdict and it is not evidence of malice. It is a statement of capability that is expensive to fake completely and cheap to read, which is a rare combination.
Its real value is comparative. A binary whose imports match its claimed purpose raises no question. One whose imports describe a different program than its name, its version resource and its install location suggest is not an unknown: it is a specific finding, at a confidence level you can state out loud.
More of this
New posts on what a file tells you before you run it: headers, signatures, manifests, overlays, the fields most inventory never reads. Only when there is something worth the write-up.
We intend to offer a tool that does this across every file on an endpoint. You get one message when you can use it.