成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

DEX文件混淆加密

antz / 1848人閱讀

摘要:原文地址文件混淆加密前言混淆加密主要是為了隱藏文件中關(guān)鍵的代碼,力度從輕到重包括靜態(tài)變量的隱藏函數(shù)的重復(fù)定義函數(shù)的隱藏以及整個(gè)類的隱藏。

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

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

0x00 前言

混淆加密主要是為了隱藏 dex 文件中關(guān)鍵的代碼,力度從輕到重包括:靜態(tài)變量的隱藏、函數(shù)的重復(fù)定義、函數(shù)的隱藏、以及整個(gè)類的隱藏?;煜蟮?dex 文件依舊可以通過 dex2jar jade 等工具的反編譯成 Java 源碼,但是里面關(guān)鍵的代碼已經(jīng)看不到了。
效果圖:

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

0x01 dex格式分析

dex 文件格式在上一篇有進(jìn)行了比較詳細(xì)的介紹,具體可看dex文件格式分析,這里簡(jiǎn)單的介紹一下整個(gè) dex 文件的布局。

1.header(dex頭部)
header 概述了整個(gè) 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 是校驗(yàn)值,修改后需要對(duì)其進(jìn)行修復(fù)

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

class_defs 存儲(chǔ)了類的定義也是我們修改的重點(diǎn)

data 是數(shù)據(jù)存儲(chǔ)區(qū),包括所有的數(shù)據(jù)

2.類型數(shù)組節(jié)區(qū)
類型數(shù)組節(jié)區(qū)包括了string_ids, type_ids, proto_ids, field_ids, method_ids。分別表示:字符串,類型,函數(shù)簽名,屬性,函數(shù)。每個(gè)節(jié)區(qū)都保存了對(duì)應(yīng)類型數(shù)據(jù)數(shù)組,可以用 010Editor 分析二進(jìn)制文件數(shù)據(jù)。
屬性示例:

3.類定義
類定義是修改的重點(diǎn),這里保存了所有類的結(jié)構(gòu),也是整個(gè) dex 文件中結(jié)構(gòu)最復(fù)雜的部分。其中包括了:靜態(tài)屬性變量、成員數(shù)形變量,虛函數(shù),直接函數(shù),靜態(tài)函數(shù)等數(shù)據(jù)。

0x02 實(shí)現(xiàn)功能

通過分析 dex 文件格式,現(xiàn)在可以實(shí)現(xiàn)的混淆加密主要包括四種:

靜態(tài)變量隱藏

函數(shù)重復(fù)定義

函數(shù)隱藏

類定義隱藏

四種混淆加密的實(shí)現(xiàn)方式都是通過修改 class_def 結(jié)構(gòu)體中字段實(shí)現(xiàn)的??梢酝ㄟ^ json 格式了解一下 class_def 的結(jié)構(gòu)(這里只列出來要用到的字段):

{
  "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: 類名序號(hào),值是type_ids的一個(gè)index

class_def: 類定義結(jié)構(gòu)體

static_values_off: 靜態(tài)變量值偏移

class_data_off: 類定義偏移

class_data: 類定義結(jié)構(gòu)體

direct_methods_size: 直接函數(shù)個(gè)數(shù)

virtual_methods_size: 虛函數(shù)個(gè)數(shù)

virtual_methods: 虛函數(shù)結(jié)構(gòu)體

code_off: 函數(shù)代碼偏移

通過上面的字段介紹其實(shí)很容易得到四個(gè)功能的實(shí)現(xiàn)方案,下面一個(gè)一個(gè)介紹。

1.靜態(tài)變量隱藏

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

這里的靜態(tài)數(shù)組數(shù)據(jù)沒有成功隱藏,因?yàn)槲乙膊恢涝趺锤恪?

2.函數(shù)重復(fù)定義

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

實(shí)現(xiàn)方式:讀取第一個(gè)函數(shù)的代碼偏移地址,將接下來的函數(shù)偏移地址都修改為第一的值。

實(shí)現(xiàn)效果:

3.函數(shù)隱藏

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

實(shí)現(xiàn)效果:

4.類定義隱藏

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

實(shí)現(xiàn)效果:

