Skip to content
academia.sh

Lesson 22 / 22

Reverse Engineering Fundamentals

Measuring reverse engineering's place in defense: the total defense value of twenty fictional indicators being thirty-eight, twelve items coming out of the raw binary through a string scan, symbol stripping together with string hiding bringing those twelve down to zero, three items being impossible to extract in any deployment and visible only in the victim's own record, and the chain columns of the course's twenty-two lessons being gathered into a single table.

Published Updated

In this lesson

  • Actually measures, with a string scan, how many indicators can be extracted from a binary
  • Counts which analysis step each countermeasure breaks and how many items it hides
  • Separates out the items that cannot be extracted by any means and shows defense's remaining resource
Contents

The previous lesson counted an item reaching the endpoint but never asked what that item was. Most of the time, all the defense has in hand is that item: no source, no documentation, no known author. Reverse engineering is the work of extracting from that item an indicator the defense can use.

This lesson takes the class from the other end of the measurement. In the eight lessons so far, the chain belonged to the attacker and the defense was the one breaking it. Here the chain belongs to the defense: acquiring the sample, doing static extraction, doing dynamic observation, converting the indicators into a rule, and deploying the rule to the fleet. The breaking side is the countermeasures. Throughout the measurement, no real sample, real tool, or real analysis step is written; the binary in this lesson is generated by itself, and the scan is actually run.

Three Types of Indicator, One Type of Gap

The items that can be extracted from a binary are not all one type. Items of string and symbol type exist as text inside the binary; a scan gives them up directly. Items of behavior type are not written into the binary; they are only seen once the item is run. A fourth type cannot be extracted by any means at all: the item does not carry that information, it gets it from the victim’s own environment.

This fourth type is this lesson’s actual point. No matter how detailed an analysis report is, it cannot say what the item’s address on the target network is, what a derived session key is, or which organization was targeted. Those items are only seen in the victim’s own record.

TA33: the indicator inventory is fictional; defense is the item’s value for a detection rule. TA34: the binary is actually generated; the items carrying text are written into regions, the rest of the region is filled with bytes from the generator. TA35: the string scan collects printable byte sequences of four and longer. TA36: the defense’s analysis chain is five steps, and each countermeasure breaks one step.

// tm/binary.mjs — a fictional binary item and the indicators inside it (model). No real
// malware, no real sample, and no real analysis tool; the binary is generated here.
export const INDICATOR = [
  { name: "config-key",        type: "string", text: "measurement.setting.threshold", defense: 2 },
  { name: "component-label",   type: "string", text: "counter-collector-1", defense: 1 },
  { name: "send-interval",     type: "string", text: "send-interval=900", defense: 2 },
  { name: "filename-pattern",  type: "string", text: "temp-measurement-data", defense: 3 },
  { name: "retry-limit",       type: "string", text: "retry-limit-threshold", defense: 1 },
  { name: "build-label",       type: "string", text: "build-2026-04-11", defense: 1 },
  { name: "version-label",     type: "string", text: "settings-signature-v3", defense: 1 },
  { name: "func-connect",      type: "symbol", text: "connect", defense: 2 },
  { name: "func-collectData",  type: "symbol", text: "collectData", defense: 2 },
  { name: "func-writeLog",     type: "symbol", text: "writeLog", defense: 1 },
  { name: "func-timer",        type: "symbol", text: "setTimer", defense: 2 },
  { name: "func-readConfig",   type: "symbol", text: "readConfig", defense: 1 },
  { name: "startup-order",           type: "behavior", defense: 3 },
  { name: "file-write-pattern",      type: "behavior", defense: 3 },
  { name: "timing-interval",         type: "behavior", defense: 2 },
  { name: "restart-response",        type: "behavior", defense: 2 },
  { name: "resource-usage-profile",  type: "behavior", defense: 1 },
  { name: "target-network-address",  type: "unextractable", defense: 3 },
  { name: "session-key",             type: "unextractable", defense: 3 },
  { name: "victim-identity",         type: "unextractable", defense: 2 },
];

function generator(seed) {
  let s = seed >>> 0;
  return () => (s = (s * 1664525 + 1013904223) >>> 0) & 255;
}

