首页
社区
课程
招聘
[原创]【银行逆向百例】18Android逆向之libDexHelper.so梆梆加固壳解密 frida-dexdump脱壳
发表于: 19小时前 434

[原创]【银行逆向百例】18Android逆向之libDexHelper.so梆梆加固壳解密 frida-dexdump脱壳

19小时前
434

“ 只要有想见的人,就不是孤单一个人。——《夏目友人帐》S3E4 ”

01环境版本

环境:
电脑,Windows 11 专业版 23H2

501K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6v1K9h3q4G2f1%4g2u0L8X3k6G2f1$3g2U0i4K6u0r3d9X3W2S2L8#2y4#2d9h3&6X3L8#2y4W2j5#2)9#2k6W2b7H3x3r3I4K6i4K6g2X3g2$3W2F1x3e0p5`.

软件:
Florida,16.1.8

6fdK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6k6L8r3q4J5L8$3c8Q4x3V1k6r3L8r3!0J5K9h3c8S2i4K6u0r3M7X3g2D9k6h3q4K6k6i4y4Q4x3V1k6@1j5h3N6Q4x3V1j5`.16.1.8

frida-dexdump

ad5K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Z5L8s2g2%4j5g2)9J5c8X3k6J5K9h3c8S2i4K6u0V1k6r3g2^5k6s2g2E0M7l9`.`.

02操作步骤

1、梆梆加固企业版
图片描述

2、查看APP完整路径,拷贝libDexHelper.so到本地

adb shell dumpsys window | grep mCurrentFocus
adb shell pm path com.xxx.xxx
adb 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 struct
import 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 = None
for 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_vaddr
if 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 key
HEADER_DECRYPT_LEN = 0x40     # 头部解密长度
# 从文件尾部偏移0x104E3D读取密钥材料
KEY_MATERIAL_OFFSET = 0x104E3D
KEY_MATERIAL_LEN = 0x14
HEADER_KEY_LEN = 0x10         # 前0x10字节作为头部解密key
key_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()}")
# 提取加密的payload
encrypted_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})")
# 拼接完整的解壳后payload
decrypted_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 os
import struct
import zlib
import hashlib
def 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 True
def 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验证
图片描述


[培训]《冰与火的战歌:Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

收藏
免费 3
打赏
分享
最新回复 (3)
雪    币: 4727
活跃值: (4799)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
这个版本 AI 纯静态秒了
18小时前
0
雪    币: 55
活跃值: (420)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
6 florida就过了为啥不用原班frida意义更大
17小时前
0
雪    币: 22
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
BBA
5小时前
0
游客
登录 | 注册 方可回帖
返回