0x03 數(shù)據(jù)讀取

上面一個(gè)章節(jié)主要介紹了功能實(shí)現(xiàn)的原理,接下來要介紹具體實(shí)現(xiàn)了。要實(shí)現(xiàn)修改 class_def 中字段,首先要把整個(gè) dex 文件結(jié)構(gòu)解析出來,當(dāng)然可以只是我們需要的字段。在工具中我定義的 dex 結(jié)構(gòu)如下,因?yàn)?class_def 結(jié)構(gòu)比較復(fù)雜所以獨(dú)立了一個(gè)包定義:

→ 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

也許你可能會(huì)疑問,我們功能實(shí)現(xiàn)時(shí)候只需要修改 class_def 為什么還需要讀取 string_ids 這些區(qū)段。這是因?yàn)橄裆厦嫣岬降?class_def -> class_idx 保存的其實(shí)是 type_ids 中的序號(hào),而 type_ids 中保存的是 string_ids 的序號(hào)。

為了靈活配置,運(yùn)行工具的時(shí)候我們只需要配置好要隱藏的類名,比如需要隱藏某個(gè)類的實(shí)現(xiàn) hack_me_size: cc.gnaixx.samp.core.EntranceImpl, 配置文件的具體實(shí)現(xiàn)下個(gè)章節(jié)介紹。

DexFile.java 定義了整個(gè) dex 文件結(jié)構(gòu), 實(shí)現(xiàn)比較簡(jiǎn)單只有一個(gè) read(byte[] dexBuff) 函數(shù)讀取整個(gè) 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 因?yàn)樗4媪似渌?jié)區(qū)的偏移地址和個(gè)數(shù)。

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

知道了各個(gè)節(jié)區(qū)的偏移地址和個(gè)數(shù)接下來的讀取就比較簡(jiǎn)單了,比如 string_ids 節(jié)區(qū)的讀取。

StringIds.java:

public class StringIds {

    class StringId {
        int dataOff;            //字符串偏移位置
        Uleb128 utf16Size;      //字符串長(zhǎng)度
        byte data[];            //字符串?dāng)?shù)據(jù)

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

其他節(jié)區(qū)的讀取和 string_ids 類似,但是 class_def 節(jié)區(qū)結(jié)構(gòu)比較復(fù)雜,讀取起來可能比較麻煩。但是其實(shí)我們要用的值并不是很多,只需要關(guān)注那幾個(gè)字段就好了。

ClassDefs.java:

public class ClassDefs {

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

        public StaticValues staticValues;  // classDataOff不為0時(shí)存在
        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 特有的一種數(shù)據(jù)類型 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 個(gè) Byte 的一種不定長(zhǎng)度的編碼方式 。若第一個(gè) Byte 的最高位為 1 ,則表示還需要下一個(gè) Byte 來描述 ,直至最后一個(gè) Byte 的最高 位為 0 。每個(gè) Byte 的其余 Bit 用來表示數(shù)據(jù)。

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

ULeb128.java:

public class Uleb128 {
    byte[] realVal; //存儲(chǔ)的byte數(shù)據(jù)
    int val; //表示的整型數(shù)據(jù)

    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 最大長(zhǎng)度為4
        int bk = val;
        int len = 0;
        for (int i = 0; i < realVal.length; i++) {
            len = i + 1; //最少長(zhǎng)度為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 表示修改后的數(shù)據(jù)結(jié)構(gòu),代碼中把所有要修改的的字段都用 HackPoint 類型表示。HackPoint 類型有三個(gè)字段 type、offset、value,都是 int 類型分別表示:類型、偏移地址、原始值。類型主要有三種 uint(unsigned int)、ushort(unsigned short 2byte)、uleb128。這三種數(shù)據(jù)用 int 存儲(chǔ)都足夠了。
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;        //數(shù)據(jù)類型
    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;
    }
}

在修改完后會(huì)把所有的 HackPoint 數(shù)據(jù)寫在 dex 文件的末尾。本來 dex 文件末尾是 map_list 區(qū)段,數(shù)據(jù)格式是 :

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

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

0x05 配置文件

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

####################################################
#    hack_class:      隱藏類定義
#    hack_sf_val:     隱藏靜態(tài)變量
#    hack_me_size:    隱藏methods
#    hack_me_def:     重復(fù)函數(shù)定義(以第一個(gè)為準(zhǔn))
#####################################################


#隱藏靜態(tài)變量值
hack_sf_val: cc.gnaixx.samp.core.EntranceImpl

#重復(fù)函數(shù)定義(以第一個(gè)為準(zhǔn))
hack_me_def: cc.gnaixx.samp.core.EntranceImpl

#隱藏函數(shù)實(shí)現(xiàn)
hack_me_size: cc.gnaixx.samp.core.EntranceImpl

#隱藏整個(gè)類實(shí)現(xiàn)
hack_class: cc.gnaixx.samp.core.EntranceImpl cc.gnaixx.samp.BuildConfig

當(dāng)多個(gè)類需要實(shí)現(xiàn)同一個(gè)功能的時(shí)候只需要用空格分隔就可以了

配置文件讀取代碼:

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 文件混淆隱藏主要包括三個(gè)步驟:

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

修復(fù)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);
        }
    }
}

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