export function generateBinary({ stripSymbols = false, hideStrings = false, pack = false } = {}) {
  const r = generator(20260804);
  const byte = [];
  const place = (m) => { for (const c of m) byte.push(c.charCodeAt(0)); byte.push(0); };
  const fill = (n) => { for (let i = 0; i < n; i++) byte.push(r() | 128); };  // unprintable

  fill(24);                                                 // header region
  for (const g of INDICATOR.filter((x) => x.type === "symbol")) {
    if (stripSymbols || pack) fill(g.text.length + 1); else place(g.text);
  }
  fill(32);                                                 // code region
  for (const g of INDICATOR.filter((x) => x.type === "string")) {
    if (pack) fill(g.text.length + 1);
    else if (hideStrings) { for (const c of g.text) byte.push(c.charCodeAt(0) ^ 90); byte.push(0); }
    else place(g.text);
  }
  fill(24);
  return Uint8Array.from(byte);
}

export function scanStrings(b, minLen = 4) {
  const found = [];
  let s = "";
  for (const x of b) {
    if (x >= 32 && x <= 126) s += String.fromCharCode(x);
    else { if (s.length >= minLen) found.push(s); s = ""; }
  }
  if (s.length >= minLen) found.push(s);
  return found;
}

export const write = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(""));
// tm/measure.mjs — how many indicators come out of the binary, how many the countermeasures
// hide, which item cannot come out at any deployment. The string scan is actually run.
import { INDICATOR, generateBinary, scanStrings, write } from "./binary.mjs";

const TYPE = ["string", "symbol", "behavior", "unextractable"];
const value = (k) => k.reduce((t, g) => t + g.defense, 0);

console.log(`${INDICATOR.length} fictional indicators, total defense value ${value(INDICATOR)}\n`);

const A = [-14, 9, 12, 22];
console.log("1. indicator types");
write(A, "type", "count", "defense", "extraction path");
const PATH = { string: "string scan", symbol: "string scan",
  behavior: "dynamic observation", unextractable: "none" };
for (const t of TYPE) {
  const k = INDICATOR.filter((g) => g.type === t);
  write(A, t, k.length, value(k), PATH[t]);
}

const extracted = (option) => {
  const found = scanStrings(generateBinary(option));
  return INDICATOR.filter((g) => g.text && found.includes(g.text));
};

const DEPLOYMENT = [
  ["no countermeasure", {}],
  ["symbol stripping", { stripSymbols: true }],
  ["string hiding", { hideStrings: true }],
  ["symbol stripping + string hiding", { stripSymbols: true, hideStrings: true }],
  ["packing", { pack: true }],
];
const B = [-36, 9, 11, 11, 17];
console.log("\n2. what the string scan extracts (real run)");
write(B, "deployment", "count", "defense", "hidden", "remaining share");
const raw = extracted({});
for (const [name, s] of DEPLOYMENT) {
  const c = extracted(s);
  write(B, name, c.length, value(c), raw.length - c.length,
    `${((value(c) / value(INDICATOR)) * 100).toFixed(1)}%`);
}
console.log(`the binary is ${generateBinary({}).length} bytes (same across all deployments; only ` +
  `the content changes). Of the ${raw.length} items extracted from the raw binary, ` +
  `${raw.filter((g) => g.type === "string").length} are strings, ` +
  `${raw.filter((g) => g.type === "symbol").length} are symbols.`);

const STEP = ["sample-acquisition", "static-extraction", "dynamic-observation",
  "rule-conversion", "fleet-deployment"];
const COUNTERMEASURE = [
  { name: "symbol-stripping", breaks: 2, hides: ["symbol"] },
  { name: "string-hiding", breaks: 2, hides: ["string"] },
  { name: "packing", breaks: 2, hides: ["string", "symbol"] },
  { name: "debugger-detection", breaks: 3, hides: ["behavior"] },
  { name: "conditional-execution", breaks: 3, hides: ["behavior"] },
];
const C = [-24, 10, 20, 10, 12];
console.log("\n3. countermeasure-step mapping");
write(C, "countermeasure", "breaks", "hides type", "count", "defense");
for (const o of COUNTERMEASURE) {
  const k = INDICATOR.filter((g) => o.hides.includes(g.type));
  write(C, o.name, `step ${o.breaks}`, o.hides.join("+"), k.length, value(k));
}
console.log(`${STEP.length}-step analysis chain: ${STEP.join(" -> ")}`);

