Android的靜默安裝


原文

 

Android的靜默安裝似乎是一個很有趣很誘人的東西,但是,用普通做法,如果手機沒有root權限的話,似乎很難實現靜默安裝,因為Android並不提供顯示的Intent調用,一般是通過以下方式安裝apk:

?
1
2
3
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive" );
startActivity(intent);

但是,這並沒有真正的實現靜默安裝,因為有用戶界面,會讓用戶知道。那么,怎么在后台悄悄的安裝APK呢?只能試圖去看看Android系統源碼正常安裝APK的過程,我這邊下載的源碼是Android5.0系統的,5個G的大小,但是可能由於Android5.0有一些安全方面的更新,跟之前的版本還是有一定的差距的,但是,學會一個之后再去學另一個相似的過程,那就簡單許多了,就像學會了C語言,再學Java,也並非什么難事。

Android系統把所有的Permission(權限)依據其潛在風險划分為四個等級,即"normal"、 "dangerous"、 "signature"、 "signatureOrSystem"。APK的安裝對應的權限是 INSTALL_PACKAGES,權限等級屬於后兩者。所以,最終想實現APK的靜默安裝,必然需要一些特殊的處理,執行安裝的這個進程,須為系統進程。
那么,我們就來看看Android自身是如何實現安裝APK的。安裝的命令是pm install... 我們定位到系統源碼的/frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.java這個文件,他實現了pm命令,我們看runInstall方法,這就是APK的安裝過程。

?
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
private void runInstall() {
   int installFlags = 0 ;
   int userId = UserHandle.USER_ALL;
   String installerPackageName = null ;
  
   String opt;
  
   String originatingUriString = null ;
   String referrer = null ;
   String abi = null ;
  
   while ((opt=nextOption()) != null ) {
     if (opt.equals( "-l" )) {
       installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
     } else if (opt.equals( "-r" )) {
       installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
     } else if (opt.equals( "-i" )) {
       installerPackageName = nextOptionData();
       if (installerPackageName == null ) {
         System.err.println( "Error: no value specified for -i" );
         return ;
       }
     } else if (opt.equals( "-t" )) {
       installFlags |= PackageManager.INSTALL_ALLOW_TEST;
     } else if (opt.equals( "-s" )) {
       // Override if -s option is specified.
       installFlags |= PackageManager.INSTALL_EXTERNAL;
     } else if (opt.equals( "-f" )) {
       // Override if -s option is specified.
       installFlags |= PackageManager.INSTALL_INTERNAL;
     } else if (opt.equals( "-d" )) {
       installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
     } else if (opt.equals( "--originating-uri" )) {
       originatingUriString = nextOptionData();
       if (originatingUriString == null ) {
         System.err.println( "Error: must supply argument for --originating-uri" );
         return ;
       }
     } else if (opt.equals( "--referrer" )) {
       referrer = nextOptionData();
       if (referrer == null ) {
         System.err.println( "Error: must supply argument for --referrer" );
         return ;
       }
     } else if (opt.equals( "--abi" )) {
       abi = checkAbiArgument(nextOptionData());
     } else if (opt.equals( "--user" )) {
       userId = Integer.parseInt(nextOptionData());
     } else {
       System.err.println( "Error: Unknown option: " + opt);
       return ;
     }
   }
  
   if (userId == UserHandle.USER_ALL) {
     userId = UserHandle.USER_OWNER;
     installFlags |= PackageManager.INSTALL_ALL_USERS;
   }
  
   final Uri verificationURI;
   final Uri originatingURI;
   final Uri referrerURI;
  
   if (originatingUriString != null ) {
     originatingURI = Uri.parse(originatingUriString);
   } else {
     originatingURI = null ;
   }
  
   if (referrer != null ) {
     referrerURI = Uri.parse(referrer);
   } else {
     referrerURI = null ;
   }
  
   // Populate apkURI, must be present
   final String apkFilePath = nextArg();
   System.err.println( "\tpkg: " + apkFilePath);
   if (apkFilePath == null ) {
     System.err.println( "Error: no package specified" );
     return ;
   }
  
   // Populate verificationURI, optionally present
   final String verificationFilePath = nextArg();
   if (verificationFilePath != null ) {
     System.err.println( "\tver: " + verificationFilePath);
     verificationURI = Uri.fromFile( new File(verificationFilePath));
   } else {
     verificationURI = null ;
   }
  
   LocalPackageInstallObserver obs = new LocalPackageInstallObserver();
   try {
     VerificationParams verificationParams = new VerificationParams(verificationURI,
         originatingURI, referrerURI, VerificationParams.NO_UID, null );
  
     mPm.installPackageAsUser(apkFilePath, obs.getBinder(), installFlags,
         installerPackageName, verificationParams, abi, userId); //注意!!最終就是調用這個方法來進行安裝的
  
     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)
             + "]" );
       }
     }
   } catch (RemoteException e) {
     System.err.println(e.toString());
     System.err.println(PM_NOT_RUNNING_ERR);
   }
}

知道了這個過程之后,就大概知道怎么做了。既然系統底層把這個API屏蔽了,那就想辦法去繞過這層屏蔽,來使用它。首先想到的就是使用AIDL,不知道AIDL這東西的,先問度娘去吧~~在上面的代碼中,最終實現安裝的那一句話,mPm.installPackageAsUser(...),mPm是個什么東西?不難發現,IPackageManager類型,那么這個類從哪里來?搜尋一下,位於/frameworks/base/core/java/android/content/pm這個包底下,拷貝到我們工程目錄底下,包名不能變,只拷貝這一個文件的話,一定是不行了,會報其他的一些aidl找不到,相應地也拷貝過來。Android5.0中,aidl改動還是比較大的,所以要拷貝很多東西過來,還要進行一些改動...我也是花了挺久才改到他沒報錯。
最終,工程的目錄如下所示~~

