好友
阅读权限10
听众
最后登录1970-1-1
|
本帖最后由 ZhengY 于 2019-11-22 16:14 编辑
之前看到大佬写的 ( Android改机系列:二.全息备份原理剖析https://www.52pojie.cn/thread-999896-1-1.html)关于全息备份的原理解析便跟着大佬思路实现了全息备份,现在把具体过程和demo分享出来。
1.Xposed配置
我使用的测试手机小米4,Android版本4.4.4,所以配置的xposed是54版本并且后面备份相关源码对应的也是19版本的源码。
1.1 先拷贝xposedBridgeApi-54.jar到lib目录下,得自己创建lib目录并放入
1.2 然后在AndroidManifest.xml中配置xposed
1.3 build.gradle中compileOnly files('lib/XposedBridgeApi-54.jar')
1.4 最后在assets目录创建xposed_init文件将xposed的入口类的路径写入。
2.实现过程
2.1 app必须有FLAG_ALLOW_BACKUP属性才能进行全息备份,也就是AndroidManifest.xml文件中android:allowBackup属性设置为ture
通过hook android.content.pm.PackageParser.parseBaseApplication函数赋予app FLAG_ALLOW_BACKUP属性
从PackageParser.parseBaseApplication源码可以知道,系统获取AndroidManifest.xml中的allowBackup属性为true后将ApplicationInfo.flags进行了ai.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP运算
所以我们只需要在parseBaseApplication方法执行时对ApplicationInfo.flags进行以上运算就行了。
[Java] 纯文本查看 复制代码 final Class<?> parserPackage = XposedHelpers.findClass("android.content.pm.PackageParser$Package", classLoader);
XposedHelpers.findAndHookMethod("android.content.pm.PackageParser", classLoader, "parseApplication",
parserPackage, Resources.class, XmlPullParser.class, AttributeSet.class, int.class,
String[].class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
ApplicationInfo applicationInfo = (ApplicationInfo) XposedHelpers.getObjectField(param.args[0], "applicationInfo");
String str2 = applicationInfo.packageName;
applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
applicationInfo.flags |= ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
addLog("包名 : " + str2 + " flags : " + applicationInfo.flags);
}
});
2.2 调用系统com.android.server.backup.BackupManagerService的fullBackup函数进行备份
通过fullBackup函数源码可以看到,首先进行了权限检查,因为没有这个权限,所以需要绕过这个方法
Context.enforceCallingPermission方法内部实现就只是检查权限,没有权限throw new SecurityException异常
所以直接用空方法替换掉enforceCallingPermission方法
[Java] 纯文本查看 复制代码 XposedHelpers.findAndHookMethod("android.app.ContextImpl", classLoader, "enforceCallingPermission",
String.class, String.class, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(XC_MethodHook.MethodHookParam methodHookParam) throws Throwable {
if ("android.permission.BACKUP".equals(methodHookParam.args[0])) {
addLog("enforceCallingPermission replace : " + methodHookParam.args[1]);
return null;
} else {
addLog("enforceCallingPermission : " + methodHookParam.args[0] + " - " + methodHookParam.args[1]);
return XposedBridge.invokeOriginalMethod(methodHookParam.method, methodHookParam.thisObject,
methodHookParam.args);
}
}
});
解决掉权限问题,现在可以通过反射直接调用fullBackup方法了
[Java] 纯文本查看 复制代码 private static void backupApk(String mPkgName, String dirName) {
try {
addILog("backup apk data");
FileUtils.RecursionDeleteAllFileReName(new File(backupBasePath + mPkgName + "/backup/" + dirName));
FileUtils.mkdirs(backupBasePath + mPkgName + "/backup/" + dirName);
String backupFilePath = backupBasePath + mPkgName + "/backup/" + dirName + "/backup.ab";
File file = new File(backupFilePath);
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
file = new File(backupFilePath);
ArrayList pkgList = new ArrayList();
pkgList.add(mPkgName);
//原理是反射调用BackupManagerService.fullbackup
Class<?> serviceManager = Class.forName("android.os.ServiceManager");
Method getService = serviceManager.getMethod("getService", String.class);
getService.setAccessible(true);
IBinder iBinder = (IBinder) getService.invoke(null, "backup");
Class<?> sub = Class.forName("android.app.backup.IBackupManager$Stub");
Method asInterface = sub.getDeclaredMethod("asInterface", IBinder.class);
asInterface.setAccessible(true);
Object backupManager = asInterface.invoke(null, iBinder);
if (backupManager == null) {
addLog("backManager is null");
return;
}
ParcelFileDescriptor parcelFileDescriptor = null;
try {
parcelFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE);
String[] strArr = new String[pkgList.size()];
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
Method[] methods = backupManager.getClass().getMethods();
int length = methods.length;
int i = 0;
while (true) {
if (i >= length) {
break;
}
Method method = methods[i];
if ("fullBackup".equals(method.getName())) {
//需要android.permission.BACKUP权限,hook权限检查
// //fullBackup: uid 10065 does not have android.permission.BACKUP.
// android.Manifest.permission.BACKUP
//fullBackup原方法及参数
boolean includeApks = false;
//obb文件
boolean includeObbs = false;
//sdcard文件
boolean includeShared = false;
//是否所有app
boolean doAllApps = false;
//是否包括系统
boolean includeSystem = false;
method.setAccessible(true);
method.invoke(backupManager, new Object[]{parcelFileDescriptor,
Boolean.valueOf(includeApks), Boolean.valueOf(includeObbs),
Boolean.valueOf(includeShared), Boolean.valueOf(doAllApps),
Boolean.valueOf(includeSystem), (String[]) pkgList.toArray(strArr)});
break;
}
i++;
}
} else {
//5.1方法有所不同,暂未适配
// backupManager.fullBackup(parcelFileDescriptor, z, false, false, false, false, false, true, (String[]) arrayList.toArray(strArr));
}
} catch (Exception e2) {
addLog("Unable to invoke backup manager for backup" + e2.getMessage());
if (parcelFileDescriptor != null) {
try {
parcelFileDescriptor.close();
} catch (IOException e3) {
}
}
} finally {
if (parcelFileDescriptor != null) {
try {
parcelFileDescriptor.close();
} catch (IOException e4) {
}
}
}
} catch (ClassNotFoundException e) {
addLog("ClassNotFoundException : " + e.getMessage());
e.printStackTrace();
} catch (NoSuchMethodException e) {
addLog("NoSuchMethodException : " + e.getMessage());
e.printStackTrace();
} catch (IllegalAccessException e) {
addLog("IllegalAccessException : " + e.getMessage());
e.printStackTrace();
} catch (InvocationTargetException e) {
addLog("InvocationTargetException : " + e.getMessage());
e.printStackTrace();
}
}
调用成功后会出现系统的备份界面
这儿我用的某音作为测试App,备份前我进行了登录和关注操作
以上是登录状态,现在开始点击备份
可以看到,开始备份了。
以上是系统备份的部分日志,备份完成后可以在设置的文件目录看到备份文件,
2.3 反射调用fullRestore进行恢复备份
[Java] 纯文本查看 复制代码 public static void restoreApk(String mPkgName, String dirName) {
try {
addILog("restore apk");
String backupFilePath = backupBasePath + mPkgName + "/backup/" + dirName + "/backup.ab";
File file = new File(backupFilePath);
if (!file.exists() || file.length() == 0) {
addLog("restore error backup.ab is not exits or null");
return;
}
Class<?> serviceManager = Class.forName("android.os.ServiceManager");
Method getService = serviceManager.getMethod("getService", String.class);
getService.setAccessible(true);
IBinder iBinder = (IBinder) getService.invoke(null, "backup");
Class<?> sub = Class.forName("android.app.backup.IBackupManager$Stub");
Method asInterface = sub.getDeclaredMethod("asInterface", IBinder.class);
asInterface.setAccessible(true);
Object backupManager = asInterface.invoke(null, iBinder);
if (backupManager == null) {
addLog("backManager is null");
return;
}
ParcelFileDescriptor parcelFileDescriptor = null;
try {
parcelFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE);
if (Build.VERSION.SDK_INT <= 19) {
Method[] methods = backupManager.getClass().getMethods();
int length = methods.length;
int i = 0;
while (true) {
if (i >= length) {
break;
}
Method method = methods[i];
if ("fullRestore".equals(method.getName())) {
// //fullBackup: uid 10065 does not have android.permission.BACKUP.
// android.Manifest.permission.BACKUP
// public void fullBackup(ParcelFileDescriptor fd, boolean includeApks,
// boolean includeObbs, boolean includeShared,
// boolean doAllApps, boolean includeSystem, String[] pkgList
method.setAccessible(true);
method.invoke(backupManager, new Object[]{parcelFileDescriptor});
break;
}
i++;
}
} else {
// backupManager.fullBackup(parcelFileDescriptor, z, false, false, false, false, false, true, (String[]) arrayList.toArray(strArr));
}
if (parcelFileDescriptor != null) {
try {
parcelFileDescriptor.close();
} catch (IOException e) {
}
}
} catch (Exception e2) {
Log.e("ContentValues", "Unable to invoke restore manager for restore", e2);
if (parcelFileDescriptor != null) {
try {
parcelFileDescriptor.close();
} catch (IOException e3) {
}
}
} finally {
if (parcelFileDescriptor != null) {
try {
parcelFileDescriptor.close();
} catch (IOException e4) {
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
现在来验证是否能恢复,先清除app数据
然后反射调用fullRestore函数恢复
恢复完成后再打开某音,恢复成功,登录状态还在
2.4 下面是跳过用户确认弹窗
startConfirmationUi(int token, String action)方法内部实现就是启动了com.android.backupconfirm.BackupRestoreConfirmation这个Activity,
源码搜索com.android.backupconfirm.BackupRestoreConfirmation这个Activity看看内部实现
点进去,先看看onCreate方法,就是一般的Activity的初始化操作,我们关心的是点击了备份按钮后的操作,
继续看sendAcknowledgement方法
所以只需要拦截startConfirmationUi(int token, String action)方法,然后再获取BackupManagerService服务,调用acknowledgeFullBackupOrRestore(int token, boolean allow,String curPassword, String encPpassword, IFullBackupRestoreObserver observer) 方法就可以跳过用户确认界面了
[Java] 纯文本查看 复制代码 XposedHelpers.findAndHookMethod("com.android.server.BackupManagerService", classLoader,
"startConfirmationUi", int.class, String.class, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
addLog("action : " + methodHookParam.args[1] + " token : " + methodHookParam.args[0]);
BackupUtils.acknowledgeFullBackupOrRestore((Integer) methodHookParam.args[0]);
return true;
}
});
[Java] 纯文本查看 复制代码 public static void acknowledgeFullBackupOrRestore(int token) {
Class<?> serviceManager = null;
try {
serviceManager = Class.forName("android.os.ServiceManager");
Method getService = serviceManager.getMethod("getService", String.class);
getService.setAccessible(true);
IBinder iBinder = (IBinder) getService.invoke(null, "backup");
Class<?> sub = Class.forName("android.app.backup.IBackupManager$Stub");
Method asInterface = sub.getDeclaredMethod("asInterface", IBinder.class);
asInterface.setAccessible(true);
Object backupManager = asInterface.invoke(null, iBinder);
if (backupManager != null) {
addLog("backManager is not null");
Class<?> observer = Class.forName("android.app.backup.IFullBackupRestoreObserver");
Method acknowledgeFullBackupOrRestore = backupManager.getClass().getMethod(
"acknowledgeFullBackupOrRestore", int.class, boolean.class,
String.class, String.class, observer);
addLog("acknowledge is not null");
acknowledgeFullBackupOrRestore.setAccessible(true);
acknowledgeFullBackupOrRestore.invoke(backupManager, token, true, "",
"", new FullObserver());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
这里有个小问题acknowledgeFullBackupOrRestore方法中的IFullBackupRestoreObserver参数,在BackupRestoreConfirmation中传入的是个内部类
我的处理方法是在测试项目中创建一个一样的类,但是很不幸的是IFullBackupRestoreObserver.aidl是@hide的
@hide类是存在系统的但是无法在项目中引用,所以既然因为@hide无法引用就创建与系统@hide相同路径的类(双亲委派机制)
ok,完事。
- 需要备份sd文件可以修改fullBackup函数传入的includeShared参数属性,但是最好还是自己备份
- 5.0以上只是方法的源码不同逻辑都差不多,可以自己适配
- demo我放在github了 https://github.com/IzZzI/BackUpDemo
- 我是第一次发帖,有什么问题请大家多担待并指出,我会及时修改
|
免费评分
-
参与人数 25 | 吾爱币 +23 |
热心值 +24 |
收起
理由
|
julyice
| |
+ 1 |
谢谢@Thanks! |
qq913569
| + 1 |
+ 1 |
我很赞同! |
hzl636
| + 1 |
+ 1 |
看不懂,能否私教我一下。如果成功,有偿奉上 |
KP31
| + 1 |
+ 1 |
我很赞同! |
woichm1314
| + 1 |
+ 1 |
用心讨论,共获提升! |
我很菜
| + 1 |
+ 1 |
热心回复! |
anheiwanjia
| + 1 |
+ 1 |
&lt;font style=&quot;vertical-align: inherit;&quot;&gt;&lt;font style= |
whichway
| + 1 |
+ 1 |
感谢发布原创作品,吾爱破解论坛因你更精彩! |
生有涯知无涯
| + 1 |
+ 1 |
感谢发布原创作品,吾爱破解论坛因你更精彩! |
宋三
| + 1 |
+ 1 |
我很赞同! |
moon1943
| + 1 |
+ 1 |
很厉害的样子,没看懂,是不是可以理解成可以备份个人登录数据的意思? |
huangn2008
| + 1 |
+ 1 |
热心回复! |
奥斯特
| + 1 |
+ 1 |
用心讨论,共获提升! |
觇望
| |
+ 1 |
欢迎分析讨论交流,吾爱破解论坛有你更精彩! |
xiaoyu99
| + 1 |
+ 1 |
谢谢@Thanks! |
licyll
| |
+ 1 |
热心回复! |
紫圣
| + 1 |
+ 1 |
谢谢@Thanks! |
D.M
| + 1 |
+ 1 |
&lt;font style=&quot;vertical-align: inherit;&quot;&gt;&lt;font style= |
lhk0207
| + 1 |
+ 1 |
谢谢@Thanks! |
xlh1502735
| + 1 |
+ 1 |
热心回复! |
fei8255
| + 1 |
+ 1 |
先收藏,慢慢学习! |
撸冰花
| + 1 |
+ 1 |
热心回复! |
null788
| + 1 |
+ 1 |
谢谢@Thanks! |
szlstark
| + 1 |
|
&lt;font style=&quot;vertical-align: inherit;&quot;&gt;&lt;font style= |
风绕柳絮轻敲雪
| + 2 |
+ 1 |
感谢发布原创作品,吾爱破解论坛因你更精彩! |
查看全部评分
|