您的位置:首页 > 移动开发 > Android开发

Android 动态加载(防止逆向编译)技术

2015-06-29 09:09 417 查看
  最近工作中接到了一个研究防止逆向编译的任务.研究了几天资料,最后基本实现了防破解技术,在这个工程中,也略有一些心得体会,现整理下来分享,供大家探讨参考研究。文中如有纰漏、失实之处,请大家及时给与指正。
           防破解技术主要有四种实现方式:1.代码混淆(ProGuard)技术 2.签名比对技术 3.NDK  .so 动态库技术 4.动态加载技术.

          参考资料:http://bbs.pediy.com/showthread.php?t=137112    (感谢看雪学院 ZhWeir)

          第一种 代码混淆技术(ProGuard)  该技术主要是进行代码混淆,降低代码逆向编译后的可读性,但该技术无法防止加壳技术进行加壳(加入吸费、广告、病毒等代码),而且只要是细心的人,依然可以对代码依然可以对代码进行逆向分析,所以该技术并没有从根本解决破解问题,只是增加了破解难度。

          第二种  签名比对技术      该技术主要防止加壳技术进行加壳,但代码逆向分析风险依然存在。而且该技术并不能根本解决被加壳问题,如果破解者将签名比对代码注释掉,再编译回来,该技术就被破解了。

         第三种   NDK .so动态库技术,该技术实现是将重要核心代码全部放在C文件中,利用NDK技术,将核心代码编译成.so动态库,再用JNI进行调用。该技术虽然能将核心代码保护起来,但被加壳风险依然存在。

        第四种  动态加载技术  该技术在Java中是一个比较成熟的技术,而Android中该技术还没有被大家充分利用起来。该技术思想主要分为以下几步:

                      1.将核心代码编译成dex文件的Jar包  --> 2. 对jar包进行加密处理  3.在程序主入口利用NDK进行解密 4再利用ClassLoader将jar包进行动态加载.5.利用反射技术将ClassLoader 设置成系统的ClassLoader。

                      该技术可以有效的防止逆向分析、被破解、被加壳等问题。

                      主要优点有:

                         1.核心代码在被加密的jar中,所以破解者无法解压出class文件,如果加密秘钥被破解者拿到,那将是另外一层面的安全问题了。

                         2.该技术也可以有效防止加壳技术,代码是动态加载上来的,破解者的壳程序无法加入到已加密的jar包中,及时破解者注入壳程序入口,壳程序因为不在ClassLoader 的jar包中,所以也无法被执行起来,除非破解者替换ClassLoader的jar包,关掉NDK解密代码.但这种安装到手机上,已经不在是我们的应用,用户一定会将其卸载掉。

  所以综合起来比较,第四种动态加载技术是最安全的,但效率问题,本人并没做严格测试,粗略实验了一下,效率并没有明显降低。

           现将研究过程的技术节点分享给大家:

                     1.Jar包加密

[java]
view plaincopyprint?

// 加密解密文件//   
public static boolean enOrDecryptFile(byte[] paramArrayOfByte,  
        String sourceFilePath, String destFilePath,int mode){  
    File sourceFile = new File(sourceFilePath);  
    File destFile = new File(destFilePath);  
    CipherOutputStream cout = null;  
    FileInputStream in  = null;  
    FileOutputStream out = null;  
    if (sourceFile.exists() && sourceFile.isFile()) {  
        if (!destFile.getParentFile().exists()) {  
            destFile.getParentFile().mkdirs();  
        }  
        try {  
            destFile.createNewFile();  
            in = new FileInputStream(sourceFile);  
            out = new FileOutputStream(destFile);  
            // 获取密钥//   
            init();  
            SecretKeySpec secretKeySpec = new SecretKeySpec(defPassword, "AES");  
            Cipher cipher;  
            cipher = Cipher.getInstance("AES");  
            cipher.init(mode, secretKeySpec);  
            cout = new CipherOutputStream(out, cipher);  
            byte[] cache = new byte[CACHE_SIZE];  
            int nRead = 0;  
            while ((nRead = in.read(cache)) != -1) {  
                cout.write(cache, 0, nRead);  
                cout.flush();  
            }  
        }catch (IOException e) {  
            e.printStackTrace();  
            return false;  
        } catch (NoSuchAlgorithmException e) {  
            e.printStackTrace();  
            return false ;  
        } catch (NoSuchPaddingException e) {  
            e.printStackTrace();  
            return false ;  
        }catch (InvalidKeyException e) {  
            e.printStackTrace();  
            return false;  
        }finally{  
                if(cout != null){  
                    try {  
                        cout.close();  
                    } catch (IOException e) {  
                        e.printStackTrace();  
                    }  
                }  
                if(out != null){  
                    try {  
                        out.close();  
                    } catch (IOException e) {  
                        e.printStackTrace();  
                    }  
                }  
                if(in != null){  
                    try {  
                        in.close();  
                    } catch (IOException e) {  
                        e.printStackTrace();  
                    }  
                }  
        }  
        return true;  
    }  
    return false;  
}  

