Strings & Import Analysis
Overview
Extracting readable strings and analyzing API imports are fundamental static analysis techniques. Strings reveal URLs, IP addresses, file paths, registry keys, error messages, and command-line arguments. Import analysis reveals which system APIs a binary calls, directly indicating its capabilities (networking, file I/O, process manipulation, encryption, etc.).
String Extraction
strings Command
# GNU Binutils (strings)
# https://www.gnu.org/software/binutils/
# Extract ASCII strings (default: minimum 4 characters)
strings sample.exe
# Set minimum string length
strings -n 8 sample.exe
# Extract wide-character (UTF-16LE) strings (common in Windows malware)
strings -e l sample.exe
# Show file offset of each string (hex)
strings -t x sample.exe
# Show file offset (decimal)
strings -t d sample.exe
# Scan entire file, not just data sections (this is now the default behaviour)
strings -a sample.exe
# Combine: both ASCII and wide strings with offsets
strings -t x sample.exe > strings_ascii.txt
strings -t x -e l sample.exe > strings_wide.txt
rabin2 String Extraction
# radare2
# https://github.com/radareorg/radare2
# Extract strings from data sections
rabin2 -z sample.exe
# Extract strings from the entire binary
rabin2 -zz sample.exe
# Extract wide strings (UTF-16)
r2 -e bin.str.enc=utf16le -qc 'izz' sample.exe
What to Look For in Strings
| Category | Examples |
|---|---|
| URLs / domains | http://, https://, .com, .net, .onion |
| IP addresses | 192.168., 10.0., any dotted quad |
| File paths | C:\Windows\, /tmp/, %APPDATA% |
| Registry keys | HKLM\, HKCU\, Software\Microsoft\Windows\CurrentVersion\Run |
| Commands | cmd.exe, /bin/sh, powershell, wget, curl |
| Crypto artifacts | -----BEGIN, AES, RSA, base64-encoded blobs |
| User agents | Mozilla/, User-Agent: |
| Passwords / credentials | password, admin, login, hardcoded tokens |
| Error / debug messages | Function names, debug output, compiler artifacts |
| Mutex names | Global\, named mutexes for single-instance checks |
Filtering Interesting Strings
# GNU Binutils (strings)
# https://www.gnu.org/software/binutils/
# URLs and domains
strings sample.exe | grep -iE 'https?://|www\.|\.com|\.net|\.org'
# IP addresses
strings sample.exe | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'
# File paths (Windows)
strings sample.exe | grep -iE '[A-Z]:\\|%[A-Z]+%'
# File paths (Linux)
strings sample | grep -E '^/(tmp|etc|var|bin|usr|home)/'
# Registry keys
strings sample.exe | grep -iE 'HKLM|HKCU|HKCR|CurrentVersion\\Run'
# Email addresses
strings sample.exe | grep -oE '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
# Base64-encoded strings (long alphanumeric sequences)
strings sample.exe | grep -E '^[A-Za-z0-9+/]{20,}={0,2}$'
# Commands and shells
strings sample.exe | grep -iE 'cmd\.exe|powershell|/bin/(sh|bash)|wget|curl'
Encoded and Obfuscated Strings
Malware frequently encodes or encrypts strings to evade detection. Common techniques include:
| Technique | Detection Approach |
|---|---|
| XOR encoding | Look for repeated XOR key patterns; high entropy regions |
| Base64 | Decode long alphanumeric strings with base64 -d |
| ROT13 / Caesar | Try rotational shifts on suspicious strings |
| Stack strings | Single characters pushed to stack (visible in disassembly) |
| RC4 / AES encrypted | Requires key recovery during dynamic analysis or reversing |
| String stacking | Characters assembled one at a time in code |
# Decode a base64 string found in the binary
echo 'aHR0cDovL2V4YW1wbGUuY29t' | base64 -d
# XOR brute-force with single-byte key (Python one-liner)
python3 -c "
data = open('sample.exe','rb').read()
for key in range(1, 256):
decoded = bytes(b ^ key for b in data[0x1000:0x1100])
if b'http' in decoded or b'.exe' in decoded:
print(f'Key: {key:#04x}, Decoded: {decoded}')
"
Import Analysis
PE Imports with pefile
# pefile
# https://github.com/erocarrera/pefile
import pefile
pe = pefile.PE('sample.exe')
# List all imports by DLL
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
for entry in pe.DIRECTORY_ENTRY_IMPORT:
print(f"\n{entry.dll.decode()}")
for imp in entry.imports:
name = imp.name.decode() if imp.name else f"ordinal_{imp.ordinal}"
print(f" {hex(imp.address):12s} {name}")
# Import hash (same source code = same imphash)
print(f"\nImphash: {pe.get_imphash()}")
PE Imports with rabin2
# radare2
# https://github.com/radareorg/radare2
# List all imports
rabin2 -i sample.exe
# List imports grouped by library
rabin2 -i sample.exe | sort -t. -k1
ELF Imports
# GNU Binutils (readelf)
# https://www.gnu.org/software/binutils/
# Show dynamic symbols (imports)
readelf --dyn-syms sample | grep -E 'UND|FUNC'
# Show shared library dependencies
readelf -d sample | grep NEEDED
# radare2
# https://github.com/radareorg/radare2
rabin2 -i sample
Suspicious Import Categories
Process Manipulation:
| DLL | Function | Capability |
|---|---|---|
| kernel32.dll | CreateRemoteThread |
Inject thread into another process |
| kernel32.dll | VirtualAllocEx |
Allocate memory in another process |
| kernel32.dll | WriteProcessMemory |
Write to another process's memory |
| kernel32.dll | OpenProcess |
Open handle to another process |
| ntdll.dll | NtUnmapViewOfSection |
Process hollowing |
| kernel32.dll | QueueUserAPC |
APC injection |
Execution:
| DLL | Function | Capability |
|---|---|---|
| kernel32.dll | CreateProcessA/W |
Launch a new process |
| kernel32.dll | WinExec |
Execute a command |
| shell32.dll | ShellExecuteA/W |
Execute file/URL |
| msvcrt.dll | system |
Execute shell command |
File System:
| DLL | Function | Capability |
|---|---|---|
| kernel32.dll | CreateFileA/W |
Create or open files |
| kernel32.dll | ReadFile, WriteFile |
Read/write file contents |
| kernel32.dll | DeleteFileA/W |
Delete files |
| kernel32.dll | CopyFileA/W |
Copy files |
| kernel32.dll | MoveFileA/W |
Move/rename files |
| kernel32.dll | FindFirstFileA/W |
Directory enumeration |
Networking:
| DLL | Function | Capability |
|---|---|---|
| ws2_32.dll | socket, connect, bind |
Raw socket operations |
| ws2_32.dll | send, recv |
Data transfer |
| wininet.dll | InternetOpenA/W |
Initialize WinINet |
| wininet.dll | InternetOpenUrlA/W |
Open a URL |
| wininet.dll | HttpSendRequestA/W |
Send HTTP request |
| urlmon.dll | URLDownloadToFileA/W |
Download file from URL |
| winhttp.dll | WinHttpOpen, WinHttpSendRequest |
HTTP client |
Persistence:
| DLL | Function | Capability |
|---|---|---|
| advapi32.dll | RegSetValueExA/W |
Write registry value |
| advapi32.dll | RegCreateKeyExA/W |
Create registry key |
| advapi32.dll | CreateServiceA/W |
Install a service |
| kernel32.dll | CopyFileA/W |
Copy self to persist location |
Evasion and Anti-Analysis:
| DLL | Function | Capability |
|---|---|---|
| kernel32.dll | IsDebuggerPresent |
Detect debugger |
| kernel32.dll | CheckRemoteDebuggerPresent |
Detect remote debugger |
| kernel32.dll | GetTickCount |
Timing check (sandbox evasion) |
| kernel32.dll | Sleep |
Delay execution (sandbox evasion) |
| kernel32.dll | VirtualProtect |
Change memory permissions (unpack) |
Encryption:
| DLL | Function | Capability |
|---|---|---|
| advapi32.dll | CryptAcquireContextA/W |
Initialize crypto provider |
| advapi32.dll | CryptEncrypt, CryptDecrypt |
Encrypt/decrypt data |
| advapi32.dll | CryptGenKey |
Generate encryption key |
| bcrypt.dll | BCryptEncrypt, BCryptDecrypt |
Modern crypto API |
Minimal Imports (Red Flag)
Malware that resolves APIs at runtime via GetProcAddress or LdrGetProcedureAddress
will have very few static imports — often just kernel32.dll with
LoadLibraryA and GetProcAddress. This is a strong indicator of:
- Packed/encrypted binaries that resolve imports after unpacking
- Malware intentionally hiding capabilities from static import analysis
- Shellcode loaders
# pefile
# https://github.com/erocarrera/pefile
import pefile
pe = pefile.PE('sample.exe')
# Count total imports
total = 0
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
for entry in pe.DIRECTORY_ENTRY_IMPORT:
total += len(entry.imports)
print(f"Total imports: {total}")
# Very low count (< 10) with LoadLibraryA + GetProcAddress = suspicious
Export Analysis
Exports are functions a DLL exposes for other modules to call. Malware DLLs often use exports to provide entry points for different functionality.
# pefile
# https://github.com/erocarrera/pefile
import pefile
pe = pefile.PE('sample.dll')
if hasattr(pe, 'DIRECTORY_ENTRY_EXPORT'):
for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
name = exp.name.decode() if exp.name else "N/A"
print(f" Ordinal: {exp.ordinal} Name: {name} "
f"Address: {hex(pe.OPTIONAL_HEADER.ImageBase + exp.address)}")
Suspicious export names include DllRegisterServer (COM registration),
ServiceMain (service entry), and generic names like Start, Run, Init.