nmarshall commited on
Commit
6787ca3
·
1 Parent(s): 6dafc4b

Recover message read modes and bounded bit assembly

Browse files
docs/ASSET_PIPELINE_TODO.md CHANGED
@@ -212,3 +212,5 @@ Area-state follow-up: [flooding and bit output](research/AREA_FLOODING.md) compo
212
  Snapshot follow-up: [area-mask lifecycle](research/SNAPSHOT_AREAS.md) connects supplied area labels to bounded native frame masks and message requests. Establish actual map area limits/reference validity before runtime conversion and renderer/network integration (RE-336).
213
 
214
  Client-mask follow-up: [client parsing](research/CLIENT_MASKS.md) bounds accepted area-mask length to 32 in the sampled Linux parser. Map-derived area limits, real protocol decoding and renderer mask interpretation still require validation before asset/runtime integration (RE-340).
 
 
 
212
  Snapshot follow-up: [area-mask lifecycle](research/SNAPSHOT_AREAS.md) connects supplied area labels to bounded native frame masks and message requests. Establish actual map area limits/reference validity before runtime conversion and renderer/network integration (RE-336).
213
 
214
  Client-mask follow-up: [client parsing](research/CLIENT_MASKS.md) bounds accepted area-mask length to 32 in the sampled Linux parser. Map-derived area limits, real protocol decoding and renderer mask interpretation still require validation before asset/runtime integration (RE-340).
215
+
216
+ Message-bit follow-up: [read modes and assembly](research/MESSAGE_BITS.md) establishes bounded cursor/assembly behavior with supplied Huffman symbols. Recover actual tree/codebooks and real packets before network/renderer integration with converted map assets (RE-344).
docs/FINDINGS.md CHANGED
@@ -366,3 +366,9 @@ Bounded qagame paths recover bomb-plant/inactivity correction requests, direct f
366
  **F-113 — Observed byte-reader boundary:** signed post-readcount <= cursize returns the supplied result's zero-extended low byte; larger readcounts return 0xffffffff. Malformed signed fixtures are local comparison probes, not valid runtime messages; actual bit/cursor updates remain open. [CLIENT-002](research/CLIENT_MASKS.md).
367
 
368
  **F-114 — Observed data-copy behavior:** positive lengths request one eight-bit read per byte even after overflow, storing 0xff past supplied cursize; signed nonpositive lengths exit. Supplied snapshot reset leaves unused mask bytes zero. This is bounded native instruction composition, not encoded-packet or original-runtime conformance. [CLIENT-003](research/CLIENT_MASKS.md).
 
 
 
 
 
 
 
366
  **F-113 — Observed byte-reader boundary:** signed post-readcount <= cursize returns the supplied result's zero-extended low byte; larger readcounts return 0xffffffff. Malformed signed fixtures are local comparison probes, not valid runtime messages; actual bit/cursor updates remain open. [CLIENT-002](research/CLIENT_MASKS.md).
367
 
368
  **F-114 — Observed data-copy behavior:** positive lengths request one eight-bit read per byte even after overflow, storing 0xff past supplied cursize; signed nonpositive lengths exit. Supplied snapshot reset leaves unused mask bytes zero. This is bounded native instruction composition, not encoded-packet or original-runtime conformance. [CLIENT-003](research/CLIENT_MASKS.md).
369
+
370
+ **F-115 — Observed read-mode resets:** both Linux ELF64 engines clear readcount +32 and bit +36; ordinary/OOB entry sets mode +8 to zero/one, retaining other sampled bytes. Whole message initialization and mode lifecycle remain open. [BITS-001](research/MESSAGE_BITS.md).
371
+
372
+ **F-116 — Observed OOB bit reads:** sampled nonzero mode reads only absolute widths 8/16/32 with little-endian loads and direct cursor increments, without consulting cursize locally; unsupported widths request an error. The -32 top-bit-set tail yields all ones through x86 shift masking. Actual legal caller/memory/runtime/platform behavior remains open. [BITS-002](research/MESSAGE_BITS.md).
373
+
374
+ **F-117 — Observed compressed reader assembly:** raw low remainder bits precede supplied Huffman symbol bytes; readcount becomes signed bit/8 +1. Signed tails use the remaining byte-multiple width after remainder subtraction. Huff_getBit instructions update cursor and bloc; real tree decoding, packets and original-runtime conformance remain open. [BITS-003](research/MESSAGE_BITS.md).
docs/PROVISIONAL_TODO.md CHANGED
@@ -524,3 +524,5 @@ Research progress: [area flooding and bit output](research/AREA_FLOODING.md) com
524
  Research progress: [snapshot area-mask lifecycle](research/SNAPSHOT_AREAS.md) completes bounded RE-333–335. Full networking/renderer/runtime milestones remain open; RE-336 tracks real viewpoint selection, codecs, client parsing and mask consumers.
525
 
526
  Research progress: [client area-mask parsing](research/CLIENT_MASKS.md) completes bounded RE-337–339. Broad protocol/renderer/runtime milestones remain open; RE-340 tracks actual codec/cursors, validation/publication and real packet/platform coverage.
 
 
 
