学习了F8师傅的《Android应用加固保护开发入门》,记录下笔记~
原理 本篇的加壳是Java层的加壳,即将要保护的类从dex文件里面抽离出来,编译成一个独立的dex文件后对dex文件整体进行加密(如异或加密),在被保护程序执行前,先解密被抽离的dex并将其加载到内存中,再开始执行,这样将在一定程度上提高静态分析的难度。于是我们需要的操作有:
将关键类抽离并进行加密处理
在项目源码中删除已经被抽离的类,因为它们将会在以后从其他地方加载(如被加密的文件解密后再加载)
在被保护程序用户逻辑执行前,对被保护的dex进行解密操作并将其加载到内存中
做必要的解析与修复,执行到这个阶段壳相关的代码已经执行完成,再跳转到源程序的起始点执行即可。
下面将以例子演示以上操作:
原程序 源程序主要有两个类,其他布局资源什么的根据此可以自行写出。MainActivity
是第一个执行的Activity,它做的事如下注释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package betamao.jk;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.util.Log;import android.widget.TextView;public class MainActivity extends AppCompatActivity { static final String TAG = "BetaMao" ; protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); Log.d(TAG, "onCreate: MainActivity!" ); setContentView(R.layout.activity_main); TextView tvWorld = (TextView) findViewById(R.id.textView2); TextView t2World = (TextView) findViewById(R.id.textView); t2World.setText("MainActivity has been loaded~" ); if (getApplication() instanceof MyApplication){ tvWorld.setText("Application has been loaded" ); }else { tvWorld.setText("Application hasnt been loaded" ); } } }
MyApplication
里面对两个方法进行了覆写,其实目前也就只添加了一条输出日志功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package betamao.jk;import android.app.Application;import android.content.Context;import android.util.Log;public class MyApplication extends Application { public void onCreate () { super .onCreate(); Log.d("BetaMao" ,"onCreate: MyApplocation" ); } protected void attachBaseContext (Context bass) { super .attachBaseContext(bass); Log.d("BetaMao" ,"MyApplication attachBaseContext" ); } }
现在若运行程序,tvWorld
的值其实是Application hasnt been loaded
(可以试试),因为它还需要在在AndroidManifest.xml
中添加android:name=".MyApplication"
项。之后程序运行,结果如下: 现在将会开始手动加壳,我们的目的是在不破坏原有程序功能的前提下保护代码,所以加壳后运行的结果必须应该是和上图一样的。
手动加壳 抽离被保护代码并处理 这里自己只写了上面提到的两个类,那么要保护的也就是这两个类了。
解压apk后反编译dex:java -jar ShakaApktool.jar bs classes.dex -o classes
删除多余文件及,只保留:MainActivity.smali
和MyApplication.smali
两个文件及目录结构
回编译:java -jar ShakaApktool.jar s classes -o encrypt.dex
对项目进行处理
删除项目里MainActivity
和MyApplication
两个类的源码
将处理后的encrypt.dex
放到项目下的资源目录下
编写壳代码加载被保护的类 此处的壳代码的作用是在原程序代码运行前先执行,对被保护的代码进行解密及加载初始化等操作,所以我们需要找到应用程序员能够使用的最早(越早越好,否则需要手动实现很多被保护代码已经实现的功能 )的执行的方法,在那个地方我们能够获取到控制权并开始执行壳代码,在最开始学习Android开发时都被告知在AndroidManifest.xml
中指定"Main"Activity
,那么程序将从这个指定的Activity开始执行:
1 2 3 4 5 6 <activity android:name =".MainActivity" > <intent-filter > <action android:name ="android.intent.action.MAIN" /> <category android:name ="android.intent.category.LAUNCHER" /> </intent-filter > </activity >
实际上它并不是我们能够取得控制权的最早执行的地方,阅读源码(在app的Launcher2里 ),会发现Android应用在启动后执行"Main"Activity
前会先创建Application
(若存在)并执行它里面的attachBaseContext
和 onCreate
方法,当然上面的例子中已经有输出日志可以验证:
1 2 3 01-21 09:42:03.989 2752-2752/? D/BetaMao: MyApplication attachBaseContext 01-21 09:42:03.990 2752-2752/? D/BetaMao: onCreate: MyApplocation 01-21 09:42:04.448 2752-2752/betamao.jk D/BetaMao: onCreate: MainActivity!
所以思路就是: 1.新建一个Application类,将会在里面实现本部分所诉功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package betamao.jk;import android.app.Application;import android.content.Context;public class TkApplication extends Application { @Override public void onCreate () { super .onCreate(); } @Override protected void attachBaseContext (Context base) { super .attachBaseContext(base); } }
并且在AndroidManifest.xml
中把Application的android:name
属性值改为.TkApplication
。同时activity的android:name
的值不变化,由于它所指向的.MainActivity
已经被删除所以报错红色标注,这在低版本的Android studio或者sdk(不知道具体是哪个,后来我两者都用高版本就没出现问题了 )中会报Could not identify launch activity: Default Activity not found
错误,这个鬼让我找了一天都没找到原因和解决办法 2.写解密加载代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 protected void attachBaseContext (Context base) { super .attachBaseContext(base); File cache = getDir("grege" ,MODE_PRIVATE); String srcDex = cache + "/encrypt.dex" ; File dexFile = FileManager.releaseAssetsFile(this ,"encrypt.dex" ,srcDex,null ); DexClassLoader cl = new DexClassLoader(srcDex,getDir("BetaMao" ,MODE_PRIVATE).getAbsolutePath(), getApplicationInfo().nativeLibraryDir,getClassLoader()); Object currentActivityThread = JavaRef.invokeStaticMethod("android.app.ActivityThread" ,"currentActivityThread" ,new Class[]{},new Class[]{}); ArrayMap mPakages = (ArrayMap) JavaRef.getFeildObject("android.app.ActivityThread" ,"mPackages" ,currentActivityThread); WeakReference wr = (WeakReference) mPakages.get(getPackageName()); JavaRef.setFeildObject("android.app.LoadedApk" ,"mClassLoader" ,wr.get(),cl); }
3.此时类已经加载完成了,但是若继续运行会发现结果和原程序结果不一样: 上面已经提到壳代码应该尽可能早的执行,此处在application中执行已经比较早了,但是原程序也存在application,那么两者之间是存在一定的冲突的,也就是说此处的TkApplication
执行完以后,程序不会再去执行原来的MyApplication
了,那么MainActivity
里getApplication()
得到的是系统类加载器中的TkApplication
而不在是MyApplication
,同时MyApplication
也不再是由系统类加载器加载而是由新建的类加载器加载,所以还需要对application再做修复。所谓修复就是手动实现MyApplication
的创建并且让其替换掉TkApplication
所占据的数据结构,这里再次阅读源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 private void handleBindApplication (AppBindData data) {Application app = data.info.makeApplication(data.restrictedBackupMode, null ); public Application makeApplication (boolean forceDefaultAppClass,Instrumentation instrumentation) { String appClass = mApplicationInfo.className; app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext); public Application newApplication (ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return newApplication(cl.loadClass(className), context); static public Application newApplication (Class<?> clazz, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Application app = (Application)clazz.newInstance(); app.attach(context); final void attach (Context context) { attachBaseContext(context); mLoadedApk = ContextImpl.getImpl(context).mPackageInfo; } return app; } } appContext.setOuterContext(app); mActivityThread.mAllApplications.add(app); mApplication = app; } mInitialApplication = app; instrumentation.callApplicationOnCreate(app); public void callApplicationOnCreate (Application app) { app.onCreate(); } }
如上代码注释所述,只要仿照Launcher直接调用makeApplication
创建原来的Application并且替换掉相关数据即可达到目的,实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public void onCreate () { super .onCreate(); Object currentActivityThread = JavaRef.invokeStaticMethod("android.app.ActivityThread" , "currentActivityThread" ,new Class[]{},new Object[]{}); Object mBoundApplication = JavaRef.getFieldObject("android.app.ActivityThread" , "mBoundApplication" ,currentActivityThread); Object loadedApkInfo = JavaRef.getFieldObject("android.app.ActivityThread$AppBindData" , "info" ,mBoundApplication); JavaRef.setFieldObject("android.app.LoadedApk" ,"mApplication" ,loadedApkInfo,null ); String className = "betamao.jk.MyApplication" ; ApplicationInfo appInfoLoadedApke = (ApplicationInfo) JavaRef.getFieldObject("android.app.LoadedApk" , "mApplicationInfo" ,loadedApkInfo); appInfoLoadedApke.className = className; ApplicationInfo appinfoInAppBindData = (ApplicationInfo) JavaRef.getFieldObject("android.app.ActivityThread$AppBindData" , "appInfo" ,mBoundApplication); appinfoInAppBindData.className = className; Application oldApplication = (Application) JavaRef.getFieldObject("android.app.ActivityThread" , "mInitialApplication" ,currentActivityThread); ArrayList<Application> mAllApplications = (ArrayList<Application>) JavaRef.getFieldObject("android.app.ActivityThread" , "mAllApplications" ,currentActivityThread); mAllApplications.remove(oldApplication); Application orgApp = (Application) JavaRef.invokeMethod("android.app.LoadedApk" , "makeApplication" ,new Class[]{boolean .class,Instrumentation.class}, loadedApkInfo,new Object[]{false ,null }); orgApp.onCreate(); JavaRef.setFieldObject("android.app.ActivityThread" ,"mInitialApplication" , currentActivityThread,orgApp); return ; }
现在结果已经和原来一样了,而且日志也和原来一样说明MyApplication
完成了和原程序中一样的操作~
参考
i春秋:https://www.ichunqiu.com/course/58853 https://www.jianshu.com/p/640e956cf41c