当前位置: 首页 > news >正文

二代壳脱壳新思路:Hook CreateFromRawDexFile捕获原始DEX

1. 为什么“二代壳”让传统脱壳方法集体失效?——从Dex加载链路说起

你有没有试过用经典的dumpdex脚本在Android 10+设备上跑,结果dump出来的dex文件一打开就是满屏java.lang.ClassNotFoundException?或者用dex2oat反编译出的odex,反汇编后发现关键类全被替换成一堆a.b.c.d这种无意义包名?这不是你的工具坏了,也不是你操作错了——是目标App已经穿上了“二代壳”的铠甲,而你还在用对付一代壳的刀去砍它。

所谓“二代壳”,核心不在加壳本身,而在运行时Dex的动态构造与内存加载方式的根本性重构。一代壳(比如早期360加固、腾讯乐固)本质是把原始dex加密后藏在so里,启动时解密到内存再用DexClassLoader加载;而二代壳(如某为、某讯、某游系主流商用壳)直接绕开了DexClassLoader这条标准路径:它不加载完整dex,而是把原始字节码拆成碎片,通过自定义的ClassLinker钩子,在FindClass调用链最底层,用art::mirror::Class::InitializeFromDexFile这类私有API,将零散的字节码块拼装成art::mirror::Class对象,直接注入到ART运行时的ClassTable中。整个过程不生成任何可dump的.dex文件,也不经过DexFile::OpenMemory这个传统dump入口。

这就导致三个致命后果:第一,frida-trace -i "DexFile::OpenMemory"完全静默——因为根本没调用;第二,objection android hooking watch class_method --dump-argsloadClass无效——因为类不是靠ClassLoader.loadClass()加载的;第三,Fart(Frida ART Dump)默认模式下dump出的dex无法反编译——因为它只dump了“被ART解析后的类结构”,而非原始字节码,缺少method code item的完整指令流。

我去年帮一个金融类App做兼容性测试时,就卡在这个点上整整两周。当时用Frida Hookart::ClassLinker::FindClass,发现返回的mirror::Class*对象里GetDexCache()->GetDexFile()为空指针;转头Hookart::DexFile::Open,连一次调用都没有。直到翻到AOSP 12.0源码里art/runtime/class_linker.cc第2847行那句注释:“Classes may be defined without a backing DexFile”,才真正意识到:我们面对的不是“加密dex”,而是“无dex类定义”。

所以,“新思路”不是换个工具,而是重新锚定脱壳的观测坐标系:从“找dex文件”转向“捕获类定义时刻”,从“静态dump”转向“动态重建”。Frida提供的是实时hook能力,Fart提供的是ART内存结构解析能力,二者结合的关键,是找到那个类字节码尚未被ART解析、但原始字节仍完整保留在内存中的黄金窗口期——这个窗口,就在art::DexFile::CreateFromRawDexFile被调用前的raw_dex_file参数里。

提示:这个raw_dex_file通常是一段malloc分配的内存,内容是原始dex的header+class_def_item+code_item等完整结构,但被壳做了异或/RC4混淆。Fart的--raw模式正是为此设计,但它需要你先定位到这段内存地址——而这,正是Frida要干的事。

2. Frida Hook的精准落点选择:为什么不是FindClass,而是DexFile::CreateFromRawDexFile?

很多初学者看到“脱壳”第一反应就是HookFindClass,这很自然——毕竟类加载是我们最熟悉的入口。但实测下来,在二代壳环境下,HookFindClass不仅效率极低,而且90%以上会失败。原因有三:

第一,FindClass是高频调用函数,每new一个对象、每反射一个类都会触发。在大型App里,一秒内可能调用上千次。Frida在如此高频率的函数上设Hook,会导致应用卡顿、ANR甚至崩溃,尤其在Android 12+的StrictMode下,系统会直接杀掉异常线程。

第二,FindClass的参数是const char* descriptor(如"Lcom/example/MainActivity;"),它只告诉你“要找哪个类”,不包含任何字节码信息。你Hook到它,只能知道“壳正在加载MainActivity”,但不知道它的字节码在哪——就像你知道快递员要去送包裹,却不知道包裹在哪个货车里。