524
  Research progress: [snapshot area-mask lifecycle](research/SNAPSHOT_AREAS.md) completes bounded RE-333–335. Full networking/renderer/runtime milestones remain open; RE-336 tracks real viewpoint selection, codecs, client parsing and mask consumers.
525
 
526
  Research progress: [client area-mask parsing](research/CLIENT_MASKS.md) completes bounded RE-337–339. Broad protocol/renderer/runtime milestones remain open; RE-340 tracks actual codec/cursors, validation/publication and real packet/platform coverage.
527
+
528
+ Research progress: [message read modes and assembly](research/MESSAGE_BITS.md) completes bounded RE-341–343. Broad networking/runtime milestones remain open; RE-344 tracks real Huffman codecs, caller invariants and packet/platform fixtures.
docs/RESEARCH_LEDGER.md CHANGED
@@ -201,3 +201,7 @@ Completed bounded RE-333–335 with `analyze_snapshot_areas.py`: ten verified gr
201
  ## 2026-10-11 — Client snapshot mask parsing and reader boundaries
202
 
203
  Completed bounded RE-337–339 with `analyze_client_masks.py`: three verified Linux client graphs, one reset-argument slice, 22 unsigned guard probes, 210 signed byte-reader probes and 53 unrolled data-reader compositions. CLIENT-001–003/F-112–114 distinguish reset requests from supplied memset effects, EOF return words from low-byte 0xff, signed cursor comparison from actual bit/Huffman cursor updates, and exact destination copying from full snapshot publication. PUSH/POP omission and stopped helper boundaries are explicit; no original process or native callee executes. Eight independent tests check reset/guard/error arguments, signed returns, copy/EOF continuation/tail/canaries/loop coverage, trace boundaries and evaluator CMOVLEL/BL/unknown-flag behavior. The full suite passes 286 tests. New and preceding spatial reports regenerate byte-identically; storage, links and whitespace checks pass. RE-336 remains open; RE-340 tracks actual codecs/cursors, bad-message checks, full validation/publication, renderer semantics and real-packet/map/original-runtime/all-platform coverage.
 
 
 
 
 
201
  ## 2026-10-11 — Client snapshot mask parsing and reader boundaries
202
 
203
  Completed bounded RE-337–339 with `analyze_client_masks.py`: three verified Linux client graphs, one reset-argument slice, 22 unsigned guard probes, 210 signed byte-reader probes and 53 unrolled data-reader compositions. CLIENT-001–003/F-112–114 distinguish reset requests from supplied memset effects, EOF return words from low-byte 0xff, signed cursor comparison from actual bit/Huffman cursor updates, and exact destination copying from full snapshot publication. PUSH/POP omission and stopped helper boundaries are explicit; no original process or native callee executes. Eight independent tests check reset/guard/error arguments, signed returns, copy/EOF continuation/tail/canaries/loop coverage, trace boundaries and evaluator CMOVLEL/BL/unknown-flag behavior. The full suite passes 286 tests. New and preceding spatial reports regenerate byte-identically; storage, links and whitespace checks pass. RE-336 remains open; RE-340 tracks actual codecs/cursors, bad-message checks, full validation/publication, renderer semantics and real-packet/map/original-runtime/all-platform coverage.
204
+
205
+ ## 2026-10-11 — Message read modes and bit assembly
206
+
207
+ Completed bounded RE-341–343 with `analyze_message_bits.py`: eight verified graphs across both Linux ELF64 engines, eight exact reset probes, 600 OOB and 688 compressed reader compositions. BITS-001–003/F-115–117 distinguish padded backing storage from cursize, direct OOB loads from compressed cursors, composed raw-bit instructions from supplied Huffman symbols/code lengths, and requested signed width from the mutated width used by the tail. Nonbyte and 32-bit signed-tail effects are recorded without assuming legal packet reachability or cross-platform equivalence. Prologue/frame omission and explicit caller-register restoration are documented; original processes and every native callee remain unexecuted. Eight independent tests verify reset fields/canaries, dispatch/errors, little-endian/sign/cursor results, raw/symbol assembly, helper arguments/global writes, cursor formula, signed edge examples, paired outputs and restricted MOVZWL/NEGL/SHRL evaluator behavior. All 294 tests pass; new and preceding reports regenerate byte-identically, and storage/link/whitespace checks pass. RE-340 remains open; RE-344 tracks Huffman initialization/tree/codebooks/receive/transmit/put-bit, actual writer/reader packets, legal width/mode/cursor callers and full runtime/platform/snapshot/renderer conformance.
docs/REVERSE_ENGINEERING_TODO.md CHANGED
@@ -407,3 +407,8 @@ Purpose: turn the supplied installation into an evidence-backed specification of
407
  - [x] RE-338 Recover bounded byte-reader low-byte/EOF returns and signed cursor comparison ([CLIENT-002](research/CLIENT_MASKS.md)).
