app加固原理(二)

减少入侵性

前言

  • 上次我们写的代码会有下面这样的问题,我们看到四大组件获的的application是壳程序里的application,那这样壳程序是不能提供别人使用的,所以这次讲的是怎么减少这样的入侵
  • 那就是找到 application的加载过程,然后把真实的application替换成原包里设置的


    1562854402300.png
  • 先上两张图


    Application绑定过程.png

    Application绑定过程2.png
  • 上面两张图是用户点击桌面app启动的流程,可以参考Activity启动流程 最后会调用 bindApplication 方法
public final void bindApplication(String processName, ApplicationInfo appInfo,
                List<ProviderInfo> providers, ComponentName instrumentationName,
                ProfilerInfo profilerInfo, Bundle instrumentationArgs,
                IInstrumentationWatcher instrumentationWatcher,
                IUiAutomationConnection instrumentationUiConnection, int debugMode,
                boolean enableBinderTracking, boolean trackAllocation,
                boolean isRestrictedBackupMode, boolean persistent, Configuration config,
                CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
                String buildSerial, boolean autofillCompatibilityEnabled) {

            if (services != null) {
                if (false) {
                    // Test code to make sure the app could see the passed-in services.
                    for (Object oname : services.keySet()) {
                        if (services.get(oname) == null) {
                            continue; // AM just passed in a null service.
                        }
                        String name = (String) oname;

                        // See b/79378449 about the following exemption.
                        switch (name) {
                            case "package":
                            case Context.WINDOW_SERVICE:
                                continue;
                        }

                        if (ServiceManager.getService(name) == null) {
                            Log.wtf(TAG, "Service " + name + " should be accessible by this app");
                        }
                    }
                }

                // Setup the service cache in the ServiceManager
                ServiceManager.initServiceCache(services);
            }

            setCoreSettings(coreSettings);

            AppBindData data = new AppBindData();
            data.processName = processName;
            data.appInfo = appInfo;
            data.providers = providers;
            data.instrumentationName = instrumentationName;
            data.instrumentationArgs = instrumentationArgs;
            data.instrumentationWatcher = instrumentationWatcher;
            data.instrumentationUiAutomationConnection = instrumentationUiConnection;
            data.debugMode = debugMode;
            data.enableBinderTracking = enableBinderTracking;
            data.trackAllocation = trackAllocation;
            data.restrictedBackupMode = isRestrictedBackupMode;
            data.persistent = persistent;
            data.config = config;
            data.compatInfo = compatInfo;
            data.initProfilerInfo = profilerInfo;
            data.buildSerial = buildSerial;
            data.autofillCompatibilityEnabled = autofillCompatibilityEnabled;
            sendMessage(H.BIND_APPLICATION, data);
        }

绑定 application的时候会 发送一个消息,注意类型是BIND_APPLICATION,然后我们看处理的地方

public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case BIND_APPLICATION:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    AppBindData data = (AppBindData)msg.obj;
                    handleBindApplication(data);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
            }
            //..........忽略.............
}   

继续调用 handleBindApplication(data); 从字面意思翻译是 处理绑定application

