首页
社区
课程
招聘
[原创]android系统加固的思路及实现
发表于: 2018-6-28 19:15 5537

[原创]android系统加固的思路及实现

2018-6-28 19:15
5537
本人安全与逆向都是菜鸟一名,在做智能硬件的开发,前段时间发现应用组那边使用第三方付费加固方案,我就觉得奇怪了,apk是跑在我们自己开发的智能硬件上,还用得着用第三方方案吗?我个人觉得第三方加固方案基本上都是针对公开的apk进行加固的,他们的方案不一定比自己配合android系统做的加固方案好吧。于是乎,半个月前开始上网看一些破解与逆向的帖子,文章。于是就根据他们的破解逆向思路,设计了我自己的加固方案。放上来是想向各位大神求助几个问题,以及想求证一下,我这样子做是否真的对加固有帮助,是否有意义。

1.dex加固
我看文章,java的逆向,基本上都是逆向dex。既然逆这个,那就针对这点做文章吧。我是在android4.4上做开发,用的dalvik虚拟机。
apk编译时dx工具把.class文件转换成dex文件,那我就改dx工具源码就行了。实现也比较简单,修改了magic,以及判断了magic,如果是
我修改过的就对dex进行加密。
diff --git a/android/dalvik/dx/src/com/android/dx/dex/DexOptions.java b/android/dalvik/dx/src/com/android/dx/dex/DexOptions.java
old mode 100644
new mode 100755
index db0c7a2..66e00a9
--- a/android/dalvik/dx/src/com/android/dx/dex/DexOptions.java
+++ b/android/dalvik/dx/src/com/android/dx/dex/DexOptions.java
@@ -29,10 +29,44 @@ public class DexOptions {
     public boolean forceJumbo = false;
 
     /**
+     * API level to target in order to produce the most modern file
+     * format
+     */
+    public static final int API_CURRENT = 14;
+
+    /** API level to target in order to suppress extended opcode usage */
+    public static final int API_NO_EXTENDED_OPCODES = 13;
+
+    /** common prefix for all dex file "magic numbers" */
+    public static final String MAGIC_PREFIX = "dox\n";
+
+    /** common suffix for all dex file "magic numbers" */
+    public static final String MAGIC_SUFFIX = "\0";
+
+    /** dex file version number for the current format variant */
+    public static final String VERSION_CURRENT = "036";
+
+    /** dex file version number for API level 13 and earlier */
+    public static final String VERSION_FOR_API_13 = "035";
+
+    public static String apiToMagic(int targetApiLevel) {
+        String version;
+
+        if (targetApiLevel >= API_CURRENT) {
+            version = VERSION_CURRENT;
+        } else {
+            version = VERSION_FOR_API_13;
+        }
+
+        return MAGIC_PREFIX + version + MAGIC_SUFFIX;
+    }
+
+    /**
      * Gets the dex file magic number corresponding to this instance.
      */
     public String getMagic() {
-        return DexFormat.apiToMagic(targetApiLevel);
+    	return DexFormat.apiToMagic(targetApiLevel);
+//        return apiToMagic(targetApiLevel);
     }
 
     /**
diff --git a/android/dalvik/dx/src/com/android/dx/dex/file/DexFile.java b/android/dalvik/dx/src/com/android/dx/dex/file/DexFile.java
old mode 100644
new mode 100755
index 01a5e4b..83c548c
--- a/android/dalvik/dx/src/com/android/dx/dex/file/DexFile.java
+++ b/android/dalvik/dx/src/com/android/dx/dex/file/DexFile.java
@@ -36,6 +36,10 @@ import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.zip.Adler32;
 
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+import javax.crypto.spec.IvParameterSpec;
+
 /**
  * Representation of an entire {@code .dex} (Dalvik EXecutable)
  * file, which itself consists of a set of Dalvik classes.
@@ -589,6 +593,11 @@ public final class DexFile {
         calcSignature(barr);
         calcChecksum(barr);
 
+		String magic = getDexOptions().getMagic();
+		if(magic.startsWith("dox")) {
+			encryptDex(barr);
+		}
+
         if (annotate) {
             wordData.writeIndexAnnotation(out, ItemType.TYPE_CODE_ITEM,
                     "\nmethod code index:\n\n");
@@ -660,4 +669,21 @@ public final class DexFile {
         bytes[10] = (byte) (sum >> 16);
         bytes[11] = (byte) (sum >> 24);
     }
+
+    private static void encryptDex(byte[] bytes) {
+    	try {
+			byte[] tmp = new byte[bytes.length];
+			System.arraycopy(bytes, 0, tmp, 0, bytes.length);
+			SecretKeySpec keySpec = new SecretKeySpec("myencryptokeyAES".getBytes(), "AES");
+			IvParameterSpec ivspec = new IvParameterSpec("AESmyencryptokey".getBytes());
+			Cipher cipher = Cipher.getInstance("AES/CBC/NOPADDING");
+			int Len = (bytes.length-0x70)/16;
+			int iLen = Len*16;
+			cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivspec);
+			cipher.doFinal(tmp, bytes.length-iLen, iLen, bytes, bytes.length-iLen);
+    	} catch (Exception ex) {
+			throw new RuntimeException(ex);
+    	}
+    }
+
 }

2.dalvik解密dex
既然dex被加密了,那么dalvik就需要做解密操作,解密也比较简单,判断一下magic,发现是加密过的就进行解密。
diff --git a/android/dalvik/libdex/DexSwapVerify.cpp b/android/dalvik/libdex/DexSwapVerify.cpp
old mode 100644
new mode 100755
index ff47ab5..aac9f3b
--- a/android/dalvik/libdex/DexSwapVerify.cpp
+++ b/android/dalvik/libdex/DexSwapVerify.cpp
@@ -31,6 +31,9 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <openssl/aes.h>
+#include <openssl/pkcs7.h>
+
 #ifndef __BYTE_ORDER
 # error "byte ordering not defined"
 #endif
@@ -2799,7 +2802,7 @@ bool dexHasValidMagic(const DexHeader* pHeader)
     const u1* magic = pHeader->magic;
     const u1* version = &magic[4];
 
-    if (memcmp(magic, DEX_MAGIC, 4) != 0) {
+    if (memcmp(magic, DEX_MAGIC, 4) != 0 && memcmp(magic, DEX_MY_MAGIC, 4) != 0) {
         ALOGE("ERROR: unrecognized magic number (%02x %02x %02x %02x)",
             magic[0], magic[1], magic[2], magic[3]);
         return false;
@@ -2819,6 +2822,26 @@ bool dexHasValidMagic(const DexHeader* pHeader)
     return true;
 }
 
+void decryptDex(u1* addr, int len)
+{
+    ALOGE("decryptDex");
+    AES_KEY aes;
+    unsigned char ivec[17] = "AESmyencryptokey";
+    unsigned char key[17] = "myencryptokeyAES";
+    AES_set_decrypt_key(key, 128, &aes);
+
+    int Len = (len-0x70)/16;
+    int iLen = Len*16;
+    unsigned char* tmp = new unsigned char[iLen];
+//    ALOGE("decrypt, byte[0x70]=%x byte[0x71]=%x byte[0x72]=%x byte[0x73]=%x byte[0x74]=%x byte[75]=%x byte[76]=%x byte[77]=%x",
+//        addr[0x70], addr[0x71], addr[0x72], addr[0x73], addr[0x74], addr[0x75], addr[0x76], addr[0x77]);
+    AES_cbc_encrypt((const unsigned char*)(addr+len-iLen), tmp, iLen, &aes, ivec, AES_DECRYPT);
+//    ALOGE("tmp, byte[0x70]=%x byte[0x71]=%x byte[0x72]=%x byte[0x73]=%x byte[0x74]=%x byte[75]=%x byte[76]=%x byte[77]=%x",
+//        tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7]);
+    memcpy(addr+len-iLen, tmp, iLen);
+    delete tmp;
+}
+
 /*
  * Fix the byte ordering of all fields in the DEX file, and do
  * structural verification. This is only required for code that opens
@@ -2846,6 +2869,12 @@ int dexSwapAndVerify(u1* addr, int len)
     }
 
     if (okay) {
+        if(memcmp(pHeader->magic, DEX_MY_MAGIC, 4) == 0) {
+            decryptDex(addr, len);
+        }
+    }
+
+    if (okay) {
         int expectedLen = (int) SWAP4(pHeader->fileSize);
         if (len < expectedLen) {
             ALOGE("ERROR: Bad length: expected %d, got %d", expectedLen, len);
@@ -2959,7 +2988,7 @@ int dexSwapAndVerifyIfNecessary(u1* addr, int len)
         return 0;
     }
 
-    if (memcmp(addr, DEX_MAGIC, 4) == 0) {
+    if (memcmp(addr, DEX_MAGIC, 4) == 0 || memcmp(addr, DEX_MY_MAGIC, 4) == 0) {
         // It is an unoptimized dex file.
         return dexSwapAndVerify(addr, len);
     }

3.so加固
目前只做了手动加固so,首先加密PT_LOAD segments, 然后加密sections header,从网上的文章看,dalvik并不需要setcions header,所以不用解密这块。
最后加密elf header
void encryptContent(unsigned char* addr, int len) {
    if(len < 16) {
        ALOGE("enctyptContent len=%d is too low", len);
        return;
    }
    ALOGE("enctyptContent");
    AES_KEY aes;
    unsigned char ivec[17] = "AESmyencryptokey";
    unsigned char key[17] = "myencryptokeyAES";
    AES_set_encrypt_key(key, 128, &aes);

    int Len = len/16;
    int iLen = Len*16;
    unsigned char* tmp = new unsigned char[iLen];
    ALOGE("encrypt, byte[0x0]=%x byte[0x1]=%x byte[0x2]=%x byte[0x3]=%x byte[0x4]=%x byte[5]=%x byte[6]=%x byte[7]=%x",
        addr[0x0], addr[0x1], addr[0x2], addr[0x3], addr[0x4], addr[0x5], addr[0x6], addr[0x7]);
    AES_cbc_encrypt(addr, tmp, iLen, &aes, ivec, AES_ENCRYPT);
    ALOGE("dst, byte[0x0]=%x byte[0x1]=%x byte[0x2]=%x byte[0x3]=%x byte[0x4]=%x byte[5]=%x byte[6]=%x byte[7]=%x",
        tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7]);
    memcpy(addr, tmp, iLen);
    delete tmp;
}

int main(int argc, char *argv[]) {
    ALOGE("argv[1]=%s", argv[1]);
    name_ = argv[1];
    int len = strlen(name_);
    int offset = 0;
    int i;
    unsigned char* content;
    if (strcasecmp(name_ + len -2, "so") == 0) {
        fd_ = open(name_, O_RDWR);
        if(fd_ < 0) {
            goto _error;
        }
        if(read(fd_, &header_, sizeof(header_)) != sizeof(header_)) {
            ALOGE("Read ELF header error");
            goto _error;
        }
        if(header_.e_ident[EI_MAG0] != ELFMAG0) {
            ALOGE("ELF header error mag0:%x", header_.e_ident[EI_MAG0]);
            goto _error;
        }

        ALOGE("encrypt sections header");
        shdr_num_ = header_.e_shnum;
        unsigned char sections_head[shdr_num_*sizeof(Elf32_Shdr)];
        lseek(fd_, header_.e_shoff, SEEK_SET);
        if(read(fd_, sections_head, shdr_num_*sizeof(Elf32_Shdr)) != shdr_num_*sizeof(Elf32_Shdr)) {
            ALOGE("Read sections header error");
            goto _error;
        }
        encryptContent(sections_head, shdr_num_*sizeof(Elf32_Shdr));
        lseek(fd_, header_.e_shoff, SEEK_SET);
        write(fd_, sections_head, shdr_num_*sizeof(Elf32_Shdr));

        ALOGE("encrypt PT_LOAD Segments");
        phdr_num_ = header_.e_phnum;
        lseek(fd_, header_.e_phoff, SEEK_SET);
        for(i = phdr_num_-1; i >= 0; i--) {
            lseek(fd_, header_.e_phoff+i*sizeof(Elf32_Phdr), SEEK_SET);
            if(read(fd_, &phdr, sizeof(Elf32_Phdr)) != sizeof(Elf32_Phdr)) {
                ALOGE("Read phdr header error");
                goto _error;
            }
            ALOGE("phdr.p_type 0x%x, phdr.p_offset 0x%x, phdr.p_filesz 0x%x", phdr.p_type, phdr.p_offset, phdr.p_filesz);
            if(phdr.p_type == PT_LOAD && phdr.p_filesz >= (16+sizeof(header_))) {
                offset = phdr.p_offset;
                len = phdr.p_filesz;
                if(phdr.p_offset < (sizeof(header_)+phdr_num_*sizeof(Elf32_Phdr))) {
                    offset =  sizeof(header_)+phdr_num_*sizeof(Elf32_Phdr);
                    len -= sizeof(header_)+phdr_num_*sizeof(Elf32_Phdr)-phdr.p_offset;
                }
                lseek(fd_, offset, SEEK_SET);
                content = (unsigned char*) malloc(len);
                if(read(fd_, content, len) != len) {
                    ALOGE("Read phdr content error");
                    goto _error;
                }
                encryptContent(content, len);
                lseek(fd_, offset, SEEK_SET);
                write(fd_, content, len);
                free(content);
            }
        }

        ALOGE("encrypt elf header");
        encryptContent((unsigned char*)(&header_)+4, sizeof(header_)-4);
        header_.e_ident[EI_MAG0] = 0xf7;

        lseek(fd_, 0, SEEK_SET);
        write(fd_, &header_, sizeof(header_));
    }

_error:
    if (fd_ >= 0)
        close(fd_);
    return 0;
}

4.linker解密so
我们知道so都靠Linker来加载,那就修改linker就行了。按照加密的思路,反向操作就好了。
diff --git a/android/bionic/linker/linker_phdr.cpp b/android/bionic/linker/linker_phdr.cpp
index a591584..f51ae63 100755
--- a/android/bionic/linker/linker_phdr.cpp
+++ b/android/bionic/linker/linker_phdr.cpp
@@ -34,6 +34,9 @@
 #include "linker.h"
 #include "linker_debug.h"
 
+#include <openssl/aes.h>
+#include <openssl/pkcs7.h>
+
 /**
   TECHNICAL NOTE ON ELF LOADING.
 
@@ -140,6 +143,28 @@ bool ElfReader::Load() {
          FindPhdr();
 }
 
+void decryptContent(unsigned char* addr, int len) {
+    if(len < 16) {
+        DL_ERR("enctyptContent len=%d is too low", len);
+        return;
+    }
+    DL_ERR("decryptContent");
+    AES_KEY aes;
+    unsigned char ivec[17] = "AESmyencryptokey";
+    unsigned char key[17] = "myencryptokeyAES";
+    AES_set_decrypt_key(key, 128, &aes);
+
+    int Len = len/16;
+    int iLen = Len*16;
+    unsigned char tmp[iLen];
+    DL_ERR("decrypt, byte[0x0]=%x byte[0x1]=%x byte[0x2]=%x byte[0x3]=%x byte[0x4]=%x byte[5]=%x byte[6]=%x byte[7]=%x",
+        addr[0x0], addr[0x1], addr[0x2], addr[0x3], addr[0x4], addr[0x5], addr[0x6], addr[0x7]);
+    AES_cbc_encrypt(addr, tmp, iLen, &aes, ivec, AES_DECRYPT);
+    DL_ERR("dst, byte[0x0]=%x byte[0x1]=%x byte[0x2]=%x byte[0x3]=%x byte[0x4]=%x byte[5]=%x byte[6]=%x byte[7]=%x",
+        tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7]);
+    memcpy(addr, tmp, iLen);
+}
+
 bool ElfReader::ReadElfHeader() {
   ssize_t rc = TEMP_FAILURE_RETRY(read(fd_, &header_, sizeof(header_)));
   if (rc < 0) {
@@ -150,11 +175,14 @@ bool ElfReader::ReadElfHeader() {
     DL_ERR("\"%s\" is too small to be an ELF executable", name_);
     return false;
   }
+  if(header_.e_ident[EI_MAG0] == MY_ELFMAG0) {
+    decryptContent((unsigned char*)(&header_)+4, sizeof(header_)-4);
+  }
   return true;
 }
 
 bool ElfReader::VerifyElfHeader() {
-  if (header_.e_ident[EI_MAG0] != ELFMAG0 ||
+  if ((header_.e_ident[EI_MAG0] != ELFMAG0 && header_.e_ident[EI_MAG0] != MY_ELFMAG0) ||
       header_.e_ident[EI_MAG1] != ELFMAG1 ||
       header_.e_ident[EI_MAG2] != ELFMAG2 ||
       header_.e_ident[EI_MAG3] != ELFMAG3) {
@@ -376,6 +404,20 @@ bool ElfReader::LoadSegments() {
         DL_ERR("couldn't map \"%s\" segment %d: %s", name_, i, strerror(errno));
         return false;
       }
+      if(header_.e_ident[EI_MAG0] == MY_ELFMAG0) {
+        mprotect((void*)seg_page_start,seg_page_end - seg_page_start, PROT_READ | PROT_EXEC | PROT_WRITE);
+        Elf32_Addr seg_page_offset = PAGE_OFFSET(seg_start);
+        int offset = 0;
+        int len = phdr->p_filesz;
+        if(phdr->p_offset < (sizeof(header_)+phdr_num_*sizeof(Elf32_Phdr))) {
+            offset =  sizeof(header_)+phdr_num_*sizeof(Elf32_Phdr);
+            len -= (sizeof(header_)+phdr_num_*sizeof(Elf32_Phdr)-phdr->p_offset);
+        }
+        DL_ERR("offset:0x%x, len:0x%x", offset, len);
+        unsigned char * content = (unsigned char*)seg_addr+seg_page_offset;
+        decryptContent(content+offset, len);
+        mprotect((void*)seg_page_start,seg_page_end - seg_page_start, PFLAGS_TO_PROT(phdr->p_flags));
+      }
     }
 
     // if the segment is writable, and does not end on a page boundary,

5.一些疑问,希望大神能指导一下
1)我so只加固了这些,可靠吗?没有加固别的,主要是因为我并没有看到linker有加载其他的,比如Segments,我只看到有加载PT_LOAD类型的,其他没看到。
也没有看到linker有加载sections段,不知道这些linker是如何处理的?另外从腾讯那边写的文章https://segmentfault.com/a/1190000007276536看,也是只加密PT_LOAD段。我目前多加密了两个header,我估计像lda那些工具都无法直接对so进行一些分析了吧。
2)从网上对Linker的分析看,linker会对自己做自枚举。那我能够对linker本身做加密吗。因为我感觉我这样做之后,就很难静态逆向了。目前要想静态逆向我的dex,必需先逆libdvm.so,而逆.so又要必须先逆linker吧,所以理论上dex要比so更加安全了,当然上述都是从静态逆向的角度看。动态调试还没有了解过,不是很清楚。希望有大神可以指导一下动态调试加固的方向。
3)我这样子做究竟有意义吗,是否能增加安全啊?

[课程]FART 脱壳王!加量不加价!FART作者讲授!

收藏
免费 1
支持
分享
最新回复 (6)
雪    币: 59
活跃值: (680)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
你需要修改dalvik。。这加固有啥意义。。
2018-6-29 10:31
0
雪    币: 268
活跃值: (610)
能力值: ( LV3,RANK:35 )
在线值:
发帖
回帖
粉丝
3
看攻击者能不能在你的智能硬件上调试了,如果直接能在你的智能硬件上调试,感觉就没多大意义了。
2018-6-29 17:06
0
雪    币: 201
活跃值: (26)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
lfyyy 看攻击者能不能在你的智能硬件上调试了,如果直接能在你的智能硬件上调试,感觉就没多大意义了。
这就说明意义就是把攻击者限制在只能在我的设备上做调试了。比起不加固,或者第三方加固,把静态分析和借助其他机器动态调试的路堵住了。我这几天看了一下动态调试的技术,基本上都是要利用ptrace的,而原生android系统本身只有在native crash的时候才会用到ptrace去拿crash现场信息。ptrace本身是要内核支持的,我只要在内核关闭掉ptrace,现在流行的动态调试方法应该就不能用了。
2018-7-2 09:18
0
雪    币: 3712
活跃值: (1386)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
5
第三方加固基本也是通过了解修改Android系统而做到通用,如果像你这种把更多的难度限制在你自己的硬件上的话,应该方法很多很多,因为不需要通用,只需要适配自己的硬件设备。但是应该会对你后期的维护有影响。
2018-7-6 19:54
0
雪    币: 7
活跃值: (223)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
区别就是:厂商做的是一个APK兼容了N多个设备,你做的只是一个设备
2018-7-6 20:41
0
游客
登录 | 注册 方可回帖
返回
//