【原创】一个dex脱壳脚本

一个IDA脚本,通过kill方法实现Dalvik平台上的DEX文件脱壳。作者分享了一个实用的DEX脱壳工具,并讨论了脱壳思路及如何绕过某些防御措施。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转:http://bbs.pediy.com/showthread.php?p=1459711#post1459711


标 题: 【原创】一个dex脱壳脚本
作 者: QEver
时 间: 2017-01-03,15:06:50
链 接: http://bbs.pediy.com/showthread.php?t=214999

一个ida脚本,配合kill方法,可以实现脱绝大部分运行于dalvik上的dex壳。至于具体用法,自行领悟吧。

代码:
__author__ = 'QEver'

DUMP_FILE_PREFIX = r'd:/'
USER_DEX_FILES_OFFSET = 0x330
LOADED_CLASSES_OFFSET = 0xAC
JAR_NAME_OFFSET = 0x24
SIZE_OF_DEY_HEADER = 0x28
DESC_OFFSET_OF_CLASS_OBJECT = 0x18
METHOD_OFFSET_OF_CLASS_OBJECT = 0x60
SIZE_OF_STRUCT_METHOD = 0x38

import os
import idaapi
import binascii


def read_data(ea, size):
    return idaapi.dbg_read_memory(ea, size)


def read_dword(ea):
    val = int(binascii.hexlify(idaapi.dbg_read_memory(ea, 4)), 16)
    r = (val & 0xff) << 24 | (val & 0xff00) << 8 | (val & 0xff0000) >> 8 | (val & 0xff000000) >> 24
    return r


def read_bool(ea):
    val = int(binascii.hexlify(idaapi.dbg_read_memory(ea, 1)), 16)
    return val


def read_str(ea, max=256):
    c = ''
    while True:
        x = idaapi.dbg_read_memory(ea, 1)
        ea = ea + 1
        max = max - 1
        if x == '\0' or max < 0:
            break
        c += x
    return c


class DvmDex:
    def __init__(self, ea):
        self.ea = ea
        self.pDexFile = read_dword(ea)
        self.baseAddr = read_dword(self.pDexFile + 0x2c)

    def p(self):
        print 'pDexFile = %x' % self.pDexFile
        print 'baseAddr = %x' % self.baseAddr


class ClassObject:
    def __init__(self, ea):
        self.ea = ea
        self.desc_addr = read_dword(ea + DESC_OFFSET_OF_CLASS_OBJECT)

    def get_descriptor(self):
        return read_str(self.desc_addr)


class DexOrJar:
    def __init__(self, ea):
        self.ea = ea
        self.filename = read_dword(ea)
        self.isDex = read_bool(ea + 4)
        self.pRawDexFile = read_dword(ea + 8)
        self.pJarFile = read_dword(ea + 12)

    def get_filename(self):
        if self.isDex == 0:
            return read_str(read_dword(self.pJarFile + JAR_NAME_OFFSET))
        return read_str(self.filename)

    def get_dvmdex(self):
        if self.isDex == 0:
            return read_dword(self.pJarFile + JAR_NAME_OFFSET + 4)
        return read_dword(self.pRawDexFile + 4)

    def p(self):
        print 'filename = %s' % read_str(self.filename)
        print 'isDex = %d' % self.isDex
        print 'pRawDexFile = %x' % self.pRawDexFile
        print 'pJarFile = %x' % self.pJarFile


class HashTable:
    def __init__(self, ea):
        self.ea = ea
        self.tableSize = -1
        self.numEntries = -1
        self.pEntries = None
        self.do_init()

    def do_init(self):
        self.tableSize = read_dword(self.ea)
        self.numEntries = read_dword(self.ea + 4)
        self.pEntries = read_dword(self.ea + 12)

    def get_table_size(self):
        return self.tableSize

    def get_num_entries(self):
        return self.numEntries

    def get_pentries(self):
        return self.pEntries

    def p(self):
        print 'tableSize = %d' % self.tableSize
        print 'numEntries = %d' % self.numEntries
        print 'pEntries = %x' % self.pEntries


class Method:
    def __init__(self, ea):
        self.ea = ea

    def get_name(self):
        addr = self.ea + 0x10
        return read_str(read_dword(addr))

    def get_insns(self):
        addr = self.ea + 0x20
        return read_dword(addr)

    def get_address(self):
        return self.ea


def get_gdvm_address():
    return idaapi.get_debug_name_ea("gDvm")


