Download scripts/analyze_auth_drop.py from nmarshall/urt-rs: direct link, hf CLI and curl.
- Browser
- Download file 6.23 kB
-
https://huggingface.co/nmarshall/urt-rs/resolve/2d840a0fc49ec00f91cf1f8384861985afa519af/scripts/analyze_auth_drop.py
- Command line
-
hf download hf://nmarshall/urt-rs@2d840a0fc49ec00f91cf1f8384861985afa519af/scripts/analyze_auth_drop.py
-
curl -L -o analyze_auth_drop.py https://huggingface.co/nmarshall/urt-rs/resolve/2d840a0fc49ec00f91cf1f8384861985afa519af/scripts/analyze_auth_drop.py
6.23 kB
| #!/usr/bin/env python3 | |
| """Preserve the auth-drop call boundary and its native invalid-index continuation.""" | |
| import gzip | |
| import json | |
| from pathlib import Path | |
| import re | |
| from analyze_native import parse, take | |
| from analyze_native_dispatch import recover | |
| from analyze_qvm import decode, write_json | |
| from analyze_vm_extensions import verified, native_functions | |
| def ascii_literal(data, offset, end): | |
| if not 0 <= offset < end <= len(data): | |
| return None | |
| stop = data.find(b'\0', offset, end) | |
| if stop < 0: | |
| return None | |
| payload = data[offset:stop] | |
| if len(payload) < 4 or not all(32 <= c <= 126 or c in (9, 10, 13) for c in payload): | |
| return None | |
| return payload.decode('ascii') | |
| def invalid_continuation(case, lines): | |
| # Restrict this recognizer to the actual signed-index guard pattern. | |
| guards = [re.search(r'\t(js|jge)\s+0x([0-9a-f]+)', line) for line in case['first_instructions']] | |
| guards = [g for g in guards if g] | |
| if len(guards) != 2 or [g[1] for g in guards] != ['js', 'jge'] or guards[0][2] != guards[1][2]: | |
| raise ValueError('unrecognized auth-drop index guards') | |
| address = int(guards[0][2], 16) | |
| indexes = {} | |
| for i, line in enumerate(lines): | |
| m = re.match(r'\s*([0-9a-f]+):', line) | |
| if m: | |
| indexes[int(m[1], 16)] = i | |
| if address not in indexes: | |
| raise ValueError('guard continuation is not disassembled') | |
| index = indexes[address] | |
| continuation = lines[index:index+2] | |
| if len(continuation) != 2 or not re.search(r'\txorl\s+%eax, %eax', continuation[0]) or not re.search(r'\tjmp\s+0x', continuation[1]): | |
| raise ValueError('invalid index does not match known zero-result continuation') | |
| target = int(re.search(r'\tjmp\s+0x([0-9a-f]+)', continuation[1])[1], 16) | |
| if target not in indexes: | |
| raise ValueError('missing common return continuation') | |
| start = indexes[target] | |
| epilogue = [] | |
| for line in lines[start:start+20]: | |
| epilogue.append(line.strip()) | |
| if re.search(r'\tretq?\s*$', line): | |
| break | |
| m = re.search(r'\t(\w+)\s*(.*)', line) | |
| if not m or m[1] not in ('movq', 'addq', 'popq', 'nop', 'nopl', 'nopw') or '%eax' in m[2] or '%rax' in m[2]: | |
| raise ValueError('common return could alter result or control flow') | |
| else: | |
| raise ValueError('common return not found within bound') | |
| return {'address': address, 'instructions': [l.strip() for l in continuation], | |
| 'common_return_address': target, 'common_return_instructions': epilogue, | |
| 'result': 0} | |
| def main(): | |
| output = {'native': {}, 'limitations': 'Linux ELF64 static boundary evidence. Literal operands are candidates, ' | |
| 'not whole-program dataflow. No runtime reference fixtures or all-platform contract.'} | |
| for label in ('linux--Quake3-UrT-Ded.x86_64', 'linux--Quake3-UrT.x86_64'): | |
| report = json.loads(Path(f'docs/research/native/{label}.json').read_text()) | |
| data = verified((Path('.cache/UrbanTerror43')/report['source']['path']).read_bytes(), report['source']['sha256']) | |
| layout, _ = parse(data) | |
| artifact = report['artifacts']['disassembly.txt'] | |
| text = verified(gzip.open(artifact['path'], 'rb').read(), artifact['uncompressed_sha256']).decode() | |
| functions = native_functions(text) | |
| dispatch = recover(data, text, 'SV_GameSystemCalls') | |
| case = next(c for c in dispatch['cases'] if c['numbers_sharing_target'] == [603]) | |
| literals = [] | |
| function = functions['SV_Auth_DropClient'] | |
| for line in function['lines']: | |
| for match in re.finditer(r'\$0x([0-9a-f]+)', line): | |
| address = int(match[1], 16) | |
| section = next((s for s in layout['sections'] if s['flags'] & 2 and not s['zero_fill'] and not s['executable'] and | |
| s['address'] <= address < s['address']+s['bytes']), None) | |
| if section: | |
| offset = section['file_offset']+address-section['address'] | |
| value = ascii_literal(data, offset, section['file_offset']+section['bytes']) | |
| if value: | |
| literals.append({'address': address, 'file_offset': offset, 'text': value, | |
| 'instruction': line.strip()}) | |
| output['native'][label] = {'source': report['source'], 'disassembly_sha256': artifact['uncompressed_sha256'], | |
| 'case': case, 'invalid_index': invalid_continuation(case, text.splitlines()), | |
| 'function': function, 'literal_operand_candidates': literals} | |
| report = json.loads(Path('docs/research/qvm/qagame.json').read_text()) | |
| data = verified(Path(report['source']['member']['output']).read_bytes(), report['sha256']) | |
| header, instructions = decode(data) | |
| site = next(i for i in range(1, len(instructions)) if instructions[i]['opcode'] == 'CALL' and | |
| instructions[i-1]['opcode'] == 'CONST' and instructions[i-1]['operand'] == -604) | |
| start = next(i for i in range(site, -1, -1) if instructions[i]['opcode'] == 'ENTER') | |
| end = next(i for i in range(site+1, len(instructions)) if instructions[i]['opcode'] == 'ENTER') | |
| strings = [] | |
| for ins in instructions[start:end]: | |
| if ins['opcode'] == 'CONST' and instructions[ins['index']+1]['opcode'] == 'ARG' and 0 <= ins['operand'] < header['data_length']+header['literal_length']: | |
| offset = header['data_offset']+ins['operand'] | |
| value = ascii_literal(data, offset, header['data_offset']+header['data_length']+header['literal_length']) | |
| if value: | |
| strings.append({'instruction': ins['index'], 'vm_address': ins['operand'], 'text': value}) | |
| output['qvm'] = {'source': report['source'], 'sha256': report['sha256'], 'call_instruction': site, | |
| 'containing_entry': start, 'instructions': instructions[start:end], | |
| 'literal_operand_candidates': strings} | |
| write_json(Path('docs/research/auth-drop.json'), output) | |
| print('Recovered both auth-drop invalid-index zero-return paths and literal operands') | |
| if __name__ == '__main__': | |
| main() | |