dex中方法指令隐藏

dex中方法指令隐藏

  通过了解dex文件格式,获取到指定方法的指令码的偏移,将指令码清空,达到对指定方法实现的隐藏效果。

原理

  dex文件格式这里就不详讲了,可以通过010editor直观得看到dex文件中各个区域信息。
  关键是我们怎么获取到对应方法的指令码。

AB135E84-4557-4F37-BE37-250B72DA71A4

  我们看到,首先获取到class_defs_item,找到类信息的偏移(class_data_off),得到class_data_item,里面有direct方法列表和virtual方法列表,每一项记录了对应方法的code_item的偏移,由该偏移我们就能找到存放指令码的区域。

  即然要实现指定方法的查找,就需要解析dex,解析出所有方法并存放起来,再由我们指定取出,来将其指令置空。

解析dex文件代码

  通过这篇文章中的提示,修改了部分代码,完成了对指定方法的置空。

实现

  在解析dex代码中,我们将string、type、proto都先存放起来,以便对方法名、参数类型解析时获取。
  按照之前讲的索引过程,就需要先解析class_def_item,得到class_data_item

public static void parseClassIds(byte[] srcByte)
 |
\/
private static ClassDefItem parseClassDefItem(byte[] srcByte)
 |
\/
public static void parseClassData(byte[] srcByte)

  我编译得到的dex部分class_def_item的class_data_off为0,这里我加了个对0的判断。
  得到class_data_item后就可以获取对应类的方法列表。

private static ClassDataItem parseClassDataItem(byte[] srcByte, int offset)

  这个函数在解析class_data_item时,4个字段的类型应该是uleb128,源代码中直接byte转int,故修改了size计算函数。
  class_data_item中有direct方法列表和virtual方法列表,在遍历方法时需要分开遍历。

public static void parseCode(byte[] srcByte)
 |
\/
private static CodeItem parseCodeItem(byte[] srcByte, int offset)

  在解析code_item时,关键是获取到指令码数组和指令码的文件偏移。

1
2
3
4
5
6
7
8
short[] insnsAry = new short[item.insns_size];
int aryOffset = offset + 16;
item.insnsoffset=aryOffset;
for(int i=0;i<item.insns_size;i++){
byte[] insnsByte = Utils.copyByte(srcByte, aryOffset+i*2, 2);
insnsAry[i] = Utils.byte2Short(insnsByte);
}
item.insns = insnsAry;

  遍历方法函数,利用directMethodCodeItemMapvirtualMethodCodeItemMap将所有的方法存放起来,一个方法名对应一个方法实例。
  部分method_item的code_off为0,也需要忽略处理。

private static String getMethodSignStr(MethodIdsItem methodItem)由方法中各个字段,获取到方法的类名、方法名、参数名、返回类型,组成一个字符串。数据来源就是之前得到的string、type、proto三个列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
codeItemMap.putAll(ParseDexUtils.directMethodCodeItemMap);
codeItemMap.putAll(ParseDexUtils.virtualMethodCodeItemMap);

for (String key : codeItemMap.keySet()) {
System.out.println("key:"+key);
if(key.equals(className+methodName)){
CodeItem codeitem=codeItemMap.get(key);

int insns_size=codeitem.insns_size;
int insnsoffset = codeitem.insnsoffset;

System.out.println("ins_size:"+insns_size+"ins_offset:"+insnsoffset);

byte[] nopBytes = new byte[insns_size*2];
for(int i =0;i<nopBytes.length;i++) {
nopBytes[i]=0;
}

srcByte=Utils.replaceBytes(srcByte, nopBytes, insnsoffset);
byte[] signvalue=Utils.signature(srcByte,32);
srcByte=Utils.replaceBytes(srcByte, signvalue, 12);

byte[] checksum=Utils.checksum_bin(srcByte, 12);
srcByte=Utils.replaceBytes(srcByte, checksum, 8);

Utils.saveFile("dex/classes_tmp.dex", srcByte);
break;
}
}

  将所有方法合并成一个map,遍历搜索指定方法签名。获取到对应的code_item,取出指令码偏移,指令码个数,挨个置空。之后修复dex的signature和checksum。完成后替换原apk中的dex即可。
  之后讲解指令码还原部分。

完成版代码