408
  - [x] RE-339 Compose exact unrolled mask copies with supplied bit returns/readcounts, EOF continuation and zero-tail/canary checks ([CLIENT-003](research/CLIENT_MASKS.md)).
409
  - [ ] RE-340 Recover actual bit/Huffman codec and readcount updates, overflow/bad-message checks, full snapshot/delta/player/entity validation/publication and renderer mask interpretation; validate real packets/maps, original-runtime and all platforms before Rust/Bevy/Solari integration.
 
 
 
 
 
 
407
  - [x] RE-338 Recover bounded byte-reader low-byte/EOF returns and signed cursor comparison ([CLIENT-002](research/CLIENT_MASKS.md)).
408
  - [x] RE-339 Compose exact unrolled mask copies with supplied bit returns/readcounts, EOF continuation and zero-tail/canary checks ([CLIENT-003](research/CLIENT_MASKS.md)).
409
  - [ ] RE-340 Recover actual bit/Huffman codec and readcount updates, overflow/bad-message checks, full snapshot/delta/player/entity validation/publication and renderer mask interpretation; validate real packets/maps, original-runtime and all platforms before Rust/Bevy/Solari integration.
410
+
411
+ - [x] RE-341 Recover paired exact readcount/bit/mode resets and retained bytes ([BITS-001](research/MESSAGE_BITS.md)).
412
+ - [x] RE-342 Recover bounded OOB width/error dispatch, little-endian reads and cursor/signed-tail effects ([BITS-002](research/MESSAGE_BITS.md)).
413
+ - [x] RE-343 Compose raw-bit instructions and supplied compressed symbol assembly, cursor/global writes and remaining-width signed tails ([BITS-003](research/MESSAGE_BITS.md)).
414
+ - [ ] RE-344 Recover Huffman tree initialization/frequencies/codebooks/receive/transmit/put-bit, full writer-reader and real-packet compositions, overflow/truncation and legal signed width/mode/cursor callers; validate snapshot/renderer integration and original-runtime/all-platform contracts.
docs/research/CLIENT_MASKS.md CHANGED
@@ -31,3 +31,5 @@ The server requests only its mask's used byte span; the client guard caps that s
31
  Eight independent tests cover reset arguments, both unsigned guards and error arguments, signed cursor/low-byte return, exact copy requests and bytes, continued EOF requests, canaries/tails/unrolled lengths, stopped CALL boundaries, and restricted evaluator CMOVLEL/BL semantics with unknown-flag rejection. Reports include source/disassembly hashes and native trace addresses or hashes. The evaluator extension adds only signed conditional move and the BL register alias.
32
 
33
  RE-340: recover actual bit/Huffman encoding and readcount updates, message overflow/bad-read handling, all snapshot/delta/player/entity validation and publication, mask renderer interpretation and map-area validity; compose real encoded packets and original-runtime/all-platform fixtures. Implement Rust protocol/Bevy/Solari integration only against a complete validated contract.
 
 
 
31
  Eight independent tests cover reset arguments, both unsigned guards and error arguments, signed cursor/low-byte return, exact copy requests and bytes, continued EOF requests, canaries/tails/unrolled lengths, stopped CALL boundaries, and restricted evaluator CMOVLEL/BL semantics with unknown-flag rejection. Reports include source/disassembly hashes and native trace addresses or hashes. The evaluator extension adds only signed conditional move and the BL register alias.
32
 
33
  RE-340: recover actual bit/Huffman encoding and readcount updates, message overflow/bad-read handling, all snapshot/delta/player/entity validation and publication, mask renderer interpretation and map-area validity; compose real encoded packets and original-runtime/all-platform fixtures. Implement Rust protocol/Bevy/Solari integration only against a complete validated contract.
