zl程序教程

您现在的位置是:首页 >  移动开发

当前栏目

【Android 安全】DEX 加密 ( Application 替换 | 分析 ContentProvider 组件中调用 getApplication() 获取的 Application 二 )

Android组件安全加密 分析 获取 调用 替换
2023-06-13 09:17:49 时间

文章目录

前置博客 : 【Android 安全】DEX 加密 ( Application 替换 | 分析 ContentProvider 组件中调用 getApplication() 获取的 Application )

一、 ActivityThread 中的 installProvider 方法 ( 创建 ContentProvider 内容提供者 )


在 installProvider 方法中 , 通过 反射创建 ContentProvider ;

// ★ 反射创建 ContentProvider 
localProvider = (ContentProvider)cl.
    loadClass(info.name).newInstance();

在 创建 ContentProvider 之后 , 调用了 attachInfo 函数 , 注意此处与 Activity , Service , BrocastReceiver 不同 , 这三个组件创建后调用的是 attach 函数 ;

// XXX Need to create the correct context for this provider.
// ★ 创建 ContentProvider 之后 , 调用了 attachInfo 函数 
// 注意此处与 Activity , Service , BrocastReceiver 不同 , 
// 这三个组件创建后调用的是 attach 函数
localProvider.attachInfo(c, info);

这里分析 attachInfo 中的 c 参数 , 也就是 Context 上下文的获取过程 :

声明空的 Context c 对象 ,

// ★ 该上下文对象很重要 
Context c = null;

获取 ApplicationInfo 信息 ApplicationInfo ai , 即 AndroidManifest.xml 中配置的 application 节点信息

// 该 ApplicationInfo 是 AndroidManifest.xml 中配置的 application 节点信息
ApplicationInfo ai = info.applicationInfo;

进行如下三个分支的判定 :

  • 分支一 : if (context.getPackageName().equals(ai.packageName)) : 在应用中配置的代理 Application 包名与真实 Application 包名都是相等的 ;
  • 分之二 : else if (mInitialApplication != null && mInitialApplication.getPackageName().equals(ai.packageName)) : 与分支一类似 , 也是要求包名相等 ;
  • 分支三 : 上面两个分支没有命中 , 就执行第三个分支 ;
			// ★ 该上下文对象很重要 
            Context c = null;
			// 该 ApplicationInfo 是 AndroidManifest.xml 中配置的 application 节点信息
            ApplicationInfo ai = info.applicationInfo;
			
			// 该 context 是 ProxyApplication , 代理 Application 
            if (context.getPackageName().equals(ai.packageName)) {
				// 在应用中配置的代理 Application 包名与真实 Application 包名都是相等的
				// 该分支是命中的 
                c = context;
            } else if (mInitialApplication != null &&
                    mInitialApplication.getPackageName().equals(ai.packageName)) {
				// 该分支中 mInitialApplication 就是 Context context 参数 , 肯定不为空 
				// 该分支无法命中 
                c = mInitialApplication;
            } else {
				
				// 上述两个分支都无法命中 , 才进入该分支 
				// 需要将代理 Application 的包名 与 真实应用的包名设置成不同的
				// 此时上面两个分支都无法命中 
                try {
                    c = context.createPackageContext(ai.packageName,
                            Context.CONTEXT_INCLUDE_CODE);
                } catch (PackageManager.NameNotFoundException e) {
                    // Ignore
                }
            }

上面的分支一 与 分支二 都是将 代理 Application 分支 , 因此必须要命中第三个分支 ;

如果将 代理 Application 的 getPackageName() 获取包名的方法返回空 , 此时肯定无法命中前两个分支 , 只能命中第三分支 ;

相关代码示例 :

public final class ActivityThread {

