版权归作者所有,如有转发,请注明文章出处: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
输出目录结构示例:
- 合并模式(默认)
javascript
<output_dir>/smali/com/example/...
- 不合并模式(每个 dex 输出在独立目录)
javascript
<output_dir>/classes/com/example/...
<output_dir>/classes2/com/example/...