34
+
35
+ Bit-reader follow-up: [message modes and assembly](MESSAGE_BITS.md) recovers bounded OOB reads, raw bit instructions and compressed cursor/sign-tail behavior (RE-341–343). Huffman symbols/code lengths remain supplied; real packet decoding and full snapshot validation remain open.
docs/research/MESSAGE_BITS.md ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Message read modes, raw bits and compressed assembly
2
+
3
+ Reproduce with `python3 scripts/analyze_message_bits.py`; validate with `python3 scripts/test_message_bits.py`. [message-bits.json](message-bits.json) retains four hash-verified graphs per Linux ELF64 engine: MSG_BeginReading, MSG_BeginReadingOOB, MSG_ReadBits and Huff_getBit. This follows [client mask reader boundaries](CLIENT_MASKS.md).
4
+
5
+ The collector evaluates bounded native instruction slices using virtual message, data and stack addresses. Prologue register saves/restores are excluded. Huff_getBit instructions are composed across stopped calls with explicit caller-register restoration; Huff_offsetReceive never decodes a tree and instead receives supplied symbol/cursor effects. Original processes and every native callee remain unexecuted. Backing bytes are deliberately padded independently of cursize; these fixtures characterize local access and comparison behavior, not safe real packet loading, valid malformed states or runtime conformance.
6
+
7
+ ## BITS-001 — Read-mode resets (RE-341)
8
+
9
+ Eight paired probes cover two sentinel initializations of both mode-reset functions. They write readcount at message +32 and bit cursor at +36 to zero. MSG_BeginReading sets the mode word at +8 to zero, while MSG_BeginReadingOOB sets it to one. Every other byte in the supplied 40-byte record retains its sentinel, and outside canaries remain unchanged. These are native Linux layouts, not a portable Rust structure. Initialization/allocation and write-mode transitions remain separate work.
10
+
11
+ ## BITS-002 — Out-of-band reads (RE-342)
12
+
13
+ Six hundred paired OOB compositions cover widths 8/16/32, negative counterparts, and unsupported 0/1/7/9/24/33; four constant byte patterns; initial readcounts -1/0/3; cursize 0/8; and an additional nonzero mode value two. Any sampled nonzero mode selects this path. Negative requested widths are negated and set a local signed-return flag before dispatch.
14
+
15
+ Only absolute widths 8,16,32 select byte, zero-extended little-endian word and little-endian dword loads. Readcount increases by 1/2/4 and bit cursor by 8/16/32. The sampled path does not compare cursize before accessing the backing data. Padded fixtures including negative readcount and zero cursize therefore still produce values; this does not establish legal packet states or memory safety. Unsupported widths request Com_Error with code one, the absolute width and the verified format `can't read %d bits\n`. Error execution is stopped before the callee and fixture cursors remain unchanged.
16
+
17
+ The signed tail tests the top requested bit and ORs a mask produced by a native 32-bit shift. For -8 and -16 this extends the sign normally. For -32 with bit 31 set, x86 masks the shift count to zero, so the tail ORs 0xffffffff: for example a supplied repeated 0x96 dword becomes 0xffffffff rather than 0x96969696. Positive 32-bit reads retain the full loaded word. Legal callers, other architectures and original-runtime observations are still required before choosing compatibility behavior.
18
+
19
+ ## BITS-003 — Raw remainder, compressed symbols and cursor/sign tail (RE-343)
20
+
21
+ Six hundred eighty-eight paired compressed compositions cover every nonnegative width 0–32, negative widths -1/-7/-8/-9/-15/-16/-17/-24/-31/-32, four backing/symbol patterns and initial bit cursors zero/seven. The reader consumes `abs(width) % 8` raw bits first, then one Huff_offsetReceive request per remaining byte. Raw bits occupy successive low result bits; supplied symbols occupy successive higher eight-bit lanes.
22
+
23
+ The composed Huff_getBit instructions select `(data[bit >> 3] >> (bit & 7)) & 1`, using signed arithmetic shift for the byte index. They increment the supplied cursor and store the same incremented word to the verified global `bloc`. There is no cursize parameter or bounds check in this helper. The collector composes these actual instructions for raw bits, while every compressed symbol and code length is explicitly supplied.
24
+
25
+ Each Huff_offsetReceive request forwards a supplied tree pointer (0x40000), a stack symbol-output pointer, the data pointer and message +36 as the bit-cursor pointer. Symbols cycle from the chosen byte with increments of 37 modulo 256. Supplied code lengths are `5 + ordinal % 4`, with ordinals starting at one. These lengths are test inputs, not recovered Huffman code lengths. Actual tree initialization, codebooks, decoding/encoding and malformed-code handling remain open.
26
+
27
+ After all requests, the reader writes `readcount = (signed_bit_cursor >> 3) + 1`. Our nonnegative bounded fixtures verify this formula, including width zero and exact-byte bit positions: bit zero yields readcount one, and bit eight yields two. Thus compressed readcount is not simply the number of requested payload bytes and is not a ceiling division at byte boundaries. Complete wrap/negative-cursor lifecycle is untested.
28
+
29
+ The remainder is subtracted from the local width register before the signed tail. The tail tests and extends using this **remaining byte-multiple width**, not the original request. With supplied all-one raw bits, -1 returns 1 and -7 returns 127; a supplied -31 composition returns 0xff927fff. Width -32 also has the shift-count effect described above: symbols `[129,166,203,240]` assemble to 0xf0cba681 for +32 but become 0xffffffff for -32. These observations are local native behavior; they are not a complete protocol interpretation or proof that those signed widths are used by valid game messages.
30
+
31
+ ## Validation and next work
32
+
33
+ Eight independent tests verify exact mode resets/canaries, OOB width-error dispatch, byte order/sign/cursor increments, raw-bit and symbol assembly, helper arguments/global writes and supplied cursor formula, nonbyte/32-bit signed examples, paired engine outputs, and restricted MOVZWL/NEGL/SHRL evaluator semantics. Each composition retains trace counts/hashes; full verified graphs retain instruction addresses. No native CALL instruction executes implicitly.
34
+
35
+ RE-344: recover Huffman initialization/frequency tables/tree/codebooks and bounded receive/transmit/put-bit behavior; compose writers with readers and real encoded packets, including overflow/truncation; audit legal signed widths, mode/bit/readcount invariants and actual callers; validate snapshot publication/renderer semantics and all original-runtime/platform contracts before Rust implementation.
docs/research/README.md CHANGED
@@ -125,3 +125,5 @@ The extension collector adds five focused tests for native tail calls, command-r
125
  [Snapshot area-mask lifecycle and message arguments](SNAPSHOT_AREAS.md) adds RE-333–335: bounded ring/reset, native area union/complement and length/data request spans. Reproduce with `python3 scripts/analyze_snapshot_areas.py`; check with `python3 scripts/test_snapshot_areas.py`. Full viewpoint/entity/PVS/portal/lifecycle/codec/client/renderer/runtime/platform contracts remain open.