隱藏靜態(tài)變量值:

//隱藏靜態(tài)變量初始化
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();  //獲取靜態(tài)變量數(shù)據(jù)偏移
            hackPoints.add(point);                          //添加修改點(diǎn)
            classDefItem.staticValueOff.value = 0;          //將靜態(tài)變量的偏移改為0(隱藏賦值)
        }
    });
}

函數(shù)重復(fù)定義:

//重復(fù)函數(shù)定義
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) {
            //以第一個(gè)為默認(rèn)值
            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;
                }
            }
        }
    });
}

函數(shù)隱藏:

//隱藏函數(shù)定義
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(); //同時(shí)需改虛函數(shù)和直接函數(shù)
            HackPoint virtualPoint = classDefItem.classData.virtualMethodsSize.clone();
            hackPoints.add(directPoint);
            hackPoints.add(virtualPoint);
            classDefItem.classData.directMethodsSize.value = 0;
            classDefItem.classData.virtualMethodsSize.value = 0;
        }
    });
}

隱藏類:

//隱藏靜態(tài)變量初始化
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();  //獲取靜態(tài)變量數(shù)據(jù)偏移
            hackPoints.add(point);                          //添加修改點(diǎn)
            classDefItem.staticValueOff.value = 0;          //將靜態(tài)變量的偏移改為0(隱藏賦值)
        }
    });
}

添加 HackPoint 數(shù)據(jù)到 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 轉(zhuǎn) 二進(jìn)制
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();
}

//小端二進(jìn)制
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 文件都是以小端數(shù)據(jù)保存

2.修復(fù)Header

Header 中修復(fù)的數(shù)據(jù)有三個(gè):

文件長(zhǎng)度

checksum

signature

修改代碼:

//修改header
private void hackHeader() {
    //修改文件長(zhǎng)度
    Header header = dexFile.header;
    header.fileSize = this.dexBuff.length;
    header.write(dexBuff); //需要先修改文件長(zhǎng)度,才能計(jì)算signature checksum
    //修復(fù) signature 校驗(yàn)
    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,才能計(jì)算checksum,凸
    //修復(fù) checksum 校驗(yàn)
    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);
}

//計(jì)算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;
}

//計(jì)算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;
}
//計(jì)算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還原

相對(duì)于加密解密過程簡(jiǎn)單了很多,只要根據(jù) HackPoint 數(shù)據(jù)一一修復(fù)就好了。這里簡(jiǎn)單的說下修復(fù)步驟:

讀取 Header 中 map_list 的偏移地址和個(gè)數(shù),因?yàn)?HackPoint 數(shù)據(jù)保存在 map_list 之后

讀取 HackPoint 數(shù)據(jù)并修復(fù) dex 文件

修復(fù) Header 中的 file_size、checksum、signature

java 實(shí)現(xiàn)

修復(fù)關(guān)鍵源碼:

