国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

DEX文件混淆加密

antz / 1835人閱讀

摘要:原文地址文件混淆加密前言混淆加密主要是為了隱藏文件中關鍵的代碼,力度從輕到重包括靜態變量的隱藏函數的重復定義函數的隱藏以及整個類的隱藏。

現在部分 app 出于安全性(比如加密算法)或者用戶體驗(熱補丁修復bug)會考慮將部分模塊采用熱加載的形式 Load。所以針對這部分的 dex 進行加密是有必要的,如果 dex 是修復的加密算法,你總不想被人一下就反編譯出來吧。當然也可以直接用一個加密算法對 dex 進行加密,Load 前進行解密就可以了,但是最好的加密就是讓人分不清你是否加密了。一般逆向過程中拿到一個可以直接反編譯成 java 源碼的 dex 我們很可能就認為這個 dex 文件是沒有加密可以分析的。

原文地址: DEX文件混淆加密

0x00 前言

混淆加密主要是為了隱藏 dex 文件中關鍵的代碼,力度從輕到重包括:靜態變量的隱藏、函數的重復定義、函數的隱藏、以及整個類的隱藏。混淆后的 dex 文件依舊可以通過 dex2jar jade 等工具的反編譯成 Java 源碼,但是里面關鍵的代碼已經看不到了。
效果圖:

源碼地址和使用說明在 github 上 hidex-hack

0x01 dex格式分析

dex 文件格式在上一篇有進行了比較詳細的介紹,具體可看dex文件格式分析,這里簡單的介紹一下整個 dex 文件的布局。

1.header(dex頭部)
header 概述了整個 dex 文件的分布情況,包括了:magic, checksum, signature, file_size, header_size, endian_tag, link, map, string_ids, type_ids, proto_ids, field_ids, method_ids, class_defs, data

checksumsignature 是校驗值,修改后需要對其進行修復

string_ids, type_ids, proto_ids, field_ids, method_ids 作為類型數組節區(我瞎起的)保存了不同類型的值

class_defs 存儲了類的定義也是我們修改的重點

data 是數據存儲區,包括所有的數據

2.類型數組節區
類型數組節區包括了string_ids, type_ids, proto_ids, field_ids, method_ids。分別表示:字符串,類型,函數簽名,屬性,函數。每個節區都保存了對應類型數據數組,可以用 010Editor 分析二進制文件數據。
屬性示例:

3.類定義
類定義是修改的重點,這里保存了所有類的結構,也是整個 dex 文件中結構最復雜的部分。其中包括了:靜態屬性變量、成員數形變量,虛函數,直接函數,靜態函數等數據。

0x02 實現功能

通過分析 dex 文件格式,現在可以實現的混淆加密主要包括四種:

靜態變量隱藏

函數重復定義

函數隱藏

類定義隱藏

四種混淆加密的實現方式都是通過修改 class_def 結構體中字段實現的。可以通過 json 格式了解一下 class_def 的結構(這里只列出來要用到的字段):

{
  "class_def": {
    "class_idx": 01
    "static_values_off": 000,       
    "class_data_off": 001,          
    "class_data": {                 
        "direct_methods_size": 001,  
        "virtual_methods_size": 002, 
        "virtual_methods":[          
            {
                "code_off": 003 
            },
            {
                "code_off": 004
            }
        ]
    }
  }
}

字段含義:

class_idx: 類名序號,值是type_ids的一個index

class_def: 類定義結構體

static_values_off: 靜態變量值偏移

class_data_off: 類定義偏移

class_data: 類定義結構體

direct_methods_size: 直接函數個數

virtual_methods_size: 虛函數個數

virtual_methods: 虛函數結構體

code_off: 函數代碼偏移

通過上面的字段介紹其實很容易得到四個功能的實現方案,下面一個一個介紹。

1.靜態變量隱藏

static_vaules_off 保存了每個類中靜態變量的值的偏移量,指向 data 區里的一個列表,格式為 encode_array_item,如果沒有此項內容,該值為0。所以要實現靜態變量賦值隱藏只需要將 static_values_off 值修改為0。
實現效果:

這里的靜態數組數據沒有成功隱藏,因為我也不知道怎么搞。?

2.函數重復定義

