D308 - Ask the guest which field it wants, rather than guessing the structure
decided ยท 2026-08-27
D306
left all five payloads dying at 0x1 having called nothing,
because each takes a pointer in rdi and calls through it,
and nothing here knows that structure's layout.
The obvious move is to guess offsets, one boot per candidate. The
better one is to make the guest say. Both are settings on
EntryArgument, which is what D153 built it for.
Markers: which field, exactly, in one boot
Fill every slot with a different unmapped address. The guest reads one, uses it, faults - and the faulting address says which slot it came from. Not one boot per candidate offset: one boot for the whole structure.
elfldr 0.25 fetch from 0x5e2700000000 slot 0, +0
klogsrv 0.9 fetch from 0x5e2700000000 slot 0, +0
shsrv 0.20 fetch from 0x5e2700000000 slot 0, +0
ftpsrv 0.21.1 fetch from 0x5e2700000000 slot 0, +0
pldmgr 0.5.1 fetch from 0x5e2700000000 slot 0, +0
The first member is a function pointer and it is called immediately. Five programs, five authors, one answer, to the byte. The stride is wide so a displacement the guest adds lands inside the slot it came from - the fault then names both the field and the offset within it.
Then: answer everything, and see how far it gets
A second setting points every slot at code that returns zero. The wall moved three times:
| instrument | klogsrv | ftpsrv | elfldr |
|---|---|---|---|
| nothing | 0x241b |
0x241b |
0x241b |
| every field answers | 0x24a2 |
0x7d42 |
- |
| slot 0 callable, rest writable | 0x2708 |
0x7fa8 |
0x4a48 |
The middle row taught something the first could not: after calling slot zero the guest wrote through a pointer out of the block, into the read-execute stub page. So the structure holds data pointers as well as functions, and handing every slot one executable address cannot tell the two apart. Slot zero now gets the returning page and every other slot its own writable one.
Where it stops, and why that is a good place
illegal instruction at image+0x2708. The bytes
there:
0f 0b 0f 0b cc cc cc cc
ud2, twice, with int3 padding - a
compiler-emitted deliberate trap. The guest is not
derailed into data; it ran its own code, checked something, and rejected
what it was given. That distinction is exactly the one D303 insists on,
and this lands on the right side of it: the payload is far enough in to
be validating content rather than presence.
What this says about the method
The diagnostic was wrong before the guest was. The
first answering stub was three bytes in a page of zeros, the guest
entered it at +0xa, ran off the end into 00 00
- add [rax], al - and faulted on a write. The report said
the guest wrote to a bad address, which was true and useless: the wrong
thing was the instrument. A page of ret fixed it.
That is principle 3 one level up, again, and the third time this session: a tool is as capable of plausible output as a stub is.
And the next step is cheaper by reading than by running
The guest is now rejecting content, not presence, so more boots buy
less: markers find which field, and no number of them says what
a field must contain. The payload SDK is open source
and documents its own handoff ABI, which principle 1 permits reading as
prose - recorded published, credited, and written out in
our own words rather than pasted, since they are GPL-3.0 and this is
not.
Then the ladder does its job: published, promoted to
measured when the marker run agrees.