---
title: 'Memory Dump Analysis'
source: 'https://academia.sh/en/courses/security-operations/memory-dump-analysis'
course: 'Security Operations and Incident Response'
language: en
updated: '2026-09-26T03:02:57+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Arranges evidence items into an order of volatility by their lifetime'
    - 'Calculates the number of items lost under three different collection orders on the same set'
    - 'Separates the effect of team size from order on losses, and counts the questions answerable only from memory'
---

# Memory Dump Analysis

Establishing an order of volatility and counting the items a collection order costs: 28 evidence items are arranged into 10 volatility classes; with a single collector, the correct order loses 7 items, host-by-host collection loses 12, and largest-item-first collection loses 18. A three-person team following the correct order has 0 losses, a six-person team following the wrong order has 13; the wrong order is also the fastest collection to finish, because a lost item spends no time. The memory dump is relevant to 8 of 12 questions and answers 4 of them on its own.

The previous lesson counted the source's seven zones and showed that most evidence items sit in zones that are not files. Memory was not on that list. Its absence is not an omission — it is the definition itself: memory is not on disk, and no copying scope reaches it. Swap-space residue carries fragments dropped out of memory, but it is only a fragment, and it is among the zones that change the most during copying.

Memory opens a separate lesson in this topic because it changes the unit of measure. In disk imaging the question was "how many items does scope bring back," and scope was a decision that could be widened. In memory the question becomes "**when**," and time cannot be widened. This lesson measures that time: items are ordered by their lifetime, three different collection orders are run over the same set, and the item cost of each order is counted. The measurement is a **model**; **no memory-dump extraction tool or command is written**, what is measured is order and item count.

- **FE13.** An item's **lifetime** is the time that passes before it is lost if collection has not begun. Lifetime comes from the item's class, and it is fictional.
- **FE14.** The **order of volatility** is the ascending ordering of items by their lifetime: network state, process list, open handles, session state, memory content, temporary files, then disk and remote records.
- **FE15.** Collection is **sequential**: a collector takes one item at a time and is occupied for that item's duration. As the team grows, the number of parallel queues grows.
- **FE16.** An item is lost, and spends no time, if its lifetime runs out **before** collection begins on it.
- **FE17.** Time zero is the moment the response team gains access to the hosts; detection and decision times are not counted in this lesson.
- **FE18.** The item set is the same as the first lesson's set: six hosts, 28 items.

## Order of Volatility

```js
// forensics/order.mjs — MODEL of order of volatility and the items lost by collection order.
// No memory-extraction tool or command is written; what is measured is order and item count.

// class -> [lifetime(s): time before it is lost if collection has not begun, collection duration(s)]
export const CLASS = {
  "network-state": [120, 45], "process-list": [240, 60], "open-handle": [300, 55],
  "session-state": [600, 70], "memory-content": [900, 420], "temp-file": [3600, 180],
  "flow-record": [86400, 240], "remote-log": [2592000, 300],
  "disk-image": [604800, 5400], "configuration": [604800, 90],
};

export const HOST = {
  "branch-endpoint-3": ["network-state", "process-list", "open-handle", "session-state",
    "memory-content", "temp-file", "disk-image", "configuration"],
  "field-laptop-14": ["network-state", "process-list", "session-state", "memory-content",
    "disk-image"],
  "collector-2": ["network-state", "process-list", "open-handle", "session-state",
    "memory-content", "disk-image", "configuration"],
  "portal-frontend": ["network-state", "process-list", "memory-content", "configuration"],
  "central-log": ["remote-log", "configuration"],
  "flow-collector": ["flow-record", "configuration"],
};

export const SET = Object.entries(HOST).flatMap(([host, classes]) =>
  classes.map((c) => ({ name: `${host}/${c}`, host, class: c,
    life: CLASS[c][0], duration: CLASS[c][1] })));

// Three orderings: by volatility, host by host (arrival order), largest item first.
export const ORDER = {
  "by volatility": [...SET].sort((a, b) => a.life - b.life || a.duration - b.duration),
  "host by host": [...SET],
  "largest first": [...SET].sort((a, b) => b.duration - a.duration),
};

// Collection: n collectors run in parallel; the next item in line goes to the first free collector.
// An item is lost if collection starts on it after its lifetime has run out, and it spends no time.
export function collect(order, n) {
  const free = Array(n).fill(0), lost = [], taken = [];
  for (const k of order) {
    const i = free.indexOf(Math.min(...free)), start = free[i];
    if (start > k.life) lost.push({ ...k, start });
    else { taken.push({ ...k, start }); free[i] = start + k.duration; }
  }
  return { taken, lost, duration: Math.max(...free) };
}
```

