发布于8月23日8月23日 一、项目概述DroidPlugin是由360手机助手团队开发的Android插件化框架,于2015年首次发布,目前处于终止更新状态。核心目标是在免Root环境下实现APK文件的“免安装运行”,即无需经过系统安装流程即可直接加载并运行插件中的四大组件Activity、Service、BroadcastReceiver、ContentProvider核心技术特性免Root的应用层Hook:通过动态代理技术拦截系统关键API,无需依赖Root权限即可实现插件组件加载,突破系统对组件注册的硬性限制。零安装与系统目录零占用:插件APK无需安装到系统应用目录/data/app,可直接从存储加载,支持“独立安装应用”与“纯插件”两种运行模式。完全隔离机制:插件间、插件与宿主间实现代码与资源的沙箱化隔离,避免组件冲突与数据干扰,保障运行独立性。二、技术原理分析DroidPlugin的核心实现围绕“逆向绕过系统组件管理机制”展开,通过“Manifest欺骗→AMS拦截→资源路由→插件Service前台化”的完整调用链,实现插件组件的非法加载与运行。以下为关键技术链路的代码与原理解析:完整调用链解析:Manifest欺骗→AMS拦截→资源路由→插件Service前台化下面把DroidPlugin实现「Manifest欺骗→AMS拦截→资源路由→插件Service前台化」的完整调用链、关键类和核心代码片段一次性梳理给你。所有行号以2016-12-20最后一次主干代码为准,类路径保持不变,可直接在仓库里grep验证。1.Manifest欺骗(让未注册的组件合法通过AMS检查)核心目标:绕过Android系统对组件必须在Manifest中注册的静态检查,为插件组件提供“合法身份”。1.1宿主占坑实现文件:host/src/main/AndroidManifest.xml关键代码:0102030405060708091011<activity android:name=".stub.StubActivityC0" android:exported="false"/> <activity android:name=".stub.StubActivityC1" android:exported="false"/> <!-- 共预注册C0-C99 100个StubActivity --> <service android:name=".stub.StubServiceC0" android:exported="false"/> <service android:name=".stub.StubServiceC1" android:exported="false"/> <!--共预注册C0-C99 100个StubService-->释义:宿主在Manifest中预先注册100个无实际业务逻辑的“占位组件”(StubActivity、StubService等),命名遵循“C0-C99”规则,满足系统对组件注册的静态校验要求,为后续插件组件替换提供“壳身份”。1.2运行时偷梁换柱Hook点:IActivityManager代理类HookedActivityManagerHandler实现文件:com/morgoo/droidplugin/hook/handle/HookedActivityManagerHandler.java关键代码:0102030405060708091011121314151617public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("startActivity".equals(method.getName())) { Intent raw = (Intent) args[2]; // 把插件真实Component替换为未占用的Stub组件 ComponentName stub = StubSelector.selectStubActivity(raw); raw.setComponent(stub); // 欺骗AMS,使其认为启动的是宿主注册的Stub组件 } return method.invoke(mBase, args); }释义:通过动态代理拦截系统startActivity等方法,将插件的真实组件(如com.example.plugin.MainActivity)替换为宿主中未被占用的Stub组件(如StubActivityC0),使AMS(ActivityManagerService)误认为启动的是已注册的合法组件,绕过注册检查。1.3AMS放行后换真身Hook点:ActivityThread.mH的LAUNCH_ACTIVITY事件实现文件:HookedActivityThreadHandler.java关键代码:010203040506070809101112131415case LAUNCH_ACTIVITY: ActivityClientRecord r = (ActivityClientRecord) msg.obj; Intent intent = r.intent; // 从Intent中提取插件真实组件,替换Stub组件 ComponentName plugin = IntentCompat.getSelector(intent); r.intent.setComponent(plugin); // 后续通过反射正常创建插件Activity实例 break;释义:在AMS放行组件启动请求后,通过Hook ActivityThread的消息处理机制,将Intent中的Stub组件换回插件真实组件,确保最终启动的是插件中的实际业务组件,完成“欺骗-还原”闭环。2.AMS拦截(所有Binder入口都被动态代理替换)核心目标:拦截系统服务(AMS、PMS)的Binder调用,伪造插件“已安装”的假象,实现组件生命周期管理。 2.1Hook 时机实现文件:PluginHelper.java关键代码:010203040506070809101112131415[url=home.php?mod=space&uid=1892347]@Override[/url] protected void attachBaseContext(Context base) { PluginHelper.getInstance().applicationAttachBaseContext(base); // 在宿主初始化时Hook系统服务 AMSHook.hookActivityManager(context); // Hook AMS PMSHook.hookPackageManager(context); // Hook PMS super.attachBaseContext(base); }释义:在宿主 Application 初始化阶段,通过反射 Hook AMS(ActivityManagerService)和 PMS(PackageManagerService)的 Binder 接口,替换为框架自定义的代理对象,实现对系统服务调用的全程拦截。2.2IActivityManager 代理链代理链路:AMSHook→HookedActivityManagerHandler拦截方法:startActivity、startService、stopService、bindService、unbindService、getContentProvider、registerReceiver 等。关键逻辑:将插件的组件调用请求(如startService(pluginServiceIntent))替换为宿主 Stub 组件的调用请求,使系统始终认为操作的是宿主内部组件,规避跨应用组件调用限制。2.3IPackageManager 代理链代理链路:PMSHook →HookedPackageManagerHandler拦截方法:getPackageInfo ()、queryIntentActivities ()、getApplicationInfo () 等。关键逻辑:当系统查询插件包信息时,返回伪造的 “已安装” 数据(如插件包名、版本、权限等),使系统误认为插件已通过正常流程安装,从而允许其组件被调用。3. 资源路由(插件独立 Resources,与宿主隔离)核心目标:实现插件与宿主的资源完全隔离,避免资源ID冲突(如R.drawable.icon 重名导致的显示异常)。 3.1创建插件LoadedApk实现文件:PluginProcessManager.java关键代码:01020304050607080910111213141516171819LoadedApk pluginApk = Reflector.with(host).field("mPackages") .call("get", pluginPkgName).get(); // 为插件创建独立的AssetManager AssetManager am = AssetManager.class.newInstance(); Reflector.with(am).method("addAssetPath", String.class) .call(pluginApk.getResDir()); // 加载插件APK的资源路径 // 基于独立AssetManager创建插件Resources Resources pluginRes = new Resources(am, host.getResources().getDisplayMetrics(), host.getResources().getConfiguration());释义:通过反射为每个插件创建独立的AssetManager和Resources实例,仅加载插件APK中的资源文件(res、assets目录),使插件资源与宿主资源完全隔离,避免因资源ID重复导致的冲突。3.2Context 注入实现类:PluginContext(继承 ContextWrapper)关键重写方法:0102030405060708091011121314151617181920212223@Override public Resources getResources() { return mPluginResources; // 返回插件独立Resources } @Override public AssetManager getAssets() { return mPluginResources.getAssets(); // 返回插件独立AssetManager } @Override public String getPackageName() { return mPluginPkgName; // 返回插件包名,而非宿主包名 }释义:通过自定义PluginContext覆盖资源获取方法,确保插件代码中调用getResources()、getAssets()等接口时,访问的是插件自身的资源,而非宿主资源,实现资源路由的彻底隔离。3.3Activity 启动时挂接实现文件:HookedInstrumentation.java关键代码:010203040506070809101112131415@Override public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Activity pluginActivity = (Activity) clazz.newInstance(); // 创建插件专属Context并注入Activity Context pluginCtx = new PluginContext(realIntent, pluginRes, pluginPkgName); Reflector.with(pluginActivity).field("mBase").set(pluginCtx); // 替换Activity的mBase为插件Context return pluginActivity; }释义:在插件Activity实例化时,将其内部的Context(mBase)替换为插件专属的PluginContext,确保Activity生命周期中所有资源访问均指向插件自身,彻底切断与宿主资源的关联。4. 插件Service前台化(防止被系统回收)核心目标:通过复用宿主前台服务身份,提升插件Service的进程优先级,避免因系统内存不足被杀死。 4.1仍然用StubService 占位实现文件:host/src/main/AndroidManifest.xml关键代码:12345<service android:name=".stub.StubServiceC0" android:foregroundServiceType="media|location" android:exported="false"/>释义:宿主预注册的StubService在Manifest中声明为前台服务(foregroundServiceType),使其具备系统赋予的高优先级,为插件Service复用前台身份提供基础。4.2StubService.onStartCommand () 里二次转发实现文件:StubService.java关键代码:01020304050607080910111213141516171819202122232425public int onStartCommand(Intent raw, int flags, int startId) { // 从Intent中提取插件Service的真实请求 Intent pluginIntent = raw.getParcelableExtra(EXTRA_TARGET_INTENT); // 获取插件Service实例 Service plugin = PluginServiceManager.getService(pluginIntent); if (plugin != null) { // 复用StubService的前台身份,提升插件Service优先级 startForeground(notificationId, notification); // 转发启动命令给插件Service return plugin.onStartCommand(pluginIntent, flags, startId); } return super.onStartCommand(raw, flags, startId); }释义:StubService作为“壳服务”接收系统启动命令后,提取插件Service的真实请求,复用自身前台服务身份(调用startForeground ()),并将命令转发给插件Service,使插件Service间接获得前台优先级,降低被系统回收的概率。4.3前台通知持久化实现逻辑:由宿主统一管理前台通知的创建与更新,即使插件进程处于空转状态(无活跃操作),系统仍会识别为前台服务,维持进程优先级≥FOREGROUND,保障插件核心功能的持续运行。三、局限性分析DroidPlugin的设计缺陷与技术限制使其在逆向场景中面临多重障碍,主要包括:1. 通知限制技术根源:PluginContext的资源隔离机制导致插件无法直接访问系统通知服务的自定义资源接口。具体表现:无法发送带自定义RemoteLayout或通过R.drawable.XXX指定图标的Notification,插件通知图标会被强制转为Bitmap,可能导致显示异常。逆向影响:常规调试无法直接跟踪插件通知逻辑,需通过Hook NotificationManagerService拦截通知发送过程,增加调试复杂度。2. IntentFilter限制技术根源:插件组件未在系统中真正注册,其声明的IntentFilter无法被系统索引。具体表现:插件的Service、Activity等组件无法通过隐式Intent被外部应用调用,仅支持显式Intent启动。逆向影响:动态调试时无法通过系统Intent分发机制唤醒插件组件,需静态解析插件Manifest提取组件信息,手动构造显式Intent触发逻辑。3. Native 支持不足技术根源:Hook机制仅作用于Java层,未对Native层(.so文件)函数进行拦截与适配。具体表现:含Native代码的插件(如游戏、加密加固应用)可能无法运行,存在进程崩溃或功能失效风险。逆向影响:Java层调试工具(如AndroidStudioDebugger)对Native逻辑失效,需结合IDAPro 等工具单独分析.so 文件,补充逆向链路。4. 静态广播处理缺陷技术根源:插件静态广播被转为动态广播处理,依赖插件进程存活状态。具体表现:若插件进程未启动或意外终止,静态广播事件无法触发,导致依赖广播的逻辑(如开机启动、网络变化监听)失效。逆向影响:调试静态广播需维持插件进程存活,需通过宿主主动启动插件 Service,增加调试环境稳定性维护成本。
参与讨论
你可以现在发布并稍后注册. 如果你有帐户,现在就登录发布帖子.