class_def -> class_data -> virtual_methods -> code_ff 表示的是某個類中某個函數的代碼偏移地址。這里需要提到一個概念:Java 中所有函數實現都是虛函數,這一點和 C++ 是不一樣的,所有這里修改的都是 virtual_methodscode_off

實現方式:讀取第一個函數的代碼偏移地址,將接下來的函數偏移地址都修改為第一的值。

實現效果:

3.函數隱藏

class_def -> class_data -> virtual_methods_sizeclass_def -> class_data -> direct_methods_size 記錄了類定義中函數的個數,如果沒有定義函數則該值為0。所以只要將該值改為0,函數定義就會被隱藏。

實現效果:

4.類定義隱藏

class_def -> class_data_off 保存了具體類定義的偏移地址,也就是 class_def -> class_data 的地址,如果該值為0則所有實現將被隱藏。隱藏后會把類定義的所有東西都隱藏包括成員變量,成員函數,靜態變量,靜態函數。

實現效果:

0x03 數據讀取

上面一個章節主要介紹了功能實現的原理,接下來要介紹具體實現了。要實現修改 class_def 中字段,首先要把整個 dex 文件結構解析出來,當然可以只是我們需要的字段。在工具中我定義的 dex 結構如下,因為 class_def 結構比較復雜所以獨立了一個包定義:

→ tree -L 2
.
├── DexFile.java
├── FieldIds.java
├── Header.java
├── MapList.java
├── MethodIds.java
├── ProtoIds.java
├── StringIds.java
├── TypeIds.java
└── cladef
    ├── ClassData.java
    ├── ClassDefs.java
    ├── Code.java
    ├── EncodedField.java
    ├── EncodedMethod.java
    ├── EncodedValue.java
    └── StaticValues.java

也許你可能會疑問,我們功能實現時候只需要修改 class_def 為什么還需要讀取 string_ids 這些區段。這是因為像上面提到的 class_def -> class_idx 保存的其實是 type_ids 中的序號,而 type_ids 中保存的是 string_ids 的序號。

為了靈活配置,運行工具的時候我們只需要配置好要隱藏的類名,比如需要隱藏某個類的實現 hack_me_size: cc.gnaixx.samp.core.EntranceImpl, 配置文件的具體實現下個章節介紹。

DexFile.java 定義了整個 dex 文件結構, 實現比較簡單只有一個 read(byte[] dexBuff) 函數讀取整個 dex 文件格式。

DexFile.java:

public class DexFile {
    public static final int HEADER_LEN = 0x70;

    public Header header;
    public StringIds stringIds;
    public TypeIds typeIds;
    public ProtoIds protoIds;
    public FieldIds fieldIds;
    public MethodIds methodIds;
    public ClassDefs classDefs;
    public MapList mapList;

    //reader dex
    public void read(byte[] dexBuff){
        //read header
        byte[] headerbs = subdex(dexBuff, 0, HEADER_LEN);
        header = new Header(headerbs);

        //read string_ids
        stringIds = new StringIds(dexBuff, header.stringIdsOff, header.stringIdsSize);

        //read type_ids
        typeIds = new TypeIds(dexBuff, header.typeIdsOff, header.typeIdsSize);

        //read proto_ids
        protoIds = new ProtoIds(dexBuff, header.protoIdsOff, header.protoIdsSize);

        //read field_ids
        fieldIds = new FieldIds(dexBuff, header.fieldIdsOff, header.fieldIdsSize);

        //read method_ids
        methodIds = new MethodIds(dexBuff, header.methodIdsOff, header.methodIdsSize);

        //read class_defs
        classDefs = new ClassDefs(dexBuff, header.classDefsOff, header.classDefsSize);

        //read map_list
        mapList = new MapList(dexBuff, header.mapOff);
    }
}

第一步要先讀取 header 因為它保存了其他節區的偏移地址和個數。

Header.java:

public class Header {

    public byte[]  magic           = new byte[MAGIC_LEN];
    public int     checksum;
    public byte[]  signature       = new byte[SIGNATURE_LEN];
    public int     fileSize;
    public int     headerSize;
    public int     endianTag;
    public int     linkSize;
    public int     linkOff;
    public int     mapOff;
    public int     stringIdsSize;
    public int     stringIdsOff;
    public int     typeIdsSize;
    public int     typeIdsOff;
    public int     protoIdsSize;
    public int     protoIdsOff;
    public int     fieldIdsSize;
    public int     fieldIdsOff;
    public int     methodIdsSize;
    public int     methodIdsOff;
    public int     classDefsSize;
    public int     classDefsOff;
    public int     dataSize;
    public int     dataOff;