All three ordering candidates are habits actually encountered in practice. **Host-by-host** collection is the most natural: the team sits down at one machine, takes whatever is on it, then moves to the next. **Largest-first** collection looks like a precaution: if the most expensive item takes the longest, it should be started earliest. **Order of volatility**, by contrast, looks not at an item's value or size, but only at its lifetime.

## Items Lost When the Order Is Wrong

```js
// forensics/measure.mjs — item-loss count for three orderings and two team sizes.
import { CLASS, SET, ORDER, collect } from "./order.mjs";

const y = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join("").trimEnd());

const volatile = SET.filter((k) => k.life <= 3600);
console.log(`model: ${SET.length} evidence items, ${Object.keys(CLASS).length} volatility classes, ` +
  `${volatile.length} items volatile, collecting all of them takes ${SET.reduce((a, k) => a + k.duration, 0)} s`);

const A = [-16, 9, 9, 7, 14, 14, 14];
console.log("");
y(A, "class", "life s", "dur. s", "items", "1p/correct", "1p/host", "1p/largest");
const t = Object.fromEntries(Object.keys(ORDER).map((d) => [d, collect(ORDER[d], 1)]));
for (const c of Object.keys(CLASS)) {
  const n = SET.filter((k) => k.class === c).length;
  y(A, c, CLASS[c][0], CLASS[c][1], n,
    ...Object.keys(ORDER).map((d) => `${t[d].lost.filter((k) => k.class === c).length} lost`));
}
y(A, "total", "-", "-", SET.length,
  ...Object.keys(ORDER).map((d) => `${t[d].lost.length} lost`));

const B = [-16, 7, 9, 8, 14, 11];
console.log("");
y(B, "order", "people", "taken", "lost", "volatile lost", "end s");
for (const n of [1, 3, 6])
  for (const d of Object.keys(ORDER)) {
    const s = collect(ORDER[d], n);
    y(B, d, n, s.taken.length, s.lost.length,
      s.lost.filter((k) => k.life <= 3600).length, s.duration);
  }
```

```
model: 28 evidence items, 10 volatility classes, 18 items volatile, collecting all of them takes 19790 s

class              life s   dur. s  items    1p/correct       1p/host    1p/largest
network-state         120       45      4        1 lost        3 lost        4 lost
process-list          240       60      4        2 lost        3 lost        4 lost
open-handle           300       55      2        1 lost        1 lost        2 lost
session-state         600       70      3        0 lost        2 lost        3 lost
memory-content        900      420      4        3 lost        3 lost        4 lost
temp-file            3600      180      1        0 lost        0 lost        1 lost
flow-record         86400      240      1        0 lost        0 lost        0 lost
remote-log        2592000      300      1        0 lost        0 lost        0 lost
disk-image         604800     5400      3        0 lost        0 lost        0 lost
configuration      604800       90      5        0 lost        0 lost        0 lost
total                   -        -     28        7 lost       12 lost       18 lost

order            people    taken    lost volatile lost      end s
by volatility         1       21       7             7      18310
host by host          1       16      12            12      18020
largest first         1       10      18            18      17190
by volatility         3       28       0             0       6770
host by host          3       19       9             9       6640
largest first         3       10      18            18       5760
by volatility         6       28       0             0       5925
host by host          6       25       3             3       5705
largest first         6       15      13            13       5400
```

The first table is this lesson's core, and its last row carries three numbers: **7, 12, 18**. The same 28 items, the same durations, the same single collector; only the order changes. In the order of volatility, seven items are lost; in host-by-host collection, twelve; in largest-item-first collection, eighteen. The last number is the entire volatile set: by the time the first 5,400-second disk image finishes, no item with a lifetime under an hour is still standing.