def dump_all_dex(prefix=DUMP_FILE_PREFIX):
    gdvm = get_gdvm_address()
    print '[*] gDvm = 0x%x' % gdvm
    user_dex_files = read_dword(gdvm + USER_DEX_FILES_OFFSET)
    print '[*] gDvm.user_dex_files = 0x%x' % user_dex_files

    ht = HashTable(user_dex_files)

    max_size = ht.get_table_size()
    size = ht.get_num_entries()
    p = ht.get_pentries()
    print '[*] Found %s items in Dex Table' % size
    for i in range(max_size):
        x = read_dword(p)

        p += 8
        if x == 0:
            continue
        doj = DexOrJar(x)
        print '[*] Dex in Address 0x%x, isDex = %d' % (x, doj.isDex)
        addr = doj.get_dvmdex()
        name = doj.get_filename()
        print '[*] found file : %s , dvmdex = 0x%x' % (name, addr)

        dd = DvmDex(addr)
        addr = dd.baseAddr
        base = addr - SIZE_OF_DEY_HEADER
        size = read_dword(addr + 0x20) + SIZE_OF_DEY_HEADER
        flag = read_str(base, 3)
        if flag != 'dey':
            base = addr
            size = size = read_dword(addr + 0x20)

        print '[*] found odex file = 0x%x <%s>, size = 0x%x' % (base, read_str(base, 7).replace('\n', '.'), size)

        name = os.path.basename(name)
        path = os.path.join(prefix, name)

        print '[*] Write to %s' % path
        data = read_data(base, size)

        f = open(path, 'wb')
        f.write(data)
        f.close()
        print '[*] Finish Write'


def find_class(name):
    gDvm = get_gdvm_address()
    loaded_classes = read_dword(gDvm + LOADED_CLASSES_OFFSET)

    ht = HashTable(loaded_classes)
    max_size = ht.get_table_size()
    size = ht.get_num_entries()
    p = ht.get_pentries() + 4
    print 'Finding for %d items, may take a long time...' % max_size
    for i in range(max_size):
        x = read_dword(p)
        p = p + 8
        if x == 0:
            continue
        c = ClassObject(x)
        s = c.get_descriptor()
        if s == name:
            print '[*] Found Class <%s> : 0x%x' % (name, x)
            return x
        if s.find(name) != -1:
            print '[*] Found Class <%s> : 0x%x' % (s, x)


def list_method(class_addr):
    x = ClassObject(class_addr)
    print '[*] List All Method of %s' % x.get_descriptor()
    m = class_addr + METHOD_OFFSET_OF_CLASS_OBJECT
    directMethodCount = read_dword(m)
    directMethodTable = read_dword(m + 4)
    virtualMethodCount = read_dword(m + 8)
    virtualMethodTable = read_dword(m + 12)

    for i in range(directMethodCount):
        method = Method(directMethodTable)
        directMethodTable = directMethodTable + SIZE_OF_STRUCT_METHOD
        print method.get_name(), hex(method.get_address())

    for i in range(virtualMethodCount):
        method = Method(virtualMethodTable)
        virtualMethodTable = virtualMethodTable + SIZE_OF_STRUCT_METHOD
        print method.get_name()


def list_all_class():
    gDvm = get_gdvm_address()
    loaded_classes = read_dword(gDvm + LOADED_CLASSES_OFFSET)

    ht = HashTable(loaded_classes)
    max_size = ht.get_table_size()
    size = ht.get_num_entries()
    p = ht.get_pentries() + 4
    for i in range(max_size):
        x = read_dword(p)
        p = p + 8
        if x == 0:
            continue
        c = ClassObject(x)
        print c.get_descriptor()

if __name__ == '__main__':
    #list_all_class()
    #find_class('alibaba')
    #list_method(class_addr) 
    dump_all_dex('d:/')