    public Header(byte[] headerBuff) {
        Reader reader = new Reader(headerBuff, 0);
        this.magic = reader.subdex(MAGIC_LEN);
        this.checksum = reader.readUint();
        this.signature = reader.subdex(SIGNATURE_LEN);
        //......
    }

    public void write(byte[] dexBuff){
        Writer writer = new Writer(dexBuff, 0);
        writer.replace(magic, MAGIC_LEN);
        writer.writeUint(checksum);
        writer.replace(signature, SIGNATURE_LEN);
        //.....
    }
}

知道了各個節區的偏移地址和個數接下來的讀取就比較簡單了,比如 string_ids 節區的讀取。

StringIds.java:

public class StringIds {

    class StringId {
        int dataOff;            //字符串偏移位置
        Uleb128 utf16Size;      //字符串長度
        byte data[];            //字符串數據

        public StringId(int dataOff, Uleb128 uleb128, byte[] data) {
            this.dataOff = dataOff;
            this.utf16Size = uleb128;
            this.data = data;
        }
    }

    StringId stringIds[];

    public StringIds(byte[] dexBuff, int off, int size) {
        this.stringIds = new StringId[size];

        Reader reader = new Reader(dexBuff, off);
        for (int i = 0; i < size; i++) {
            int dataOff = reader.readUint();
            Uleb128 utf16Size = getUleb128(dexBuff, dataOff);
            byte[] data = subdex(dexBuff, dataOff + 1, utf16Size.getVal());
            StringId stringId = new StringId(dataOff, utf16Size, data);
            stringIds[i] = stringId;
        }
    }

    public String getData(int id) {
        //return "(" + id + ")" + new String(stringIds[id].data);
        return new String(stringIds[id].data);
    }
}

其他節區的讀取和 string_ids 類似,但是 class_def 節區結構比較復雜,讀取起來可能比較麻煩。但是其實我們要用的值并不是很多,只需要關注那幾個字段就好了。

ClassDefs.java:

public class ClassDefs {

    public class ClassDef {
        public int          classIdx;       //class類型,對應type_ids
        public int          accessFlags;    //訪問類型,enum
        public int          superclassIdx;  //supperclass類型,對應type_ids
        public int          interfacesOff;  //接口偏移,對應type_list
        public int          sourceFileIdx;  //源文件名,對應string_ids
        public int          annotationsOff; //class注解,位置位于data區,對應annotation_direcotry_item
        public HackPoint    classDataOff;   //class具體用到的數據,位于data區,格式為class_data_item,描述class的field,method,method執行代碼
        public HackPoint    staticValueOff; //位于data區,格式為encoded_array_item

        public StaticValues staticValues;  // classDataOff不為0時存在
        public ClassData    classData;     // staticValueOff不為0存在

        public ClassDef(int classIdx, int accessFlags,
                        int superclassIdx, int interfacesOff,
                        int sourceFileidx, int annotationsOff,
                        HackPoint classDataOff, HackPoint staticValueOff) {
            this.classIdx = classIdx;
            this.accessFlags = accessFlags;
            this.superclassIdx = superclassIdx;
            this.interfacesOff = interfacesOff;
            this.sourceFileIdx = sourceFileidx;
            this.annotationsOff = annotationsOff;
            this.classDataOff = classDataOff;
            this.staticValueOff = staticValueOff;
        }

        public void setClassData(ClassData classData){
            this.classData = classData;
        }

        public void setStaticValue(StaticValues staticValues){
            this.staticValues = staticValues;
        }
    }

    int      offset; //偏移位置
    int      size;   //大小

    public ClassDef classDefs[];

