private static void install(final Application app, final String process_name_or_tag, final CondomOptions options) { final int pos_colon = process_name_or_tag.indexOf(':'); final String tag = pos_colon > 0 ? process_name_or_tag.substring(pos_colon + 1) : process_name_or_tag; FULL_TAG = "Condom:" + tag; TAG = CondomCore.asLogTag(FULL_TAG); final CondomCore condom = new CondomCore(app, options, TAG); try { installCondomProcessActivityManager(condom); installCondomProcessPackageManager(condom); Log.d(TAG, "Global condom is installed in current process"); } catch (final Exception e) { condom.logConcern(TAG_INCOMPATIBILITY, e.getMessage()); Log.e(TAG, "Error installing global condom in current process", e); } }
@CheckResult <R, T extends Throwable> R proceed(final OutboundType type, final @Nullable Intent intent, final @Nullable R negative_value, final WrappedValueProcedureThrows<R, T> procedure) throws T { final String target_pkg = intent != null ? getTargetPackage(intent) : null; if (target_pkg != null) { if (mBase.getPackageName().equals(target_pkg)) return procedure.proceed(); // Self-targeting request is allowed unconditionally if (shouldBlockRequestTarget(type, intent, target_pkg)) return negative_value; } final int original_flags = intent != null ? adjustIntentFlags(type, intent) : 0; try { return procedure.proceed(); } finally { if (intent != null) intent.setFlags(original_flags); } }
private CondomContext(final CondomCore condom, final @Nullable Context app_context, final @Nullable @Size(max=16) String tag) { super(condom.mBase); final Context base = condom.mBase; mCondom = condom; mApplicationContext = app_context != null ? app_context : this; mBaseContext = new Lazy<Context>() { @Override protected Context create() { return new PseudoContextImpl(CondomContext.this); }}; TAG = CondomCore.buildLogTag("Condom", "Condom.", tag); }
@Override public boolean bindService(final Intent intent, final ServiceConnection conn, final int flags) { final boolean result = mCondom.proceed(OutboundType.BIND_SERVICE, intent, Boolean.FALSE, new CondomCore.WrappedValueProcedure<Boolean>() { @Override public Boolean proceed() { return CondomContext.super.bindService(intent, conn, flags); }}); if (result) mCondom.logIfOutboundPass(TAG, intent, CondomCore.getTargetPackage(intent), CondomCore.CondomEvent.BIND_PASS); return result; }
@Override public ComponentName startService(final Intent intent) { final ComponentName component = mCondom.proceed(OutboundType.START_SERVICE, intent, null, new CondomCore.WrappedValueProcedure<ComponentName>() { @Override public ComponentName proceed() { return mApplication.startService(intent); }}); if (component != null) mCondom.logIfOutboundPass(TAG, intent, component.getPackageName(), CondomCore.CondomEvent.START_PASS); return component; }
switch (method_name) { case "broadcastIntent": // int broadcastIntent(IApplicationThread caller, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, String/[23+] String[] requiredPermissions, [18+ int appOp], [23+ Bundle options], boolean serialized, boolean sticky, [16+ int userId]); result = mCondom.proceed(OutboundType.BROADCAST, (Intent) args[1], Integer.MIN_VALUE, new CondomCore.WrappedValueProcedureThrows<Integer, Throwable>() { @Override public Integer proceed() throws Throwable { return (Integer) CondomProcessActivityManager.super.invoke(proxy, method, args); }}); case "bindService": intent = (Intent) args[2]; result = mCondom.proceed(OutboundType.BIND_SERVICE, intent, 0, new CondomCore.WrappedValueProcedureThrows<Integer, Throwable>() { @Override public Integer proceed() throws Throwable { return (Integer) CondomProcessActivityManager.super.invoke(proxy, method, args); if (result > 0) mCondom.logIfOutboundPass(FULL_TAG, intent, CondomCore.getTargetPackage(intent), CondomCore.CondomEvent.BIND_PASS); return result; case "startService": intent = (Intent) args[1]; final ComponentName component = mCondom.proceed(OutboundType.START_SERVICE, intent, null, new CondomCore.WrappedValueProcedureThrows<ComponentName, Throwable>() { @Override public ComponentName proceed() throws Throwable { return (ComponentName) CondomProcessActivityManager.super.invoke(proxy, method, args); }}); if (component != null) mCondom.logIfOutboundPass(FULL_TAG, intent, component.getPackageName(), CondomCore.CondomEvent.START_PASS); return component; case "getContentProvider": final String name = (String) args[1]; if (! mCondom.shouldAllowProvider(mCondom.mBase, name, PackageManager.MATCH_ALL)) // MATCH_ALL as special hint to ask the hooked IPackageManager.resolveContentProvider() to bypass. return null; // Actually blocked by IPackageManager.resolveContentProvider() which is called in shouldAllowProvider() above. break;
final List<ResolveInfo> list = mCondom.proceedQuery(outbound_type, (Intent) args[0], new CondomCore.WrappedValueProcedureThrows<List<ResolveInfo>, Exception>() { @Override public List<ResolveInfo> proceed() throws Exception { return asList(result); return mCondom.proceed(OutboundType.QUERY_SERVICES, intent, null, new CondomCore.WrappedValueProcedureThrows<ResolveInfo, Throwable>() { @Override public ResolveInfo proceed() throws Throwable { if (! mCondom.mExcludeBackgroundServices && mCondom.mOutboundJudge == null) return (ResolveInfo) CondomProcessPackageManager.super.invoke(proxy, method, args); final int flags = (int) args[1]; if ((flags & PackageManager.MATCH_ALL) != 0) return provider; // MATCH_ALL will be used by the hooked IActivityManager.getContentProvider(). return mCondom.shouldAllowProvider(provider) ? provider : null; case "getInstalledApplications": case "getInstalledPackages": mCondom.logConcern(FULL_TAG, "IPackageManager." + method_name); break;
@Override public PackageInfo getPackageInfo(final String pkg, final int flags) throws NameNotFoundException { final PackageInfo info = mCondom.proceed(OutboundType.GET_PACKAGE_INFO, pkg, null, new CondomCore.WrappedValueProcedureThrows<PackageInfo, NameNotFoundException>() { @Override public PackageInfo proceed() throws NameNotFoundException { return CondomPackageManager.super.getPackageInfo(pkg, flags); } }); if (info == null) throw new NameNotFoundException(pkg); if ((flags & PackageManager.GET_PERMISSIONS) != 0 && ! mCondom.getSpoofPermissions().isEmpty() && mCondom.getPackageName().equals(pkg)) { final List<String> requested_permissions = info.requestedPermissions == null ? new ArrayList<String>() : new ArrayList<>(Arrays.asList(info.requestedPermissions)); final List<String> missing_permissions = new ArrayList<>(mCondom.getSpoofPermissions()); missing_permissions.removeAll(requested_permissions); if (! missing_permissions.isEmpty()) { requested_permissions.addAll(missing_permissions); info.requestedPermissions = requested_permissions.toArray(new String[requested_permissions.size()]); } // Even if all permissions to spoof are already requested, the permission granted state still requires amending. if (SDK_INT >= JELLY_BEAN) { final int[] req_permissions_flags = info.requestedPermissionsFlags == null ? new int[requested_permissions.size()] : Arrays.copyOf(info.requestedPermissionsFlags, requested_permissions.size()); for (int i = 0; i < info.requestedPermissions.length; i++) if (mCondom.shouldSpoofPermission(info.requestedPermissions[i])) req_permissions_flags[i] = PackageInfo.REQUESTED_PERMISSION_GRANTED; info.requestedPermissionsFlags = req_permissions_flags; } } return info; }
@Override public int checkPermission(final String permName, final String pkgName) { return mCondom.proceed(OutboundType.CHECK_PERMISSION, pkgName, PERMISSION_DENIED, new CondomCore.WrappedValueProcedure<Integer>() { @Override public Integer proceed() { return CondomPackageManager.super.checkPermission(permName, pkgName); } }); }
@Override public List<PackageInfo> getInstalledPackages(final int flags) { mCondom.logConcern(TAG, "PackageManager.getInstalledPackages"); return super.getInstalledPackages(flags); }
/** * This is the very first (probably only) API you need to wrap the naked {@link Context} under protection of <code>CondomContext</code> * * @param base the original context used before <code>CondomContext</code> is introduced. * @param tag the optional tag to distinguish between multiple instances of <code>CondomContext</code> used parallel. */ public static @CheckResult CondomContext wrap(final Context base, final @Nullable @Size(max=13) String tag, final CondomOptions options) { if (base instanceof CondomContext) { final CondomContext condom = ((CondomContext) base); Log.w("Condom", "The wrapped context is already a CondomContext (tag: " + condom.TAG + "), tag and options specified here will be ignore."); return condom; } final Context app_context = base.getApplicationContext(); final CondomCore condom = new CondomCore(base, options, CondomCore.buildLogTag("Condom", "Condom.", tag)); if (app_context instanceof Application) { // The application context is indeed an Application, this should be preserved semantically. final Application app = (Application) app_context; final CondomApplication condom_app = new CondomApplication(condom, app, tag); // TODO: Application instance should be unique across CondomContext. final CondomContext condom_context = new CondomContext(condom, condom_app, tag); condom_app.attachBaseContext(base == app_context ? condom_context : new CondomContext(condom, app, tag)); return condom_context; } else return new CondomContext(condom, base == app_context ? null : new CondomContext(condom, app_context, tag), tag); }
static String buildLogTag(final String default_tag, final String prefix, final @Nullable String tag) { return tag == null || tag.isEmpty() ? default_tag : asLogTag(prefix + tag); }
void logIfOutboundPass(final String tag, final Intent intent, final @Nullable String target_pkg, final CondomEvent event) { if (target_pkg != null && ! mBase.getPackageName().equals(target_pkg)) log(tag, event, target_pkg, intent.toString()); }
return proceed(type, intent, Collections.<T>emptyList(), new WrappedValueProcedureThrows<List<T>, E>() { @Override public List<T> proceed() throws E { final List<T> candidates = procedure.proceed(); if (candidates != null && mOutboundJudge != null && (intent == null || getTargetPackage(intent) == null)) { // Package-targeted intent is already filtered by OutboundJudge in proceed(). final Iterator<T> iterator = candidates.iterator(); while (iterator.hasNext()) { final T candidate = iterator.next(); final String pkg = pkg_getter.apply(candidate); if (pkg != null && shouldBlockRequestTarget(type, intent, pkg)) // Dry-run is checked inside shouldBlockRequestTarget() iterator.remove(); // TODO: Not safe to assume the list returned from PackageManager is modifiable. } } return candidates; }}); }
/** * This is the very first (probably only) API you need to wrap the naked {@link Context} under protection of <code>CondomContext</code> * * @param base the original context used before <code>CondomContext</code> is introduced. * @param tag the optional tag to distinguish between multiple instances of <code>CondomContext</code> used parallel. */ public static @CheckResult CondomContext wrap(final Context base, final @Nullable @Size(max=13) String tag, final CondomOptions options) { if (base instanceof CondomContext) { final CondomContext condom = ((CondomContext) base); Log.w("Condom", "The wrapped context is already a CondomContext (tag: " + condom.TAG + "), tag and options specified here will be ignore."); return condom; } final Context app_context = base.getApplicationContext(); final CondomCore condom = new CondomCore(base, options); if (app_context instanceof Application) { // The application context is indeed an Application, this should be preserved semantically. final Application app = (Application) app_context; final CondomApplication condom_app = new CondomApplication(condom, app, tag); final CondomContext condom_context = new CondomContext(condom, condom_app, tag); condom_app.attachBaseContext(base == app_context ? condom_context : new CondomContext(condom, app, tag)); return condom_context; } else return new CondomContext(condom, base == app_context ? null : new CondomContext(condom, app_context, tag), tag); }
private Object proceed(final Object proxy, final Method method, final Object[] args) throws Exception { final String method_name = method.getName(); final Intent intent; switch (method_name) { case "broadcastIntent": return mCondom.proceed(OutboundType.BROADCAST, (Intent) args[1], 0/* ActivityManager.BROADCAST_SUCCESS */, new CondomCore.WrappedValueProcedureThrows<Integer, Exception>() { @Override public Integer proceed() throws Exception { return (Integer) CondomProcessActivityManager.super.invoke(proxy, method, args); }}); case "bindService": intent = (Intent) args[2]; final Integer result = mCondom.proceed(OutboundType.BIND_SERVICE, intent, 0, new CondomCore.WrappedValueProcedureThrows<Integer, Exception>() { @Override public Integer proceed() throws Exception { return (Integer) CondomProcessActivityManager.super.invoke(proxy, method, args); }}); // Result: 0 - no match, >0 - succeed, <0 - SecurityException. if (result > 0) mCondom.logIfOutboundPass(FULL_TAG, intent, CondomCore.getTargetPackage(intent), CondomCore.CondomEvent.BIND_PASS); return result; case "startService": intent = (Intent) args[1]; final ComponentName component = mCondom.proceed(OutboundType.START_SERVICE, intent, null, new CondomCore.WrappedValueProcedureThrows<ComponentName, Exception>() { @Override public ComponentName proceed() throws Exception { return (ComponentName) CondomProcessActivityManager.super.invoke(proxy, method, args); }}); if (component != null) mCondom.logIfOutboundPass(FULL_TAG, intent, component.getPackageName(), CondomCore.CondomEvent.START_PASS); return component; case "getContentProvider": final String name = (String) args[1]; if (! mCondom.shouldAllowProvider(mCondom.mBase, name, PackageManager.GET_UNINSTALLED_PACKAGES)) return null; // Actually blocked by IPackageManager.resolveContentProvider() which is called in shouldAllowProvider() above. break; } return super.invoke(proxy, method, args); }
final List<ResolveInfo> list = mCondom.proceedQuery(outbound_type, (Intent) args[0], new CondomCore.WrappedValueProcedureThrows<List<ResolveInfo>, Exception>() { @Override public List<ResolveInfo> proceed() throws Exception { return asList(result); }}); return mCondom.proceed(OutboundType.QUERY_SERVICES, intent, null, new CondomCore.WrappedValueProcedureThrows<ResolveInfo, Exception>() { @Override public ResolveInfo proceed() throws Exception { if (! mCondom.mExcludeBackgroundServices) return (ResolveInfo) CondomProcessPackageManager.super.invoke(proxy, method, args); return mCondom.shouldAllowProvider(provider) ? provider : null; case "getInstalledApplications": case "getInstalledPackages": mCondom.logConcern(FULL_TAG, "IPackageManager." + method_name); break;
@Override public boolean bindService(final Intent intent, final ServiceConnection conn, final int flags) { final boolean result = mCondom.proceed(OutboundType.BIND_SERVICE, intent, Boolean.FALSE, new CondomCore.WrappedValueProcedure<Boolean>() { @Override public Boolean proceed() { return mApplication.bindService(intent, conn, flags); }}); if (result) mCondom.logIfOutboundPass(TAG, intent, CondomCore.getTargetPackage(intent), CondomCore.CondomEvent.BIND_PASS); return result; }
@Override public ComponentName startService(final Intent intent) { final ComponentName component = mCondom.proceed(OutboundType.START_SERVICE, intent, null, new CondomCore.WrappedValueProcedure<ComponentName>() { @Override public ComponentName proceed() { return CondomContext.super.startService(intent); }}); if (component != null) mCondom.logIfOutboundPass(TAG, intent, component.getPackageName(), CondomCore.CondomEvent.START_PASS); return component; }