The seven losses under the correct order also deserve a separate reading. Even though the order is the best one, one of four network-state items, two of four process-list items, and three of four memory-content items are lost. The reason is the single-person team: taking a network state takes 45 seconds, and the fourth of four hosts comes up in the queue at the 135th second, while its lifetime is 120. **The correct order minimizes loss, it does not zero it out**; what zeroes out loss is the number of parallel queues.

The second table separates these two factors, and the comparison is not one-directional. When a three-person team works in the correct order, loss is **zero** — three people are enough for this fictional set. When a six-person team works in the wrong order, loss is **thirteen**. Twice the people does not fix the wrong order; order is more decisive than team size, because order is what consumes lifetime, and headcount only changes the rate of consumption.

The last column is the most deceptive one. The collection that finishes fastest is the one that loses the most items: with a single-person team, "largest first" finishes at 17,190 seconds while the correct order takes 18,310 seconds. The difference is not an efficiency gain, it is an accounting illusion — **a lost item spends no time.** A collection finishing early tells you something only when read together with the number of items actually taken.

## Time to Reach a Host

```js
// forensics/arrival.mjs — effect of host-access delay on loss under the correct order.
import { SET, ORDER } from "./order.mjs";

const y = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join("").trimEnd());
const REMOTE = ["branch-endpoint-3", "field-laptop-14"];   // the two field hosts the team is traveling to

// An item must be started after arriving at the host and before its lifetime runs out.
function collect(order, n, delay) {
  const free = Array(n).fill(0), lost = [];
  for (const k of order) {
    const arrival = REMOTE.includes(k.host) ? delay : 0;
    const i = free.indexOf(Math.min(...free)), start = Math.max(free[i], arrival);
    if (start > k.life) lost.push(k);
    else free[i] = start + k.duration;
  }
  return lost;
}

const A = [-12, 9, 9, 12, 14];
y(A, "delay s", "people", "lost", "remote host", "central host");
for (const g of [0, 600, 1200, 2400])
  for (const n of [3, 6]) {
    const k = collect(ORDER["by volatility"], n, g);
    y(A, g, n, k.length, k.filter((x) => REMOTE.includes(x.host)).length,
      k.filter((x) => !REMOTE.includes(x.host)).length);
  }
const remoteVolatile = SET.filter((k) => REMOTE.includes(k.host) && k.life <= 3600).length;
console.log(`\nvolatile items at the two remote hosts: ${remoteVolatile}; at a 2400-second delay, ` +
  `team size no longer changes the loss`);
```

```
delay s        people     lost remote host  central host
0                   3        0           0             0
0                   6        0           0             0
600                 3        6           5             1
600                 6        5           5             0
1200                3        9           9             0
1200                6        9           9             0
2400                3        9           9             0
2400                6        9           9             0

volatile items at the two remote hosts: 10; at a 2400-second delay, team size no longer changes the loss
```

The seventeenth claim defined time zero as "the moment the team gains access to the hosts" and used the same moment for every host. Two hosts are in the field; the team is traveling to them. A six-hundred-second trip raises loss from zero to **six** for a three-person team working in the correct order. At twelve hundred seconds, loss rises to **nine** and stays there: nine of the ten volatile items at the two field hosts run out their lifetime before the team arrives, and the only item still standing is the temp file, whose lifetime is 3,600 seconds.

Two rows of this table also deserve a separate reading. At six hundred seconds, a three-person team loses six items and a six-person team loses five; the one item that differs is a central one, and it was left waiting in the queue of a collector held up by a remote host. After twelve hundred seconds, team size changes nothing, because the lost items were lost before anyone arrived. **Past a certain point, delay renders team size meaningless.**

The first lesson calculated that shortening the chain reduces the expected open item count from 7.57 to 4.16, and noted that its cost was "the time it takes a qualified person to travel to the field." This table is that cost's number: a twelve-hundred-second trip is worth nine items.

## Questions Answerable Only from Memory