第三,也是最关键的一点:二代壳的FindClass实现往往做了深度定制。它内部可能根本不调用DexFile::Open,而是直接从内存池里取出预构建的mirror::Class对象。此时HookFindClass拿到的返回值,已经是ART解析后的类对象,原始字节码早已被销毁。

那么,真正的黄金落点在哪?答案是art::DexFile::CreateFromRawDexFile。我们来看它的函数签名(以AOSP 12.0为例):

static std::unique_ptr<const DexFile> CreateFromRawDexFile( const uint8_t* dex_file, size_t size, std::string* error_msg, bool verify, bool verify_checksum);

注意第一个参数:const uint8_t* dex_file。这就是原始dex字节码在内存中的起始地址!size参数则告诉了你这段内存的长度。只要我们能在这个函数刚被调用、字节码还未被解析前,把dex_file指向的内存块完整读出来,就拿到了最干净的原始dex——哪怕它被混淆了,也比ART解析后丢失code item的版本强十倍。

为什么这个函数调用频次低?因为一个App启动过程中,CreateFromRawDexFile最多被调用几次(对应主dex、split dex、资源dex等),远低于FindClass的千次级调用。Hook它,几乎不影响应用性能。

为什么它一定存在?因为无论壳多黑,最终都要把字节码喂给ART。ART的DexFile类是所有dex加载的统一入口,CreateFromRawDexFile是创建DexFile对象的最底层工厂函数。你可以绕过DexClassLoader,但绕不过DexFile这个数据结构——它是ART运行时识别字节码的唯一载体。

我在测试某款游戏App时,用Frida脚本同时HookFindClassCreateFromRawDexFile,结果如下:

Hook点触发次数(App启动5秒内)是否获取到原始字节码脱壳成功率
art::ClassLinker::FindClass1,247次否(仅descriptor字符串)0%
art::DexFile::CreateFromRawDexFile3次是(完整dex内存块)100%

这个对比太有说服力了。更关键的是,CreateFromRawDexFile的第三个参数std::string* error_msg是个指针,它在函数内部会被写入错误信息。这意味着,如果你Hook时修改了error_msg指向的内容,就能在函数返回前,把dex_file地址和size偷偷塞进去——这是Frida实现“内存地址回传”的经典技巧。

具体怎么Hook?不能用Interceptor.attach直接attach,因为C++符号名在不同ART版本中变化很大(比如Android 10是_ZN3art7DexFile22CreateFromRawDexFileEPKhjSt10unique_ptrIKS0_St14default_deleteIS0_EEb,Android 13变成_ZN3art7DexFile22CreateFromRawDexFileEPKhjSt10unique_ptrIKS0_St14default_deleteIS0_EE)。必须用Module.findExportByName配合符号模糊匹配:

// Frida脚本核心片段 const dexFileModule = Process.findModuleByName("libart.so"); if (dexFileModule) { // 在libart.so的导出符号中搜索含"CreateFromRawDexFile"的函数 const candidates = dexFileModule.enumerateExports() .filter(exp => exp.name.includes("CreateFromRawDexFile") && exp.type === 'function'); if (candidates.length > 0) { const targetFunc = candidates[0].address; console.log(`[+] Found CreateFromRawDexFile at ${targetFunc}`); Interceptor.attach(targetFunc, { onEnter: function(args) { this.dexAddr = args[0]; // const uint8_t* dex_file this.dexSize = args[1].toInt32(); // size_t size console.log(`[+] Raw dex detected: addr=${this.dexAddr}, size=${this.dexSize}`); // 将地址和大小存入全局变量,供onLeave使用 globalDexInfo = { addr: this.dexAddr, size: this.dexSize }; }, onLeave: function(retval) { if (globalDexInfo && globalDexInfo.addr) { // 读取原始内存 const dexBytes = Memory.readByteArray(globalDexInfo.addr, globalDexInfo.size); if (dexBytes && dexBytes.length > 0) { // 保存为临时文件,供Fart后续处理 const fileName = `/data/data/${Process.getCurrentPackageName()}/cache/raw_dex_${Date.now()}.dex`; Java.use('java.io.FileOutputStream').$new(fileName).write(dexBytes); console.log(`[+] Raw dex saved to ${fileName}`); } } } }); } }

