#!/usr/bin/env python3 """Where an NSO's text segment holds code: function ranges from .eh_frame, and data blocks in the text that no function covers. patch_nso.py and insn_census.py import it. Usage on its own: nso_code.py (lists the data blocks) A data block is a stretch of at least DATA_MIN bytes between FDE ranges in which more than a quarter of the words do not decode as instructions. Absolum's main (.NET NativeAOT on SDK 18) starts its text with 1.7 MB of data that no code references (no adrp, no relocation): 62% of its words do not decode, and 6,858 of the rest read as instructions that use x18, which a linear sweep would rewrite. Code without unwind information (zlib, libunwind's register restore) stays code: it decodes.""" import struct DATA_MIN = 0x1000 def eh_frame_hdr(img): """(eh_frame_hdr offset, its end) from the MOD0 header""" m0 = struct.unpack_from('= text_end: break if s > last: gaps.append((last, s)) last = max(last, e) if last < text_end: gaps.append((last, text_end)) out = [] for s, e in gaps: s, e = (s + 3) & ~3, e & ~3 if e - s < DATA_MIN: continue undecodable = sum(1 for a in range(s, e, 4) if next(md.disasm_lite(bytes(img[a:a + 4]), a), None) is None) if undecodable * 4 > (e - s) // 4: out.append((s, e)) return out if __name__ == '__main__': import sys from capstone import Cs, CS_ARCH_ARM64, CS_MODE_ARM p = sys.argv[1]; raw = open(p, 'rb').read() if raw[:4] == b'NSO0': import lz4.block flags = struct.unpack_from('> i & 1 else seg text_end = segs[0][2] else: img = raw; text_end = int(sys.argv[2], 0) blocks = data_blocks(img, text_end, Cs(CS_ARCH_ARM64, CS_MODE_ARM)) print(f'{p}: {len(blocks)} data blocks in text, {sum(e - s for s, e in blocks)} bytes') for s, e in blocks: print(f' {s:#x}..{e:#x} ({e - s} bytes)')