    /**
     * Installs the provider.
     *
     * Providers that are local to the process or that come from the system server
     * may be installed permanently which is indicated by setting noReleaseNeeded to true.
     * Other remote providers are reference counted.  The initial reference count
     * for all reference counted providers is one.  Providers that are not reference
     * counted do not have a reference count (at all).
     *
     * This method detects when a provider has already been installed.  When this happens,
     * it increments the reference count of the existing provider (if appropriate)
     * and returns the existing provider.  This can happen due to concurrent
     * attempts to acquire the same provider.
     */
    private ContentProviderHolder installProvider(Context context,
            ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {
				
		// ★ 声明 ContentProvider 
        ContentProvider localProvider = null;
        IContentProvider provider;
        if (holder == null || holder.provider == null) {
            if (DEBUG_PROVIDER || noisy) {
                Slog.d(TAG, "Loading provider " + info.authority + ": "
                        + info.name);
            }

			// 该上下文对象很重要 
            Context c = null;
            ApplicationInfo ai = info.applicationInfo;
            

			// 该 context 是 ProxyApplication , 代理 Application 
            if (context.getPackageName().equals(ai.packageName)) {
				// 在应用中配置的代理 Application 包名与真实 Application 包名都是相等的
				// 该分支是命中的 
                c = context;
            } else if (mInitialApplication != null &&
                    mInitialApplication.getPackageName().equals(ai.packageName)) {
				// 该分支中 mInitialApplication 就是 Context context 参数 , 肯定不为空 
				// 该分支无法命中 
                c = mInitialApplication;
            } else {
				
				// 上述两个分支都无法命中 , 才进入该分支 
				// 需要将代理 Application 的包名 与 真实应用的包名设置成不同的
				// 此时上面两个分支都无法命中 
                try {
                    c = context.createPackageContext(ai.packageName,
                            Context.CONTEXT_INCLUDE_CODE);
                } catch (PackageManager.NameNotFoundException e) {
                    // Ignore
                }
            }
            if (c == null) {
                Slog.w(TAG, "Unable to get context for package " +
                      ai.packageName +
                      " while loading content provider " +
                      info.name);
                return null;
            }

            try {
                final java.lang.ClassLoader cl = c.getClassLoader();
				
				// ★ 反射创建 ContentProvider 
                localProvider = (ContentProvider)cl.
                    loadClass(info.name).newInstance();
                provider = localProvider.getIContentProvider();
                if (provider == null) {
                    Slog.e(TAG, "Failed to instantiate class " +
                          info.name + " from sourceDir " +
                          info.applicationInfo.sourceDir);
                    return null;
                }
                if (DEBUG_PROVIDER) Slog.v(
                    TAG, "Instantiating local provider " + info.name);
                // XXX Need to create the correct context for this provider.
				// ★ 创建 ContentProvider 之后 , 调用了 attachInfo 函数 
				// 注意此处与 Activity , Service , BrocastReceiver 不同 , 
				// 这三个组件创建后调用的是 attach 函数
                localProvider.attachInfo(c, info);
            } catch (java.lang.Exception e) {
                return null;
            }
        } else {
        }
        return retHolder;
	}

}

参考路径 : frameworks/base/core/java/android/app/ActivityThread.java

二、 installProvider 方法的第三分支分析


下面代码中的三个分支就是给 ContentProvider 组件设置 Application 上下文的代码 ;

public final class ActivityThread {

    private ContentProviderHolder installProvider(Context context,
            ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {
            
			// 该上下文对象很重要 
            Context c = null;
            ApplicationInfo ai = info.applicationInfo;
            

			// 该 context 是 ProxyApplication , 代理 Application 
            if (context.getPackageName().equals(ai.packageName)) {
				// 在应用中配置的代理 Application 包名与真实 Application 包名都是相等的
				// 该分支是命中的 
                c = context;
            } else if (mInitialApplication != null &&
                    mInitialApplication.getPackageName().equals(ai.packageName)) {
				// 该分支中 mInitialApplication 就是 Context context 参数 , 肯定不为空 
				// 该分支无法命中 
                c = mInitialApplication;
            } else {
				
				// 上述两个分支都无法命中 , 才进入该分支 
				// 需要将代理 Application 的包名 与 真实应用的包名设置成不同的
				// 此时上面两个分支都无法命中 
                try {
                    c = context.createPackageContext(ai.packageName,
                            Context.CONTEXT_INCLUDE_CODE);
                } catch (PackageManager.NameNotFoundException e) {
                    // Ignore
                }
            }
            
        return retHolder;
	}

}

参考路径 : frameworks/base/core/java/android/app/ActivityThread.java

在上面的分析中 , 如果要使得分支一 context.getPackageName().equals(ai.packageName) 与分支二 mInitialApplication.getPackageName().equals(ai.packageName) , 都无法命中 , 就需要 Application 的 getPackageName 方法获取的包名不等于在 AndroidManifest.xml 中的包名 ai.packageName , 这里重写 ProxyApplication 的 getPackageName 方法 , 使该方法返回值为 “” 字符串 , 这样就无法命中前两个分支 , 只能进入 else 分支 ;

三、 ContextImpl 中 createPackageContext 方法分析


继续分析 分支三 中的内容 , 如果 Application 的 getPackageName 方法返回 “” , 将导致分支一二都没有命中 , 进入分支三 ;

public final class ActivityThread {

