安卓利用Xposed实现通话双向录音

关于MediaRecorder 的VOICE_CALL音源

在Android M以上的版本,将因为设置为VOICE_CALL,会发现调用recorder.prepare(),会抛出异常。这是由于此功能现在只开放给系统APP。

方法1 设置app的uid

根据上面提到的,我们可以想办法将我们的APP打包成系统级app,方法就是在AndroidManifest.xml中的manifest接地那中加入以下代码:

android:sharedUserId="android.uid.system"

然后用platform.pk8和platform.x509.pem两个文件以及Android提供的Signapk工具来签名。
可以参考:系统apk签名

注意,此方法只对原生系统有用,定制ROM例如miui,由于签名文件不一样,并且也没办法找到签名有关文件,所以此方法会不管用。

方法2 Xposed更改权限获取的判定

1.VOICE_CALL相关权限的验证

除了普通的录音权限意外,查看源码,会发现还需要另外一项权限,这是android-30源码中对于这个变量的解释:

/** Voice call uplink + downlink audio source
 * <p>
 * Capturing from <code>VOICE_CALL</code> source requires the
 * {@link android.Manifest.permission#CAPTURE_AUDIO_OUTPUT} permission.
 * This permission is reserved for use by system components and is not available to
 * third-party applications.
 * </p>
 */
public static final int VOICE_CALL = 4;

所以我们还需要在Manifest文件中加入下面一行:

<uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"/>

2.CAPTURE_AUDIO_OUTPUT权限的验证逻辑

这里我们用到一个网站:http://androidxref.com/
此网站可以用于搜索安卓源码中的内容,我们选择一个分支比如8.1.0_r33,然后搜索,如下图:
在这里插入图片描述
会发现,第一条结果似乎就是我们想要的内容,点进去看,搜到的这段代码内容如下:

bool captureAudioOutputAllowed(pid_t pid, uid_t uid) {
	if (getpid_cached == IPCThreadState::self()->getCallingPid()) return true;
	static const String16 sCaptureAudioOutput("android.permission.CAPTURE_AUDIO_OUTPUT");
	bool ok = checkPermission(sCaptureAudioOutput, pid, uid);
	if (!ok) ALOGE("Request requires android.permission.CAPTURE_AUDIO_OUTPUT");
	return ok;
}

原来这个权限的判断是在C++的代码里面,但是我们想的使用Xposed来解决,所以要继续往下看,寻找思路。
接下来的搜索,需要掌握一些C++语法相关内容,我们根据返回值和传入的参数,可以这样搜索:
在这里插入图片描述

记得,要用""把搜索内容引用起来,这样搜索才是完全匹配的搜索。
结合后面传入的两个参数类型,一个是pid_t,一个是uid_t,IServiceManager.cpp就是我们需要的,点进去,只需要关注三行代码:

bool res = pc->checkPermission(permission, pid, uid);

sp<IBinder> binder = defaultServiceManager()->checkService(_permission);

pc = interface_cast<IPermissionController>(binder);

第一行是获取返回值,第二行的是给pc这个对象赋值。作者虽然不熟悉C++,但是根据代码内容大胆猜测,这是名为IPermissionController的东西是一个aidl接口,所以必然有个java类会继承这个接口,接下来就去搜索extends IPermissionController.Stub就可以发现是一个名为PermissionController的类继承了它,并且这个类是在ActivityManagerService.java中。相关代码如下:

static class PermissionController extends IPermissionController.Stub {
        ActivityManagerService mActivityManagerService;
        PermissionController(ActivityManagerService activityManagerService) {
            mActivityManagerService = activityManagerService;
        }

        @Override
        public boolean checkPermission(String permission, int pid, int uid) {
            return mActivityManagerService.checkPermission(permission, pid,
                    uid) == PackageManager.PERMISSION_GRANTED;
        }

        @Override
        public String[] getPackagesForUid(int uid) {
            return mActivityManagerService.mContext.getPackageManager()
                    .getPackagesForUid(uid);
        }

        @Override
        public boolean isRuntimePermission(String permission) {
            try {
                PermissionInfo info = mActivityManagerService.mContext.getPackageManager()
                        .getPermissionInfo(permission, 0);
                return (info.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
                        == PermissionInfo.PROTECTION_DANGEROUS;
            } catch (NameNotFoundException nnfe) {
                Slog.e(TAG, "No such permission: "+ permission, nnfe);
            }
            return false;
        }
    }

3.xposed修改方法返回值

经过以上分析,我们只需要在包名为android的时候,添加如下代码就可以通过CAPTURE_AUDIO_OUTPUT的权限验证:

XposedHelpers.findAndHookMethod("com.android.server.am.ActivityManagerService$PermissionController", lpparam.classLoader,
                    "checkPermission",
                    String.class, int.class, int.class, new XC_MethodHook() {
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            String[] packages = (String[]) XposedHelpers.callMethod(param.thisObject, "getPackagesForUid", param.args[2]);
                            if (Arrays.asList(packages).contains("你的应用包名")) {
                             	if (("android.permission.CAPTURE_AUDIO_OUTPUT").equals(param.args[0])){
                             		param.setResult(true);
                             	}
                            }
                        }
                    });

本文涉及的代码内容很简单,更多地是想提供思路跟大家分享。

参考文章和链接

[1]: 【Android 进阶】Apk 使用系统签名https://www.jianshu.com/p/63d699cffa1a
[2]: Android native 权限控制流程https://blog.csdn.net/shift_wwx/article/details/80519212
[3]:Permission failure: android.permission.CAPTURE_AUDIO_OUTPUT 解决办法 https://blog.csdn.net/u012932409/article/details/103385064

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值