这段代码的关键在于onEnter中捕获args[0]args[1],它们严格对应函数签名的前两个参数。args[0]uint8_t*,Frida会自动将其转为NativePointer;args[1]size_t,需用toInt32()转换(Android 64位下size_tuint64_t,但实际dex size不会超2GB,toInt32()足够安全)。

注意:Memory.readByteArray读取的是进程内存,必须确保args[0]指向的内存页是可读的。二代壳有时会把dex内存设为PROT_READ | PROT_WRITE,防止被dump。这时需要先调用Memory.protect临时改为可读:

Memory.protect(args[0], args[1].toInt32(), 'r--'); const dexBytes = Memory.readByteArray(args[0], args[1].toInt32());

3. Fart的深度定制:如何让--raw模式真正“读懂”二代壳的混淆逻辑?

Fart(Frida ART Dump)默认的--dex模式,本质是遍历ART的ClassLinker中的ClassTable,把每个mirror::Class对象的GetDexCache()->GetDexFile()拿出来dump。这在一代壳下可行,因为DexFile对象是完整的;但在二代壳下,GetDexFile()返回空,Fart就dump不出任何东西。

--raw模式的设计初衷,正是为了解决这个问题。它的原理是:不依赖DexFile对象,而是直接扫描ART堆内存,寻找符合dex文件格式特征的内存块(比如magic number0x00646578即"dex\x00"),然后把整块内存当dex文件dump出来。听起来很完美?但实测中,--raw模式在二代壳下失败率高达80%,原因只有一个:混淆算法破坏了dex magic number

我们来看dex文件头的标准结构(前16字节):

偏移长度含义标准值(十六进制)
0x004magic64 65 78 00 ("dex\x00")
0x044checksum任意32位校验和
0x0820signatureSHA-1签名(20字节)

二代壳的混淆,往往从magic开始。常见手法有:

  • 异或混淆:对整个dex文件(包括header)逐字节异或一个固定key(如0x55)
  • RC4流加密:用硬编码的密钥对dex文件加密,header也被加密
  • 字节重排:把header的4个字节挪到文件末尾,中间插入垃圾数据

这就导致Fart的--raw模式扫描内存时,找不到64 65 78 00这个特征串,直接跳过整块内存。

解决方案不是放弃--raw,而是让Fart学会“猜”混淆算法。Fart本身支持插件式混淆处理器(obfuscation handler),但官方文档几乎没提。它的源码里有一个obfuscation_handlers.py文件,定义了几个基础处理器:

# fart/obfuscation_handlers.py class XorHandler: def __init__(self, key=0x55): self.key = key def detect(self, data: bytes) -> bool: # 检查前4字节异或key后是否等于"dex\x00" if len(data) < 4: return False return data[0] ^ self.key == 0x64 and \ data[1] ^ self.key == 0x65 and \ data[2] ^ self.key == 0x78 and \ data[3] ^ self.key == 0x00 def decode(self, data: bytes) -> bytes: return bytes([b ^ self.key for b in data])

问题来了:你怎么知道壳用的是0x55还是0xAA?总不能一个个试。我的经验是:用Frida先做一次“混淆特征采样”

回到上一节的Frida脚本,在onEnter捕获到raw_dex_file地址后,不要急着dump整块内存,而是先读取前64字节,用Python脚本快速爆破常见xor key:

// Frida脚本追加部分 onEnter: function(args) { this.dexAddr = args[0]; this.dexSize = args[1].toInt32(); // 读取前64字节用于混淆分析 const headerBytes = Memory.readByteArray(this.dexAddr, 64); if (headerBytes) { // 将字节数组转为hex字符串,发送到Python端 const hexStr = Array.from(headerBytes).map(b => b.toString(16).padStart(2,'0')).join(''); send('dex_header', hexStr); // 发送给Python主控程序 } }

Python端收到后,用以下逻辑爆破:

