博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android系统Root与静默安装
阅读量:4050 次
发布时间:2019-05-25

本文共 10813 字,大约阅读时间需要 36 分钟。

静默安装,指的是安装时无需任何用户干预,直接按默认设置安装应用。因为,它的无需用户干预,很多情况下变成了用户压根不知道,应用不知不觉就安装上了。是在推广上极为流氓的手段,很类似PC上的捆绑安装。正因为静默安装时极为流氓的推广行为,所以,其推广价格也极其高。


Android应用安装有如下四种方式

安装形式 完成方式
系统应用安装 开机时完成,需要加入开机执行的脚本,没有安装界面
网络下载应用安装 通过系统market应用完成,没有安装界面
ADB工具中进行安装 使用pm install命令,没有安装界面。
第三方应用安装 通过SD卡里的APK文件安装,有安装界面,由PackageInstaller.apk应用处理安装及卸载过程的界面。

应用安装的流程及路径

目录 主要功能
/system/app 系统自带的应用程序存放,Root权限才可更改
/data/app 用户程序安装的目录,有删除权限。安装时把apk文件复制到此目录
/data/data 存放应用程序的数据
Data/dalvik-cache 将apk中的dex文件安装到dalvik-cache目录下

安装过程 
复制APK安装包到data/app目录下,解压并扫描安装包,把dex文件(Dalvik字节码)保存到dalvik-cache目录,并data/data目录下创建对应的应用数据目录。


卸载过程 
删除安装过程中在上述三个目录下创建的文件及目录。


权限声明

Google的安全策略要求任何应用在安装确认的时候应该提示APK安装包的权限,即确认开发者在AndroidManafest.xml中声明的权限。当然,Google在Android上也做了一些操作,允许一些系统内部的应用不经过授权界面直接进行安装。而系统进入安装界面其实也是根据此intent跳转到了PackageInstaller应用来完成权限的提示与安装的。

