一文搞懂 Smali 与 Baksmali:Java 层逆向必备技能

版权归作者所有,如有转发,请注明文章出处:cyrus-studio.github.io/blog/

smali 与 baksmali

smali 和 baksmali 是用于 Android 平台中 DEX 文件的汇编器和反汇编器,广泛应用于 Android 逆向分析与调试。

smali 和 baksmali 是一对工具,分别用于:

  • smali:将 smali 代码(Java汇编语言)编译成 DEX 文件。

  • baksmali:将 DEX 文件反汇编为 smali 代码。

开源地址:

Release版本下载地址:github.com/baksmali/sm...

使用 baksmali.jar 将 .dex 文件反汇编为 .smali

arduino 复制代码
java -jar baksmali.jar disassemble "D:\Python\anti-app\app\douyin\dump_dex\base.apk.dex" -o smali
  • 输入 DEX 文件路径。

  • -o smali:指定输出目录,保存生成的 .smali 文件。

你也可以加上 --api 指定 Android API 级别:

lua 复制代码
java -jar baksmali.jar d "D:\Python\anti-app\app\douyin\dump_dex\base.apk.dex" -o smali --api 33

使用 smali.jar 将 .smali 汇编回 .dex 文件

复制代码
java -jar smali.jar assemble smali -o new_classes.dex
  • smali:输入的 smali 文件目录。

  • -o new_classes.dex:输出的 DEX 文件名。

同样可以加上 --api 参数:

css 复制代码
java -jar smali.jar assemble smali -o new_classes.dex --api 33

通过 python 脚本批量反汇编 dex

dex2smali.py 是一个用于批量反编译 Android .dex 文件为 .smali 的 Python 脚本,底层调用 baksmali.jar 进行实际转换。

支持如下功能:

  • 遍历指定目录下的所有 .dex 文件进行反编译

  • 自动排序 classes.dex, classes2.dex, ..., classesN.dex 确保按加载顺序反编译

  • 支持将多个 dex 反编译结果:合并到一个目录(默认),或分别输出(使用 --no-merge)

  • 默认输出到 <input_dir>/smali,可通过 --output 指定输出目录

代码实现如下:

python 复制代码
import os
import re
import shutil
import subprocess
from pathlib import Path
from typing import List

# 当前脚本目录
SCRIPT_DIR = Path(__file__).resolve().parent
# 固定的 baksmali.jar 路径
BAKSMALI_JAR_PATH = SCRIPT_DIR / "baksmali.jar"


def merge_smali(temp_dir: Path, output_dir: Path):
    """
    将临时目录下的 smali 文件合并到输出目录中,遇到同名文件后者覆盖前者。
    """
    for root, _, files in os.walk(temp_dir):
        for file in files:
            if file.endswith(".smali"):
                rel_path = Path(root).relative_to(temp_dir) / file
                dest_path = output_dir / rel_path
                os.makedirs(dest_path.parent, exist_ok=True)
                shutil.copyfile(os.path.join(root, file), dest_path)


def sort_dex_paths(dex_paths: List[Path]) -> List[Path]:
    """
    对包含 classes.dex、classesN.dex 的 dex 文件路径进行排序,
    确保 classes2.dex 排在 classes10.dex 前面。

    :param dex_paths: 未排序的 dex 文件路径列表
    :return: 排序后的 dex 文件路径列表
    """

    def extract_index(dex_path: Path) -> int:
        name = dex_path.name
        if name == "classes.dex":
            return 1
        match = re.match(r"classes(\d+)\.dex$", name)
        if match:
            return int(match.group(1))
        return float('inf')

    return sorted(dex_paths, key=extract_index)


def dex_to_smali(input_dir: Path, output_dir: Path = None, merge: bool = True):
    """
    将指定目录下的所有 .dex 文件转换为 smali 文件。

    参数:
        input_dir (Path): 包含 .dex 文件的输入目录。
        output_dir (Path): smali 输出目录,默认 input_dir/smali。
        merge (bool): 是否合并 smali 文件,默认 True。
    """

    if not input_dir.exists() or not input_dir.is_dir():
        print(f"❌ 输入目录无效:{input_dir}")
        return

    if not BAKSMALI_JAR_PATH.exists():
        print(f"❌ 缺少 baksmali.jar,请将其放到当前脚本目录下:{BAKSMALI_JAR_PATH}")
        return

    if output_dir is None:
        output_dir = input_dir / "smali"

    os.makedirs(output_dir, exist_ok=True)

    dex_files = list(input_dir.rglob("*.dex"))
    sorted_dex_files = sort_dex_paths(dex_files)

    for dex_file in sorted_dex_files:
        print(f"🔧 正在反编译:{dex_file}")

        temp_smali_dir = output_dir if not merge else output_dir.parent / f".temp_smali_{dex_file.stem}"
        if merge and temp_smali_dir.exists():
            shutil.rmtree(temp_smali_dir)
        os.makedirs(temp_smali_dir, exist_ok=True)

        cmd = [
            "java", "-jar", str(BAKSMALI_JAR_PATH),
            "d",
            str(dex_file),
            "-o", str(temp_smali_dir)
        ]

        try:
            subprocess.run(cmd, check=True)
            if merge:
                merge_smali(temp_smali_dir, output_dir)
                print(f"✅ 成功合并 smali 到:{output_dir}")
            else:
                print(f"✅ 输出到目录:{temp_smali_dir}")
        except subprocess.CalledProcessError as e:
            print(f"❌ 反编译失败:{dex_file}\n{e}")
        finally:
            if merge:
                shutil.rmtree(temp_smali_dir, ignore_errors=True)


if __name__ == "__main__":
    r"""
    示例用法:

        # 默认合并所有 dex 的 smali 输出到 <input_dir>/smali
        python dex2smali.py D:\Path\To\dex_dir

        # 指定输出目录(仍合并)
        python dex2smali.py D:\Path\To\dex_dir --output D:\output\smali

        # 不合并,每个 dex 文件单独输出
        python dex2smali.py D:\Path\To\dex_dir --no-merge

        # 不合并 + 指定输出目录
        python dex2smali.py D:\Path\To\dex_dir --output D:\output\smali --no-merge
    """

    import argparse

    parser = argparse.ArgumentParser(description="批量将 DEX 文件转换为 Smali")
    parser.add_argument("input_dir", type=str, help="输入 DEX 文件目录")
    parser.add_argument("--output", type=str, help="输出 smali 目录(可选)")
    parser.add_argument("--no-merge", action="store_true", help="不合并 smali 输出,按 dex 文件分开")

    args = parser.parse_args()
    input_path = Path(args.input_dir).resolve()
    output_path = Path(args.output).resolve() if args.output else None

    dex_to_smali(input_path, output_path, merge=not args.no_merge)

执行脚本批量反汇编指定目录下的 dex 文件:

csharp 复制代码
(anti-app) PS D:\Python\anti-app\dex2smali> python dex2smali.py D:\Python\anti-app\app\douyin\dex
🔧 正在反编译:D:\Python\anti-app\app\douyin\dex\base.apk.dex
✅ 成功合并 smali 到:D:\Python\anti-app\app\douyin\dex\smali
🔧 正在反编译:D:\Python\anti-app\app\douyin\dex\base.apk_classes2.dex
✅ 成功合并 smali 到:D:\Python\anti-app\app\douyin\dex\smali
🔧 正在反编译:D:\Python\anti-app\app\douyin\dex\base.apk_classes3.dex
✅ 成功合并 smali 到:D:\Python\anti-app\app\douyin\dex\smali

效果如下:

使用说明

perl 复制代码
# 默认行为:反编译并合并输出到 <input_dir>/smali
python dex2smali.py D:\AndroidApp\dump_dex

# 指定输出目录(仍合并)
python dex2smali.py D:\AndroidApp\dump_dex --output D:\Output\smali

# 不合并输出,每个 dex 单独输出
python dex2smali.py D:\AndroidApp\dump_dex --no-merge

# 不合并 + 自定义输出目录
python dex2smali.py D:\AndroidApp\dump_dex --output D:\Output\smali --no-merge

输出目录结构示例:

  1. 合并模式(默认)
javascript 复制代码
<output_dir>/smali/com/example/...
  1. 不合并模式(每个 dex 输出在独立目录)
javascript 复制代码
<output_dir>/classes/com/example/...
<output_dir>/classes2/com/example/...

完整源码

开源地址:github.com/CYRUS-STUDI...

相关推荐
Glacien1 小时前
compose动画从底层基础到顶层高级应用(三)核心API之--Transition
android
亿刀2 小时前
为什么要学习Flutter编译过程
android·flutter
suqingxiao2 小时前
android虚拟机(AVD)报错The emulator process for AVD xxx has terminated
android
whysqwhw2 小时前
OkHttp Cookie 处理机制全解析
android
Evan_ZGYF丶2 小时前
【RK3576】【Android14】ADB工具说明与使用
android·驱动开发·android14·rk3576
lemon_sjdk2 小时前
LWJGL教程(2)——游戏循环
java·人工智能·算法·游戏
幻雨様2 小时前
UE5多人MOBA+GAS 番外篇:移植Lyra的伤害特效(没用GameplayCue,因为我失败了┭┮﹏┭┮)
android·ue5
weixin_524749963 小时前
MVCC(多版本并发控制)介绍及实现原理
java·数据库
狂浪天涯3 小时前
Android 16 显示系统 | 从View 到屏幕系列 - 4 | GraphicBuffer & Gralloc
android·操作系统
skylijf3 小时前
C++ Primer(第5版)- Chapter 7. Classes -004
java·开发语言