1. 程式人生 > >Android真正的靜默安裝(android7.0靜默安裝)

Android真正的靜默安裝(android7.0靜默安裝)

其實安裝,解除安裝,都是在frameworks -> base -> cmds -> pm -> src -> com -> android -> cmmands -> pm :PackageManager.java

安裝:installPackage這個方法

解除安裝:deletePackage這個方法

關於Android應用程式的靜默安裝,很早以前我就做過一些瞭解,網上大多數給出的方案都是有嚴苛的要求:比如要獲取root許可權、或者是針對特殊的rom,甚至要自己刷機,這些方法即使能夠達到目的但是看起來也不那麼誘人,對於黑客技術而言幾乎毫無用處。最近自己也想研究一下相關技術,於是自己動手嘗試了一下,總結出了一套方法,整個過程略微複雜,中間也有很多視訊裡沒提到的各種問題,在這裡做一個總結,也分享給大家。文章末尾有例程供大家參考,如有紕漏,歡迎指正。

首先大家回憶一下正常的安裝過程,點選安裝後系統會有一個安裝介面,一路下一步直到安裝完成最後設定許可權。那麼這個安裝程式是一個系統應用——PackageInstaller.apk,底層是通過“pm”工具完成的。我們要做的就是繞過這個介面並完成安裝,下面開始我們的工作……

1、研究系統安裝工具

我們定位到Android原始碼的Pm目錄:

frameworks -> base -> cmds -> pm -> src -> com -> android -> cmmands -> pm :Pm.java

這個就是PackageManager的原始碼,用於解析Pm指令碼工具,裡面有個main函式:

public static void main(String[] args) 
{
     new Pm().run(args);
}

裡面只有一句話,跳轉到run方法,run方法比較長,我就不復制了,裡面主要是對pm命令做解析,比如pm install、pm list等等,不同的命令對應不同的函式,這裡我們關注install命令(對應的有uninstall,可以用來做靜默解除安裝)。如果run方法解析到install命令,則會呼叫runInstall()方法,裡面主要是對命令後面所帶的引數做解析,在方法中有如下語句:

PackageInstallObserver obs = new PackageInstallObserver();
try {
        mPm.installPackage(Uri.fromFile(new File(apkFilePath)), obs, installFlags,installerPackageName);
        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);

關鍵在於第三行:

 mPm.installPackage(Uri.fromFile(new File(apkFilePath)), obs, installFlags,installerPackageName);

這就是我們苦苦尋找的東西,在框架中通過installPackage方法安裝。我們只要在自己的程式中使用這個方法就可以繞過系統安裝程式進行靜默安裝。

2、在程式中呼叫安裝方法

首先新建一個自己的程式命名為SlienceInstall,如下圖:

建立靜默安裝工程


為了方便演示,只在佈局檔案里加入一個按鈕,用於啟動靜默安裝。在按鈕的觸發事件里加入安裝語句。
由於installPackage方法是例項方法,我們首先要知道mPm物件是什麼。同樣在Pm.java中可以找到mPm的定義:

IPackageManager mPm;

她是一個IPackageManager介面型別的物件,有以下語句賦值:

mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));

很明顯這裡用到了aidl,接著我們需要找到IPackageManger,這也是框架中的類,在如下路徑可以找到:

frameworks -> base -> core -> java -> android -> content -> pm : IPackageManager.aidl

我們直接把這個類複製出來放到我們的工程中,注意包名要和aidl檔案包名一致,如下圖:

加入pm的aidl檔案


我們一起看看程式報的什麼錯誤:

aidl錯誤提示


顯然在IPackage介面中引用了其他未匯入的程式,從包名可以確定這些程式都和IPackage位於同一包下,在pm資料夾下找到報錯的幾個aidl檔案一起復制進來,隨即錯誤消失,編譯通過。此時工程目錄如下:


在gen目錄下,系統自動為我們生成了aidl對應的java檔案,這是aidl的一個特性,我們可以不必理會其中的內容,如果感興趣的朋友可以開啟自行研究。

現在我們可以使用IPackageManager了,這裡回顧一下原始碼中Pm.java安裝程式的語句:

 mPm.installPackage(Uri.fromFile(new File(apkFilePath)), obs, installFlags,installerPackageName);

按照frameworks中的方法,通過她來申明一個mPm,然後賦值。將上面的賦值語句直接複製到我們的程式中,發現系統提示找不到ServiceManager。這裡可以有兩種方法解決,第一種大家應該可以想到,我們可以通過同樣的方式去frameworks裡找到這個類,複製出來使用,但這可能又會在ServiceManager中涉及到其他的類,如果都這樣去查詢、複製工作量未免太大。這裡我使用另一種方法,注意這裡只是使用了ServiceManager的靜態方法,而且frameworks中的類我們在編譯時找不到,但是在執行時是可以找到的,所以通過反射機制來呼叫函式是再好不過的了。

於是回到Pm.java中,從程式頭的一堆import中可以找到ServiceManager的地址:

...
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
...

接下來在程式中通過反射拿到ServiceManager的getService方法,程式碼如下:

Class<?> clazz = Class.forName("android.os.ServiceManager");
Method method_getService = clazz.getMethod("getService",String.class);

呼叫getService方法,並接收IBinder返回值:

IBinder bind = (IBinder) method_getService.invoke(null, "package");

