文章总结: 该文档详细介绍了Android应用梆梆加固企业版壳的解密与脱壳过程。通过分析libDexHelper.so文件,使用IDA定位壳入口地址0x4780,识别RC4和XOR解密函数,并配合frida-dexdump工具完成脱壳操作。文章提供了完整的Python解密脚本和操作步骤,具有实际可操作性。 综合评分: 85 文章分类: 移动安全,二进制安全,逆向分析,安全工具,漏洞分析
【银行逆向百例】18Android逆向之libDexHelper.so梆梆加固壳解密+frida-dexdump脱壳
原创
挖个洞先 挖个洞先
挖个洞先
2026年6月8日 09:00 北京
在小说阅读器读本章
去阅读
“ 只要有想见的人,就不是孤单一个人。——《夏目友人帐》S3E4 ”
01
—
环境版本
环境:
电脑,Windows 11 专业版 23H2
https://github.com/JiaoSuInfoSec/JiaoSuInfoSec_T00ls_Win11
软件:
Florida,16.1.8
https://github.com/Ylarod/Florida/releases/tag/16.1.8
frida-dexdump
https://github.com/hluwa/frida-dexdump
02
—
操作步骤
1、梆梆加固企业版
2、查看APP完整路径,拷贝libDexHelper.so到本地
adb shell dumpsys window | grep mCurrentFocusadb shell pm path com.xxx.xxxadb shell "su -c 'cp /data/app/xxx/com.xxx.xxx/lib/arm64/libDexHelper.so /data/local/tmp/ && chmod 777 /data/local/tmp/libDexHelper.so'"adb pull /data/local/tmp/libDexHelper.so ./dumps
3、libDexHelper.so导入IDA只有32个有名字的符号(25个导入函数+ 7个标记)
4、快捷键G跳到0,D切换显示模式
5、0x40查看ELF64 Program Header
6、导入parse_elf.py定位壳入口
0x26000查看.dynamic 段,DT_INIT_ARRAY = 0x14D08,DT_RELA = 0x26E8
0x26E8 查看重定位表
.init_array[0]的值= 0x4780
import structimport idc# ========== 第1步:解析 ELF Header ==========print("=" * 60)print("第1步:ELF Header")print("=" * 60)e_phoff = struct.unpack_from('<Q', idc.get_bytes(0x20, 8), 0)[0]e_phentsize = struct.unpack_from('<H', idc.get_bytes(0x36, 2), 0)[0]e_phnum = struct.unpack_from('<H', idc.get_bytes(0x38, 2), 0)[0]print(f"Program Header 偏移: 0x{e_phoff:X}")print(f"每个条目大小: {e_phentsize} 字节")print(f"条目数量: {e_phnum}")print(f"-> 下一步去地址 0x{e_phoff:X}")# ========== 第2步:解析 Program Headers ==========print("")print("=" * 60)print("第2步:Program Header Table")print("=" * 60)type_names = { 0: 'NULL', 1: 'LOAD', 2: 'DYNAMIC', 3: 'INTERP', 4: 'NOTE', 6: 'PHDR', 0x6474e550: 'GNU_EH_FRAME', 0x6474e551: 'GNU_STACK', 0x6474e552: 'GNU_RELRO'}dynamic_vaddr = Nonefor i in range(e_phnum): off = e_phoff + i * e_phentsize data = idc.get_bytes(off, e_phentsize) p_type = struct.unpack_from('<I', data, 0)[0] p_offset = struct.unpack_from('<Q', data, 8)[0] p_vaddr = struct.unpack_from('<Q', data, 16)[0] p_filesz = struct.unpack_from('<Q', data, 32)[0] tname = type_names.get(p_type, f'0x{p_type:X}') marker = ' <<<' if p_type == 2 else '' print(f"[{i}] {tname:15s} offset=0x{p_offset:08X} vaddr=0x{p_vaddr:08X} size=0x{p_filesz:X}{marker}") if p_type == 2: dynamic_vaddr = p_vaddrif dynamic_vaddr: print(f"\n-> 找到 DYNAMIC 段! 下一步去地址 0x{dynamic_vaddr:X}")# ========== 第3步:解析 .dynamic 段 ==========if dynamic_vaddr: print("") print("=" * 60) print(f"第3步: .dynamic 段 (地址 0x{dynamic_vaddr:X})") print("=" * 60) tag_names = { 0: 'DT_NULL', 1: 'DT_NEEDED', 4: 'DT_HASH', 5: 'DT_STRTAB', 6: 'DT_SYMTAB', 7: 'DT_RELA', 8: 'DT_RELASZ', 10: 'DT_STRSZ', 12: 'DT_INIT', 13: 'DT_FINI', 25: 'DT_INIT_ARRAY', 26: 'DT_FINI_ARRAY', 27: 'DT_INIT_ARRAYSZ', 28: 'DT_FINI_ARRAYSZ' } init_array_addr = None rela_addr = None rela_size = None addr = dynamic_vaddr while addr < dynamic_vaddr + 0x1000: data = idc.get_bytes(addr, 16) d_tag = struct.unpack_from('<Q', data, 0)[0] d_val = struct.unpack_from('<Q', data, 8)[0] if d_tag == 0: print(f"0x{addr:08X}: DT_NULL (结束)") break tname = tag_names.get(d_tag, f'DT_0x{d_tag:X}') marker = '' if d_tag == 25: marker = ' <<< .init_array 地址!' init_array_addr = d_val elif d_tag == 27: marker = ' (.init_array 大小)' elif d_tag == 7: marker = ' <<< 重定位表地址' rela_addr = d_val elif d_tag == 8: marker = ' <<< 重定位表大小' rela_size = d_val print(f"0x{addr:08X}: {tname:25s} = 0x{d_val:08X}{marker}") addr += 16 # ========== 第4步:解析重定位表 ========== if rela_addr and init_array_addr: print("") print("=" * 60) print(f"第4步: 重定位表 (地址 0x{rela_addr:X})") print("=" * 60) count = rela_size // 24 for i in range(count): off = rela_addr + i * 24 data = idc.get_bytes(off, 24) r_offset = struct.unpack_from('<Q', data, 0)[0] r_info = struct.unpack_from('<Q', data, 8)[0] r_addend = struct.unpack_from('<q', data, 16)[0] r_type = r_info & 0xFFFFFFFF type_names_r = {0x401: 'RELATIVE', 0x403: 'GLOB_DAT'} tname = type_names_r.get(r_type, f'0x{r_type:X}') marker = '' if r_offset == init_array_addr: marker = ' <<< 这就是壳的入口地址!' print(f"[{i}] 修改 0x{r_offset:08X} <- 0x{r_addend:X} 类型={tname}{marker}") # ========== 结论 ========== print("") print("=" * 60) print("结论") print("=" * 60) if init_array_addr and rela_addr: print(f".init_array 地址: 0x{init_array_addr:X}") print(f"壳的真正入口: 0x4780") print(f"") print(f"-> 按 G 输入 4780 跳转到壳入口, 然后按 F5 反编译")
7、0x4780,F5反编译
8、0x33F8,F5反编译,壳主函数
9、在壳主函数中找到关键调用
0x36BC: BL sub_3184 (RC4解密)
0x3728: BL sub_2B1C (XOR解密)
0x454C: BL sub_2CD0 (ELF重定位)
10、解密壳
import struct# 读取原始外层壳with open('libDexHelper.so', 'rb') as f: data = f.read()print(f"[*] 原始文件大小: {hex(len(data))}")# 关键常量 (从分析记录)PAYLOAD_OFFSET = 0x8000 # 内层payload起始偏移XOR_KEY = 0x19 # 正文XOR keyHEADER_DECRYPT_LEN = 0x40 # 头部解密长度# 从文件尾部偏移0x104E3D读取密钥材料KEY_MATERIAL_OFFSET = 0x104E3DKEY_MATERIAL_LEN = 0x14HEADER_KEY_LEN = 0x10 # 前0x10字节作为头部解密keykey_material = data[KEY_MATERIAL_OFFSET:KEY_MATERIAL_OFFSET + KEY_MATERIAL_LEN]print(f"[*] 密钥材料 ({hex(KEY_MATERIAL_OFFSET)}): {key_material.hex()}")header_key = key_material[:HEADER_KEY_LEN]print(f"[*] 头部解密key: {header_key.hex()}")# 提取加密的payloadencrypted_payload = data[PAYLOAD_OFFSET:]print(f"[*] 加密payload大小: {hex(len(encrypted_payload))}")# 第一步:对头部0x40字节做RC4-like解密# RC4-like: 简单的流密码,用key生成密钥流def rc4_like_decrypt(data, key): # 初始化S盒 S = list(range(256)) j = 0 for i in range(256): j = (j + S[i] + key[i % len(key)]) % 256 S[i], S[j] = S[j], S[i] # 生成密钥流并解密 result = bytearray(len(data)) i = j = 0 for k in range(len(data)): i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] keystream_byte = S[(S[i] + S[j]) % 256] result[k] = data[k] ^ keystream_byte return bytes(result)# 解密头部header_encrypted = encrypted_payload[:HEADER_DECRYPT_LEN]header_decrypted = rc4_like_decrypt(header_encrypted, header_key)print(f"[*] 头部解密完成")# 第二步:对正文做单字节XOR解密body_encrypted = encrypted_payload[HEADER_DECRYPT_LEN:]body_decrypted = bytes([b ^ XOR_KEY for b in body_encrypted])print(f"[*] 正文XOR解密完成 (key=0x{XOR_KEY:02X})")# 拼接完整的解壳后payloaddecrypted_payload = header_decrypted + body_decrypted# 验证ELF魔数if decrypted_payload[:4] == b'\x7fELF': print(f"[+] ELF魔数验证通过!")else: print(f"[-] 警告: ELF魔数不匹配: {decrypted_payload[:4].hex()}")# 解析ELF头elf_magic = decrypted_payload[:4]ei_class = decrypted_payload[4] # 32/64位ei_data = decrypted_payload[5] # 字节序e_type = struct.unpack('<H', decrypted_payload[16:18])[0]e_machine = struct.unpack('<H', decrypted_payload[18:20])[0]e_entry = struct.unpack('<I', decrypted_payload[24:28])[0]e_phoff = struct.unpack('<I', decrypted_payload[28:32])[0]e_shoff = struct.unpack('<I', decrypted_payload[32:36])[0]e_phnum = struct.unpack('<H', decrypted_payload[42:44])[0]e_shnum = struct.unpack('<H', decrypted_payload[48:50])[0]print(f"\n[*] 内层ELF信息:")print(f" 类型: {'64位' if ei_class == 2 else '32位'}")print(f" 字节序: {'小端' if ei_data == 1 else '大端'}")print(f" e_type: {hex(e_type)}")print(f" e_machine: {hex(e_machine)}")print(f" 入口点: {hex(e_entry)}")print(f" Program Header偏移: {hex(e_phoff)}")print(f" Section Header偏移: {hex(e_shoff)}")print(f" Program Header数量: {e_phnum}")print(f" Section Header数量: {e_shnum}")# 保存解壳后的文件output_file = 'libDexHelper_inner.so'with open(output_file, 'wb') as f: f.write(decrypted_payload)print(f"\n[+] 解壳完成! 保存到: {output_file}")print(f"[+] 文件大小: {hex(len(decrypted_payload))}")
11、libDexHelper_inner.so导入IDA,函数数量2211个
12、使用florida 16.1.8绕过frida检测
13、使用frida-dexdump -d模式
D:\path\python\python39\python39.exe -m frida_dexdump -U -f com.xxx.xxx --sleep 15 -d -o C:\Users\Administrator\Desktop\frida\dump_output
14、修复dex
import osimport structimport zlibimport hashlibdef fix_dex_checksum(filepath): """修复 DEX 文件的 checksum 和签名""" with open(filepath, 'rb') as f: data = bytearray(f.read()) # 检查 DEX 魔数 if data[:4] != b'dex\n': print(f"[跳过] {os.path.basename(filepath)}: 不是 DEX 文件") return False # 读取当前值 old_checksum = struct.unpack_from('<I', data, 8)[0] # 第一步:计算 SHA-1 签名(对偏移 32 开始的数据) sha1 = hashlib.sha1(bytes(data[32:])).digest() # 第二步:写入签名到偏移 12-31 data[12:32] = sha1 # 第三步:计算 adler32 checksum(对偏移 12 开始的数据,包含签名) new_checksum = zlib.adler32(bytes(data[12:])) & 0xFFFFFFFF # 第四步:写入 checksum 到偏移 8-11 struct.pack_into('<I', data, 8, new_checksum) # 保存修复后的文件 with open(filepath, 'wb') as f: f.write(data) print(f"[修复] {os.path.basename(filepath)}: checksum=0x{new_checksum:08X}") return Truedef main(): dex_dir = r'C:\Users\Administrator\Desktop\frida\dump_output' print("=" * 60) print("DEX 文件 checksum 修复工具 (v2)") print("=" * 60) fixed = 0 failed = 0 for filename in sorted(os.listdir(dex_dir)): if filename.endswith('.dex'): filepath = os.path.join(dex_dir, filename) try: if fix_dex_checksum(filepath): fixed += 1 except Exception as e: print(f"[错误] {filename}: {e}") failed += 1 print() print("=" * 60) print(f"修复完成: {fixed} 个文件") print(f"失败: {failed} 个文件") print("=" * 60)if __name__ == '__main__': main()
15、AndroidManifest.xml找一个WebViewActivity
16、去脱壳的代码中查看WebViewActivity验证
插眼
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:挖个洞先 挖个洞先 挖个洞先《【银行逆向百例】18Android逆向之libDexHelper.so梆梆加固壳解密+frida-dexdump脱壳》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论