#!/usr/bin/env python3 # -*- coding: utf-8 -*- """Build a minimal, structurally valid dex whose reference counts are known. WHY THIS EXISTS --------------- Verifying a dex reader against a real APK only shows that two readers agree. Verifying it against a program whose call graph was written down in advance shows that the *number* is right. This fixture encodes exactly two method bodies: Lcom/example/Caller;->go()V invoke-static Lcom/example/Target;->run()V x2 Lcom/example/Target;->run()V invoke-virtual Ljava/lang/String;->length()I x3 so the answers a blast-radius check must return are 3, 2 and 2. The same call graph is also written out as a smali tree, because the point of the fix this fixture verifies is that a `.dex` and a smali tree answer a needle identically. USAGE ----- python make_fixture_dex.py out.dex [--smali DIR] # then, from the skill's scripts directory: python find_refs.py out.dex 'Ljava/lang/String;->length' # expect 3 python find_refs.py out.dex 'Lcom/example/Target;->run' # expect 2 python find_refs.py out.dex 'Lcom/example/Target;' # expect 2 python find_refs.py DIR 'Ljava/lang/String;->length' # expect 3 Dependency-free (stdlib only) on purpose: the fixture must be reproducible on a machine with no Android toolchain, which is where the defect it verifies was found. Nothing here is target data. The dex is synthesised from the constants below. """ import argparse import hashlib import os import struct import zlib CALLER = 'Lcom/example/Caller;' TARGET = 'Lcom/example/Target;' OBJECT = 'Ljava/lang/Object;' STRING = 'Ljava/lang/String;' SMALI_CALLER = """.class public Lcom/example/Caller; .super Ljava/lang/Object; .method public static go()V .registers 0 invoke-static {}, Lcom/example/Target;->run()V invoke-static {}, Lcom/example/Target;->run()V return-void .end method """ SMALI_TARGET = """.class public Lcom/example/Target; .super Ljava/lang/Object; .method public static run()V .registers 1 invoke-virtual {v0}, Ljava/lang/String;->length()I invoke-virtual {v0}, Ljava/lang/String;->length()I invoke-virtual {v0}, Ljava/lang/String;->length()I return-void .end method """ EXPECTED = (('Ljava/lang/String;->length', 3), ('Lcom/example/Target;->run', 2), ('Lcom/example/Target;', 2)) def uleb(n): out = bytearray() while True: b = n & 0x7F n >>= 7 out.append(b | (0x80 if n else 0)) if not n: return bytes(out) def p16(v): return struct.pack('