最後在呼叫安裝方法之前,先來看看這個方法的幾個引數:

  • packageURI: 安裝包的地址。
  • observer:安裝完成後的回撥函式。
  • flags:安裝方式,主要有普通安裝和覆蓋安裝。1表示普通,2表示覆蓋。
  • installerPackageName:執行安裝的應用程式名。
    其中observer可以直接給null,如果想要設定回撥的話,需要新建一個類實現IPackageInstallOberver類(之前已經匯入過)。
    最後加上目標apk的檔案物件apkFile,到這裡整個安裝函式的呼叫就OK了,目前工程MainActivity程式碼如下:

    try
          {
              //反射拿到Service Manager,然後呼叫getService獲取IBinder物件
              Class<?> clazz = Class.forName("android.os.ServiceManager");
              Method method_getService = clazz.getMethod("getService",
                      String.class);
              IBinder bind = (IBinder) method_getService.invoke(null, "package");
    
              IPackageManager iPm = IPackageManager.Stub.asInterface(bind);
              //呼叫安裝函式
              iPm.installPackage(Uri.fromFile(apkFile), null, 2,apkFile.getName());
          } catch (Exception e)
          {
              e.printStackTrace();
          }

這時候可以執行一下,發現目標apk並沒有如願的靜默安裝,那麼繼續下面的步驟:

3、新增系統許可權

在系統上安裝程式是需要註冊許可權的,在Manifest檔案中加入如下語句:

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

由於然後同樣是manifest檔案,在<manifest/>節點中加入sharedUserId屬性:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.slienceinstall"
    android:sharedUserId="android.uid.system"          //宣告為系統應用
    android:versionCode="1"
    android:versionName="1.0" >
  ... ...
<manifest/>

這樣就完成了許可權的註冊,此時IDE會提示我們INSTALL_PACKAGE是系統程式的許可權,普通程式是無法獲取到的。獲取系統許可權當然沒有這麼簡單,還需要幾步操作才能完成。

4、給應用打上系統簽名

見招拆招,既然要求我們的應用是系統應用,那麼就把我們的程式變成系統程式吧。這裡也是許多關於靜默安裝的文章中要求刷機的原因,因為我們把應用放到rom中就可以讓我們的應用變成系統應用,當然我這裡講的不需要這麼做,下面將介紹這種方法。

第一步:宣告為系統應用

第二步:提取系統簽名程式

其實系統應用和普通應用的不同之處主要在於應用簽名,所以如果希望不重新編譯ROM而讓應用擁有系統許可權,只要能夠用系統簽名程式為我們的程式打上簽名就可以了。
在Android原始碼中包含了系統簽名程式,定位到系統簽名檔案目錄:

build -> tools -> signapk:SignApk.java

新建一個Java工程,命名為SignSystemApk,然後將原始碼中的SignApk.java複製進來。

第三步:提取簽名配置檔案

完成簽名還需要另外兩個檔案,分別叫platform.pk8和platform.x509.pem,在原始碼的以下路徑可以找到:

build -> target -> product -> security

找到之後複製到SignSystemApk工程裡,整個簽名工程的目錄結構如下:


那麼到這裡,整個簽名程式就完成了。

第四步:利用簽名程式給應用簽名打包

只要執行這個打包程式,就可以把我們的應用作為系統應用打包釋出了。首先將之前寫好的SlienceInstall程式打包成apk(使用自定義的簽名),複製到SingSystemApk工程裡。工程結構如下:


在執行前先要對工程進行配置:右鍵工程名,依次選擇“Run as”→“Run configurations”,然後在左側選擇“Java Application”→“New_configuration”,在右側將Project設定為SignSystemApk,接著選擇Main class。結果如下:

Main


然後將右邊的頁卡切換到“Arguments”,編輯“Program arguments”,加入以下命令:

platform.x509.pem platform.pk8 slience_install.apk system_install.apk

Arguments


配置好之後,Apply → Run。執行成功在工程目錄上點選F5重新整理,可以看到多出來一個system_install.apk檔案,這個就是我們想要的檔案。如圖:

5、靜默安裝

將SignSystemApk打包出來的安裝包安裝在手機上,然後將目標apk放到指定位置(和程式中apk的uri地址有關),點選按鈕,退出程式,可以看到我們的目標apk成功被安裝到手機上,並且沒有任何的提示。到此,靜默安裝功能完美執行,接下來可以發揮聰明才智,盡情的做“壞事”了。



作者:超低空
連結:http://www.jianshu.com/p/326ea728e5d3
來源:簡書著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

------------------------------------------------純應用方法--------------------------------------------------------------------------------------------------------------

以上是系統層的方法,剛有大佬說了個純應用層的方法,不過肯定是要有系統許可權的前提下了,下面貼方法


深圳-安卓-小菜 2018/8/3 11:23:26
public boolean silentInstall(String apkAbsolutePath)
    {
        String[] args =
                {"pm", "install", "-i", "com.gionee.aora.market", "-r",
                        apkAbsolutePath};
        if (Build.VERSION.SDK_INT >= 24)
        {
            args = new String[]
                    {"pm", "install", "-r", "-i", "com.gionee.aora.market", "--user",
                            "0", apkAbsolutePath};
        }
        String result = "EMPTY";
        ProcessBuilder processBuilder = new ProcessBuilder(args);
        Process process = null;
        InputStream inIs = null;
        try
        {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int read = -1;
            process = processBuilder.start();
            baos.write('/');
            inIs = process.getInputStream();
            while ((read = inIs.read()) != -1)
            {
                baos.write(read);
            }
            byte[] data = baos.toByteArray();
            result = new String(data, "utf_8");
            DLog.i(TAG, "resultString-------->>>" + result);
            baos.close();
        } catch (IOException e)
        {
            DLog.i(TAG, "靜默安裝無效!!!");
        } finally
        {
            try
            {
                if (inIs != null)
                {
                    inIs.close();
                }
            } catch (IOException e)
            {
            }
            if (process != null)
            {
                process.destroy();
            }
        }
        return result.contains("Success");
    }