126
 
127
  [Client snapshot area-mask parsing](CLIENT_MASKS.md) adds RE-337–339: temporary reset/length guards, byte-reader EOF and bounded unrolled data copies. Reproduce with `python3 scripts/analyze_client_masks.py`; check with `python3 scripts/test_client_masks.py`. Actual codecs/cursors, full snapshot publication and renderer/runtime/platform contracts remain open.
 
 
 
125
  [Snapshot area-mask lifecycle and message arguments](SNAPSHOT_AREAS.md) adds RE-333–335: bounded ring/reset, native area union/complement and length/data request spans. Reproduce with `python3 scripts/analyze_snapshot_areas.py`; check with `python3 scripts/test_snapshot_areas.py`. Full viewpoint/entity/PVS/portal/lifecycle/codec/client/renderer/runtime/platform contracts remain open.
126
 
127
  [Client snapshot area-mask parsing](CLIENT_MASKS.md) adds RE-337–339: temporary reset/length guards, byte-reader EOF and bounded unrolled data copies. Reproduce with `python3 scripts/analyze_client_masks.py`; check with `python3 scripts/test_client_masks.py`. Actual codecs/cursors, full snapshot publication and renderer/runtime/platform contracts remain open.
128
+
129
+ [Message read modes and bit assembly](MESSAGE_BITS.md) adds RE-341–343: exact resets, OOB loads/errors/cursors and raw-bit/compressed-symbol/sign-tail compositions. Reproduce with `python3 scripts/analyze_message_bits.py`; check with `python3 scripts/test_message_bits.py`. Huffman trees, writers, legal caller invariants and real packet/runtime/platform conformance remain open.
docs/research/message-bits.json ADDED
The diff for this file is too large to render. See raw diff
 