const D = [-34, 10, 12, 17];
console.log("\n4. remaining indicators (countermeasures cumulative)");
write(D, "deployment", "count", "defense", "remaining share");
const remaining = (types) => INDICATOR.filter((g) => !types.includes(g.type) && g.type !== "unextractable");
for (const [name, t] of [["none", []], ["static hiding", ["string", "symbol"]],
  ["dynamic hiding", ["behavior"]], ["both together", ["string", "symbol", "behavior"]]]) {
  const k = remaining(t);
  write(D, name, k.length, value(k), `${((value(k) / value(INDICATOR)) * 100).toFixed(1)}%`);
}
const cant = INDICATOR.filter((g) => g.type === "unextractable");
console.log(`items unextractable in any deployment: ${cant.length}, defense value ` +
  `${value(cant)} (${((value(cant) / value(INDICATOR)) * 100).toFixed(1)}%); ` +
  `these items are only seen in the victim's own record`);
20 fictional indicators, total defense value 38

1. indicator types
type              count     defense       extraction path
string                7          11           string scan
symbol                5           8           string scan
behavior              5          11   dynamic observation
unextractable         3           8                  none

2. what the string scan extracts (real run)
deployment                              count    defense     hidden  remaining share
no countermeasure                          12         19          0            50.0%
symbol stripping                            7         11          5            28.9%
string hiding                               5          8          7            21.1%
symbol stripping + string hiding            0          0         12             0.0%
packing                                     0          0         12             0.0%
the binary is 280 bytes (same across all deployments; only the content changes). Of the 12 items extracted from the raw binary, 7 are strings, 5 are symbols.

3. countermeasure-step mapping
countermeasure              breaks          hides type     count     defense
symbol-stripping            step 2              symbol         5           8
string-hiding               step 2              string         7          11
packing                     step 2       string+symbol        12          19
debugger-detection          step 3            behavior         5          11
conditional-execution       step 3            behavior         5          11
5-step analysis chain: sample-acquisition -> static-extraction -> dynamic-observation -> rule-conversion -> fleet-deployment

4. remaining indicators (countermeasures cumulative)
deployment                             count     defense  remaining share
none                                      17          30            78.9%
static hiding                              5          11            28.9%
dynamic hiding                            12          19            50.0%
both together                              0           0             0.0%
items unextractable in any deployment: 3, defense value 8 (21.1%); these items are only seen in the victim's own record

What the Scan Extracts

The second output is a real run: printable byte sequences are collected in the generated 280-byte binary and matched against the inventory. 12 items come out of the raw binary — 7 strings, 5 symbols — and their defense value is 19, half of the inventory’s total value.

Countermeasures bring this number down step by step. Symbol stripping brings 12 down to 7, string hiding brings it to 5. When the two are applied together, or when a single packing step is used, the scan gives zero items. When the number drops to zero, the binary is still 280 bytes and still does exactly the same job; the only thing that changes is whether a static scan can read anything out of it.

This is the measured form of the most common misconception in analysis reports: a scan coming back empty does not show the item is harmless — it shows that scan did not work on that item. The same binary, the same behavior, a different presentation.

Which Step Each Countermeasure Breaks

The third output maps the five countermeasures to the defense’s analysis chain. Three of them break the second step — static extraction. Two of them break the third step — dynamic observation.

None of them break the first step. Acquiring the sample is the link countermeasures cannot reach: if the item has reached the endpoint, it is in the defense’s hands. The fourth and fifth steps — rule conversion and fleet deployment — are also outside the countermeasures’ scope; their speed depends on the defense’s own operation.

A conclusion fitting this course’s measure follows from this. The defense’s analysis chain has two breakable links, and both are in the middle. The start and end of the chain are links the attacker cannot reach. The choice of countermeasure looks at which step the defense has less backup for: static extraction’s backup is dynamic observation; dynamic observation has no backup.

The Unextractable Item

The fourth output counts the remaining indicators. With no countermeasure at all, 17 items and a defense value of 30 are extractable. With static hiding applied, 5 items and a value of 11 remain; with dynamic hiding applied, 12 items and a value of 19. With both applied together, nothing at all comes out of the binary itself.