    public ClassDefs(byte[] dexBuff, int off, int size) {
        this.offset = off;
        this.size = size;

        Reader reader = new Reader(dexBuff, off);
        classDefs = new ClassDef[size];
        for (int i = 0; i < size; i++) {
            int classIdx = reader.readUint();
            int accessFlags = reader.readUint();
            int superclassIdx = reader.readUint();
            int interfacesOff = reader.readUint();
            int sourcFileIdx = reader.readUint();
            int annotationOff = reader.readUint();

            HackPoint classDataOff = new HackPoint(HackPoint.UINT, reader.getOff(), reader.readUint());
            HackPoint staticValueOff = new HackPoint(HackPoint.UINT, reader.getOff(), reader.readUint());

            ClassDef classDef = new ClassDef(
                    classIdx, accessFlags,
                    superclassIdx, interfacesOff,
                    sourcFileIdx, annotationOff,
                    classDataOff, staticValueOff);

            if(staticValueOff.value != 0){
                Reader reader1 = new Reader(dexBuff, staticValueOff.value);
                Uleb128 staticSize = reader1.readUleb128();
                StaticValues staticValues = new StaticValues(staticSize);
                classDef.setStaticValue(staticValues);
            }

            if(classDataOff.value != 0){
                classDef.setClassData(new ClassData(dexBuff, classDataOff.value));
            }
            classDefs[i] = classDef;
        }
    }