private void handleBindApplication(AppBindData data) {
        
        Application app;
        final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
        final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
        try {
            // If the app is being launched for full backup or restore, bring it up in
            // a restricted environment with the base application class.
            app = data.info.makeApplication(data.restrictedBackupMode, null);

            // Propagate autofill compat state
            app.setAutofillCompatibilityEnabled(data.autofillCompatibilityEnabled);
            // @4
            mInitialApplication = app;

            // don't bring up providers in restricted mode; they may depend on the
            // app's custom Application class
            if (!data.restrictedBackupMode) {
                if (!ArrayUtils.isEmpty(data.providers)) {
                    installContentProviders(app, data.providers);
                    // For process that contains content providers, we want to
                    // ensure that the JIT is enabled "at some point".
                    mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
                }
            }

            // Do this after providers, since instrumentation tests generally start their
            // test thread at this point, and we don't want that racing.
            try {
                mInstrumentation.onCreate(data.instrumentationArgs);
            }
            catch (Exception e) {
                throw new RuntimeException(
                    "Exception thrown in onCreate() of "
                    + data.instrumentationName + ": " + e.toString(), e);
            }
            try {
                mInstrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {
                if (!mInstrumentation.onException(app, e)) {
                    throw new RuntimeException(
                      "Unable to create application " + app.getClass().getName()
                      + ": " + e.toString(), e);
                }
            }
        } finally {
            // If the app targets < O-MR1, or doesn't change the thread policy
            // during startup, clobber the policy to maintain behavior of b/36951662
            if (data.appInfo.targetSdkVersion < Build.VERSION_CODES.O_MR1
                    || StrictMode.getThreadPolicy().equals(writesAllowedPolicy)) {
                StrictMode.setThreadPolicy(savedPolicy);
            }
        }

        //................忽略...................
 }

主要是对application的各种初始化,主要看 app = data.info.makeApplication(data.restrictedBackupMode, null);这行代码就是生成application ,然后传了两个参数。 然后看 @4 标记这里,前面说要替换系统所有用application的地方这里就是一处,制作出来后的application赋值给mInitialApplication

public final class LoadedApk {

    //.........忽略............
    private Application mApplication;
    //.........忽略............
    
    public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
            //初始化了直接返回
        if (mApplication != null) {
            return mApplication;
        }

        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");

        Application app = null;
        //获取功能清单下application节点的的name属性 
        String appClass = mApplicationInfo.className;
        if (forceDefaultAppClass || (appClass == null)) {
            appClass = "android.app.Application";
        }

        try {
            java.lang.ClassLoader cl = getClassLoader();
            if (!mPackageName.equals("android")) {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                        "initializeJavaContextClassLoader");
                initializeJavaContextClassLoader();
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            }
            //创建上下文
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            //初始化 传三个参数类加载器、代理类、上下文
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            appContext.setOuterContext(app);
        } catch (Exception e) {
            if (!mActivityThread.mInstrumentation.onException(app, e)) {
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                throw new RuntimeException(
                    "Unable to instantiate application " + appClass
                    + ": " + e.toString(), e);
            }
        }
        //@2
        mActivityThread.mAllApplications.add(app);
        //@3
        mApplication = app;

        if (instrumentation != null) {
            try {
                instrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {
                if (!instrumentation.onException(app, e)) {
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    throw new RuntimeException(
                        "Unable to create application " + app.getClass().getName()
                        + ": " + e.toString(), e);
                }
            }
        }

        // Rewrite the R 'constants' for all library apks.
        SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers();
        final int N = packageIdentifiers.size();
        for (int i = 0; i < N; i++) {
            final int id = packageIdentifiers.keyAt(i);
            if (id == 0x01 || id == 0x7f) {
                continue;
            }

            rewriteRValues(getClassLoader(), packageIdentifiers.valueAt(i), id);
        }

        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

        return app;
    }
    //..........忽略.............
}

LoadedApk这个类就 APK在内存中的表示,可以得到如代码,资料,功能清单等资料
app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext); 这行调用的方法

public Application newApplication(ClassLoader cl, String className, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        //一个工厂 制作出来了Application
        Application app = getFactory(context.getPackageName())
                .instantiateApplication(cl, className);
         //绑定上下文
        app.attach(context);
        return app;
}
public @NonNull Application instantiateApplication(@NonNull ClassLoader cl,
            @NonNull String className)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        return (Application) cl.loadClass(className).newInstance();
}

咦,看到这里不就是反射吗,原来是从这里创建的,然后下一行代码 绑定

 public class Application extends ContextWrapper implements ComponentCallbacks2 {
  //..........忽略.............
    /**
     * @hide
     */
    /* package */ final void attach(Context context) {
        attachBaseContext(context);
        mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
    }
 //..........忽略.............
}

看到这里明白了把,这就是为什么之前把解密dex和加载dex文件放到attachBaseContext()里面了。

好了,到现在知道了从该哪里加载application了,那就好办了, appContext.setOuterContext(app); 这里加载进来的,我只需要把 ContextImpl 这个类反射出来,加载好的 的application设置到mOuterContext这个属性上就好了
然后这里 @2 mActivityThread.mAllApplications.add(app);还用到了 把application加载到一个集合里
还有这里 @3 mApplication = app;
最后 @4 instrumentation.callApplicationOnCreate(app);
还有有时会用到 getContext().getApplicationInfo().className 所有要替换 ApplicationInfo -> className

总结要替换的 application

ContextImpl->mOuterContext(app)         通过Application的attachBaseContext回调参数获取
ActivityThread->mActivityThread(ArrayList)  通过ContextImpl的mMainThread属性获取
LoadedApk->mApplication         通过ContextImpl的mPackageInfo属性获取
ActivityThread->mInitialApplication         通过ContextImpl的mMainThread属性获取
ApplicationInfo -> className

application与四大组件的关系

  • Activity
    • getApplication()得到application,在attach() 方法里 mApplication = application; 赋值
    • 在ActivityThread中会接收消息 RELAUNCH_ACTIVITY
    handleRelaunchActivity()---> handleRelaunchActivityInner() -->handleLaunchActivity();
    
    final Activity a = performLaunchActivity(r, customIntent);
    //activity创建
    activity =mInstrumentation.newActivity()
    
    调用了activity.attach() 完成绑定
    Application app=r.packageInfo.makApplicatio()
    
  • Service
    在ActivityThread中会接收消息CREATE_SERVICE
    
    源码和Acitivty类同
    
  • BroadCastReciver
     在ActivityThread中会接收消息 RECEIVER
    
     handlerReceiver()        receiver.onReceive(context.getReceiverRestrictedContext(),data.intent);
    
      把上下文件封装了一层,以防止用户在接收者中进行注册广播和绑定服务
     ReceiverRestrictedContext -> registerReceiver(){
      throw new ReceiverCallNotAllowedException(
                     "BroadcastReceiver components are not allowed to register   to receive intents");
                     }
     在ActivityThread中会接收消息BIND_APPLICATION 
    
  • ContentProvider
    ContentProvider
    
    在ActivityThread中会接收消息 BIND_APPLICATION
    handleBindApplication()
    
    AcitivityThread中 handleBindApplication方法中   if(!data.restrictedBackupMode){}
    //安装
    installContentProviders()
    installProvider()
    
    同样是通过newInstance反射创建
    localProvider.attachInfo(c);
    mContext=context;
    
              
    让if(context.getPackageName不等于ai.packagename)我们就可以切换  application成功
    然后不走前两个if 走下面的
    c=context.createPackageContext();
    
    ContextImpl.java中的实现
    //这里返回Context,那我么重新这个方法,把创建的application返回就行  了。
    @Override
    public Context createPackageContext(String packageName, int flags)
    

代码实现

  • 今天就不贴代码了,需要的小伙伴在 GitHub上看我的实现吧

效果

1562920242534.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容

  • 2.1 Activity 2.1.1 Activity的生命周期全面分析 典型情况下的生命周期:在用户参与的情况下...
    AndroidMaster阅读 3,031评论 0 8
  • 本文重点介绍应用程序的启动过程,应用程序的启动过程实际上就是应用程序中的默认Activity的启动过程,本文将详细...
    天宇sonny阅读 393评论 1 0
  • 本文出自 Eddy Wiki ,转载请注明出处:http://eddy.wiki/interview-androi...
    eddy_wiki阅读 3,256评论 0 20
  • 别人的总结不一定适合自己,所以尽量多做一些自己的总结,针对自己的薄弱点重点说明,适当的借鉴别人,少走一些弯路。最重...
    renkuo阅读 7,386评论 2 48
  • 笔记
    近俺者赤阅读 248评论 0 0