Lesson 19 / 22
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.
Published Updated
In this lesson
- 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
Contents
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
// 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
// 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
// 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
// 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.
To keep your progress and take notes, Log in
My notes
Log in to take notes.