baksmali 首先执行的第一个main 函数
public static void main(String[] args) throws IOException {
Locale locale = new Locale("en", "US");
Locale.setDefault(locale);
CommandLineParser parser = new PosixParser();
CommandLine commandLine;
try {
commandLine = parser.parse(options, args);
} catch (ParseException ex) {
usage();
return;
}
baksmaliOptions options = new baksmaliOptions();
boolean disassemble = true; // 需要反编译
...
//中间有一部分获取命令行参数的代码,暂时省略
String[] remainingArgs = commandLine.getArgs();
Option[] clOptions = commandLine.getOptions();
...
//解析完成命令行参数
//首先判断机器cpu的个数,确定多个cpu能够同时工作,以提高解析效率
if (options.jobs <= 0) {
options.jobs = Runtime.getRuntime().availableProcessors();
if (options.jobs > 6) {
options.jobs = 6;
}
}
//判断api的版本号,当大于17的时候,设置检测包的私有访问属性
if (options.apiLevel >= 17) {
options.checkPackagePrivateAccess = true;
}
String inputDexFileName = remainingArgs[0];
//打开目标文件
File dexFileFile = new File(inputDexFileName);
if (!dexFileFile.exists()) {
System.err.println("Can't find the file " + inputDexFileName);
System.exit(1);
}
//Read in and parse the dex file
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, options.apiLevel); // 重点1
//主要判断odex文件的一些代码,省略
...
//反汇编dex文件,生成一个又一个的smali文件
boolean errorOccurred = false;
if (disassemble) {
errorOccurred = !baksmali.disassembleDexFile(dexFile, options); // 重点2
}
if (doDump) {
if (dumpFileName == null) {
dumpFileName = commandLine.getOptionValue(inputDexFileName + ".dump");
}
dump.dump(dexFile, dumpFileName, options.apiLevel);
}
if (errorOccurred) {
System.exit(1);
}
}
关于main函数的分析主要有两点,需要重点研究一下,一个是
//Read in and parse the dex file
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, options.apiLevel); // 重点1
另外一个就是
errorOccurred = !baksmali.disassembleDexFile(dexFile, options); // 重点2
我们首先看 DexFileFactory.loadDexFile(dexFileFile, options.apiLevel); 这个函数做了什么事情
public static DexBackedDexFile loadDexFile(String path, int api) throws IOException {
return loadDexFile(new File(path), new Opcodes(api));
}
其中 new Opcodes(api) 根据传入的api版本号 生成了 Opcodes 这个对象
这个对象主要是将 dalvik 虚拟机所有的指令code 映射到 一张 hashmap中,索引是本身的指令名称
比如 move-result-wide, if-ne ,invoke-static/range 这些指令,而结果是相应的枚举类,其实本身 Opcode 这个类将dalvik 虚拟机支持的指令进行了很好的代码诠释,在理解了整个代码框架以后,可以重点关注一下
真正调用的 loadDexFile 函数如下:
public static DexBackedDexFile loadDexFile(File dexFile, @Nonnull Opcodes opcodes) throws IOException {
...
//首先判断文件是否为一个压缩文件,如果是的话解压缩后提取dex文件进行解析
InputStream inputStream = new BufferedInputStream(new FileInputStream(dexFile));
try {
return DexBackedDexFile.fromInputStream(opcodes, inputStream); // 重点 1
} catch (DexBackedDexFile.NotADexFile ex) {
// just eat it
}
// Note: DexBackedDexFile.fromInputStream will reset inputStream back to the same position, if it fails
try {
return DexBackedOdexFile.fromInputStream(opcodes, inputStream);
} catch (DexBackedOdexFile.NotAnOdexFile ex) {
// just eat it
}
throw new ExceptionWithContext("%s is not an apk, dex file or odex file.", dexFile.getPath());
}
我们依然跟着重点1 进入到 DexBackedDexFile.fromInputStream(opcodes, inputStream); 这个函数
public static DexBackedDexFile fromInputStream(@Nonnull Opcodes opcodes, @Nonnull InputStream is)
throws IOException {
if (!is.markSupported()) {
throw new IllegalArgumentException("InputStream must support mark");
}
is.mark(44);
byte[] partialHeader = new byte[44];
try {
ByteStreams.readFully(is, partialHeader);
} catch (EOFException ex) {
throw new NotADexFile("File is too short");
} finally {
is.reset();
}
//验证一下魔幻数和dex文件头部
verifyMagicAndByteOrder(partialHeader, 0);
byte[] buf = ByteStreams.toByteArray(is);
return new DexBackedDexFile(opcodes, buf, 0, false); //继续跟踪下去
}
private DexBackedDexFile(Opcodes opcodes, @Nonnull byte[] buf, int offset, boolean verifyMagic) {
super(buf);
this.opcodes = opcodes;
if (verifyMagic) {
verifyMagicAndByteOrder(buf, offset);
}
stringCount = readSmallUint(HeaderItem.STRING_COUNT_OFFSET);
stringStartOffset = readSmallUint(HeaderItem.STRING_START_OFFSET);
typeCount = readSmallUint(HeaderItem.TYPE_COUNT_OFFSET);
typeStartOffset = readSmallUint(HeaderItem.TYPE_START_OFFSET);
protoCount = readSmallUint(HeaderItem.PROTO_COUNT_OFFSET);
protoStartOffset = readSmallUint(HeaderItem.PROTO_START_OFFSET);
fieldCount = readSmallUint(HeaderItem.FIELD_COUNT_OFFSET);
fieldStartOffset = readSmallUint(HeaderItem.FIELD_START_OFFSET);
methodCount = readSmallUint(HeaderItem.METHOD_COUNT_OFFSET);
methodStartOffset = readSmallUint(HeaderItem.METHOD_START_OFFSET);
classCount = readSmallUint(HeaderItem.CLASS_COUNT_OFFSET);
classStartOffset = readSmallUint(HeaderItem.CLASS_START_OFFSET);
}
其实这个函数很简单,就是通过传入的文件流通过dex文件头找到了 dex 文件中的各个索引表的起始地址,索引数量等信息,然后返回一个实例对象给上层,以方便后面的调用
注:这里需要对dex文件的格式有一定的了解,读者可以查阅相关的文档。
分析完了
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, options.apiLevel);
这条语句,我们得到了 DexBackedDexFile 类的一个实例对象,这个对象里面包含什么东西,总结一下有以下内容
<*> private final Opcodes opcodes; 要解析dex文件的dalvik虚拟机的指令集合
<*> 这个dex文件中各种索引的开始地址,索引个数等信息
比如
private final int protoCount;
private final int protoStartOffset;
这两个成员变量主要就是为后面的方法列表提供弹药,保存的是在这个dex文件中实现或者调用的方法信息的字段
比如 你的dex里面有个这样的方法,
int testcall(String test)
那么在 这个表中一定有一个 IL 类型的函数原型,其中 I表示返回类型为 int,L 表示这个函数有一个参数,并且参数是一个对象类型
具体是什么对象呢,在这个表中其实是根据偏移来保存对象的类型的,本身proto这个表中并不提供方法信息的,而是为方法提供函数调用原型,略为有点绕,不过习惯了就好。
<*> dex文件的文件流,以便再进行深入的查询
ok,我们再回到main函数,看后面的一个关键调用
errorOccurred = !baksmali.disassembleDexFile(dexFile, options);
这个调用总体说来,就是完成了 将dex文件转换成一个一个smali文件的艰巨任务!
public static boolean disassembleDexFile(DexFile dexFile, final baksmaliOptions options) {
...
//根据传入的文件夹路径创建文件夹
File outputDirectoryFile = new File(options.outputDirectory);
if (!outputDirectoryFile.exists()) {
if (!outputDirectoryFile.mkdirs()) {
System.err.println("Can't create the output directory " + options.outputDirectory);
return false;
}
}
//排序并生成dex文件中的所有 类定义实例到类定义的列表中
//sort the classes, so that if we're on a case-insensitive file system and need to handle classes with file
//name collisions, then we'll use the same name for each class, if the dex file goes through multiple
//baksmali/smali cycles for some reason. If a class with a colliding name is added or removed, the filenames
//may still change of course
List<? extends ClassDef> classDefs = Ordering.natural().sortedCopy(dexFile.getClasses()); // 重点1
if (!options.noAccessorComments) {
options.syntheticAccessorResolver = new SyntheticAccessorResolver(classDefs);
}
//生成文件的扩展名,为.smali
final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDirectoryFile, ".smali");
//根据 options.jobs 的值来生成处理 smali文件的线程数量
ExecutorService executor = Executors.newFixedThreadPool(options.jobs);
List<Future<Boolean>> tasks = Lists.newArrayList();
for (final ClassDef classDef: classDefs) {
tasks.add(executor.submit(new Callable<Boolean>() {
@Override public Boolean call() throws Exception {
return disassembleClass(classDef, fileNameHandler, options); //回调的解析函数,重点2
}
}));
}
...
}
可以看出来,这个函数主要做了这么几件事情
<*>创建了要生成smali文件的文件夹目录
<*>生成了解析dex文件所有的类实例
<*>开启多线程运行的机制,以类为单位来生成一个又一个的 smali文件,当然文件的扩展名名.smali
故事1
List<? extends ClassDef> classDefs = Ordering.natural().sortedCopy(dexFile.getClasses()); // 重点1
这个函数主要分为两部分
dexFile.getClasses() 这个函数其实是调用的是 DexBackedDexFile 这个类的 getClasses 方法
函数如下
public Set<? extends DexBackedClassDef> getClasses() {
return new FixedSizeSet<DexBackedClassDef>() {
@Nonnull
@Override
public DexBackedClassDef readItem(int index) {
return new DexBackedClassDef(DexBackedDexFile.this, getClassDefItemOffset(index));
}
@Override
public int size() {
return classCount;
}
};
}
其实就是返回一个 new FixedSizeSet<DexBackedClassDef>() 这个匿名类,然后
Ordering.natural().sortedCopy(new FixedSizeSet<DexBackedClassDef>()),这个方法会在内部调用到
new FixedSizeSet<DexBackedClassDef>() 这个类中的继承的两个方法 readItem 和 size,其中
readItem 这个方法,根据传进来的index值来实例化 DexBackedClassDef 类,加入到
List<? extends ClassDef> classDefs 这个列表中去
我们再来看 这条语句
return new DexBackedClassDef(DexBackedDexFile.this, getClassDefItemOffset(index));
public int getClassDefItemOffset(int classIndex) {
if (classIndex < 0 || classIndex >= classCount) {
throw new InvalidItemIndex(classIndex, "Class index out of bounds: %d", classIndex);
}
return classStartOffset + classIndex*ClassDefItem.ITEM_SIZE;
}
很简单,就是从dex文件中找到 指定class的索引地址,dex文件中表示class的其实是个比较复杂的结构,需要好好理解一下,休息一下见下篇继续
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。