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.

pe-formatstatic-analysiswindows

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 own
  • advapi32, registry, services, tokens, privilege manipulation
  • ws2_32, wininet, winhttp, network capability, at descending levels of abstraction
  • crypt32, bcrypt, cryptography, which is as common in backup tooling as in ransomware
  • psapi, tlhelp32, process enumeration
  • user32 with hooking functions, input capture
  • netapi32, 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.

One field, any address. Those two things and nothing else, and replying to any of it removes you.