这里写代码片`我们在应用程序中控制安装应用APP,其实就是发送一个如下的intent。去调用packageinstaller进行安装,具体的操作代码如下:

/* 安装apk */Intent intent = new Intent();intent.setAction(Intent.ACTION_VIEW);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);intent.setDataAndType(Uri.parse("file://"+ fileName),   "application/vnd.android.package-archive");context.startActivity(intent);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

对比应用正常安装的流程,静默安装的本质就是去掉如下图所示的用户授权同意安装的过程,直接进行应用安装。

这里写图片描述


源码分析

阅读过源码后我们知道,系统的安装过程其实是调用了系统中的PackageInstaller来完成的。希望做到静默安装,就是找到一个方法,绕过PackageInstaller中的权限授予提示,继续完成安装的步骤。 
所以,思路很简单,我们可以从两方面去操作:

  • 找到PackageInstaller源码,跳过权限授予提醒,直接调用后面的安装API即可完成安装。(这样能够良好的兼容正常安装,不易出错)
  • 使用pm install 命令进行安装。

调用PackageInstaller中隐藏的API

查看PackageInstaller源码我们能够发现,其实PackageInstaller也是通过使用PackageManager进行安装的。调用的是其installPackage方法,但是此方法是一个abstract,且是对外不可见的(hide),

定义如下所示:

public abstractclass PackageManager {………/** * 安装应用APK文件 * @param packageURI 待安装的APK文件位置,可以是'file:'或'content:' URI. * @param observer 一个APK文件安装状态的观察器 * @param flags 安装形式 INSTALL_FORWARD_LOCK, INSTALL_REPLACE_EXISTING, INSTALL_ALLOW_TEST. * @paraminstallerPackageName APK安装包的PackageName */// @SystemApipublic abstract void installPackage(UripackageURI, PackageInstallObserverobserver,int flags,        StringinstallerPackageName);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

且PackageManager与installPackage两者皆为abstract抽象的。其具体实现都在ApplicationPackageManager中,其installPackage中的实现为:

final classApplicationPackageManager extends PackageManager {   ......   ApplicationPackageManager(ContextImpl context, IPackageManager pm) {       mContext = context;        mPM = pm;   }   @Override   public void installPackage(Uri packageURI, IPackageInstallObserver observer,intflags, String installerPackageName){        try {            mPM.installPackage(packageURI, observer, flags, installerPackageName);        } catch (RemoteException e) {            // Should never happen!        }   }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

可见调用的installPackage方法为IPackageManager中的installPackage方法。在ContextImpl中通过调用 
ActivityThread.getPackageManager()获得IPackageManager实例对象。而在在ActivityThread.getPackageManager()方法中,是调用SystemService中的名为package的Service来实例化的。代码如下:

staticIPackageManager sPackageManager;public staticIPackageManager getPackageManager() {   if (sPackageManager != null) {        return sPackageManager;   }   IBinder b = ServiceManager.getService("package");   sPackageManager = IPackageManager.Stub.asInterface(b);   return sPackageManager;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

因为,installPackage是系统的API,为了使用PackageManagerService.installPackage(),考虑通过反射机制可以调用installPackage()。

但其中难以得到的是其参数中的IPackageInstallObserver类型,我们看来一下IPackageInstallObserver,发现IPackageInstallObserver是由aidl文件定义的。这个也难不倒我们,通过aidl文件的特性,将IPackageInstallObserver.aidl文件拷到本地程序中,可以得到类IPackageInstallObserver.calss,通过它反射出installPackage()方法。

但在invoke调用该方法时,却无法得到IPackageInstallObserver的实例对象,IPackageInstallObserver的实例对象必须通过IPackageInstallObserver.Stub.asInterface(Binder binder)方式得到,无法得到与其绑定的Binder对象,因而无法执行反射出来的方法。

其次,应为是系统API,需要声明安装应用的权限:android.permission.INSTALL_PACKAGES。当时这类比较敏感的权限不是说声明系统就会给予的,还需要我们的安装包APK文件拥有与系统相同的签名,才能完成静默安装操作。这个方式的静默安装,对于广泛的推广应用是不现实的。

使用pm命令安装

pm 命令是Android里面PackageManage的命令行,用于安装包的操作。而系统也主要是提供我们在adb 
shell中进行使用pm命令,因此pm命令也存在与“/system”目录下,当然,拥有了Root权限后的应用程序就能够使用它进行静默安装了。

具体的操作代码如下所示:

// xxx.apk放置在内置储存的根目录下execCommand("system/bin/pminstall -r " + "sdcard/xxx.apk");// 执行commandpublic booleanexecCommand(String cmd) {   Process process = null;   try {        process = Runtime.getRuntime().exec(cmd);        process.waitFor();   } catch (Exception e) {        return false;   } finally {        try {           process.destroy();        } catch (Exception e) {        }   }   return true;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

pm命令源码目录: 
/frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.java,

我们查看其源码,如下:

public finalclass Pm {   IPackageManager mPm;   IUserManager mUm;   private WeakHashMap
mResourceCache =
new WeakHashMap
();
private String[] mArgs;
private
int mNextArg;
private String mCurArgData;
private
static
final String PM_NOT_RUNNING_ERR =
"Error: Could not access thePackage Manager. Is the systemrunning?";
public
static
void
main(String[] args) {
new Pm().run(args); }
/** * 解析命令参数 * @param args 参数 */
public
void
run(String[] args) {
boolean validCommand =
false;
if (args.length <
1) { showUsage();
return; } mUm =IUserManager.Stub.asInterface(ServiceManager.getService(
"user")); mPm =IPackageManager.Stub.asInterface(ServiceManager.getService(
"package"));
if (mPm ==
null) { System.err.println(PM_NOT_RUNNING_ERR);
return; } ......
if(
"install".equals(op)) { runInstall();
return; } ...... }
/** * 开始安装 */
private
void
runInstall() { ......
// 安装逻辑的具体调用 PackageInstallObserver obs = newPackageInstallObserver();
try { VerificationParamsverificationParams =
new VerificationParams(verificationURI, originatingURI, referrerURI, VerificationParams.NO_UID,
null); mPm.installPackageWithVerificationAndEncryption(apkURI, obs, installFlags, installerPackageName, verificationParams, encryptionParams);
synchronized (obs) {
while (!obs.finished) {
try { obs.wait(); }
catch(InterruptedException e) { } }
if (obs.result ==PackageManager.INSTALL_SUCCEEDED) { System.out.println(
"Success"); }
else { System.err.println(
"Failure[" +installFailureToString(obs.result) +
"]"); } } }
catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); } } ......}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

发现其实pm命令也是调用了PackageManager中的安装方法,只不过是一个验证和加密的方法installPackageWithVerificationAndEncryption进行安装的。即,它的安装过程与PackageInstaller是一样的。

而我们安装应用APP的时候,可以是自己的APK安装包文件存储在两个地方“data/app”与“system/app”下,静默安装的时候一般情况都是选择将自己的APK文件push到“system/app”目录下, 因为此目录是系统应用的目录,在此目录下的恶意应用,进行偷发短信、窃取邮件等操作,用户是很难察觉的。


删除预装

大部分的普通用户Root手机的主要目的就是删除系统预先安装的应用程序,要删除它们,我们首先要知道什么是预装应用,它们存放在哪里。或者我们换一个思路来看看,系统制造商将应用程序的APK文件存放在哪里才能变为系统的应用。

1. 系统默认的常规应用存放处

Android系统的捆绑应用软件基本安装在“/system/app”文件夹下,删除下面的对应的了第三方软件APK文件即可完美卸载。我们知道“/system”是系统的目录,对此目录进行操作需要Root权限,所以我们删除预装应用需要Root手机。每个系统程序基本上都是成对的,对应的删除掉后缀分别是.apk 和.odex(优化过的dex文件)文件即可删除预装应用。

如下图,使用了Root Explorer查看“/system/app”目录。则能够看到了系统中的所有的系统内置应用程序。 
这里写图片描述

2. 修改系统引导的预装

对于存放Apk文件到”/system/app“目录下已经是很普通的预装方式了,这就导致了,预装应用很容易就被卸载掉。恶意的手机ROM就会想着更加恶心的方法来留住预装应用,比如修改系统ROM的逻辑,让系统在开机的时候检测一下自己的预装是否完整然后重新安装。那么,当然系统预装应用的安装文件也会在另一个保存一份。

这类的预装应用,又称为“开机静默安装”,常用的方式就是修改init.rc,添加一个开机执行的脚本,在脚本中调用一个Service使用pm install命令批量安装应用。 
如,自顶一个一个init.local.rc内容如下:

#Preinstallonproperty:dev.bootcomplete=1         start loadpreinstallsserviceloadpreinstalls /system/bin/logwrapper /system/bin/loadpreinstalls.sh         disabled         oneshot
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在系统的init.rc脚本中调用init.local.rc如下:

#在sysinit前面加# Include extrainit file             import /system/etc/init.local.rc         而具体的预装脚本存在/system/bin/ loadpreinstalls.sh# do preinstall jobif [ ! -e /data/.notfirstrun ]thenecho "dopreinstall sys" >> /system/log.txt#安装/system/preinstall下的所有apk文件    APKLIST=`ls /system/preinstall/*.apk`   for INFILES in $APKLIST   do     echo  setup package:$INFILES     pm install -r $INFILES   doneecho "dopreinstall sd" >> /system/log.txt#安装/sdcard/preinstall下的所有apk文件   APKLIST=`ls /sdcard/preinstall/*.apk`   for INFILES in $APKLIST   do     echo  setup package:$INFILES     pm install -r $INFILES   done   echo "do preinstall ok" >> /system/log.txt   busybox touch /data/.notfirstrun   fiecho "============================================">> /system/log.txtexit
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

此方式做预装用户在使用Root后删除掉/system/app下的已安装应用后,系统重启后又会执行启动脚本自动重装预装应用回来,且预装apk文件的存放目录根据不同的系统ROM还不一样,是极为流氓的推广策略。当然,我们通过分析已经看到了,如果要删除此类的预装应用,只需要全盘的扫描apk文件再进行删除即可。

你可能感兴趣的文章
慢慢欣赏linux 内核模块引用
查看>>
kprobe学习
查看>>
慢慢欣赏linux phy驱动初始化2
查看>>
慢慢欣赏linux CPU占用率学习
查看>>
2020年终总结
查看>>
Homebrew指令集
查看>>
React Native(一):搭建开发环境、出Hello World
查看>>
React Native(二):属性、状态
查看>>
JSX使用总结
查看>>
React Native(四):布局(使用Flexbox)
查看>>
React Native(七):Android双击Back键退出应用
查看>>
Android自定义apk名称、版本号自增
查看>>
adb command not found
查看>>
Xcode 启动页面禁用和显示
查看>>
【剑指offer】q50:树中结点的最近祖先
查看>>
二叉树的非递归遍历
查看>>
【leetcode】Reorder List (python)
查看>>
【leetcode】Linked List Cycle (python)
查看>>
【leetcode】Linked List Cycle (python)
查看>>
【leetcode】Candy(python)
查看>>