安卓利用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