Fresh demonstration: this is a small, benign program created for this walkthrough. It is not a malware sample or a report from a past investigation. Source code, a recorded disassembly and executable checks are available below.
The Analysis Question
What makes this compiled program report an alert, and which inputs does it reject first?
The fixture accepts a hexadecimal representation of a six-byte telemetry frame. Its scope is deliberately small enough to connect individual instructions to a behavioral explanation. The exercise complements non-binary reverse engineering through JavaScript: both require a testable account of behavior, even though the representations and tools differ.
This is a guided reconstruction with source available for verification, rather than a blind recovery exercise. Function symbols are retained. For a first pass, read the instruction listing before opening the source.
Establish the Build and Evidence
The recorded build used Apple clang 21.0.0, targeting ARM64 on macOS with -O1 optimization. The output is a 64-bit Mach-O executable. These details matter because another architecture or compiler may express the same decision differently.
- Fixture source
- Full recorded ARM64 disassembly
- Verification script
- Build instructions, hashes and limitations
Save the source and verification script in one directory, then run:
node verify.mjs
This requires Node.js 22 or newer and a C11 compiler. The script builds and runs only the adjacent published fixture. It is not a sandbox or a tool for executing unknown samples.
Recover the Checks in Order
In the recorded listing, classify_frame starts at 0x100000460. Its first instructions compare the length with six, the first byte with 0xa5, and the second byte with one. Each mismatch reaches the same return value, two.
That establishes a candidate header rule. It does not yet explain how the remaining bytes affect the result.
The next block repeatedly loads one byte, combines it with an accumulator using XOR, and increments an index until five bytes have been processed. It compares the result with byte five. A mismatch returns three. The loop bounds distinguish the bytes being checked from the stored check byte.
| Byte offset | Recovered interpretation | Evidence |
|---|---|---|
| 0 | Magic value 0xa5 |
Constant comparison before the loop |
| 1 | Version value 1 |
Second constant comparison |
| 2 | Flags; bit zero participates in the final decision | Byte load and final AND |
| 3–4 | Unsigned little-endian reading | Halfword load, target byte order and discriminating inputs |
| 5 | XOR of bytes 0–4 | Loop bound and subsequent comparison |
Explain the Optimized Decision
Here is the relevant excerpt from the actual build:
00000001000004a8 ldurh w8, [x0, #0x3]
00000001000004ac ldrb w9, [x0, #0x2]
00000001000004b0 cmp w8, #0xbb8
00000001000004b4 cset w8, hi
00000001000004b8 and w0, w9, w8
00000001000004bc ret
0xbb8 is 3000. The unsigned comparison produces a zero-or-one value through cset. Combining that value with the flags byte retains only bit zero. There is no separate branch for each part of the original condition.
The recovered rule is: after header and checksum validation, report an alert only when bit zero is set and the unsigned reading is strictly greater than 3000. Arm’s condition-code reference explains the unsigned hi condition. The explanation here is also checked against the fixture’s source and runtime results.
Try to Disprove the Explanation
Testing only one alert frame would leave several competing explanations intact. The verification script instead includes cases that separate them:
| Hypothesis under test | Inputs or comparison | Observed result |
|---|---|---|
| Threshold is strictly greater than 3000 | Readings 2999, 3000, 3001 with bit zero set | Normal, normal, alert |
| Only flag bit zero enables the alert | Flags 0, 2, 1, 3 at reading 3001 | Normal, normal, alert, alert |
| Reading uses little-endian order | Data bytes 0c 01 versus 01 0c |
268 is normal; 3073 alerts |
| A valid reading is insufficient when the check byte is wrong | Replace the check byte with zero | Checksum error |
| Header rejection precedes the reading decision | Change magic or version and recompute the check byte | Header error |
All 18 hand-calculated cases check both printed output and process exit status. They include malformed hex, short and long input, and zero and maximum readings. This is bounded behavioral validation, not exhaustive proof of correctness.
Findings and Limits
The demonstrated method connects byte-level structure, optimized machine instructions, a recovered decision rule and independent expected results. It also shows why a readable decompilation is only one part of analysis: byte order, boundary conditions and rejection order still need validation.
The XOR byte is a corruption check, not authentication. Someone able to replace the frame can recompute it. In a real connected system, deciding who may supply telemetry would require a separate trust-boundary analysis. This fixture does not implement a physical device, an AI system or a secure protocol.
The exercise also omits packing, stripped symbols, indirect calls and anti-analysis behavior. Its wrapper always passes six decoded bytes to the classifier, so the classifier’s internal length-error branch is observed statically but not exercised by the command-line tests. These limits keep the conclusion proportional to the evidence.
For an optional follow-up, import your locally compiled executable into Ghidra, confirm its detected architecture, run analysis and compare the decompiler’s expression with this listing. The official Ghidra introduction describes that workflow. The published evidence here was collected with otool; it does not claim a completed Ghidra analysis.