这一篇解析dex文件.还是由上一篇文章MainActivity生成的dex文件.dex文件比较大,我就不贴16进制代码了,大致讲一下具体怎么操作.由于手工解析太困难了,所以我就借助代码和两篇参考文章来解析的
Android逆向之旅---解析编译之后的Dex文件格式
Android dex文件解析
接下来开始解析.
1.头文件
头文件格式包含magic, checksum,file_size等信息,但是要注意他们都是小端数据,所以需要调换顺序
private static void parseHeader(byte[] content) {
System.out.println("magic:0x" + Utils.bytesToString(content, 0, 8, false));// 默认0x6465780a30333500=dex\n035\0
System.out.println("checksum:0x" + Utils.bytesToString(content, 8, 4, true));
System.out.println("siganature:0x" + Utils.bytesToString(content, 12, 20, true));
String file_size = Utils.bytesToString(content, 32, 4, true);
System.out.println("file_size:0x" + file_size + "=" + Integer.parseInt(file_size, 16));
String header_size = Utils.bytesToString(content, 36, 4, true);
System.out.println("header_size:0x" + header_size + "=" + Integer.parseInt(header_size, 16));
System.out.println("endian_tag:0x" + Utils.bytesToString(content, 40, 4, true));// 默认小端
String number = Utils.bytesToString(content, 44, 4, true);
System.out.println("link_size:0x" + number + "=" + Integer.parseInt(number, 16));
number = Utils.bytesToString(content, 48, 4, true);
System.out.println("link_off:0x" + number + "=" + Integer.parseInt(number, 16));
number = Utils.bytesToString(content, 52, 4, true);
System.out.println("map_off:0x" + number + "=" + Integer.parseInt(number, 16));
number = Utils.bytesToString(content, 56, 4, true);
System.out.println("string_ids_size:0x" + number + "=" + Integer.parseInt(number, 16));
number = Utils.bytesToString(content, 60, 4, true);
System.out.println("string_ids_off:0x" + number + "=" + Integer.parseInt(number, 16));
number = Utils.bytesToString(content, 64, 4, true);
System.out.println("type_ids_size:0x" + number + "=" + Integer.parseInt(number, 16));
number = Utils.bytesToString(content, 68, 4, true);
System.out.println("type_ids_off:0x" + number + "=" + Integer.parseInt(number, 16));
number = Utils.bytesToString(content, 72, 4, true);
System.out.println("proto_ids_size:0x" + number + "=" + Integer.parseInt(number, 16));
number = Utils.bytesToString(content, 76, 4, true);
System.out.println("proto_ids_off:0x" + number + "=" + Integer.parseInt(number, 16));
number = Utils.bytesToString(content, 80, 4, true);
System.out.println("field_ids_size:0x" + number + "=" + Integer.parseInt(number, 16));
number = Utils.bytesToString(content, 84, 4, true);
System.out.println("field_ids_off:0x" + number + "=" + Integer.parseInt(number, 16));
number = Utils.bytesToString(content, 88, 4, true);
System.out.println("method_ids_size:0x" + number + "=" + Integer.parseInt(number, 16));
number = Utils.bytesToString(content, 92, 4, true);
System.out.println("method_ids_off:0x" + number + "=" + Integer.parseInt(number, 16));
number = Utils.bytesToString(content, 96, 4, true);
System.out.println("class_defs_size:0x" + number + "=" + Integer.parseInt(number, 16));
number = Utils.bytesToString(content, 100, 4, true);
System.out.println("class_defs_off:0x" + number + "=" + Integer.parseInt(number, 16));
number = Utils.bytesToString(content, 104, 4, true);
System.out.println("data_size:0x" + number + "=" + Integer.parseInt(number, 16));
number = Utils.bytesToString(content, 108, 4, true);
System.out.println("data_off:0x" + number + "=" + Integer.parseInt(number, 16));
}
Utils.bytesToString的代码我后面会贴上,现在需要记住最后一个参数true表示是小端数据
2.string_ids
接下来就是string_ids,要注意数据格式,offset是偏移地址,找到偏移地址以后,读出对应的string,这个string直接读,不需要小端转化.另外uleb128的数据格式需要去了解(解析的string需要存起来,有用)
private static void parseStringIds(byte[] content) throws Exception {
// 观察parseHeader的日志.string_ids_off+string_ids_size*4=type_ids_off
int string_ids_size = Integer.parseInt(Utils.bytesToString(content, 56, 4, true), 16);// string条数
System.out.println("总共string:" + string_ids_size);
int string_ids_off = Integer.parseInt(Utils.bytesToString(content, 60, 4, true), 16);
for (int i = 0; i < string_ids_size; i++) {
String string_ids_item = Utils.bytesToString(content, string_ids_off + i * 4, 4, true);// 获取item
int index = Integer.parseInt(string_ids_item, 16);// 获取每个string对应的偏移值
string_ids.add(index);
Pair uleb128 = Utils.readUnsignedLeb128(content, index);
int string_size = Utils.coypByte(content, uleb128.first, uleb128.second);// 获取string对应的长度
String string = new String(Utils.bytesToBytes(content, uleb128.first, string_size, false), "utf-8");// 注意不需要大小端
string_datas.add(string);
if (i >= 1000 && i <= 2000) {
// System.out.println(string);
}
}
}
3.type_ids
注意格式,同时找到的index,这个index需要去对应的string数组里面找
private static void parseTypeIds(byte[] content) throws Exception {
int type_ids_size = Integer.parseInt(Utils.bytesToString(content, 64, 4, true), 16);
System.out.println("总共type:" + type_ids_size);
int type_ids_off = Integer.parseInt(Utils.bytesToString(content, 68, 4, true), 16);
for (int i = 0; i < type_ids_size; i++) {
String type_ids_item = Utils.bytesToString(content, type_ids_off + i * 4, 4, true);// 获取item
int index = Integer.parseInt(type_ids_item, 16);// 获取每个type对应的偏移值
type_ids.add(index);
String string = string_datas.get(index);
if (i >= 10 && i <= 20) {
System.out.println(string);
}
}
}
4.proto_ids
数据结构比上面的复杂
public class ProtoIdsItem {
public int shorty_idx;
public String shorty_idx_name;//自己添加的,不是公共字段
public int return_type_idx;
public String return_type_idx_name;//自己添加的,不是公共字段
public int parameters_off;
// 存数据用的,自己添加的,不是公共字段
public List parametersList = new ArrayList();
public int parameterCount;
@Override
public String toString() {
return "shorty_idx_name:" + shorty_idx_name + ",return_type_idx_name:" + return_type_idx_name + ",parameterCount:" + parameterCount;
}
}
private static void parseProtoIds(byte[] content) throws Exception {
int proto_ids_size = Integer.parseInt(Utils.bytesToString(content, 72, 4, true), 16);
System.out.println("总共proto:" + proto_ids_size);
int proto_ids_off = Integer.parseInt(Utils.bytesToString(content, 76, 4, true), 16);
for (int i = 0; i < proto_ids_size; i++) {
int base_index = proto_ids_off + i * 12;
ProtoIdsItem item = new ProtoIdsItem();
item.shorty_idx = Integer.parseInt(Utils.bytesToString(content, base_index, 4, true), 16);
item.shorty_idx_name = string_datas.get(item.shorty_idx);
item.return_type_idx = Integer.parseInt(Utils.bytesToString(content, base_index + 4, 4, true), 16);
item.return_type_idx_name = string_datas.get(type_ids.get(item.return_type_idx));
item.parameters_off = Integer.parseInt(Utils.bytesToString(content, base_index + 8, 4, true), 16);
if (item.parameters_off > 0) {
item.parameterCount = Integer.parseInt(Utils.bytesToString(content, item.parameters_off, 4, true), 16);
for (int j = 0; j < item.parameterCount; j++) {
// size是int,但是index却是short
int index = Integer.parseInt(Utils.bytesToString(content, item.parameters_off + 4 + j * 2, 2, true),
16);
String name = string_datas.get(type_ids.get(index));
item.parametersList.add(name);
}
} else {
item.parameterCount = 0;
}
proto_ids.add(item);
}
}
注意size是short,只需要两位
5.field_ids
解析过程和proto_ids有点类似
public class FieldIdsItem {
public short class_idx;
public String class_name;//自己添加的,不是公共字段
public short type_idx;
public String type_name;//自己添加的,不是公共字段
public int name_idx;
public String name;//自己添加的,不是公共字段
@Override
public String toString() {
return "class_name:" + class_name + ",type_name:" + type_name + ",name:" + name;
}
}
private static void parseFieldIds(byte[] content) {
int field_ids_size = Integer.parseInt(Utils.bytesToString(content, 80, 4, true), 16);
int field_ids_off = Integer.parseInt(Utils.bytesToString(content, 84, 4, true), 16);
for (int i = 0; i < field_ids_size; i++) {
int base_index = field_ids_off + i * 8;
FieldIdsItem item = new FieldIdsItem();
item.class_idx = (short) Integer.parseInt(Utils.bytesToString(content, base_index, 2, true), 16);
item.class_name = string_datas.get(type_ids.get(item.class_idx));
item.type_idx = (short) Integer.parseInt(Utils.bytesToString(content, base_index + 2, 2, true), 16);
item.type_name = string_datas.get(type_ids.get(item.type_idx));
item.name_idx = Integer.parseInt(Utils.bytesToString(content, base_index + 4, 4, true), 16);
item.name = string_datas.get(item.name_idx);
field_ids.add(item);
// System.out.println(item);
}
}
6.method_ids
public class MethodIdsItem {
public short class_idx;
public String class_name;//自己添加的,不是公共字段
public short proto_idx;
public String proto_name;//自己添加的,不是公共字段
public int name_idx;
public String name;//自己添加的,不是公共字段
@Override
public String toString() {
return "class_name:" + class_name + ",proto_name:" + proto_name + ",name:" + name;
}
}
private static void parseMethodIds(byte[] content) {
int method_ids_size = Integer.parseInt(Utils.bytesToString(content, 88, 4, true), 16);
int method_ids_off = Integer.parseInt(Utils.bytesToString(content, 92, 4, true), 16);
for (int i = 0; i < method_ids_size; i++) {
int base_index = method_ids_off + i * 8;
MethodIdsItem item = new MethodIdsItem();
item.class_idx = (short) Integer.parseInt(Utils.bytesToString(content, base_index, 2, true), 16);
item.class_name = string_datas.get(type_ids.get(item.class_idx));
item.proto_idx = (short) Integer.parseInt(Utils.bytesToString(content, base_index + 2, 2, true), 16);
item.proto_name = proto_ids.get(item.proto_idx).shorty_idx_name;
item.name_idx = Integer.parseInt(Utils.bytesToString(content, base_index + 4, 4, true), 16);
item.name = string_datas.get(item.name_idx);
method_ids.add(item);
// System.out.println(item);
}
}
7.class
class的结构如下(注意,大多都是index)
public int class_idx;
public int access_flags;
public int superclass_idx;
public int interfaces_off;
public int source_file_idx;
public int annotations_off;
public int class_data_off;
public int static_value_off;
这是最复杂的,但是有了上面几个的解析其实对应写起来也不算难,本人写过,但是快写吐了,就没贴出来,具体的方法和上面的也是一样的
可以看到,其实dex文件比class文件要复杂得多.一方面是小端排列,另一方面需要寻址.最重要的一点是,class文件的类索引里面所有的信息都是直接排进去的,但是dex文件里面的类都是存的索引,dex文件更为紧凑.也就是意味着,如果需要修改dex文件,那么他的成本会比修改class文件难得多