urt-rs / scripts /analyze_chat_expansion.py
nmarshall's picture
Recover chat expansion states and substitution keys
9038fbf
Raw History Blame Contribute Delete
5.42 kB
#!/usr/bin/env python3
"""Recover bounded radio parser states, key comparisons and substitution branches."""
import json
from pathlib import Path
from analyze_deferred_auth import partition_function, trace_slice
from analyze_game_cvars import vm_string
from analyze_qvm import decode, write_json
from analyze_vm_extensions import verified
BASE=0x40000000
def compare(ins,left,right,bound=99999):
a,b=0x10000,0x20000
memory={a+i:v for i,v in enumerate(left+b'\0')}
memory.update({b+i:v for i,v in enumerate(right+b'\0')})
return trace_slice(ins,69387,69525,{}, {48:a,52:b,56:bound},
byte_memory=memory,limit=4096,capture_stack=True,halt_on_leave=True)
def main():
q=json.loads(Path('docs/research/qvm/qagame.json').read_text())
data=verified(Path(q['source']['member']['output']).read_bytes(),q['sha256']);h,ins=decode(data)
functions={str(e):partition_function(ins,e,data,h) for e in [69386,185212,186435]}
keys=[]
for i in range(185212,186435):
if ins[i]['opcode']=='CONST' and ins[i]['operand']==69386 and ins[i+1]['opcode']=='CALL':
literal=ins[i-5]
if literal['opcode']!='CONST' or ins[i-4]['opcode']!='ARG' or ins[i-4]['operand']!=12:
raise ValueError('key comparison pattern changed')
keys.append({'key':vm_string(data,h,literal['operand']),'address':literal['operand'],
'compare_call':i+1,'bound':ins[i-3]['operand'],'window':ins[i-11:i+8]})
comparisons=[]
for k in keys:
key=k['key'].encode('ascii')
for candidate in [key,key.upper(),key[:-1],key+b'x']:
t=compare(ins,candidate,key)
comparisons.append({'key':k['key'],'candidate':candidate.decode('ascii'),'trace':t})
# Per-character slices: no copy/append/lookup callee is entered.
chars=[]
for marker,key,ch in [(0,'',ord('x')),(0,'',36),(36,'foo',35),(35,'',ord('a')),
(36,'h',ord('P')),(36,'foo',ord('1')),(36,'foo',32),
(36,'foo',255),(0,'',0)]:
bm={BASE+32:ch,BASE+182:marker}
bm.update({BASE+183+i:v for i,v in enumerate(key.encode()+b'\0')})
t=trace_slice(ins,186481,186676,{}, {28:0,336:len(key),376:0x50000,380:0x60000},byte_memory=bm)
chars.append({'pending_marker':marker,'pending_key':key,'byte':ch,'trace':t})
lookup_results=[]
for result in [0,0x70000]:
t=trace_slice(ins,186609,186676,{}, {340:result,380:0x60000},byte_memory={BASE+182:36})
lookup_results.append({'supplied_lookup_return':result,'trace':t})
tails=[]
for marker in [0,35,36]:
t=trace_slice(ins,186693,186719,{}, {380:0x60000},byte_memory={BASE+182:marker})
tails.append({'pending_marker':marker,'trace':t})
lengths=[]
for length in [149,150,151]:
t=trace_slice(ins,186449,186719,{}, {344:length,380:0x60000},
byte_memory={BASE+182:0},limit=256)
lengths.append({'supplied_source_length':length,'trace':t})
entity,record=0x10000,0x20000
health=[]
for value in [-1,0,24,25,49,50,74,75,100]:
for state,override in [(0,0),(3,0),(4,0),(0,1)]:
t=trace_slice(ins,185237,186431,{entity+684:value,entity+520:record,
record+4:state,record+4324:override},{1144:entity})
health.append({'raw_health':value,'raw_state':state,'override':override,'trace':t})
hp=[]
for state,override in [(0,0),(3,0),(4,0),(0,1)]:
t=trace_slice(ins,185350,186431,{entity+684:42,entity+520:record,record+4:state,
record+4324:override},{1144:entity})
hp.append({'raw_state':state,'override':override,'trace':t})
side_effects=[]
for pointer,now in [(0,1000),(0x30000,1000),(0x30000,2147483640)]:
t=trace_slice(ins,185987,186031,{entity+520:record,record+6168:pointer,34992532:now},
{1144:entity},byte_memory={},capture_stack=True,halt_on_leave=True)
side_effects.append({'object_pointer':pointer,'now':now,'trace':t})
addresses=[77862,77857,77850,77847,78120,78109,78095,78087,78079,78074,78068,
78066,78037,78014,78009,78000,77991,77972,77959,77936]
addresses.extend(k['address'] for k in keys)
strings={str(a):vm_string(data,h,a) for a in addresses}
out={'source':q['source'],'sha256':q['sha256'],'functions':functions,'keys':keys,
'comparison_traces':comparisons,'character_traces':chars,'lookup_result_traces':lookup_results,
'pending_tail_traces':tails,'source_length_traces':lengths,'health_traces':health,'hp_traces':hp,
'momo_traces':side_effects,'strings':strings,
'limitations':'Static partitions and bounded synthetic instruction slices. No original process or full '
'QVM executes. CALLs return zero placeholders; source lengths and isolated lookup returns are supplied '
'inputs, not executed callees. No composed parser output or formatting/copy/append execution. '
'Timer, weapon, location, leader and crosshair bodies are retained but not fully evaluated; field '
'identities, ownership, marker lifetime and original runtime/platform behavior remain open.'}
write_json(Path('docs/research/chat-expansion.json'),out)
print(f'Recovered {len(keys)} substitution keys, parser state and health/momo slices')
if __name__=='__main__':main()