修復FART dump下來的dex

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);
    }
}

是不是很簡單,本文完。

3. 完整代碼

https://github.com/luoyesiqiu/DexRepair

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章