scripts/analyze_collision_composition.py CHANGED
@@ -64,8 +64,8 @@ def evaluate(graph,start,stops,memory,registers,limit=128,capture_registers=Fals
64
  if op in ['nop','nopl','nopw']:pass
65
  elif op in ['movb','movl','movq','movss','movabsq']:
66
  a,b=args.split(', ');width=8 if op in ['movq','movabsq'] else 1 if op=='movb' else 4;write(b,read(a,width),width)
67
- elif op=='movzbl':
68
- a,b=args.split(', ');write(b,read(a,1),4)
69
  elif op=='sbbl':
70
  if below is None:raise ValueError('unmodeled carry flag')
71
  a,b=args.split(', ');write(b,read(b,4)-read(a,4)-int(below),4)
@@ -106,11 +106,13 @@ def evaluate(graph,start,stops,memory,registers,limit=128,capture_registers=Fals
106
  elif len(parts)==3:a,b,dest=parts;v,w=read(a,width),read(b,width)
107
  else:raise ValueError('unsupported multiply operands')
108
  write(dest,v*w,width);equal=None;signed_le=signed_lt=negative=None;ge=greater=below=None
109
- elif op in ['sarl','shll']:
 
 
110
  a,b=args.split(', ');amount=read(a,4)&31;v=read(b,4)
111
  if amount:
112
  signed_v=v if v<2**31 else v-2**32
113
- write(b,signed_v>>amount if op=='sarl' else v<<amount,4)
114
  equal=None;signed_le=signed_lt=negative=None;ge=greater=below=None
115
  elif op=='shlq':
116
  a,b=args.split(', ');write(b,read(b,8)<<(read(a,8)&63),8)
 
64
  if op in ['nop','nopl','nopw']:pass
65
  elif op in ['movb','movl','movq','movss','movabsq']:
66
  a,b=args.split(', ');width=8 if op in ['movq','movabsq'] else 1 if op=='movb' else 4;write(b,read(a,width),width)
67
+ elif op in ['movzbl','movzwl']:
68
+ a,b=args.split(', ');write(b,read(a,1 if op=='movzbl' else 2),4)
69
  elif op=='sbbl':
70
  if below is None:raise ValueError('unmodeled carry flag')
71
  a,b=args.split(', ');write(b,read(b,4)-read(a,4)-int(below),4)
 
106
  elif len(parts)==3:a,b,dest=parts;v,w=read(a,width),read(b,width)
107
  else:raise ValueError('unsupported multiply operands')
108
  write(dest,v*w,width);equal=None;signed_le=signed_lt=negative=None;ge=greater=below=None
109
+ elif op=='negl':
110
+ write(args,-read(args,4),4);equal=None;signed_le=signed_lt=negative=None;ge=greater=below=None
111
+ elif op in ['sarl','shll','shrl']:
112
  a,b=args.split(', ');amount=read(a,4)&31;v=read(b,4)
113
  if amount:
114
  signed_v=v if v<2**31 else v-2**32
115
+ write(b,signed_v>>amount if op=='sarl' else v>>amount if op=='shrl' else v<<amount,4)
116
  equal=None;signed_le=signed_lt=negative=None;ge=greater=below=None
117
  elif op=='shlq':
118
  a,b=args.split(', ');write(b,read(b,8)<<(read(a,8)&63),8)
scripts/analyze_message_bits.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Synthetic native read-mode, OOB and compressed reader compositions."""
3
+ import gzip
4
+ import itertools
5
+ import json
6
+ from pathlib import Path
7
+ from analyze_area_queries import site
8
+ from analyze_bsp_references import global_address,trace_fields
9
+ from analyze_chat_paths import native_string
10
+ from analyze_collision_composition import evaluate,get_word,put_word
11
+ from analyze_engine_control import control_flow
12
+ from analyze_native import elf
13
+ from analyze_qvm import write_json
14
+ from analyze_vm_extensions import native_functions,verified
15
+
16
+
17
+ def run(g,start,stops,m,regs):return evaluate(g,start,stops,m,regs,limit=10000,capture_registers=True)
18
+
19
+
20
+ def reader(graphs,width,mode,pattern,cursor=0,initial_bit=0,size=0):
21
+ g=graphs['MSG_ReadBits'];rows=g['instructions'];message,data,stack=0x10000,0x20000,0x30000
22
+ start=site(g,'testl','%esi, %esi');end=next(x['address'] for x in rows if x['opcode']=='movl' and x['operands'].split('#')[0].strip()=='%ebp, %eax')
23
+ calls={x['address']:i for i,x in enumerate(rows) if x['opcode']=='callq'}
24
+ m={}
25
+ for x in rows:
26
+ if x['opcode']=='movq' and '(%rip)' in x['operands']:put_word(m,global_address(x),0x40000,8)
27
+ put_word(m,message+8,mode);put_word(m,message+16,data,8);put_word(m,message+28,size);put_word(m,message+32,cursor);put_word(m,message+36,initial_bit)
28
+ # Padding bytes model local memory access, not a valid message length.
29
+ for i in range(-8,24):m[data+i]=[0,255,0x96,0x81][pattern]
30
+ regs={'%r12':message,'%r15':width&0xffffffff,'%rsi':width&0xffffffff,'%rsp':stack}
31
+ trace=[];requests=[];symbols=[];raw_bits=[];children=[];error=None
32
+ while True:
33
+ t=run(g,start,list(calls)+[end],m,regs);trace+=t['trace'];m=t['memory'];regs=t['registers']
34
+ if t['stop']==end:break
35
+ i=calls[t['stop']];name=rows[i]['operands']
36
+ if '<Com_Error>' in name:
37
+ error={'arguments':[regs[k] for k in ['rdi','rsi','rdx']]};break
38
+ if '<Huff_getBit>' in name:
39
+ requests.append(['Huff_getBit',regs['rdi'],regs['rsi']]);h=graphs['Huff_getBit'];ret=next(x['address'] for x in h['instructions'] if x['opcode']=='retq')
40
+ child=run(h,h['instructions'][0]['address'],[ret],m,{'%rdi':regs['rdi'],'%rsi':regs['rsi']});m=child['memory'];regs['rax']=child['registers']['rax'];raw_bits.append(regs['rax']);children+=child['trace']
41
+ elif '<Huff_offsetReceive>' in name:
42
+ requests.append(['Huff_offsetReceive',regs['rdi'],regs['rsi'],regs['rdx'],regs['rcx']])
43
+ symbol=([0,255,0x96,0x81][pattern]+37*len(symbols))&255;symbols.append(symbol)
44
+ # Huffman tree decoding remains stopped; symbol and variable code length supplied.
45
+ put_word(m,regs['rsi'],symbol);put_word(m,regs['rcx'],get_word(m,regs['rcx'])+5+len(symbols)%4)
46
+ else:raise ValueError('unexpected helper '+name)
47
+ if len(requests)>36:raise ValueError('fixture request bound')
48
+ start=rows[i+1]['address']
49
+ return {'width':width,'mode_word':mode,'pattern':pattern,'initial_readcount':cursor,'initial_bit':initial_bit,'cursize':size,
50
+ 'result_word':None if error else regs['rbp']&0xffffffff,'error':error,'final_readcount':get_word(m,message+32),'final_bit':get_word(m,message+36),
51
+ 'remaining_width_word':regs['r15']&0xffffffff,'raw_bits':raw_bits,'supplied_symbols':symbols,'requests':requests,
52
+ 'final_raw_bit_global':get_word(m,global_address(next(x for x in graphs['Huff_getBit']['instructions'] if '(%rip)' in x['operands']))) if raw_bits else None,
53
+ 'child_trace':trace_fields({'trace':children},False),**trace_fields({'trace':trace},False)}
54
+
55
+
56
+ def main():
57
+ out={'native':{},'limitations':'Two Linux ELF64 engines, bounded synthetic read slices only. Original processes/callees never execute. Prologue saves/restores excluded and virtual stack supplied. Huff_getBit instructions composed with explicit caller-register restoration; Huff_offsetReceive symbol/cursor effects supplied. Padded backing memory is distinct from cursize. No real Huffman tree/writer/packet/error execution, ABI, legal malformed-state reachability or all-platform conformance.'}
58
+ for label in ['linux--Quake3-UrT-Ded.x86_64','linux--Quake3-UrT.x86_64']:
59
+ r=json.loads(Path(f'docs/research/native/{label}.json').read_text());data=verified((Path('.cache/UrbanTerror43')/r['source']['path']).read_bytes(),r['source']['sha256']);meta,_=elf(data)
60
+ a=r['artifacts']['disassembly.txt'];fs=native_functions(verified(gzip.open(a['path'],'rb').read(),a['uncompressed_sha256']).decode());graphs={n:control_flow(fs[n]) for n in ['MSG_BeginReading','MSG_BeginReadingOOB','MSG_ReadBits','Huff_getBit']}
61
+ resets=[]
62
+ for name,initial in itertools.product(['MSG_BeginReading','MSG_BeginReadingOOB'],[0,0xa5]):
63
+ g=graphs[name];m={0x10000+i:initial for i in range(-1,41)};end=next(x['address'] for x in g['instructions'] if x['opcode']=='retq');t=run(g,g['instructions'][0]['address'],[end],m,{'%rdi':0x10000})
64
+ resets.append({'function':name,'initial_byte':initial,'record_bytes':[t['memory'][0x10000+i] for i in range(40)],'canaries':[t['memory'][0xffff],t['memory'][0x10028]],**trace_fields(t,True)})
65
+ oob=[reader(graphs,w,mode,p,cursor,19,size) for w,mode,p,cursor,size in itertools.product([8,16,32,-8,-16,-32,0,1,7,9,24,33],[1],[0,1,2,3],[-1,0,3],[0,8])]
66
+ oob += [reader(graphs,w,2,p,0,19,0) for w,p in itertools.product([8,16,32,0,7,-8],[1,2])]
67
+ compressed=[reader(graphs,w,0,p,0,bit,0) for w,p,bit in itertools.product(list(range(0,33))+[-1,-7,-8,-9,-15,-16,-17,-24,-31,-32],[0,1,2,3],[0,7])]
68
+ g=graphs['MSG_ReadBits'];err=next(x for x in g['instructions'] if x['opcode']=='movl' and x['operands'].split('#')[0].strip().endswith(', %esi') and '$0x' in x['operands']);addr=int(err['operands'].split(',')[0][1:],0)
69
+ bit_g=graphs['Huff_getBit'];bloc=global_address(next(x for x in bit_g['instructions'] if '(%rip)' in x['operands']))
70
+ out['native'][label]={'source':r['source'],'disassembly_sha256':a['uncompressed_sha256'],'graphs':graphs,'error_format':native_string(data,meta['sections'],addr),'bit_global_address':bloc,'reset_probes':resets,'oob_probes':oob,'compressed_probes':compressed}
71
+ write_json(Path('docs/research/message-bits.json'),out)
72
+ print('Recovered eight resets, 600 OOB and 688 compressed reader compositions')
73
+
74
+
75
+ if __name__=='__main__':main()
scripts/test_message_bits.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Independent reset, byte-order, bit assembly, cursor and sign-tail oracles."""
3
+ import json
4
+ from pathlib import Path
5
+ import unittest
6
+ from analyze_collision_composition import evaluate,put_word
7
+ from test_bsp_planes import graph
8
+
9
+
10
+ class MessageBitTests(unittest.TestCase):
11
+ @classmethod
12
+ def setUpClass(cls):cls.r=json.loads(Path('docs/research/message-bits.json').read_text())
13
+
14
+ def test_reset_exact_fields_and_canaries(self):
15
+ for n in self.r['native'].values():
16
+ for p in n['reset_probes']:
17
+ expected=[p['initial_byte']]*40
18
+ for offset,value in [(8,int(p['function'].endswith('OOB'))),(32,0),(36,0)]:expected[offset:offset+4]=list(value.to_bytes(4,'little'))
19
+ self.assertEqual(p['record_bytes'],expected);self.assertEqual(p['canaries'],[p['initial_byte']]*2)
20
+
21
+ def test_oob_width_errors_and_cursize_independence(self):
22
+ for n in self.r['native'].values():
23
+ self.assertEqual(len(n['oob_probes']),300)
24
+ for p in n['oob_probes']:
25
+ accepted=abs(p['width']) in [8,16,32];self.assertEqual(p['error'] is None,accepted)
26
+ if not accepted:
27
+ self.assertEqual(p['error']['arguments'][0],1);self.assertEqual(p['error']['arguments'][2],abs(p['width']))
28
+ self.assertEqual(p['final_readcount'],p['initial_readcount']&0xffffffff);self.assertEqual(p['final_bit'],19)
29
+ self.assertEqual(p['requests'],[])
30
+
31
+ def test_oob_little_endian_signed_result_and_cursors(self):
32
+ for n in self.r['native'].values():
33
+ for p in n['oob_probes']:
34
+ if p['error']:continue
35
+ width=abs(p['width']);byte=[0,255,0x96,0x81][p['pattern']];v=int.from_bytes(bytes([byte])*(width//8),'little')
36
+ if p['width']<0 and v&(1<<(width-1)):v|=(0xffffffff<<(width&31))&0xffffffff
37
+ self.assertEqual(p['result_word'],v&0xffffffff)
38
+ self.assertEqual(p['final_readcount'],(p['initial_readcount']+width//8)&0xffffffff);self.assertEqual(p['final_bit'],19+width)
39
+
40
+ def test_compressed_raw_bits_symbols_and_assembly(self):
41
+ for n in self.r['native'].values():
42
+ self.assertEqual(len(n['compressed_probes']),344)
43
+ for p in n['compressed_probes']:
44
+ width=abs(p['width']);remainder=width%8;byte=[0,255,0x96,0x81][p['pattern']]
45
+ raw=[(byte>>((p['initial_bit']+i)%8))&1 for i in range(remainder)]
46
+ self.assertEqual(p['raw_bits'],raw)
47
+ self.assertEqual(p['final_raw_bit_global'],p['initial_bit']+remainder if remainder else None)
48
+ symbols=[(byte+37*i)&255 for i in range(width//8)];self.assertEqual(p['supplied_symbols'],symbols)
49
+ v=sum(bit<<i for i,bit in enumerate(raw))+sum(sym<<(remainder+8*i) for i,sym in enumerate(symbols))
50
+ remaining=width-remainder;self.assertEqual(p['remaining_width_word'],remaining)
51
+ # The native tail tests the mutated remaining width, not the original request.
52
+ if p['width']<0 and remaining and v&(1<<(remaining-1)):v|=(0xffffffff<<(remaining&31))&0xffffffff
53
+ self.assertEqual(p['result_word'],v&0xffffffff)
54
+
55
+ def test_compressed_cursor_formula_and_helper_arguments(self):
56
+ for n in self.r['native'].values():
57
+ for p in n['compressed_probes']:
58
+ width=abs(p['width']);final=p['initial_bit']+width%8+sum(5+(i+1)%4 for i in range(width//8))
59
+ self.assertEqual(p['final_bit'],final);self.assertEqual(p['final_readcount'],final//8+1)
60
+ self.assertEqual(len(p['requests']),width%8+width//8)
61
+ for q in p['requests']:
62
+ if q[0]=='Huff_getBit':self.assertEqual(q[1:],[0x20000,0x10024])
63
+ else:self.assertEqual(q[1:],[0x40000,0x30014,0x20000,0x10024])
64
+
65
+ def test_nonbyte_signed_tail_examples(self):
66
+ for n in self.r['native'].values():
67
+ rows={p['width']:p for p in n['compressed_probes'] if p['pattern']==1 and p['initial_bit']==0}
68
+ self.assertEqual(rows[-1]['result_word'],1);self.assertEqual(rows[-7]['result_word'],127)
69
+ self.assertEqual(rows[-8]['result_word'],0xffffffff);self.assertEqual(rows[-31]['result_word'],0xff927fff)
70
+ high={p['width']:p for p in n['compressed_probes'] if p['pattern']==3 and p['initial_bit']==0}
71
+ self.assertEqual(high[32]['result_word'],0xf0cba681);self.assertEqual(high[-32]['result_word'],0xffffffff)
72
+
73
+ def test_paired_outputs_and_no_calls_in_bit_helper(self):
74
+ a,b=self.r['native'].values()
75
+ for key in ['reset_probes','oob_probes','compressed_probes']:
76
+ def clean(rows):
77
+ return [{k:(None if k=='error' and v is None else {'arguments':[v['arguments'][0],v['arguments'][2]]} if k=='error' else v) for k,v in p.items() if k not in ['trace','trace_sha256','child_trace']} for p in rows]
78
+ self.assertEqual(clean(a[key]),clean(b[key]))
79
+ for n in [a,b]:self.assertFalse(any(x['opcode']=='callq' for x in n['graphs']['Huff_getBit']['instructions']))
80
+
81
+ def test_restricted_word_load_negate_logical_shift(self):
82
+ g=graph([('movzwl','(%rdi), %eax'),('negl','%eax'),('shrl','$0x1, %eax'),('nop','')]);m={};put_word(m,100,0x80ff,2)
83
+ t=evaluate(g,0,[3],m,{'%rdi':100},capture_registers=True);self.assertEqual(t['registers']['rax'],((-0x80ff)&0xffffffff)>>1)
84
+
85
+
86
+ if __name__=='__main__':unittest.main()