# python主控脚本 def guess_xor_key(header_hex: str) -> int: """爆破xor key,返回最可能的key值""" header_bytes = bytes.fromhex(header_hex) # 测试常见key:0x00~0xFF,但优先测试0x55, 0xAA, 0xFF, 0x00 common_keys = [0x55, 0xAA, 0xFF, 0x00, 0x12, 0x34, 0x78, 0x90] for key in common_keys + list(range(0x00, 0x100)): # 对header前4字节异或 try: dec0 = header_bytes[0] ^ key dec1 = header_bytes[1] ^ key dec2 = header_bytes[2] ^ key dec3 = header_bytes[3] ^ key if dec0 == 0x64 and dec1 == 0x65 and dec2 == 0x78 and dec3 == 0x00: print(f"[+] XOR key found: 0x{key:02X}") return key except: continue return None # 收到Frida消息后调用 def on_message(message, data): if message['type'] == 'send' and message['payload'] == 'dex_header': key = guess_xor_key(message['data']) if key is not None: # 将key传回Frida,用于后续dump frida_script.post({'type': 'set_key', 'key': key})

这样,Fart的--raw模式就能带上正确的key参数运行:

# 使用自定义XorHandler并指定key fart --raw --obfuscator xor --key 0x55 --output ./dumped/

更进一步,如果壳用的是RC4,Fart也支持。你需要先从so文件里提取RC4密钥(通常在JNI_OnLoadJava_com_xxx_Security_init里),然后写一个Rc4Handler

from Crypto.Cipher import ARC4 class Rc4Handler: def __init__(self, key: bytes): self.cipher = ARC4.new(key) def detect(self, data: bytes) -> bool: # RC4加密后magic不固定,但可以检查解密后是否符合dex结构 try: dec = self.cipher.decrypt(data[:64]) return dec[0] == 0x64 and dec[1] == 0x65 and dec[2] == 0x78 and dec[3] == 0x00 except: return False def decode(self, data: bytes) -> bytes: return self.cipher.decrypt(data)

实操心得:我在分析某电商App时,发现它的RC4密钥是硬编码在libsecurity.so.rodata段,偏移0x12A8处,长度16字节。用readelf -x .rodata libsecurity.so | grep -A 2 "12a8"就能快速定位。记住:密钥永远在so里,不在dex里——这是二代壳的铁律。

4. 从raw dex到可反编译dex:混淆还原的三道关卡与实战修复

拿到Fart dump出的raw_dex文件,只是万里长征第一步。此时的文件大概率无法被jadxdex2jar正常解析,报错通常是:

  • ERROR: Invalid dex file, no magic found
  • java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
  • Error: Could not parse DEX file: Bad version number

这些错误背后,是二代壳施加的三道混淆关卡,必须逐个击破:

4.1 关卡一:Header篡改——Magic与Version字段的双重欺骗

dex文件头的第0-3字节是magic,第4-7字节是checksum,第8-27字节是signature,第28-31字节是file_size,第32-35字节是header_size,第36-39字节是endian_tag,第40-43字节是link_size……其中,version字段位于第44-47字节(dex format version,标准值为0x00303030即"000")。

二代壳常做的手脚:

  • 把magic改成0x00646579("dey\x00"),让检测工具误判为非法文件
  • 把version改成0x00313131("111"),导致dex2oat拒绝加载
  • 把file_size字段减去100,让解析器读取越界

修复方法:用十六进制编辑器(如HxD或xxd)手动修正。以000版本为例:

# 查看原始header xxd -l 64 raw_dex.dex | head -n 4 # 修正magic(0x00-0x03)和version(0x2C-0x2F) printf '\x64\x65\x78\x00' | dd of=raw_dex.dex bs=1 seek=0 conv=notrunc printf '\x30\x30\x30\x00' | dd of=raw_dex.dex bs=1 seek=44 conv=notrunc # 修正file_size(0x1C-0x1F)为实际文件大小(假设为1234567字节,小端序) printf '\x27\x11\x12\x00' | dd of=raw_dex.dex bs=1 seek=28 conv=notrunc

提示:file_size必须精确等于ls -l raw_dex.dex | awk '{print $5}'的结果,否则dexdump会报Invalid file size。我习惯用Python脚本自动化:

with open("raw_dex.dex", "r+b") as f: size = os.path.getsize("raw_dex.dex") # 写入小端序的size(4字节) f.seek(28) f.write(size.to_bytes(4, 'little'))

4.2 关卡二:StringId与TypeId的全局偏移错乱

dex文件的string_ids区(存放所有字符串的索引表)和type_ids区(存放所有类名的索引表)的起始偏移,记录在header的第64-67字节(string_ids_off)和第68-71字节(type_ids_off)。二代壳为了增加解析难度,会把这两个偏移值加上一个随机数(如0x1000),让解析器跳到错误位置读取。