    public void write(byte[] dexBuff){
        Writer writer = new Writer(dexBuff, offset);
        for(int i=0; i

這里需要介紹一下 dex 特有的一種數據類型 LEB128 官方介紹如下:

LEB128 ("Little-Endian Base 128") is a variable-length encoding for arbitrary signed or unsigned integer quantities. The format was borrowed from the DWARF3 specification. In a .dex file, LEB128 is only ever used to encode 32-bit quantities.

Each LEB128 encoded value consists of one to five bytes, which together represent a single 32-bit value. Each byte has its most significant bit set except for the final byte in the sequence, which has its most significant bit clear. The remaining seven bits of each byte are payload, with the least significant seven bits of the quantity in the first byte, the next seven in the second byte and so on. In the case of a signed LEB128 (sleb128), the most significant payload bit of the final byte in the sequence is sign-extended to produce the final value. In the unsigned case (uleb128), any bits not explicitly represented are interpreted as 0.

也就是說 LEB128 是基于 1 個 Byte 的一種不定長度的編碼方式 。若第一個 Byte 的最高位為 1 ,則表示還需要下一個 Byte 來描述 ,直至最后一個 Byte 的最高 位為 0 。每個 Byte 的其余 Bit 用來表示數據。

代碼中用 ULeb128.java(unsigned 無符號) 表示是該結構,通過分析Android源碼 Leb128.h可以知道 LEB128 雖然表示的是不定長格式,但是在 Android 中只用到了4 個byte,所以只需要用int表示就可以了。

ULeb128.java:

public class Uleb128 {
    byte[] realVal; //存儲的byte數據
    int val; //表示的整型數據

    public Uleb128(byte[] realVal, int val){
        this.realVal = realVal;
        this.val = val;
    }

    public int getSize(){
        return this.realVal.length;
    }

    public int getVal(){
        return this.val;
    }

    public byte[] getRealVal(){
        return this.realVal;
    }
}

Bytes to ULEB128:

//Reader.java
public Uleb128 readUleb128() {
        int value = 0;
        int count = 0;
        byte realVal[] = new byte[4];
        boolean flag = false;
        do {
            flag = false;
            byte seg = buffer[offset];
            if ((seg & 0x80) == 0x80) { //高8位為1
                flag = true;
            }
            seg = (byte) (seg & 0x7F);
            value += seg << (7 * count);
            realVal[count] = buffer[offset];
            count++;
            offset++;
        } while (flag);
        return new Uleb128(BufferUtil.subdex(realVal, 0, count), value);
    }

Integer to ULEB128:

//Trans.java
public static Uleb128 intToUleb128(int val) {
        byte[] realVal = new byte[]{0x00, 0x00, 0x00, 0x00}; //int 最大長度為4
        int bk = val;
        int len = 0;
        for (int i = 0; i < realVal.length; i++) {
            len = i + 1; //最少長度為1
            realVal[i] = (byte) (val & 0x7F); //獲取低7位的值
            if (val > (0x7F)) {
                realVal[i] |= 0x80; //高位為1 加上去
            }
            val = val >> 7;
            if (val <= 0) break;
        }
        Uleb128 uleb128 = new Uleb128(BufferUtil.subdex(realVal, 0, len), bk);
        return uleb128;
    }
0x04 HackPoint格式

HackPoint 表示修改后的數據結構,代碼中把所有要修改的的字段都用 HackPoint 類型表示。HackPoint 類型有三個字段 type、offset、value,都是 int 類型分別表示:類型、偏移地址、原始值。類型主要有三種 uint(unsigned int)、ushort(unsigned short 2byte)、uleb128。這三種數據用 int 存儲都足夠了。
HackPoint.java:

public class HackPoint implements Cloneable {

    public static final int UINT = 0x01;
    public static final int USHORT = 0x02;
    public static final int ULEB128 = 0x03;

    public int type;        //數據類型
    public int offset;      //偏移地址
    public int value;       //原始值

    public HackPoint(int type, int offset, int val) {
        this.type = type;
        this.offset = offset;
        this.value = val;
    }

    @Override
    public HackPoint clone() {
        HackPoint hp = null;
        try {
            hp = (HackPoint) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return hp;
    }
}

在修改完后會把所有的 HackPoint 數據寫在 dex 文件的末尾。本來 dex 文件末尾是 map_list 區段,數據格式是 :

struct map_list{
    ushort type;
    ushort unused;
    uint size;
    uint offset;
};

剛好是 12 byte, 所以 HackPoint 寫入 dex 文件的格式為:

0x05 配置文件

配置文件的定義比較簡單看一下示例就知道了:

####################################################
#    hack_class:      隱藏類定義
#    hack_sf_val:     隱藏靜態變量
#    hack_me_size:    隱藏methods
#    hack_me_def:     重復函數定義(以第一個為準)
#####################################################


#隱藏靜態變量值
hack_sf_val: cc.gnaixx.samp.core.EntranceImpl

#重復函數定義(以第一個為準)
hack_me_def: cc.gnaixx.samp.core.EntranceImpl

#隱藏函數實現
hack_me_size: cc.gnaixx.samp.core.EntranceImpl

#隱藏整個類實現
hack_class: cc.gnaixx.samp.core.EntranceImpl cc.gnaixx.samp.BuildConfig

當多個類需要實現同一個功能的時候只需要用空格分隔就可以了

配置文件讀取代碼:

public static Map> readConfig(String path) {
    try {
        Map> config = new HashMap<>();
        FileReader fr = new FileReader(path);
        BufferedReader br = new BufferedReader(fr);
        String line;
        while ((line = br.readLine()) != null) {
            if (!line.startsWith("#") && !line.equals("")) {
                String conf[] = line.split(":");
                if (conf.length != 2) {
                    log("warning", "error config at :" + line);
                    System.exit(0);
                }

                String key = conf[0];
                String values[] = conf[1].split(" ");
                List valueList = new ArrayList<>();
                for (int i = 0; i < values.length; i++) {
                    if (values[i] != null && !values[i].equals("")) {
                        valueList.add(values[i]);
                    }
                }
                config.put(key, valueList);
            }
        }
        fr.close();
        br.close();
        return config;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}
0x06 dex混淆隱藏

dex 文件混淆隱藏主要包括三個步驟:

修改 HackPoint 并保存到 dex 文件末尾

修復Header

1.修改 HackPoint

通過取得的配置文件中的配置類遍歷 class_def_item

//查找配置文件所在類位置
private void seekHP(ClassDefs.ClassDef[] classDefItem, List conf, String type, SeekCallBack callBack){
    if (conf == null) {
        return;
    }
    for (int i = 0; i < conf.size(); i++) {
        String classname = conf.get(i);
        boolean isDef = false;
        for (int j = 0; j < classDefItem.length; j++) {
            String className = dexFile.typeIds.getString(dexFile, classDefItem[j].classIdx); //查找順序 class_idx => type_ids => string_ids
            className = pathToPackages(className); //獲取類名
            if (className.equals(classname)) {
                callBack.doHack(classDefItem[j], this.hackPoints); //具體操作
                log(type, conf.get(i));
                isDef = true;
            }
        }
        if (isDef == false) {
            log("warning", "con"t find class:" + classname);
        }
    }
}

//具體操作回調處理
interface SeekCallBack {
    void doHack(ClassDefs.ClassDef classDefItem, List hackPoints);
}

隱藏靜態變量值:

//隱藏靜態變量初始化
private void hackSfVal(ClassDefs.ClassDef[] classDefItem, List conf) {
    seekHP(classDefItem, conf, Constants.HACK_SF_VAL, new SeekCallBack() {
        @Override
        public void doHack(ClassDefs.ClassDef classDefItem, List hackPoints) {
            HackPoint point = classDefItem.staticValueOff.clone();  //獲取靜態變量數據偏移
            hackPoints.add(point);                          //添加修改點
            classDefItem.staticValueOff.value = 0;          //將靜態變量的偏移改為0(隱藏賦值)
        }
    });
}

函數重復定義:

//重復函數定義
private void hackMeDef(ClassDefs.ClassDef[] classDefItem, List conf){
    seekHP(classDefItem, conf, Constants.HACK_ME_DEF, new SeekCallBack() {
        @Override
        public void doHack(ClassDefs.ClassDef classDefItem, List hackPoints) {
            //以第一個為默認值
            int virtualMeSize = classDefItem.classData.virtualMethodsSize.value;
            int virtualMeCodeOff = 0;
            for (int i = 0; i < virtualMeSize; i++) {
                if (i == 0) {
                    virtualMeCodeOff = classDefItem.classData.virtualMethods[i].codeOff.value;
                }else{
                    HackPoint point = classDefItem.classData.virtualMethods[i].codeOff.clone();
                    hackPoints.add(point);
                    classDefItem.classData.virtualMethods[i].codeOff.value = virtualMeCodeOff;
                }
            }
        }
    });
}

函數隱藏:

//隱藏函數定義
private void hackMeSize(ClassDefs.ClassDef[] classDefItem, List conf){
    seekHP(classDefItem, conf, Constants.HACK_ME_SIZE, new SeekCallBack() {
        @Override
        public void doHack(ClassDefs.ClassDef classDefItem, List hackPoints) {
            HackPoint directPoint = classDefItem.classData.directMethodsSize.clone(); //同時需改虛函數和直接函數
            HackPoint virtualPoint = classDefItem.classData.virtualMethodsSize.clone();
            hackPoints.add(directPoint);
            hackPoints.add(virtualPoint);
            classDefItem.classData.directMethodsSize.value = 0;
            classDefItem.classData.virtualMethodsSize.value = 0;
        }
    });
}

隱藏類:

//隱藏靜態變量初始化
private void hackSfVal(ClassDefs.ClassDef[] classDefItem, List conf) {
    seekHP(classDefItem, conf, Constants.HACK_SF_VAL, new SeekCallBack() {
        @Override
        public void doHack(ClassDefs.ClassDef classDefItem, List hackPoints) {
            HackPoint point = classDefItem.staticValueOff.clone();  //獲取靜態變量數據偏移
            hackPoints.add(point);                          //添加修改點
            classDefItem.staticValueOff.value = 0;          //將靜態變量的偏移改為0(隱藏賦值)
        }
    });
}

添加 HackPoint 數據到 dex 文件:

 //保留修改信息
private void appendHP() {
    byte[] pointsBuff = new byte[]{};
    for (int i = 0; i < hackPoints.size(); i++) {
        byte[] pointBuff = hackpToBin(hackPoints.get(i));
        pointsBuff = BufferUtil.append(pointsBuff, pointBuff, pointBuff.length);
    }
    dexBuff = BufferUtil.append(dexBuff, pointsBuff, pointsBuff.length);
}

//hackPoint 轉 二進制
public static byte[] hackpToBin(HackPoint point) {
    ByteBuffer bb = ByteBuffer.allocate(4 * 3);
    bb.put(intToBin_Lit(point.type));
    bb.put(intToBin_Lit(point.offset));
    bb.put(intToBin_Lit(point.value));
    return bb.array();
}

//小端二進制
public static byte[] intToBin_Lit(int integer){
    byte[] bin = new byte[]{
            (byte) ((integer >> 0) & 0xFF),
            (byte) ((integer >> 8) & 0xFF),
            (byte) ((integer >> 16) & 0xFF),
            (byte) ((integer >> 24) & 0xFF)
    };
    return bin;
}

dex 文件都是以小端數據保存

2.修復Header

Header 中修復的數據有三個:

文件長度

checksum

signature

修改代碼:

//修改header
private void hackHeader() {
    //修改文件長度
    Header header = dexFile.header;
    header.fileSize = this.dexBuff.length;
    header.write(dexBuff); //需要先修改文件長度,才能計算signature checksum
    //修復 signature 校驗
    log("old_signature", binToHex(dexFile.header.signature));
    byte[] signature = signature(dexBuff, SIGNATURE_LEN + SIGNATURE_OFF);
    header.signature = signature;
    log("new_signature", binToHex(signature));
    header.write(dexBuff); //需要先寫sinature,才能計算checksum,凸
    //修復 checksum 校驗
    log("old_checksum", intToHex(dexFile.header.checksum));
    int checksum = checksum_Lit(dexBuff, CHECKSUM_LEN + CHECKSUM_OFF);
    header.checksum = checksum;
    log("new_checksum", intToHex(checksum));
    header.write(dexBuff);
}

//計算signature
public static byte[] signature(byte[] data, int off) {
    int len = data.length - off;
    byte[] signature = SHA1(data, off, len);
    return signature;
}
//sha1算法
public static byte[] SHA1(byte[] decript, int off, int len) {
    try {
        MessageDigest digest = MessageDigest.getInstance("SHA-1");
        digest.update(decript, off, len);
        byte messageDigest[] = digest.digest();
        return messageDigest;
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    return null;
}

//計算checksum 值
public static int checksum_Lit(byte[] data, int off) {
    byte[] bin = checksum_bin(data, off);
    int value = 0;
    for (int i = 0; i < UINT_LEN; i++) {
        int seg = bin[i];
        if (seg < 0) {
            seg = 256 + seg;
        }
        value += seg << (8 * i);
    }
    return value;
}
//計算checksum
public static byte[] checksum_bin(byte[] data, int off) {
    int len = data.length - off;
    Adler32 adler32 = new Adler32();
    adler32.reset();
    adler32.update(data, off, len);
    long checksum = adler32.getValue();
    byte[] checksumbs = new byte[]{
            (byte) checksum,
            (byte) (checksum >> 8),
            (byte) (checksum >> 16),
            (byte) (checksum >> 24)};
    return checksumbs;
}

該部分代碼地址: HidexHandle.java

0x07 dex還原

相對于加密解密過程簡單了很多,只要根據 HackPoint 數據一一修復就好了。這里簡單的說下修復步驟:

讀取 Header 中 map_list 的偏移地址和個數,因為 HackPoint 數據保存在 map_list 之后

讀取 HackPoint 數據并修復 dex 文件

修復 Header 中的 file_size、checksum、signature

java 實現

修復關鍵源碼:

//修復dex文件
public byte[] redex() {
    int mapOff = getUint(dexBuff, MAP_OFF_OFF); //獲取map_off
    int mapSize = getUint(dexBuff, mapOff); //獲取map_size
    int hackInfoStart = mapOff + UINT_LEN + (mapSize * MAP_ITEM_LEN); //獲取 hackinfo 開始地址
    int hackInfoLen = dexBuff.length - hackInfoStart; //獲取hackinfo 長度
    hackInfoBuff = subdex(dexBuff, hackInfoStart, hackInfoLen); //獲取hack數據
    int dexLen = dexBuff.length - hackInfoLen;
    dexBuff = subdex(dexBuff, 0, dexLen); //截取原始dex長度
    HackPoint[] hackPoints = Trans.binToHackP(hackInfoBuff);  //修復hack點
    for (int i = 0; i < hackPoints.length; i++) {
        log("hackPoint", JSON.toJSONString(hackPoints[i]));
        recovery(hackPoints[i]);
    }
    byte[] fileSize = intToBin_Lit(dexLen); //修復文件長度
    replace(dexBuff, fileSize, FILE_SIZE_OFF, UINT_LEN);
    byte[] signature = signature(dexBuff, SIGNATURE_LEN + SIGNATURE_OFF); //修復signature校驗
    replace(dexBuff, signature, SIGNATURE_OFF, SIGNATURE_LEN);
    byte[] checksum = checksum_bin(dexBuff, CHECKSUM_LEN + CHECKSUM_OFF); //修復checksum校驗
    replace(dexBuff, checksum, CHECKSUM_OFF, CHECKSUM_LEN);
    log("fileSize", dexLen);
    log("signature", binToHex(signature));
    log("checksum", binToHex_Lit(checksum));
    return this.dexBuff;
}
//還原原始值
private void recovery(HackPoint hackPoint) {
    Writer writer = new Writer(this.dexBuff, hackPoint.offset);
    if (hackPoint.type == HackPoint.USHORT) {
        writer.writeUshort(hackPoint.value);
    }
    else if (hackPoint.type == HackPoint.UINT) {
        writer.writeUint(hackPoint.value);
    }
    else if (hackPoint.type == HackPoint.ULEB128) {
        Uleb128 uleb128 = Trans.intToUleb128(hackPoint.value);
        writer.writeUleb128(uleb128);
    }
}
c++ 實現

工具本身就是為了實現安全加固,那么用 java 實現意義就小了很多,所以工具包里面的實現我是用 NDK 開發的。

修復關鍵源碼:

//解密dex
void recode(char* source, uint sourceLen, char* target, uint* targetLen){
    uint mapOff = readUint(source, MAP_OFF_OFF); //獲取map_off
    uint mapSize = readUint(source, mapOff); //獲取map_size
    LOGD("mapInfo: {map_off:%d, map_size:%d}", mapOff, mapSize);

    uint hackInfoOff = mapOff + UINT_LEN + (mapSize * MAP_ITEM_LEN); //定位hackInfo位置
    uint hackInfoLen = sourceLen - hackInfoOff; //hackInfo長度
    char* hackInfo = (char *) calloc(hackInfoLen, sizeof(char));
    memcpy(hackInfo, source + hackInfoOff, hackInfoLen); //復制hackInfo
    LOGD("hackInfo: {hackInfo_off:%d, hackInfo_len}", hackInfoOff, hackInfoLen);

    uint hackPointSize = hackInfoLen / sizeof(HackPoint); //獲取hackPoint結構體
    HackPoint* hackPoints = (HackPoint *) calloc(hackPointSize, sizeof(HackPoint));
    initHP(hackPoints, hackInfo, hackPointSize); //將hockInfo 轉化為結構體

    *targetLen = hackInfoOff;
    memcpy(target, source, *targetLen); //恢復原始長度

    //恢復數據
    for(int i=0; i

完整源碼地址: hidex.cpp

0x09 總結

整體功能還是比較簡單,實現的代碼也不是很復雜,但是這些都需要基于對 dex 文件格式的了解的前提下。
另外該工具存在一個缺點,dex 的加載問題。Android中加載 dex 的 DexClassLoad 只支持文件路徑加載,不像 java 中的 ClassLoad 可以支持二進制流加載,所以在加載 dex 是就存在加密后的 dex 緩存,這是非常危險的。所以下個研究的點也就是自定義 DexClassLoad 實現不落地加載。(很多安全加固廠商老早就實現了?)。
雖然功能不算強大,也有不少缺點,不過也花了自己不少時間研究,對 dex 文件格式也有點了解,也算值得了。

原文地址: DEX文件混淆加密

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66643.html

相關文章

  • Android 開發怎樣做代碼加密混淆

    摘要:網易資深安全工程師鐘亞平在今年的安卓巴士全球開發者論壇上做了安卓逆向與保護的演講完整演講內容請見這里一文了解安卓逆向分析與保護機制,其中就談到了關于代碼混淆的問題。就是一個混淆代碼的開源項目,能夠對字節碼進行混淆縮減體積優化等處理。 歡迎訪問網易云社區,了解更多網易技術產品運營經驗。 在大公司怎么做android代碼混淆的?發現他們的軟件用apktool反編譯居然沒看到classes....

    incredible 評論0 收藏0
  • Android防護掃盲篇

    摘要:為了防止這種現象,我們可以對字節碼進行混淆。動態鏈接庫是目標文件的集合,目標文件在動態鏈接庫中的組織方式是按照特殊方式形成的。 一、已知防護策略 1.不可或缺的混淆 Java 是一種跨平臺、解釋型語言,Java 源代碼編譯成的class文件中有大量包含語義的變量名、方法名的信息,很容易被反編譯為Java 源代碼。為了防止這種現象,我們可以對Java字節碼進行混淆。混淆不僅能將代碼中的類...

    CastlePeaK 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<