//修復(fù)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 長(zhǎng)度
    hackInfoBuff = subdex(dexBuff, hackInfoStart, hackInfoLen); //獲取hack數(shù)據(jù)
    int dexLen = dexBuff.length - hackInfoLen;
    dexBuff = subdex(dexBuff, 0, dexLen); //截取原始dex長(zhǎng)度
    HackPoint[] hackPoints = Trans.binToHackP(hackInfoBuff);  //修復(fù)hack點(diǎn)
    for (int i = 0; i < hackPoints.length; i++) {
        log("hackPoint", JSON.toJSONString(hackPoints[i]));
        recovery(hackPoints[i]);
    }
    byte[] fileSize = intToBin_Lit(dexLen); //修復(fù)文件長(zhǎng)度
    replace(dexBuff, fileSize, FILE_SIZE_OFF, UINT_LEN);
    byte[] signature = signature(dexBuff, SIGNATURE_LEN + SIGNATURE_OFF); //修復(fù)signature校驗(yàn)
    replace(dexBuff, signature, SIGNATURE_OFF, SIGNATURE_LEN);
    byte[] checksum = checksum_bin(dexBuff, CHECKSUM_LEN + CHECKSUM_OFF); //修復(fù)checksum校驗(yàn)
    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++ 實(shí)現(xiàn)

工具本身就是為了實(shí)現(xiàn)安全加固,那么用 java 實(shí)現(xiàn)意義就小了很多,所以工具包里面的實(shí)現(xiàn)我是用 NDK 開發(fā)的。

修復(fù)關(guān)鍵源碼:

//解密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長(zhǎng)度
    char* hackInfo = (char *) calloc(hackInfoLen, sizeof(char));
    memcpy(hackInfo, source + hackInfoOff, hackInfoLen); //復(fù)制hackInfo
    LOGD("hackInfo: {hackInfo_off:%d, hackInfo_len}", hackInfoOff, hackInfoLen);

    uint hackPointSize = hackInfoLen / sizeof(HackPoint); //獲取hackPoint結(jié)構(gòu)體
    HackPoint* hackPoints = (HackPoint *) calloc(hackPointSize, sizeof(HackPoint));
    initHP(hackPoints, hackInfo, hackPointSize); //將hockInfo 轉(zhuǎn)化為結(jié)構(gòu)體

    *targetLen = hackInfoOff;
    memcpy(target, source, *targetLen); //恢復(fù)原始長(zhǎng)度

    //恢復(fù)數(shù)據(jù)
    for(int i=0; i

完整源碼地址: hidex.cpp

0x09 總結(jié)

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

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

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/66643.html

相關(guān)文章

  • Android 開發(fā)怎樣做代碼加密混淆?

    摘要:網(wǎng)易資深安全工程師鐘亞平在今年的安卓巴士全球開發(fā)者論壇上做了安卓逆向與保護(hù)的演講完整演講內(nèi)容請(qǐng)見這里一文了解安卓逆向分析與保護(hù)機(jī)制,其中就談到了關(guān)于代碼混淆的問題。就是一個(gè)混淆代碼的開源項(xiàng)目,能夠?qū)ψ止?jié)碼進(jìn)行混淆縮減體積優(yōu)化等處理。 歡迎訪問網(wǎng)易云社區(qū),了解更多網(wǎng)易技術(shù)產(chǎn)品運(yùn)營(yíng)經(jīng)驗(yàn)。 在大公司怎么做android代碼混淆的?發(fā)現(xiàn)他們的軟件用apktool反編譯居然沒看到classes....

    incredible 評(píng)論0 收藏0
  • Android防護(hù)掃盲篇

    摘要:為了防止這種現(xiàn)象,我們可以對(duì)字節(jié)碼進(jìn)行混淆。動(dòng)態(tài)鏈接庫是目標(biāo)文件的集合,目標(biāo)文件在動(dòng)態(tài)鏈接庫中的組織方式是按照特殊方式形成的。 一、已知防護(hù)策略 1.不可或缺的混淆 Java 是一種跨平臺(tái)、解釋型語言,Java 源代碼編譯成的class文件中有大量包含語義的變量名、方法名的信息,很容易被反編譯為Java 源代碼。為了防止這種現(xiàn)象,我們可以對(duì)Java字節(jié)碼進(jìn)行混淆?;煜粌H能將代碼中的類...

    CastlePeaK 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<