#!/usr/bin/env python3 # -*- coding: utf-8 -*- """字节级定点 patch dex 里的字符串常量,完全不做 smali 往返。 为什么不用 smali 往返:实测 classes7.dex 经 baksmali->smali 整树重建后, `io.ktor.client.engine.HttpClientEngine` 的 synthetic access bridge 被破坏, 运行时报 IncompatibleClassChangeError(Found interface, but class was expected)。 class_def 的 access_flags 看不出问题,所以只能改用"只改字节"的方式。 本脚本做法(对 dex 结构零改动): 1. 把目标字符串替换成**等长**的另一个字符串 —— 长度相同则 string_data_item 的 uleb128 长度前缀不变,dex 的偏移、索引、表全部不变; 2. 重算 dex header 的 signature(SHA-1, 从 offset 32 起) 与 checksum(adler32, 从 offset 12 起)。 用法:python dex_strpatch.py 要求 len(new) == len(old),且 old 在 dex 中出现次数恰好为 1。 """ import hashlib import struct import sys import zlib def _uleb128(data, off): result = 0 shift = 0 while True: b = data[off] off += 1 result |= (b & 0x7F) << shift if not (b & 0x80): break shift += 7 return result, off def _read_strings(data): off = struct.unpack(' 0 else None nxt = entries[idx + 1][1] if idx + 1 < len(entries) else None if prev is not None and nb <= prev: print('[order] FAIL: new %r <= prev %r' % (nb, prev)) return False if nxt is not None and nb >= nxt: print('[order] FAIL: new %r >= next %r' % (nb, nxt)) return False print('[order] OK: %r < %r < %r' % (prev, nb, nxt)) return True def main(): src, dst, old, new = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4] ob, nb = old.encode('utf-8'), new.encode('utf-8') if len(ob) != len(nb): print('[FAIL] length mismatch: %d vs %d (must be equal)' % (len(ob), len(nb))) return 1 data = bytearray(open(src, 'rb').read()) if data[:8] != b'dex\n035\x00' and data[:4] != b'dex\n': print('[warn] unexpected magic: %r' % bytes(data[:8])) n = data.count(ob) print('[info] occurrences of %r = %d' % (old, n)) if n != 1: print('[FAIL] expected exactly 1 occurrence') return 1 off = data.find(ob) # 关键:dex 规范要求 string_ids 表按字符串内容有序。等长替换虽然不动偏移, # 但会改变该条目在表中的字典序位置 —— 一旦越界,整个 dex 会被 ClassLoader # 拒绝(表现为 ClassNotFoundException: )。所以这里先做区间校验。 if not _order_ok(data, off, ob, nb): print('[FAIL] replacement would break string_ids ordering. ' 'Pick a string that stays between its two neighbours.') return 1 data[off:off + len(ob)] = nb print('[ok] patched at offset 0x%x: %r -> %r' % (off, old, new)) # signature = SHA-1 over data[32:], stored at 12..32 sig = hashlib.sha1(bytes(data[32:])).digest() data[12:32] = sig # checksum = adler32 over data[12:], stored at 8..12 (little endian) chk = zlib.adler32(bytes(data[12:])) & 0xFFFFFFFF data[8:12] = struct.pack('