吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 28408|回复: 75
上一主题 下一主题
收起左侧

[Android 分享] Android改机:全息备份实现(xposed)

  [复制链接]
跳转到指定楼层
楼主
ZhengY 发表于 2019-11-22 15:40 回帖奖励
本帖最后由 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 &amp;lt;font style=&amp;quot;vertical-align: inherit;&amp;quot;&amp;gt;&amp;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 &amp;lt;font style=&amp;quot;vertical-align: inherit;&amp;quot;&amp;gt;&amp;lt;font style=
lhk0207 + 1 + 1 谢谢@Thanks!
xlh1502735 + 1 + 1 热心回复!
fei8255 + 1 + 1 先收藏,慢慢学习!
撸冰花 + 1 + 1 热心回复!
null788 + 1 + 1 谢谢@Thanks!
szlstark + 1 &amp;lt;font style=&amp;quot;vertical-align: inherit;&amp;quot;&amp;gt;&amp;lt;font style=
风绕柳絮轻敲雪 + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
6662680 发表于 2019-11-22 19:53
让我想到了XX抹机和NZT
推荐
chbird 发表于 2019-11-22 19:28
推荐
撸冰花 发表于 2019-11-22 20:21
沙发
DA111 发表于 2019-11-22 18:12
我啥也看不懂
3#
rockze 发表于 2019-11-22 18:21

能在不同的手机里备份还原吗?
4#
feng3593 发表于 2019-11-22 19:04
表示一脸懵逼的路过
6#
果果没有糖葫芦 发表于 2019-11-22 19:49
都是大佬
8#
职业学生哥 发表于 2019-11-22 20:01
高大上,小白看不懂,不过是有需要这技术的,能写成软件最好了
9#
Angel泠鸢 发表于 2019-11-22 20:20
xp可还行,我都看不懂哟
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 10:46

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表