1. 情景
在使用脫殼機對Android App脫殼的時候,我們常常會得到有nop指令填充的函數的dex,nop是No Operation的縮寫,意為無操作。
上面是一個函數的smali代碼,可以清晰的看到函數被多個nop指令填充,如果這個函數解析成Java代碼,則得到一個空的函數:
某些殼在加固的時候會把dex文件中的函數的真正函數體給抽掉,用nop指令來填充,nop指令在這個過程中只用作占位,當函數執行的時候再把真正的代碼給還原回去,加固殼用這種方式來保護函數中真正的代碼。我們修復的過程就是把函數的真正代碼寫回dex文件的過程。函數修復后:
2. 修復
FART在dump dex的同時,還把dex的CodeItem給dump了下來,這給我們修復dex提供了極大的便利,CodeItem中存着函數中真正代碼,CodeItem dump下來后存在.bin
文件中。所以我們修復的時候,讀取.bin
文件,把CodeItem填入Dex文件的相應的位置即可。
我們打開.bin
文件,可以看到它由多項形如以下格式的文本組成,每一項代表一個函數
{name:void junit.textui.ResultPrinter.printFailures(junit.framework.TestResult),method_idx:1565,offset:52440,code_item_len:46,ins:BQACAAQAAADpYAIADwAAAG4QpwUEAAwAbhCmBQQACgEbAhYFAABuQBsGAyEOAA==};
我們來看這些數據都是什么:
name
指示函數的全名,包括完整的參數類型和返回值類型method_idx
是函數在method_ids中的索引offset
指示函數的insns相對於dex文件的偏移code_item_len
CodeItem的長度ins
base64字符串,解碼后是dex結構中的insns,即函數的真正的代碼
在dex修復過程中,對我們有用的是offset和ins,可以編寫代碼將它們從.bin
文件中提取出來:
public static List<CodeItem> convertToCodeItems(byte[] bytes){
String input = new String(bytes);
List<CodeItem> codeItems = new ArrayList<>();
Pattern pattern = Pattern.compile("\\{name:(.+?),method_idx:(\\d+),offset:(\\d+),code_item_len:(\\d+),ins:(.+?)\\}");
Matcher matcher = pattern.matcher(input);
while(matcher.find()){
int offset = Integer.parseInt(matcher.group(3));
String insBase64 = matcher.group(5);
byte[] ins = Base64.getDecoder().decode(insBase64);
CodeItem codeItem = new CodeItem(offset,ins);
codeItems.add(codeItem);
}
return codeItems;
}
CodeItem類:
public class CodeItem{
public CodeItem(long offset, byte[] byteCode) {
this.offset = offset;
this.byteCode = byteCode;
}
public long getOffset() {
return offset;
}
public void setOffset(long offset) {
this.offset = offset;
}
public byte[] getByteCode() {
return byteCode;
}
public void setByteCode(byte[] byteCode) {
this.byteCode = byteCode;
}
@Override
public String toString() {
return "CodeItem{" +
"offset=" + offset +
", byteCode=" + Arrays.toString(byteCode) +
'}';
}
private long offset;
private byte[] byteCode;
}
接着將需要的修復dex復制一份,把insns填充到被復制出來的dex的相應位置,即修復過程:
public static void repair(String dexFile, List<CodeItem> codeItems){
RandomAccessFile randomAccessFile = null;
String outFile = dexFile.endsWith(".dex") ? dexFile.replaceAll("\\.dex","_repair.dex") : dexFile + "_repair.dex";
//copy dex
byte[] dexData = IoUtils.readFile(dexFile);
IoUtils.writeFile(outFile,dexData);
try{
randomAccessFile = new RandomAccessFile(outFile,"rw");
for(int i = 0 ; i < codeItems.size();i++){
CodeItem codeItem = codeItems.get(i);
randomAccessFile.seek(codeItem.getOffset());
randomAccessFile.write(codeItem.getByteCode());
}
}
catch (Exception e){
e.printStackTrace();
}
finally {
IoUtils.close(randomAccessFile);
}
}
是不是很簡單,本文完。