• android加固系列—6.仿爱加密等第三方加固平台之动态加载dex防止apk被反编译


    【版权所有,转载请注明出处。出处:http://www.cnblogs.com/joey-hua/p/5402599.html 】

    此方案的目的是隐藏源码防止直接性的反编译查看源码,原理是加密编译好的最终源码文件(dex),然后在一个新项目中用新项目的application启动来解密原项目代码并加载到内存中,然后再把当前进程替换为解密后的代码,art模式下也没问题。好了,废话不多说,来看代码,下面是最终想运行的项目,也称为原项目:

    这是原项目的目录,项目名叫Hello,就两个activity,第一个activity的布局如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >
        
    <TextView 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="我是Hello项目的FirstActivity"
        android:textSize="20sp"
        />
    <Button
        android:id="@+id/btn"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="跳转"
        />
    </LinearLayout>
    

    非常简单,没什么好说的,运行效果如下:

    接下来介绍dex加密工具,名字叫DexJiami,关键代码如下:

    	private static final String root = "C:";
    	private static final String path = root + "/Users/lenovo/Documents/mygithub/android-protection/LoadDex/dexFileTmp";
    	private static final String path_encrypt_source = path + "/mycode.jar";
    	private static final String path_encrypt = path+"/mycode.dat";
    	private static String cmd_dex2jar = "cmd /c "+root+" && cd "+path+ " && jar cvf " 
    			+ path_encrypt_source + " classes.dex";
    	
    	
    	private static String key="GiEhjghmZIO7RTWyycQ9PQ==";
    	/*
    	 * 需要生成新的秘钥时使用
    	 */
    //	static {
    //		try {
    //			key = AESUtils.getSecretKey();
    //		} catch (Exception e) {
    //			e.printStackTrace();
    //		}
    //	}
    

    这个path就是你准备把dex放置的一个临时文件夹,生成加密文件用;key的值就是加密和解密用的秘钥,咱们用的AES对称加解密,这里的key值是通过这个静态块来生成的,生成一次就行了,直接copy到引号,如果想生成新的秘钥就去掉注释行。

    public static void main(String[] args) throws Exception {
    		dex2Jar();	//给dex打成jar包
    		encrypt();	//对jar加密并生成为.dat文件
    	}
    

    如上,这是从我公司项目的自动化打包工具上裁剪了的,现在整个工具就两个方法,第一个是把原项目的源码dex文件打成jar包,然后再对jar包用key值加密并生成dat文件。

    加密工具使用方法,先从原项目的/bin目录下找到classes.dex,这个文件就是原项目所有的java源码生成的最终的字节码文件,android系统运行你写的程序就是加载这个dex文件。把它拷贝到你自定义的dexFileTmp文件夹,然后run这个加密工具,最终生成的文件如下:

    最终要用的就是mycode.dat这个文件,怎么用待会再说,得先介绍第二个android项目DexLoader,这里称为启动项目,先介绍java版的,后面再介绍jni版,先看下java版的目录结构:

    这里注意,除了上面的这几个java文件不一样之外,所有的资源文件和AndroidManifest都是直接从原项目拷贝过来的,还有就是把上面的mycode.dat文件拷贝至启动项目的assets目录下,下面的关键代码为首先在进程的系统文件夹下面创建一个自定义的app_cache文件夹,作为加密文件的中转文件夹,然后解密dat文件并变成jar,变成jar包后系统才能识别并把classes.dex抽取出来。

    File odex = this.getDir("cache", MODE_PRIVATE);
    		String odexPath = odex.getAbsolutePath();
    		System.out.println("odexPath--->" + odexPath);
    		try {
    			Util.decryptFile(this, odexPath+"/mycode.jar");
    		} catch (Exception e2) {
    			// TODO Auto-generated catch block
    			e2.printStackTrace();
    		}
    

    接下来的关键代码如下,把解密后的jar包,也就是原项目的代码加载至内存:

    DexClassLoader dLoader = new DexClassLoader(odexPath+"/mycode.jar", odexPath, 
    				"/data/data/" + base.getPackageName() + "/lib/",
    		 base.getClassLoader().getParent());
    

    接下来程序就会走onCreate方法,大量用到了RefInvoke类,这是一个专门调用反射的类,目的是获取系统的private变量和执行private方法,就不多介绍了,最终替换当前进程的代码为刚才加载至内存的代码,最终程序运行如下:

    而且还可以跳转至第二个activity。大功告成!

     下面介绍一下Jni版,先看下目录结构

     

    对比java版可以看出,解密的代码不见了,其实是被封装到了jni层,下面是java层的关键代码:

    static{
    		System.loadLibrary("load");
    	}
    
    	@Override
    	protected void attachBaseContext(Context base) {
    		super.attachBaseContext(base);
    		Object currentActivityThread = RefInvoke.invokeStaticMethod(
    				"android.app.ActivityThread", "currentActivityThread",
    				new Class[] {}, new Object[] {});
    		String packageName = getPackageName();
    		Map mPackages = (Map) RefInvoke.getFieldOjbect(
    				"android.app.ActivityThread", currentActivityThread,
    				"mPackages");
    		WeakReference wr = (WeakReference) mPackages.get(packageName);
    
    		run(this, base, "/data/data/"+ base.getPackageName() + "/lib/", base.getClassLoader().getParent(),wr.get());
    	}
    	
    	private native String run(Application wrapper, Context context, String libPath,ClassLoader classLoader, Object obj);
    

    可以对比java版的代码,attachBaseContext里的部分关键代码都封装在run函数里,目的就是隐藏解密算法。jni层的代码就不多介绍了,代码原理本身和java版是一样的,只不过多了在jni层获取java层的对象和方法的过程。后续工作还需要对so文件加壳及反调试,本版本中并没有,当然本版本也有一定的安全作用,毕竟可以防止反编译apk直接查看源码。运行结果和上面一致,也可以跳转到第二个activity。

    最后我们再来看下爱加密处理后的源码,将爱加密处理后的apk解压,对classes.dex执行反编译查看源码,以及assets目录下的ijiami.dat,如下图:

    分析而知爱加密也是运用了动态解密加载dex文件并替换当前进程的方式实现了隐藏原项目源码的行为。

    一个码农的日常 

    加密工具

    原项目

    java版启动项目

    jni版启动项目

  • 相关阅读:
    SQL2005四个排名函数(row_number、rank、dense_rank和ntile)的比较
    浅谈数据库索引
    移动网络应用开发中,使用 HTTP 协议比起使用 socket 实现基于 TCP 的自定义协议有哪些优势?
    http协议学习系列
    隐藏帧技术
    第2章:标准输入与输出
    第1章:认识Shell脚本
    Cisco配置单臂路由及静态路由
    Cisco交换机端口聚合(EtherChannel)
    Cisco配置VLAN+DHCP中继代理+NAT转发上网
  • 原文地址:https://www.cnblogs.com/joey-hua/p/5402599.html
Copyright © 2020-2023  润新知