// 加密解密文件//
public static boolean enOrDecryptFile(byte[] paramArrayOfByte,
String sourceFilePath, String destFilePath,int mode){
File sourceFile = new File(sourceFilePath);
File destFile = new File(destFilePath);
CipherOutputStream cout = null;
FileInputStream in  = null;
FileOutputStream out = null;
if (sourceFile.exists() && sourceFile.isFile()) {
if (!destFile.getParentFile().exists()) {
destFile.getParentFile().mkdirs();
}
try {
destFile.createNewFile();
in = new FileInputStream(sourceFile);
out = new FileOutputStream(destFile);
// 获取密钥//
init();
SecretKeySpec secretKeySpec = new SecretKeySpec(defPassword, "AES");
Cipher cipher;
cipher = Cipher.getInstance("AES");
cipher.init(mode, secretKeySpec);
cout = new CipherOutputStream(out, cipher);
byte[] cache = new byte[CACHE_SIZE];
int nRead = 0;
while ((nRead = in.read(cache)) != -1) {
cout.write(cache, 0, nRead);
cout.flush();
}
}catch (IOException e) {
e.printStackTrace();
return false;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return false ;
} catch (NoSuchPaddingException e) {
e.printStackTrace();
return false ;
}catch (InvalidKeyException e) {
e.printStackTrace();
return false;
}finally{
if(cout != null){
try {
cout.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out != null){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(in != null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return true;
}
return false;
}

                 2.jar用SDK\platform-tools\下的dx命令进行dex格式转化:命令如下:

                     dx   --dex    --output=生成的目标文件的地址(绝对路径)     需要转化的jar文件(绝对路径)

                     例如:dx --dex --output=H:\classdex.jar     H:\mainwidget.jar

                 3.再用加密工具将生成jar文件进行加密

                 4.代码动态加载:

[java]
view plaincopyprint?

File file = new File("/data/data/" + base.getPackageName() + "/.cache/");  
        if (!file.exists()) {  
            file.mkdirs();  
        }  
        try {  
            Runtime.getRuntime().exec("chmod 755 " + file.getAbsolutePath()).waitFor();  
        } catch (InterruptedException e1) {  
            // TODO Auto-generated catch block
  
            e1.printStackTrace();  
        } catch (IOException e1) {  
            // TODO Auto-generated catch block
  
            e1.printStackTrace();  
        }  
        Util.copyJarFile(this);  
        Object currentActivityThread = RefInvoke.invokeStaticMethod(  
                "android.app.ActivityThread", "currentActivityThread",  
                new Class[] {}, new Object[] {});  
        String packageName = getPackageName();  
        HashMap mPackages = (HashMap) RefInvoke.getFieldOjbect(  
                "android.app.ActivityThread", currentActivityThread,  
                "mPackages");  
        WeakReference wr = (WeakReference) mPackages.get(packageName);  
        MyClassLoader dLoader = new MyClassLoader("/data/data/"  
                + base.getPackageName() + "/.cache/classdex.jar", "/data/data/"  
                + base.getPackageName() + "/.cache", "/data/data/"  
                + base.getPackageName() + "/.cache/", base.getClassLoader());  
        try {  
            Class<?>  class1 = dLoader.loadClass("com.example.test.TestActivity");  
            Log.i("b364","----------->class1: "+class1);  
        } catch (ClassNotFoundException e){  
            Log.i("b364","----------->class not found Exception!");  
            e.printStackTrace();  
        }  
        Log.i("b364","------>PackageInfo: "+wr.get());  
        // DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,
  
        // libPath, (ClassLoader) RefInvoke.getFieldOjbect(
  
        // "android.app.LoadedApk", wr.get(), "mClassLoader"));
  
        RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",  
                wr.get(), dLoader);  

File file = new File("/data/data/" + base.getPackageName() + "/.cache/");
if (!file.exists()) {
file.mkdirs();
}
try {
Runtime.getRuntime().exec("chmod 755 " + file.getAbsolutePath()).waitFor();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
Util.copyJarFile(this);
Object currentActivityThread = RefInvoke.invokeStaticMethod(
"android.app.ActivityThread", "currentActivityThread",
new Class[] {}, new Object[] {});
String packageName = getPackageName();
HashMap mPackages = (HashMap) RefInvoke.getFieldOjbect(
"android.app.ActivityThread", currentActivityThread,
"mPackages");
WeakReference wr = (WeakReference) mPackages.get(packageName);
MyClassLoader dLoader = new MyClassLoader("/data/data/"
+ base.getPackageName() + "/.cache/classdex.jar", "/data/data/"
+ base.getPackageName() + "/.cache", "/data/data/"
+ base.getPackageName() + "/.cache/", base.getClassLoader());
try {
Class<?>  class1 = dLoader.loadClass("com.example.test.TestActivity");
Log.i("b364","----------->class1: "+class1);
} catch (ClassNotFoundException e){
Log.i("b364","----------->class not found Exception!");
e.printStackTrace();
}
Log.i("b364","------>PackageInfo: "+wr.get());
// DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,
// libPath, (ClassLoader) RefInvoke.getFieldOjbect(
// "android.app.LoadedApk", wr.get(), "mClassLoader"));
RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",
wr.get(), dLoader);

                            Demo工程详见附件:http://download.csdn.net/detail/pang3510726681/6378479
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: