ContentProvider Hook解析

5, ContentProvider Hook解析

ContentProvider和service一样,也是通过代理分发机制进行Hook,并且比service更简单,没有周期的处理,只有几个简单的方法,

例如, query/insert/delete/update等,

在AndroidManifest中注册了一个多进程的ContentProvider,


5.1 Hook过程

到此为止,还没有出现ContentProvider的Hook类,

当在插件中调用getContentResolver方法时就会调用PluginContext的getContentResolver方法,

public ContentResolver getContentResolver() {
     return new PluginContentResolver(getHostContext());
}

相当于直接利用PluginContentResolver替换掉系统的ContentResolver,并且PluginContentResolver也是继承于ContentResolver,

当调用query/insert/delete/update等方法时,会调用ContentResolver的acquireProvider/acquireExistingProvider等方法,

PluginContentResolver的acquireProvider方法如下,

if (mPluginManager.resolveContentProvider(auth, 0) != null) {
      return mPluginManager.getIContentProvider();
}
return (IContentProvider) sAcquireProvider.invoke(mBase, context, auth);

首先调用PluginManager的resolveContentProvider方法进行匹配,如果匹配上了,就需要Hook,否则说明不是插件的ContentProvider,不需要Hook。

PluginManager的getIContentProvider方法如下,

public synchronized IContentProvider getIContentProvider() {
    if (mIContentProvider == null) {
        hookIContentProviderAsNeeded();
    }
return mIContentProvider;
}

如果IcontentProviderProxy对象已经创建了就直接返回,否则就创建IcontentProviderProxy对象,

hookIContentProviderAsNeeded的主要逻辑如下,

1,获取已注册的ContentProvider的uri,然后主动调用了其call方法,

Uri uri = Uri.parse(PluginContentResolver.getUri(mContext));
mContext.getContentResolver().call(uri, "wakeup", null, null);

PluginContentResolver的getUri方法如下,

public static String getUri(Context context) {
     return "content://" + getAuthority(context);
}

getAuthority方法如下,

public static String getAuthority(Context context) {
    return context.getPackageName() + ".VirtualAPK.Provider";
}

就是在AndroidManifest中注册的RemoteContentProvider的uri。

调用call方法后最后会调用installProvider方法安装RemoteContentProvider。

2,将RemoteContentProvider对应的IcontentProvider对象利用反射替换为IcontentProviderProxy对象。

Field authority = null;
Field mProvider = null;
ActivityThread activityThread = (ActivityThread) ReflectUtil.getActivityThread(mContext);
Map mProviderMap = (Map) ReflectUtil.getField(activityThread.getClass(), activityThread, "mProviderMap");
Iterator iter = mProviderMap.entrySet().iterator();
while (iter.hasNext()) {
      Map.Entry entry = (Map.Entry) iter.next();
      Object key = entry.getKey();
      Object val = entry.getValue();
      String auth;
      if (key instanceof String) {
           auth = (String) key;
      } else {
           if (authority == null) {
               authority = key.getClass().getDeclaredField("authority");
               authority.setAccessible(true);
           }
           auth = (String) authority.get(key);
      }
      if (auth.equals(PluginContentResolver.getAuthority(mContext))) {
            if (mProvider == null) {
                  mProvider = val.getClass().getDeclaredField("mProvider");
                  mProvider.setAccessible(true);
            }
            IContentProvider rawProvider = (IContentProvider) mProvider.get(val);
            IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider);
            mIContentProvider = proxy;
•••

代码虽然有好几行,但是逻辑很简单, 利用循环拿到RemoteContentProvider对应的IContentProvider对象,

然后利用IContentProviderProxy Hook IcontentProvider。

IContentProviderProxy的newInstance方法如下,

public static IContentProvider newInstance(Context context, IContentProvider iContentProvider) {
    return (IContentProvider) Proxy.newProxyInstance(iContentProvider.getClass().getClassLoader(),new Class[] { IContentProvider.class }, new IContentProviderProxy(context, iContentProvider));
}

在获取插件的ContentProvider过程中进行匹配和Hook。

5.2 匹配

调用PluginManager的resolveContentProvider方法进行匹配,这个过程比Activity/service过程要简单, resolveContentProvider方法如下,

public ProviderInfo resolveContentProvider(String name, int flags) {
    for (LoadedPlugin plugin : this.mPlugins.values()) {
          ProviderInfo providerInfo = plugin.resolveContentProvider(name, flags);
          if (null != providerInfo) {
               return providerInfo;
         }
     }
return null;
}

逐个在所有插件中查询,一旦匹配就立即返回。

LoadedPlugin的resolveContentProvider方法如下,

public ProviderInfo resolveContentProvider(String name, int flags) {
     return this.mProviders.get(name);
}

直接从结构体中获取。解析插件的时候放入的。

5.3 替换

IContentProviderProxy的invoke方法如下,

wrapperUri(method, args);
try {
    return method.invoke(mBase, args);
}
•••

调用wrapperUri方法使用RemoteContentProvider替换目标的ContentProvider, wrapperUri方法主要逻辑如下,

PluginManager pluginManager = PluginManager.getInstance(mContext);
ProviderInfo info = pluginManager.resolveContentProvider(uri.getAuthority(), 0);
if (info != null) {
     String pkg = info.packageName;
     LoadedPlugin plugin = pluginManager.getLoadedPlugin(pkg);
     String pluginUri = Uri.encode(uri.toString());
     StringBuilder builder = new StringBuilder(PluginContentResolver.getUri(mContext));
     builder.append("/?plugin=" + plugin.getLocation());
     builder.append("&pkg=" + pkg);
     builder.append("&uri=" + pluginUri);
     Uri wrapperUri = Uri.parse(builder.toString());
    if (method.getName().equals("call")) {
        bundleInCallMethod.putString(KEY_WRAPPER_URI, wrapperUri.toString());
    } else {
        args[index] = wrapperUri;
    }
}

StringBuilder首先加入占坑provider的uri,然后将目标uri,pkg,plugin等参数等拼接上去,替换到args中的uri,

然后继续走原来的流程。

这里就相当于Hook 了ContentProvider的query/insert/delete/update等方法。

5.4,代理分发

调用插件的ContentProvider其实实际上会调用RemoteContentProvider的query/insert/delete/update等方法。

这些方法的调用流程完全相同,以query方法论述,

query方法如下,

public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
   ContentProvider provider = getContentProvider(uri);
   Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI));
   if (provider != null) {
         return provider.query(pluginUri, projection, selection, selectionArgs, sortOrder);
   }
return null;
}

首先调用getContentProvider方法获取插件的ContentProvider对象,然后调用query方法。这样就达到了代理的目的。

getContentProvider方法主要逻辑如下,

1,获取插件的Uri,

final PluginManager pluginManager = PluginManager.getInstance(getContext());
Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI));
final String auth = pluginUri.getAuthority();
ContentProvider cachedProvider = sCachedProviders.get(auth);
if (cachedProvider != null) {
    return cachedProvider;
}

这里分为2中情况,插件ContentProvider已启动, 未启动。

如果已经启动,就直接可以在sCachedProviders中获取并返回。

2,如果未启动就取出插件ContentProvider的uri,拿到auth。首先加载插件apk,然后匹配ContentProvider,

最后在主线程中反射生成provider对象,在调用其attachInfo方法.

synchronized (sCachedProviders) {
   LoadedPlugin plugin = pluginManager.getLoadedPlugin(uri.getQueryParameter(KEY_PKG));
   if (plugin == null) {
     try {
          pluginManager.loadPlugin(new File(uri.getQueryParameter(KEY_PLUGIN)));
        } catch (Exception e) {
          e.printStackTrace();
        }
}

   final ProviderInfo providerInfo = pluginManager.resolveContentProvider(auth, 0);
   if (providerInfo != null) {
        RunUtil.runOnUiThread(new Runnable() {
            @Override
            public void run() {
               try {
                  LoadedPlugin loadedPlugin = pluginManager.getLoadedPlugin(uri.getQueryParameter(KEY_PKG));
                  ContentProvider contentProvider = (ContentProvider) Class.forName(providerInfo.name).newInstance();
                  contentProvider.attachInfo(loadedPlugin.getPluginContext(), providerInfo);
                  sCachedProviders.put(auth, contentProvider);
                } catch (Exception e) {
                  e.printStackTrace();
            }
         }
      }, true);
      return sCachedProviders.get(auth);
    }
}

经过了这4个步骤,就可以通过代理调用插件的ContentProvider了。ContentProvider并没有生命周期的控制。

你可能感兴趣的:(ContentProvider Hook解析)