Reading width
Wide uses the full column for everything, text, diagrams, code, and exercises. Narrow keeps the standard reading width.
Text size
Scales the body text. Headings and code blocks keep their size.
In this section
Volatility Symbol Tables: banners, isfinfo, ISF Files and Why an Exact Match Is Required
Introduction
Five subs have now run plugins that worked, on an image that Volatility could read. This one is about the morning they do not, which arrives sooner than anybody expects and produces an error message that points you at the wrong thing.
Every plugin in those subs depends on layout information that is not in the image and never was, and when Volatility cannot find information matching the build it is reading, it cannot read a single field. It says so, loudly, and the checklist it prints first asks whether the file is a valid memory image that was acquired cleanly. That is the same text it prints for a file that holds no memory at all, so the symptom of a missing symbol table is indistinguishable, on screen, from the symptom of a bad capture. That ambiguity is what this sub is really about.
Scenario
A colleague sends you an image from a Linux server that was decommissioned last year, and every plugin you run stops with the same block of unsatisfied requirements and suggests the file may not be a valid memory image. You check your syntax, which is fine. You check the file, which is the right size and not obviously corrupt. Nothing in the error distinguishes this from a broken capture, and the correct conclusion is not that the capture was bad. It is that Volatility has never been able to read it at all, and the fix is a file that may no longer exist anywhere.
Confirming It Is Symbols and Not the Image
Read the error, and the exit statusMF0.1 said to run windows.info before anything else on a Windows image, because it is the cheapest proof that Volatility can read the image at all. This image is Linux, and the first plugin you point at it fails like this.
$ vol -f decommissioned.raw linux.pslist 2>/dev/null; echo $?
Volatility 3 Framework 2.28.2
Unsatisfied requirement plugins.PsList.kernel.layer_name:
Unsatisfied requirement plugins.PsList.kernel.symbol_table_name:
A translation layer requirement was not fulfilled. Please verify that:
A file was provided to create this layer (by -f, --single-location or by config)
The file exists and is readable
The file is a valid memory image and was acquired cleanly
A symbol table requirement was not fulfilled. Please verify that:
The associated translation layer requirement was fulfilled
You have the correct symbol file for the requirement
The symbol file is under the correct directory or zip file
The symbol file is named appropriately or contains the correct banner
1
Two requirements could not be met, and the exit status of 1 says the plugin never ran. 2>/dev/null hides the progress line and a closing summary of the same two requirements, both written to standard error. The symbol table requirement is the one this sub is about. The translation layer fails with it on a Linux image because Volatility finds a Linux kernel's page tables through that kernel's own symbols, so with no table it cannot build the layer either, and the first checklist starts asking about the file. MF0.7 runs a plugin against a disk image, which holds no memory at all, and gets the same two requirements and the same checklists word for word.
Compare that with the shape of other failures so you can tell them apart quickly. A plugin name that does not exist produces an error naming the plugin, and --hide-columns written straight before the plugin name produces Please select a plugin to run. Both exit with 2 rather than 1, because the command line was rejected before any requirement was tried. A file that is not memory and memory you hold no symbols for produce the same requirement errors, and nothing in this output separates them. The next section does.
The expensive mistake here is believing the first checklist. Somebody reads that the file may not be a valid memory image, writes that the capture failed, and the image is set aside or the machine, if it still exists, is captured again. On a decommissioned server there is no second capture, and an image that was fine has been written off as corrupt. The correct sentence, until the next section says otherwise, is that the image could not yet be analyzed, which is a limitation rather than a finding.
That is the same discipline this course applies to evidence, turned on the tooling instead: explain an error before reasoning from it. An error that two different causes print word for word tells you about the mechanism, not about which cause you have.
So the discipline is simple and it costs seconds. Run windows.info, or a cheap plugin for the image's operating system, on every image before anything else, read the exit status as well as the text, and treat a requirement failure as a stop rather than as a step. Nothing downstream will run until that command does.
banners, and the String That Must Match
Asking the image what it needsThe error cannot tell you whether the file is memory without matching symbols or not memory at all. The image can, because one plugin needs no symbols to run.
$ vol -f decommissioned.raw banners 2>/dev/null | fold -w 84
Volatility 3 Framework 2.28.2
Offset Banner
0x1a3c0a0 Linux version 5.14.0-284.11.1.el9_2.x86_64 (mockbuild@x86-64-03.buil
d.eng.rdu2.redhat.com) (gcc (GCC) 11.3.1 20221121 (Red Hat 11.3.1-4), GNU ld version
2.35.2-37.el9) #1 SMP PREEMPT_DYNAMIC Wed Apr 12 10:45:03 EDT 2023
fold wraps the one long line so it fits the page; Volatility prints it whole. The string is the kernel's own description of itself, written into memory when it was built, and it is what a matching symbol table must carry. Finding it also settles the question the error left open: a file with a Linux kernel banner in it is memory from a Linux kernel, not a broken capture. Read what is in it: a version, a build identifier, the machine that compiled it, the compiler version, and the linker version.
Every one of those is part of the match. The Volatility documentation is explicit that the banner in the symbol file must match the banner in the image exactly, including compilation time and compiler version, and that there is deliberately no simple way to make a near miss match. That is why a version number does not select a symbol table: two kernels an administrator would call the same version, built at different times or with different compilers, produce different banners and can lay their structures out differently.
The banners plugin works without symbols, which is the property that makes it useful here. It finds the string by scanning rather than by interpreting structures, so it is one of the few things that still works on an image nothing else can read. That is why it is the second command to run, immediately after the error.
The complementary command tells you what you already have.
$ vol --hide-columns Number -f decommissioned.raw isfinfo \
--filter 5.14.0-284 2>/dev/null
Volatility 3 Framework 2.28.2
URI Valid Identifying information
isfinfo lists the symbol tables the installation knows about and the banners they carry; --filter keeps the files whose path contains the string, and --hide-columns Number drops four count columns so the header fits. Here it returns a header and no rows: this installation holds no table for that kernel. Between the two commands you have the string the image needs and the set of strings you hold, and the question stops being why nothing works and becomes whether those two sets intersect.
Those two commands are the whole diagnosis. If the banner from the image appears in the isfinfo list, the table is present and your problem is something else entirely. If it does not, you now know precisely which file you need, expressed in the only terms that matter, and the rest of this sub is about getting it or accepting that you cannot.
Where Symbol Tables Come From
Packs, generation, and a directory on your diskSymbol tables are ordinary files, they live in a directory beside the framework, and understanding how they get there tells you what your options are when one is missing.
Volatility publishes packs for Windows, Linux and Mac, which are downloaded and placed in the symbols directory as they come. Those cover an enormous amount of ordinary work and are the reason most analysts never think about any of this. The framework also caches the mapping from identifiers to files, so repeated runs do not re-scan the whole collection.
volatility3/symbols/ where tables live
windows/ downloaded pack, keyed by PDB identifiers
linux/ downloaded pack plus anything you generate
mac/
# the framework also caches the identifier-to-file mapping separately.
For Windows the situation is comfortable, and it is worth knowing why rather than being pleasantly surprised. Microsoft publishes debugging symbols for its own builds, centrally and durably, so the information a symbol table is built from is obtainable for a Windows image years after the machine is gone. That is one vendor, with an interest in keeping an archive, doing so reliably for a long time.
For Linux there is no equivalent. Debugging information ships as a package, distributions name those packages differently, there are a great many distributions, and a distribution has no obligation to keep packages for a kernel it stopped shipping. When the package is gone, the information a symbol table would be generated from is gone.
The Volatility documentation is candid about this rather than optimistic, and says outright that it may simply not be possible to find the right symbols for a Linux image. Mac sits in between, with far fewer kernels and a single distributor, which makes the match much easier to obtain.
Where the debugging information does exist, a converter turns it into the JSON format the framework reads, and the result goes into the symbols directory where it is picked up automatically. That is the generation path, and it is mechanical once you have the input. The difficulty is never the conversion step, which is mechanical and quick. It is obtaining the debug package for one specific kernel build, years after anybody had a reason to keep it.
The Order to Work Through It
Five steps, and the fifth is a legitimate outcomeThere is a fixed sequence for working through a symbol problem, and it is worth committing to memory rather than improvising, because the day you need it you will be under time pressure.
- Read the failure first. An exit status of 2 is a command line problem, a wrong plugin name or an option in the wrong place, and an exit status of 1 with requirement errors is a layer or symbol problem. This costs seconds and rules out a whole category.
- Then the banner. It comes out of the image, it proves the file is memory, it is the exact string any matching table must carry, and without it every later step is guesswork about what you are even looking for.
- Then what you have. The answer is sometimes that the table is present and something else is wrong, which is worth discovering before you go hunting for a download.
- Then obtain one. Published packs first, then community collections for Linux organized by kernel version, then generation from the matching debug package.
- Then stop, and write it down. If the debug package no longer exists anywhere, this image cannot be analyzed with this tool, and that is a limit to record rather than a failure to hide.
Two things about that sequence are worth noticing. It runs cheapest first, so the steps that cost seconds come before the ones that cost an afternoon. And each step produces something usable even when it does not solve the problem: the banner is worth recording whether or not you find a match, and the isfinfo comparison is worth having whether or not it is the answer.
Step five is the one this sub exists to make thinkable, because analysts find it very hard to write. Reaching it does not mean you did anything wrong. It means the analyzability of that image depended on somebody keeping a debugging package for a kernel nobody runs any more, and that decision was made by people who were not thinking about forensics at all.
Writing it down is far more useful than a quiet absence of findings. A report saying the image could not be analyzed, naming the banner and what was needed, tells the next person exactly what to obtain and tells the organization something actionable about its own estate.
There is one more thing worth doing at step five, which is to check whether another image of the same build exists somewhere in your own collection. Symbol tables are per build rather than per machine, so a table obtained for one server works for every server running that exact kernel, and organizations frequently have dozens of machines on one build.
It also has a consequence for acquisition that belongs in a procedure rather than in an analyst's head. If symbols may become unobtainable later, capturing the information that identifies the build at the time of collection costs nothing and preserves the option. That is the same class of decision as recording which machine an image came from.
Why a Near Match Is Refused
The alternative is confident nonsenseThe obvious complaint about all of this is that the tool should be more forgiving. If it has symbols for a kernel one patch level away, why not use them and warn the analyst?
The answer is what those symbols would produce. A symbol table is a map from field names to positions within a structure, so using the wrong map does not fail; it reads bytes from the wrong places and reports them as values. The output has the right shape, the right columns and the right number of rows, and its contents are meaningless.
the fields are read from slightly wrong offsets ...
PID PPID ImageFileName CreateTime
4 0 Syst\x00\xf3.. 1601-01-01 00:00:00 UTC
19216 61455 (blank) 2148-09-02 11:04:33 UTC
61456 28293 ????.exe (blank)
Now consider the position that puts you in. You would be reading plausible-shaped output with no way to tell it apart from a correct reading, and any finding built on it would be fabricated. Worse, a partly-wrong table might read some fields correctly and others not, producing a listing that is mostly sensible with a few wrong values scattered through it, which is far harder to detect than total nonsense.
Refusing to guess is therefore a design decision in your favor, and it is worth recognizing as one rather than resenting it. A tool that offered an override would be offering to make its own output untrustworthy on request, and the analyst who used it would have no way to know which rows to believe.
It is also worth understanding that the refusal is not a conservative default somebody could relax with more engineering effort. The framework matches on the banner because the banner is the only thing that reliably identifies a build, and there is deliberately no mechanism for declaring two banners equivalent. That is a considered position rather than an unfinished feature.
The same reasoning is why an internal-consistency check on the output is worth running even when symbols matched. A creation time before the machine booted, a parent identifier larger than any the system issues, a name with unprintable characters in it: those are the signature of a reading problem, and they are the only warning you get in the rare cases where something partly works.
Failure Modes That Are Not Symbols
And the empty listings that are not failuresA symbol problem announces itself. An empty listing with an exit status of 0 is a different thing, the plugin ran and found nothing to print, and knowing the reasons stops you chasing the wrong fix.
A plugin can be pointed at a process that does not exist. Filtering by a PID that is not in the image returns an empty listing rather than an error, and on an image where identifiers have been reused it can return the wrong process entirely, which the section above covered. Check the process list before concluding anything about a filtered run.
A plugin can also be the wrong one for the operating system. Running a Windows plugin against a Linux image is a mistake people make more often than they admit, particularly when working from a cheat sheet, and it fails with the same requirement errors as a missing symbol table, so the error alone will not tell you which mistake you made. The plugin prefix is the guard against this and it is worth reading twice.
Only the left branch invalidates everything after it. One command tells you which branch you are on.
And a plugin can have moved, which MF0.3 met directly. A command copied from an older write-up can name a plugin at an old path, which today runs with a warning and on a later release fails with an error naming the plugin; it is the same family of problem: the tooling has changed and the documentation has not.
There is a fourth case worth adding because it is genuinely a result rather than a problem. A plugin can return nothing because there is nothing to return: a process with no unbacked executable memory produces no malfind rows, and a machine with no established connections produces no established rows. That is an answer, and the way you know it is an answer rather than a failure is that other plugins on the same image are producing output normally.
The general rule is worth stating once. An empty result is a statement about the tool until you have established otherwise, and the cost of establishing otherwise is one command. Analysts who adopt that habit early never write the sentence about no suspicious processes being found on an image that was never read.
What This Means for Your Estate
A retention question nobody ownsThis sub has a consequence that reaches well outside the analysis itself, and it is the kind of thing worth acting on quietly before an incident rather than discovering during one.
Whether an image from your estate will be analyzable in two years depends on whether the information needed to build a symbol table still exists then. For Windows machines that is somebody else's archive and it is likely to be there. For Linux machines it depends on your distribution's retention of debug packages for kernels you no longer run, which is nobody's decision in particular and is usually nobody's job to track.
The mitigation is genuinely cheap and it is a procedure change rather than a technical project. Record the kernel build identifier when a machine is decommissioned, or better, keep the matching debug package alongside whatever else is retained. Either one turns a future dead end into a lookup that takes a minute.
None of this requires a project. It requires one person to ask, once, and write the answer down somewhere the incident response procedure can find it.
There is a second and more immediate version of the same problem. An analysis environment deliberately isolated from the internet, which is a sound control, cannot download a symbol pack on the night of an incident. The packs have to be obtained in advance and carried across, and that is a task with a lead time that somebody has to have done.
Both of those are the kind of thing that gets discovered at three in the morning by somebody who cannot fix it then. Asking the question now costs an email. The answer is either reassuring, in which case you have lost ten minutes, or it identifies a gap that is cheap to close today and impossible to close on the night it matters. That asymmetry is why it is worth raising even when you are fairly sure of the answer.
Practice
Make the failure happen on purpose hands on- Find the symbols directory in your Volatility installation and look at what is in it. Seeing the files makes the dependency concrete in a way no explanation does.
- Rename that directory, add
--offlineso Volatility cannot fetch a Windows table from Microsoft's symbol server, run any plugin against an image that worked a minute ago, and read the error and the exit status. Do this deliberately, once, so the shape of the failure is familiar rather than alarming. - Run
bannersagainst every image you have and record the strings. Two machines you would describe as the same version may well give different answers. - Run
isfinfoand compare its list against those banners. Where they match you can work; where they do not you cannot, and knowing that in advance is worth having. - Write the two sentences you would put in a report for an unanalyzable image: what was attempted, and what was needed. Having them drafted turns an awkward conversation into a short one.
Extend it
These turn a one-off understanding into something your organization benefits from.
- Find out whether your analysis environment can reach the internet, and if not, who would obtain a symbol pack and how long it would take. The answer is often that nobody has considered it.
- For one Linux system in your estate, check whether the debug package for its current kernel is still available from the distribution. Then check one you decommissioned last year.
- Count the symbol files shipped for Windows in your installation. The number is a good measure of how often the underlying structures change.
- Take an image, deliberately analyze it correctly, then write down three values you would check for internal consistency if you suspected a symbol mismatch. Having the check ready is the point.
Drill one is the one most likely to surprise somebody other than you. An analysis environment that cannot reach the internet is a good control and a serious constraint, and the fact that nobody has worked out who would carry a symbol pack across is usually news to whoever owns that environment.
Drill two is the one with a finding attached. Most people are surprised by how quickly debug packages disappear, and discovering that a machine decommissioned last year is already unanalyzable is a concrete argument for a retention change that a general warning never wins.
You can tell a symbol failure from a bad capture and from a clean result, run banners to read what the image needs and isfinfo to see what you hold, explain why a near match is refused rather than warned about, work through the five steps in order, and recognize the three common empty results that are not symbol problems at all. MF0.7 builds the environment this course runs on, and the tools that capture an image in the first place.