    private ContentProviderHolder installProvider(Context context,
            ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {
           
			// 该上下文对象很重要 
            Context c = null;
            ApplicationInfo ai = info.applicationInfo;
            
			// 该 context 是 ProxyApplication , 代理 Application 
            if (context.getPackageName().equals(ai.packageName)) {
            } else if (mInitialApplication != null &&
                    mInitialApplication.getPackageName().equals(ai.packageName)) {
            } else {
				
				// 上述两个分支都无法命中 , 才进入该分支 
				// 需要将代理 Application 的包名 与 真实应用的包名设置成不同的
				// 此时上面两个分支都无法命中 
                try {
                    c = context.createPackageContext(ai.packageName,
                            Context.CONTEXT_INCLUDE_CODE);
                } catch (PackageManager.NameNotFoundException e) {
                    // Ignore
                }
            }
            
        return retHolder;
	}

}

参考路径 : frameworks/base/core/java/android/app/ActivityThread.java

分支三中调用了 , context 的 createPackageContext(ai.packageName, Context.CONTEXT_INCLUDE_CODE) 方法 , 该方法在 ContextImpl 中定义 ;

在 ContextImpl 中的 createPackageContext 方法 , 调用了 createPackageContextAsUser 方法 , 调用了如下代码 , 创建 Context 上下文 ,

ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken, user,
                    flags, null);

上述代码中创建 ContextImpl 时 , 使用的 mMainThread , pi , 都没有替换过 Application , 因此分支三创建的 ContentProvider 对应的 Application 也是代理 Application , 替换前的 Application 对象 ;

class ContextImpl extends Context {

	// 在该方法中调用了 createPackageContextAsUser 方法创建上下文
    @Override
    public Context createPackageContext(String packageName, int flags)
            throws NameNotFoundException {
        return createPackageContextAsUser(packageName, flags,
                mUser != null ? mUser : Process.myUserHandle());
    }

    @Override
    public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
            throws NameNotFoundException {
        if (packageName.equals("system") || packageName.equals("android")) {
            // The system resources are loaded in every application, so we can safely copy
            // the context without reloading Resources.
            return new ContextImpl(this, mMainThread, mPackageInfo, null, mActivityToken, user,
                    flags, null);
        }

		// 注意该 LoadedApk 对象
        LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
                flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
        if (pi != null) {
			// 创建新的 ContextImpl 
			// 此时还没有替换 Application 
            ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken, user,
                    flags, null);

            final int displayId = mDisplay != null
                    ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;

            c.setResources(createResources(mActivityToken, pi, null, displayId, null,
                    getDisplayAdjustments(displayId).getCompatibilityInfo()));
            if (c.mResources != null) {
                return c;
            }
        }

        // Should be a better exception.
        throw new PackageManager.NameNotFoundException(
                "Application package " + packageName + " not found");
    }
		
}

源码路径 : frameworks/base/core/java/android/app/ContextImpl.java

ContextImpl 中的 public Context createPackageContext(String packageName, int flags) 方法是公开方法 , 重写该方法 , 在重写的 createPackageContext 方法中 , 先进行一次 Application 替换 , 然后继续执行 super.createPackageContext 方法的后续操作 , 这样创建的 ContentProvider 中的上下文就是用户自定义的 MyApplication , 不再是 ProxyApplication ;

只有在创建 ContentProvider 时才调用到该 createPackageContext 方法 , 如果没有调用到该方法 , 说明该应用中没有配置 ContentProvider ;

四、ContentProvider 中替换 Application 的总结


ContentProvider 中替换 Application 的总结 :

  • ① 分支选择 : 首先要命中 ActivityThread 中 installProvider 方法的分支三 ;
  • ② Application 替换 : 然后要在 ContextImpl 的 createPackageContext 方法执行前进行一次 Application 替换 ;