When to Use / When Not to Use
- Use: to determine which libraries/APIs a program links; match compiler and library versions by import characteristics; locate suspicious imports (injection/network/crypto APIs); analyze export interfaces of plugins and DLLs
- Don't use: when you only need the internal logic of a function (use the decompilation skill directly)
- Don't use: when a packer statically hides imports (dynamically resolved APIs are not in the IAT—combine with [[re-tracing]] / [[re-memdump]] to retrieve them from memory)
Tool Preparation
Refer to [[re-analyze/platform-tips]]—import table analysis is a static step, no sandbox needed; anything involving execution (verifying dynamically resolved APIs) must go into a sandbox per the highest principle.
objdump / readelf(binutils)
- Linux:
apt install binutils/dnf install binutils/pacman -S binutils - macOS:
brew install binutils(gobjdump); for Mach-O useotool -L+nm -u - WSL: Linux version
- Verify:
objdump -V
pefile(Python)
- All platforms:
pip install pefile - Verify:
python3 -c "import pefile; print(pefile.__version__)"
rizin / rz-bin
- For the installation matrix see [[re-radare2]]; after installation use
rizin -vto verify - Windows: the official release works out of the box after extraction; radare2 users can use
choco install radare2for compatibility (commands below) - Verify:
rz-bin -V
Ghidra FLIRT plugin (ghidra_flirt)
- All platforms:
git clone https://github.com/nneonneo/ghidra_flirt && cd ghidra_flirt && make(requires Ghidra to be installed, see [[re-ghidra]]) - Verify: the ghidra_flirt extension appears within Ghidra
Operation Steps
-
List imported functions:
- PE:
objdump -p sample.exe | grep 'DLL Name' -A3 | head -30import pefile pe = pefile.PE('sample.exe') for e in pe.DIRECTORY_ENTRY_IMPORT: print(e.dll, [i.name for i in e.imports if i.name][:15]) - ELF:
objdump -T sample | grep UND # 未定义符号 = 导入 readelf -s sample | grep -i ' UND ' - Mach-O:
otool -L sample # 依赖 dylib 列表 nm -u sample # 未定义符号 = 导入
- PE:
-
Match libraries/compilers by import characteristics (FLIRT approach):
- FLIRT signatures: IDA
File > Load file > FLIRT signature file...(comes with a sig directory) automatically annotates library functions; for Ghidra use the ghidra_flirt plugin (Tool Preparation) - libc version fingerprint:
readelf -s sample | grep -c ''together withstrings sample | grep -E 'GLIBC_[0-9.]+'(GLIBC symbol version numbers);strings libc.so.6 | grep -m1 versionon the runtime library side - Language fingerprint: Go (
runtime.main/go1.2xstring), Rust (_ZN/__rust_*)、C++(_Zmangled)、Delphi(@System@)、.NET(mscoree + metadata) - Rich Header (PE) cross-validates the compiler version, see [[re-format-pe]]
- FLIRT signatures: IDA
-
Locate suspicious imports:
- Process injection category:
OpenProcess+VirtualAllocEx+WriteProcessMemory+CreateRemoteThread(kernel32);NtCreateThreadEx(ntdll) - Network callback category:
WSAStartup/socket/connect/HttpSendRequestA/WinHttpOpen - Crypto/data-theft category:
CryptEncrypt/CryptDecrypt/BCryptEncrypt/CryptExportKey(export keys) - Anti-debugging category:
IsDebuggerPresent/NtQueryInformationProcess/OutputDebugStringA(exception-triggered detection) - ELF side:
ptrace/socket/execve/dlopen/fork; Mach-O side:task_for_pid/_dyld_* - Annotate any hits to the analysis notes; the next step is to go to the decompilation skill to locate the call sites
- Process injection category:
-
Export table analysis (plugin/service-type samples):
- PE:
objdump -p sample.dll | grep -A30 'Export Table'import pefile pe = pefile.PE('sample.dll') for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols: print(hex(exp.address), exp.name) - ELF
.so:readelf -s sample.so | grep -v UND | grep FUNC(exported global functions) - Mach-O dylib:
nm -gU sample.dylib - The exported interface names of plugin-type samples reveal functionality (e.g., internal commands of AV/injectors/toolkits)
- PE:
-
Verify against system libraries(to determine whether system APIs are mixed in with suspicious parameters):
strings sample.exe | grep -iE '\.dll|\.so|\.dylib' # 附加库路径线索
Cross-Domain Combination
- [[re-binary-core]]: fixed invocation at step 4 of the workflow
- [[re-malware]]: screening for malicious import characteristics (injection/data theft/callback)
- [[re-cracking]]: find API call sites related to validation/registration
- Connects with [[re-format-pe]] / [[re-format-elf]] / [[re-format-macho]]; when a packer hides imports, switch to [[re-anti-analysis]] + [[re-memdump]]
Common Pitfalls and Traps
- The import table is redirected/encrypted by a packer: The statically read IAT is the packer's placeholder/encrypted—the real imports must be retrieved from memory after runtime ([[re-memdump]])
- Dynamically resolved APIs are not in the IAT:
GetProcAddress/dlopen+dlsymresolved only at runtime, not findable in the static import table—combine with [[re-tracing]] to observe - String comparison of library versions is more reliable: symbols can be stripped/obfuscated, but Go/OpenSSL version strings (
go1.21、OpenSSL 3.0.x) are in the strings - Functions imported by ordinal have only numbers and no names—need to compare against Microsoft ordinal tables or runtime library export tables
- The runtime GOT/IAT may have been rewritten by constructors: symptom—the static import table is clean, but after runtime it calls abnormal functions; cause—malicious constructors (
.init_array/TLS callbacks) overwrite GOT/IAT entries before entry (e.g.strcmp@GOT) to install hooks, invisible in the static view; countermeasure—compare import conclusions with the runtime memory view ([[re-memdump]]/[[re-tracing]]); when a sample has initialization callbacks, list the callbacks first before drawing conclusions - Family clustering uses imphash rather than import name lists: symptom—variants of the same family differ greatly in import names, and clustering by name fails; cause—import name lists are sensitive to linker version/packing; countermeasure—use pefile's
imphash(hash after normalizing imported functions) as a fingerprint; note that imphash is distorted for samples whose IAT is taken over by a packer, so unpack first before calculating