```js
// forensics/source.mjs — which question is answered by which source. A MODEL.
const y = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join("").trimEnd());

// question -> [memory dump, disk image, remote log]  1: answers, 0: does not answer
const QUESTION = {
  "which processes were running": [1, 0, 0],
  "what was the process's command line": [1, 0, 1],
  "which connections were active": [1, 0, 1],
  "content decrypted in memory": [1, 0, 0],
  "active session token": [1, 0, 0],
  "loaded kernel module": [1, 1, 0],
  "configuration in effect": [1, 0, 0],
  "configuration on disk": [0, 1, 0],
  "when did the file change": [0, 1, 0],
  "content of the deleted file": [0, 1, 0],
  "who logged in": [1, 1, 1],
  "who accessed which record": [0, 0, 1],
};
const SOURCE = ["memory dump", "disk image", "remote log"];

const A = [-37, 13, 12, 12, 9];
y(A, "question", ...SOURCE, "sources");
for (const [s, v] of Object.entries(QUESTION))
  y(A, s, ...v.map((x) => (x ? "yes" : "-")), v.reduce((a, x) => a + x, 0));

const only = SOURCE.map((_, i) =>
  Object.values(QUESTION).filter((v) => v[i] === 1 && v.reduce((a, x) => a + x, 0) === 1).length);
console.log("");
SOURCE.forEach((k, i) => console.log(`${k}: touches ` +
  `${Object.values(QUESTION).filter((v) => v[i]).length} questions, answers ${only[i]} on its own`));
console.log(`questions answered by two or three sources: ` +
  `${Object.values(QUESTION).filter((v) => v.reduce((a, x) => a + x, 0) > 1).length} / ` +
  `${Object.keys(QUESTION).length}`);
```

```
question                               memory dump  disk image  remote log  sources
which processes were running                   yes           -           -        1
what was the process's command line            yes           -         yes        2
which connections were active                  yes           -         yes        2
content decrypted in memory                    yes           -           -        1
active session token                           yes           -           -        1
loaded kernel module                           yes         yes           -        2
configuration in effect                        yes           -           -        1
configuration on disk                            -         yes           -        1
when did the file change                         -         yes           -        1
content of the deleted file                      -         yes           -        1
who logged in                                  yes         yes         yes        3
who accessed which record                        -           -         yes        1

memory dump: touches 8 questions, answers 4 on its own
disk image: touches 5 questions, answers 3 on its own
remote log: touches 4 questions, answers 1 on its own
questions answered by two or three sources: 4 / 12
```

Only four of twelve questions are answered by more than one source. The remaining eight each have a single source, and if that source is not collected, the question **stays unanswered** — it cannot be inferred from another item. The memory dump is relevant to eight questions and answers four of them on its own; those four are also questions that only have an answer on a running system.

There are two rows in the table that get mixed up with each other: **configuration in effect** and **configuration on disk**. They are separate questions and are answered by separate sources. The setting a process has read into memory differs from the disk file if that file has since been changed; which one held during an incident is exactly what is being asked. The disk image cannot answer this question, only the second one. A line in a report that says "the configuration was such-and-such" is an unmeasurable claim if it does not say which question it is the answer to.

## Summary

- The order of volatility is the ascending ordering of items by their lifetime; in the fictional set, 18 of 28 items are volatile, and collecting all of them takes 19,790 seconds.
- On the same set with a single collector, the correct order loses 7 items, host-by-host collection loses 12, and largest-item-first collection loses 18; the last figure is the entire volatile set.
- The correct order minimizes loss, it does not zero it out: a 1,200-second arrival delay to the two field hosts raises loss from 0 to 9 for a team working in the correct order, and past that point, team size no longer changes the result.
- Three people in the correct order produce 0 losses; six people in the wrong order produce 13 losses; order is more decisive than team size.
- The collection that finishes fastest is the one that loses the most items, because a lost item spends no time; finish time only means something when read together with the number of items taken.
- The memory dump is relevant to 8 of 12 questions and answers 4 of them on its own; only 4 of the questions can be answered by more than one source.

## Next Step

This lesson ordered items by **when** they are collected. Once collected, each item carries its own timestamps: moments of file change, moments of login, moments of connection in the flow record. The next step of examination is to merge these timestamps onto a single timeline, and a silent assumption sits there — that every source's clock is the same.

The next lesson tests that assumption. Events from three sources are merged into a single timeline, each source's clock skew is measured, and two numbers are worked out: how many events land in the wrong order, and how many events change position once the skew is corrected. A broken ordering is not a delay, it is a **cause-and-effect** error: two events read in the wrong order look like two events that caused each other.
