Lesson 2.1 · Program architecture· 3 min read
Malware architecture: command dispatch tables
Understand how command identifiers select internal handlers through a C function-pointer table, using an inert educational example.
- Platform:
- Cross-platform
- Language:
- C
- Difficulty:
- Beginner
What you will learn
Map command identifiers to handlers and trace known and unknown inputs through a function-pointer table.
Before you begin: Foundations module; C arrays, structs, and function declarations.
The implementation idea
A command-driven program needs a way to translate an identifier into a behavior. A dispatcher is the part of the implementation that makes that selection. Understanding this boundary helps defenders separate the logic that selects an operation from the logic that performs it when reading malware code.
This original educational example models that boundary with a small table of identifiers and function pointers. The input is a fixed local list. Both handlers only print a message, so the full example can be read and exercised without introducing a remote-control component. It illustrates an architectural pattern and makes no claim to reproduce a particular malware family.
Code example
#include <stddef.h>
#include <stdio.h>
typedef void (*handler_fn)(void);
struct command {
unsigned int id;
handler_fn handle;
};
static void report_status(void)
{
puts("status handler selected (demonstration)");
}
static void report_version(void)
{
puts("version handler selected (demonstration)");
}
static const struct command commands[] = {
{1, report_status},
{2, report_version},
};
static int dispatch(unsigned int id)
{
const size_t count = sizeof commands / sizeof commands[0];
for (size_t i = 0; i < count; ++i) {
if (commands[i].id == id) {
commands[i].handle();
return 1;
}
}
return 0;
}
int main(void)
{
const unsigned int example_ids[] = {2, 99, 1};
const size_t count = sizeof example_ids / sizeof example_ids[0];
for (size_t i = 0; i < count; ++i) {
if (!dispatch(example_ids[i])) {
printf("unknown command: %u\n", example_ids[i]);
}
}
return 0;
}
Follow the control flow
Each table entry pairs an identifier with a function of the same signature. dispatch walks those entries and invokes the matching function through its pointer. Returning 1 tells the caller that an entry matched; it is not a report about the success of an underlying operation. Unknown identifiers return 0 without calling a handler.
The first input selects the version handler, the second has no matching entry, and the third selects the status handler. The expected output is:
version handler selected (demonstration)
unknown command: 99
status handler selected (demonstration)
The incoming identifier is compared against table entries instead of being used directly as an array index. Notice also that the first matching entry wins: duplicate identifiers would make a later entry unreachable through this dispatcher. These details are part of the implementation's actual contract, even if the function's name alone does not reveal them.
Why this matters when reading malware
For a command-driven malware sample, identifying the equivalent dispatch boundary can help map the operations its implementation exposes. Start with the mapping between selectors and handlers, then inspect each handler before assigning a behavioral name. A selector such as 2 has no inherent meaning outside the program that interprets it.
In this example, the names and print statements make the mapping obvious. In another implementation, the same selection might be expressed as a switch statement or a chain of comparisons. The useful concept is the relationship between the input value and the function that eventually runs, rather than one specific source-code shape.
Defensive interpretation
Record each confirmed selector-to-handler mapping and distinguish handler selection from observed effects. Do not assume every handler executes during a particular run. The input values and the path reaching the dispatcher determine which behavior is selected.
A function-pointer table alone does not indicate maliciousness; it is also an ordinary software design technique. This example teaches how to read one internal mechanism. It does not establish a network protocol, identify a malware family, or supply a detection signature. Those conclusions require evidence from the surrounding implementation and the sample being investigated.
Exercise
Predict the handler sequence for IDs 1, 1, and 99. Does a successful dispatch prove an underlying operation succeeded?
Check your understanding
The status handler runs twice; 99 produces the unknown-command message. The dispatcher reports that a handler was selected, not the outcome of an operation.