后果是:jadx打开时显示No classes founddexdump -d raw_dex.dex输出string_id_item[0] = 0x00000000但实际字符串表在别处。

修复方法:用dexdump -f raw_dex.dex查看header信息,对比string_ids_sizestring_ids_off。正常情况下,string_ids_off应该大于header_size(0x70),且string_ids_off + string_ids_size * 4不应超过file_size。如果string_ids_off异常大(如0x100000),说明被加了偏移。

计算真实偏移:real_offset = string_ids_off - 0x1000(假设偏移量是0x1000)。然后用dd命令把string_ids区的数据块复制到正确位置:

# 计算string_ids区大小(单位:项数) string_size=$(dexdump -f raw_dex.dex | grep "string_ids_size" | awk '{print $2}') # 每项4字节,所以总字节数 = string_size * 4 string_bytes=$((string_size * 4)) # 从fake offset读取,写入real offset dd if=raw_dex.dex of=raw_dex.dex bs=1 skip=$((0x100000)) seek=$((0x100000 - 0x1000)) count=$string_bytes conv=notrunc

4.3 关卡三:ClassDef的CodeItem指针污染

这是最隐蔽也最致命的一关。class_def_item结构体的第24-27字节是class_data_off(指向class_data_item),第28-31字节是static_values_off。二代壳常把class_data_off设为一个非法地址(如0xFFFFFFFF),导致jadx在解析方法时崩溃。

验证方法:用dexdump -d raw_dex.dex | grep -A 10 "Class #0",看class_data_off字段是否为0xffffffff

修复策略不是瞎猜,而是逆向class_data_item的生成逻辑class_data_item的结构是:

  • static_fields_size(uleb128)
  • instance_fields_size(uleb128)
  • direct_methods_size(uleb128)
  • virtual_methods_size(uleb128)
  • 然后是各field/method的列表

uleb128编码的特点是:每个字节最高位为1表示还有后续字节,否则结束。所以class_data_item的起始,一定是连续的几个uleb128数字。我们在raw_dex.dex中搜索0x00 00 00 00(四个0,代表四个0-size的uleb128),大概率就是class_data_item的开头。

实战中,我用Python脚本自动定位:

def find_class_data_start(dex_path: str) -> int: with open(dex_path, "rb") as f: data = f.read() # 搜索0x00000000(四个连续0字节) for i in range(len(data)-4): if data[i:i+4] == b'\x00\x00\x00\x00': # 检查前后是否符合class_data_item结构(前4个uleb128) if is_valid_class_data(data, i): return i return -1 def is_valid_class_data(data: bytes, pos: int) -> bool: # uleb128解码函数(略) try: s1 = read_uleb128(data, pos) s2 = read_uleb128(data, pos + len_uleb128(s1)) s3 = read_uleb128(data, pos + len_uleb128(s1) + len_uleb128(s2)) s4 = read_uleb128(data, pos + len_uleb128(s1) + len_uleb128(s2) + len_uleb128(s3)) return True except: return False

找到真实class_data_off后,用xxd写入header:

# 假设真实偏移是0x8A5C0(小端序:0xC0 0x8A 0x00 0x00) printf '\xc0\x8a\x00\x00' | dd of=raw_dex.dex bs=1 seek=24 conv=notrunc

完成这三道关卡修复后,jadx-gui raw_dex.dex就能正常显示所有类和方法了。但注意:业务逻辑代码可能还在native层——这是二代壳的终极防线,需要结合unidbgQiling做动态模拟执行,那已是另一个战场。

最后分享一个小技巧:修复后的dex,用dex2jar转jar时,如果遇到Unsupported class file version,不要慌。这是因为dex版本号被壳改成了非标值(如0x00313131)。用baksmali反编译成smali,再用smali重新编译,会自动修正版本号:

baksmali d raw_dex.dex -o smali_out smali a smali_out -o fixed_dex.dex

5. 完整脱壳流程复盘:从设备连接到jadx打开的12步实操清单

纸上得来终觉浅,绝知此事要躬行。我把整个脱壳流程拆解为12个不可跳过的步骤,每一步都标注了常见坑和绕过方案。这不是理论推演,而是我在37台不同品牌、Android 8.0~14.0设备上反复验证的“抄作业”清单。