其实脚本的思想,在2014年阿里安全竞赛解题报告里面,我就已经说的很清楚了。近期简单整理了代码,看到很多人还在纠结dex脱壳问题,就当作新年礼物,给大家分享一下吧。(我认为方法比某qian的xxxhunter靠谱多了~~~~~~~
本人早已经不做破解相关工作。不过在之前帮朋友的时候,发现现在的壳已经有针对我之前提到过的"kill -19"的防御措施,深感欣慰(=。=)~不过这条思路是无法防御的,变通一下方式,就能达到同样的目的。
总之~方法是死的,人是活的。没有牛逼的工具,只有牛逼的人~

最后~本Demo只是用于演示破解思路,本人不负责维护,也不会解释任何使用相关的问题!

<think>好的,我现在要介绍Frida-dexdump这个脱壳脚本。首先,得了解用户可能对Android应用逆向和脱壳不太熟悉,所以需要从基础讲起。然后,分步骤解释Frida-dexdump是什么,它的原理、使用步骤、优缺点,以及实际应用中的注意事项。 用户可能需要知道为什么需要脱壳,比如分析恶意软件或进行安全审计。要提到常见的加固服务如梆梆、360加固,以及Frida-dexdump如何应对这些保护措施。然后,详细说明Frida-dexdump的工作原理,基于Frida的动态插桩技术,在内存中搜索DEX文件并导出。这部分要确保技术细节准确,但不过于晦涩。 接着是使用步骤,包括安装Frida和frida-dexdump,设备环境准备,运行命令,以及可能需要的参数调整。要提醒用户可能需要root或模拟器环境,以及如何确认脱壳成功。还要提到常见问题,比如多个DEX文件的情况,或者内存中DEX损坏的情况,如何处理。 优缺点方面,强调动态脱壳的有效性,但也要指出依赖运行环境和可能遗漏的情况。注意事项部分要提到法律和道德问题,确保用户明白合法使用的重要性。最后,总结Frida-dexdump在安全研究中的作用,并推荐进一步的学习资源。 需要检查是否有遗漏的关键点,比如是否需要配置Python环境,或者Frida版本兼容性问题。还要确保命令示例正确,比如安装命令是pip install frida-dexdump,运行命令是frida-dexdump -U -f com.example.app。可能还需要提到其他工具对比,如DumpDex或Xposed模块,但Frida-dexdump的优势在于灵活性和动态分析能力。 最后,确保语言通俗易懂,结构清晰,分点说明,便于用户跟随。避免使用过多专业术语,必要时进行解释。比如解释DEX文件是Android应用的字节码,脱壳就是从内存中提取被加固保护隐藏的代码。这样用户即使没有相关背景也能理解基本概念。</think>Frida-dexdump 是一款基于动态注入框架 **Frida** 开发的 Android 应用脱壳工具,主要用于从内存中提取被加固保护的 DEX 文件。以下是逐步解析: --- ### 一、脱壳原理 1. **动态注入** 通过 Frida 将脚本注入到目标 App 进程,实时监控内存中的 DEX 加载行为。 2. **内存搜索** 扫描进程内存,识别符合 DEX 文件特征(如 `dex\n035` 文件头)的数据块。 3. **修复与导出** 提取原始 DEX 字节流,修复内存偏移等问题,最终保存为可分析的 `.dex` 文件。 --- ### 二、使用步骤 1. **环境准备** - 安装 Python 和 Frida: ```bash pip install frida-tools frida-dexdump ``` - 手机/模拟器需开启 USB 调试,并运行 Frida Server。 2. **执行脱壳** - 针对正在运行的 App: ```bash frida-dexdump -U -f com.example.app ``` - `-U`:连接 USB 设备 - `-f`:指定目标包名 3. **获取结果** - 脱壳后的 DEX 文件会保存在当前目录的 `dump` 子文件夹中。 --- ### 三、核心优势 1. **对抗加固** 可绕过主流商业加固方案(如梆梆、腾讯乐固)的内存保护机制。 2. **动态脱壳** 直接提取运行时内存中的 DEX,避免静态脱壳的复杂性。 3. **灵活定制** 支持自定义搜索策略(如调整内存范围、过滤无效 DEX)。 --- ### 四、局限性 1. **依赖运行环境** - 需保持 App 处于运行状态,部分加固可能会检测 Frida 并闪退。 2. **多DEX处理** 若 App 包含多个 DEX 文件,需手动合并分析。 3. **内存残留** 若 DEX 已被卸载或覆盖,可能导致提取失败。 --- ### 五、实际应用技巧 1. **绕过反调试** 配合 Frida 脚本禁用加固的反调试检测。 2. **多进程处理** 使用 `--spawn` 参数在应用启动时注入,避免错过早期加载的 DEX。 3. **日志分析** 添加 `-v` 参数查看详细日志,定位脱壳失败原因。 --- ### 六、注意事项 - **法律合规性**:仅用于安全研究或授权测试,避免侵犯软件版权。 - **设备兼容性**:部分 Android 10+ 机型需关闭 SELinux 或使用模拟器。 - **版本更新**:定期更新 `frida-dexdump` 以适配新版加固技术。 --- 如需进一步分析脱壳后的 DEX 文件,可结合 **Jadx** 或 **Ghidra** 进行反编译。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值