The last line is this lesson’s answer. Three items — target network address, session key, and victim identity — cannot be extracted in any deployment, and their defense values total 8, 21.1% of the inventory. These items never exist in the binary at all; the item gets them from the environment it runs in.

The conclusion is this: reverse engineering gives the defense a set of indicators, but never all of them, and against countermeasures, the part it gives can drop all the way to zero. The victim’s own records are, for this reason, not an alternative to analysis but its complement: the last three items exist only there, and two of the five highest-defense-value items are in that set.

Summary

  • The items that can be extracted from a binary are four types: string and symbol via static scan, behavior seen only at runtime, and a fourth type that cannot be extracted by any means.
  • A string scan on the 280-byte fictional binary extracted 12 items (7 strings, 5 symbols), defense value 19 — half the inventory’s total value.
  • Symbol stripping brought the extracted item count down to 7, string hiding to 5; either applying both together or packing alone brought it to zero. The binary is the same size and the same function; only readability changes.
  • Three of the five countermeasures break the analysis chain’s second step, two break its third step. The first, fourth, and fifth steps are links countermeasures cannot reach.
  • Three items cannot be extracted in any deployment; their defense value is 8, 21.1% of the inventory. These items are only seen in the victim’s own record, and they are a complement to analysis, not an alternative to it.

Course Wrap-Up

Across twenty-two lessons, a single question was asked: at which link can this chain be broken, and what does breaking it late cost. The table below gathers each lesson’s three columns in order; every number is read from its own lesson.

Lesson Chain step Breakable link Cost of late breaking
Threat Actors 8 steps 8 thresholds, 1 level passes 64 items
Advanced Persistent Threat 8 steps, 330 days 5 monitored, 2 alert 48 items
Attack Lifecycle 8 steps 5 controls, 3 empty steps 20.14 items
Adversary Behavior Classifications 8 steps, 10,800 chains 72 chains unbreakable 1.39 items average
Diamond Model 8 steps, 6 links infrastructure at step 8 41 items
Known and Unknown Threats 8 controls 7 in known, 5 in unknown 19.69 items
Fundamentals of Social Engineering 6 steps 4 monitored, earliest at 3rd 9 items
Phishing Types 5 steps earliest at step 2 11 items
Other Channels 5 steps 4 in email, 0 in voice call 11 items
Physical Techniques 3 chains first step unmonitored 7, 5, 7 items
Domain Spoofing registration chain step 1 if monitoring exists 6 items gain
Watering Hole Attacks 6 steps 3rd in 3 sources, 6th in 2 pages 12 items
Awareness Programs 5 links notification breaks the 3rd link 5 items
Web Application Risks 39 steps, 9 chains control at 17 steps 480 items
Injection Attacks 6 steps step 2, at 9/12 places 480 items
Cross-Site Scripting 5, 6, and 4 steps 3 compositions do not break 340 sessions
Request Forgery 5 + 5 steps 3rd in browser, 1st on server 267 operations, 60 internal endpoints
Path Traversal 5 steps step 2, identifier mapping 30 items, 41 sensitivity
Buffer Overflow 5 steps step 1, language class 6 neighboring fields
Password Attacks 6 steps step 3, threshold’s scale 190 accounts
Drive-By Downloads 6 conditions 3rd condition, early boundary 73 endpoints
Reverse Engineering Fundamentals 5 steps, analysis step 2, countermeasure 8 defense value

The table requires naming a single rule: an attack is not a moment, it is a sequence of steps with preconditions. No row is a single event; every row has interlinked steps, and every step has a condition that must hold. The defense’s job, for this reason, is not to break the chain — the chain can already be broken in many places. The defense’s job is to find the earliest breakable link, write down its legitimate-work cost, and count how many places that link is actually deployed.

All twenty-two rows show this only on paper. Step sequences were modeled, controls were mapped to steps, coverage was counted, and cost was written down — all on data structures we wrote ourselves. Paper cannot show whether a defense actually holds; only an authorized person trying it can show that. That trial’s rules, how its scope is defined, where to stop, and in what form findings get reported were never covered in this course at all. The next course — Security Tools and Penetration Testing — measures exactly that.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close