5.1 步骤1-3:环境准备与目标确认

  1. 确认设备Root状态与Frida Server版本

    • 必须Root,且su权限为shell用户可用(adb shell su -c id应返回uid=0(root)
    • Frida Server必须与设备架构匹配:ARM64设备用frida-server-16.1.4-android-arm64.xz,不要混用ARMv7
    • 验证:adb push frida-server /data/local/tmp/ && adb shell "chmod 755 /data/local/tmp/frida-server" && adb shell "/data/local/tmp/frida-server &",然后frida-ps -U应列出所有进程
  2. 确认目标App的PID与包名

    • adb shell ps | grep "com.target.app",记下PID(如12345)
    • adb shell dumpsys package com.target.app | grep versionName,确认是最新版,避免分析旧壳
  3. 禁用目标App的防调试机制

    • 二代壳普遍集成ptrace防调试,直接frida -U -f com.target.app会闪退
    • 解决方案:用frida-trace -U -f com.target.app -i "ptrace",在onEnterargs[0] = ptrace_request.PTRACE_TRACEME,强制解除防调试
    • 或更简单:adb shell "echo 0 > /proc/sys/kernel/yama/ptrace_scope"(需Root)

5.2 步骤4-6:Frida Hook与Raw Dex捕获

  1. 运行定制化Frida脚本,捕获raw_dex内存

    • 脚本必须包含CreateFromRawDexFile的模糊符号匹配(见第2节)
    • 关键:onEnterMemory.protect(args[0], args[1].toInt32(), 'r--'),否则读取失败
    • 输出:/data/data/com.target.app/cache/raw_dex_1712345678.dex(时间戳命名,避免覆盖)
  2. 从设备拉取raw_dex文件

    • adb shell "su -c 'cp /data/data/com.target.app/cache/raw_dex_*.dex /sdcard/'"
    • adb pull /sdcard/raw_dex_*.dex ./
    • 检查:ls -lh raw_dex_*.dex,大小应在1MB~20MB之间,小于100KB说明捕获失败
  3. 用Fart --raw模式初步dump(可选)

    • fart --raw --output ./fart_dump/ --obfuscator xor --key 0x55 ./raw_dex_*.dex
    • 如果Fart报No dex found,说明混淆类型不是xor,跳过此步,直接进入手工修复

5.3 步骤7-9:Header与结构修复

  1. dexdump -f分析raw_dex header

    • dexdump -f raw_dex_*.dex | head -n 20
    • 重点关注:magicfile_sizestring_ids_offclass_defs_offclass_defs_size
    • 如果magic不是64 65 78 00,记录当前值(如64 65 79 00),准备xor修复
  2. 修正magic与version字段

    • printf '\x64\x65\x78\x00' | dd of=raw_dex_*.dex bs=1 seek=0 conv=notrunc
    • printf '\x30\x30\x30\x00' | dd of=raw_dex_*.dex bs=1 seek=44 conv=notrunc
    • printf $(printf "%08x" $(stat -c%s raw_dex_*.dex) | sed 's/../&\n/g' | tac | xargs) | xxd -r -p | dd of=raw_dex_*.dex bs=1 seek=28 conv=notrunc(自动写入file_size)
  3. 定位并修复class_data_off

    • xxd raw_dex_*.dex | grep -A 5 "0000 0000"找到00000000出现的位置(如0x8A5C0)
    • printf '\xc0\x8a\x00\x00' | dd of=raw_dex_*.dex bs=1 seek=24 conv=notrunc(写入class_def_item的class_data_off)

5.4 步骤10-12:验证与反编译

  1. dexdump -d验证基础结构

    • dexdump -d raw_dex_*.dex | head -n 50
    • 应看到Class #0Class #1等,且class_data_off值合理(如0x8A5C0)
    • 如果报Invalid offset,说明class_data_off写错了,回到步骤9重试
  2. baksmali反编译验证

    • baksmali d raw_dex_*.dex -o smali_out
    • ls smali_out/应列出com/android/等包目录
    • cat smali_out/com/target/app/MainActivity.smali | head -n 10,应看到.class.super等标准smali语法
  3. jadx-gui打开最终成果

    • jadx-gui raw_dex_*.dex
    • 成功标志:左侧包树展开,双击MainActivity.java,右侧显示可读Java代码,无// ERROR注释
    • 如果仍有// ERROR,说明某个method的code_item损坏,需用smali单独修复该方法(超出本文范围)

踩坑实录:我在华为Mate 50(Android 13)上首次运行时,步骤4的Frida脚本一直不触发CreateFromRawDexFile。排查三天才发现,华为的libart.so把该函数符号名改成了_ZN3art7DexFile22CreateFromRawDexFileEPKhjSt10unique_ptrIKS0_St14default_deleteIS0_EEb,而我的模糊匹配漏掉了末尾的`Eb

http://www.jsqmd.com/news/858280/

相关文章:

  • 2026 肇庆专业防水公司TOP5推荐:卫生间、外墙、楼顶、地下室渗漏专业公司推荐(2026年5月肇庆最新深度调研方案) - 防水百科
  • RAG上下文压缩:一个让Token消耗直接砍半的骚操作
  • OmenSuperHub:5个步骤让你的惠普游戏本性能翻倍,告别臃肿官方软件
  • 河北骏众教育
  • 金昌千足金回收银项链回收铂金首饰回收裸钻回收闲置首饰回收高价多少钱一克同城价格查询上门上门估价闲置变现转让靠谱权威排行榜 - 检测回收中心
  • 2026 来宾专业防水公司TOP5推荐:卫生间、外墙、楼顶、地下室渗漏专业公司推荐(2026年5月来宾最新深度调研方案) - 防水百科
  • AICoverGen完整教程:3步打造专属AI翻唱音乐的免费终极方案
  • 从“数据盲区”到“精准治校”:纪律高危型学生行为画像实证分析
  • 2026年一体化泵站实力厂商:河北保聚玻璃钢制品有限公司,一体化雨水/预制/提升泵站一站式直供 - 泵站报价15613348888
  • 百度文库纯净打印终极指南:如何一键去除广告并保存完美PDF文档
  • 如何用html2image实现高效HTML转图片:Python开发者完全指南
  • 三步解锁:开源AI编程工具的免费共享方案
  • 金华黄金戒指回收白银首饰回收高价铂金回收品牌钻戒回收二手白银回收本地排名正规门店专业推荐哪家靠谱二手哪家强 - 检测回收中心
  • 如何在macOS上免费运行Windows程序?Whisky终极指南揭秘
  • 南阳黄金手镯回收纯银回收白金回收50分钻石回收二手钻石回收本地排名正规门店专业推荐哪家靠谱二手哪家强 - 检测回收中心
  • 铜川黄金手镯回收纯银回收白金回收50分钻石回收二手钻石回收高价多少钱一克同城价格查询上门上门估价闲置变现转让靠谱权威排行榜 - 检测回收中心
  • Ryujinx Switch模拟器终极指南:在PC上畅玩任天堂游戏
  • 衡水黄金戒指回收白银首饰回收高价铂金回收品牌钻戒回收二手白银回收高价多少钱一克同城价格查询上门上门估价闲置变现转让靠谱权威排行榜 - 检测回收中心
  • 技术深度解析:crypto-js WordArray数据结构的底层原理与实战应用
  • 关于MySql的ONLY_FULL_GROUP_BY问题
  • MySQL数据库存储的路径反斜杠“\“消失了
  • 金华黄金手镯回收纯银回收白金回收50分钻石回收二手钻石回收高价多少钱一克同城价格查询上门上门估价闲置变现转让靠谱权威排行榜 - 检测回收中心
  • 高压线相位检测常见问题解答(2026专家版) - 速递信息
  • STM32F407用HAL库驱动42步进电机,从CubeMX配置到代码调试的完整避坑指南
  • GitHub Desktop中文界面本地化终极指南:3分钟解锁全中文Git工作流
  • 2026网络定相设备最新推荐:权威测评发布,网络定相仪选型指南 - 速递信息
  • 【H100】cuda H100 GPU硬件架构
  • Faze4开源六轴机械臂深度解析:从3D打印到工业级机器人的技术实践
  • 暗黑破坏神2存档修改器终极指南:告别重复刷装备,5分钟打造完美角色!
  • 时光回溯:当网页消失时,如何用Wayback Machine找回数字记忆?