那么,如何來使用它呢?

  • 1、先獲取系統服務android.os.ServiceManager,這個又是隱藏的,怎么辦?考驗Java水平的時候到了~~沒錯,用反射機制,來獲取ServiceManager類,以及該類里面的方法;
  • 2、有了服務之后,我們就要去拿到IPackageManager這個對象;
  • 3、調用IPackageManager里面的installPackage方法進行安裝;

實現代碼如下:

?
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package com.example.autoinstall;
  
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
  
import android.app.Activity;
import android.content.Intent;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.IPackageManager;
import android.content.pm.VerificationParams;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.View;
  
public class MainActivity extends Activity {
  
   @Override
   protected void onCreate(Bundle savedInstanceState) {
     super .onCreate(savedInstanceState);
     setContentView(R.layout.activity_main);
   }
  
   /**
    * Button點擊事件
    * @param view
    */
   public void install(View view)
   {
     String path = "" ;
     if (FileUtils.isSdcardReady()) {
       path = FileUtils.getSdcardPath();
     } else {
       path = FileUtils.getCachePath( this );
     }
     String fileName = path + "/AidlServerDemo.apk" ;
     File file = new File(fileName);
      
     try {
       if (!file.exists())
         copyAPK2SD(fileName);
       Uri uri = Uri.fromFile( new File(fileName));
             // 通過Java反射機制獲取android.os.ServiceManager
       Class<?> clazz = Class.forName( "android.os.ServiceManager" );
       Method method = clazz.getMethod( "getService" , String. class );
       IBinder iBinder = (IBinder) method.invoke( null , "package" );
       IPackageManager ipm = IPackageManager.Stub.asInterface(iBinder);
       @SuppressWarnings ( "deprecation" )
       VerificationParams verificationParams = new VerificationParams( null , null , null , VerificationParams.NO_UID, null );
             // 執行安裝(方法及詳細參數,可能因不同系統而異)
       ipm.installPackage(fileName, new PackageInstallObserver(), 2 , null , verificationParams, "" );
     } catch (Exception e) {
       // TODO Auto-generated catch block
       e.printStackTrace();
     }
  
   }
  
   // 用於顯示結果
   class PackageInstallObserver extends IPackageInstallObserver2.Stub {
  
     @Override
     public void onUserActionRequired(Intent intent) throws RemoteException {
       // TODO Auto-generated method stub
  
     }
  
     @Override
     public void onPackageInstalled(String basePackageName, int returnCode, String msg, Bundle extras) throws RemoteException {
       //returnCode<span style="font-family: Arial, Helvetica, sans-serif;">為1,就是安裝成功</span>
  
  
     }
   };
  
   /**
    * 拷貝assets文件夾的APK插件到SD
    *
    * @param strOutFileName
    * @throws IOException
    */
   private void copyAPK2SD(String strOutFileName) throws IOException {
     FileUtils.createDipPath(strOutFileName);
     InputStream myInput = this .getAssets().open( "AidlServerDemo.apk" );
     OutputStream myOutput = new FileOutputStream(strOutFileName);
     byte [] buffer = new byte [ 1024 ];
     int length = myInput.read(buffer);
     while (length > 0 ) {
       myOutput.write(buffer, 0 , length);
       length = myInput.read(buffer);
     }
     myOutput.flush();
     myInput.close();
     myOutput.close();
   }
}

每個版本的系統源碼里面的aidl可能會不一樣,所以具體調用的方法和參數,還得根據實際情況而定,需要去仔細閱讀Pm.java這個文件的源碼。
在其他版本可能只需要拷貝這4個文件:PackageManager.java、 IPackageDeleteObserver.aidl 、IPackagerInstallObserver.aidl、 IPackageMoveObserver.aidl
然后,還需在配置清單文件里面添加INSTALL_PACKAGE權限

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

然后把該應用的uid設置為系統級別的,在manifest標簽下添加以下屬性

?
1
android:sharedUserId= "android.uid.system"

僅僅這樣的話,還是沒法實現靜默安裝,因為系統並不認為你這個app是系統級別的應用,所以,還應該對該應用的APK進行系統簽名(注意:不是那個靜默安裝的APK,是這個實現靜默安裝程序的APK)。簽名過程如下:
總共需要三個文件:

  • 1、SignApk.jar                      %系統源碼%/out/host/linux-x86/framework/signapk.jar
  • 2、platform.x509.pem          %系統源碼%/build/target/product/security/platform.x509.pem
  • 3、platform.pk8                    %系統源碼%/build/target/product/security/platform.pk8

打開終端,執行命令 java -jar SignApk.jar platform.x509.pem platform.pk8 未簽名APK 簽名后APK,例如
java -jar SignApk.jar platform.x509.pem  platform.pk8 AutoInstall.apk AutoInstall_new.apk 

之后,把簽名過后的APK安裝到手機上,打開,點擊靜默安裝,在去程序頁看看,發現安裝成功~~

      

 

本文主要是提供了一種實現靜默安裝的思路,但是具體怎么做到兼容各個系統,舉一反三,


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM