高版本Android如何利用反射調用系統隱藏的遠程服務攔截來電
要說攔截Android系統來電,就不得不說起在低版本的時候Android提供給開發人員使用的一個方法:endCall(),但由於Google後來考慮到對於一部手機來說,最重要的功能就是打電話了,如果這個功能隨隨便便就被人屏蔽了,安全性太差,所以在高版本的Android將這個方法屏蔽了,不再在TelephoneManager中暴露這個方法。
那麼我們下面的目標就是要想辦法調用到這個方法,當然首先我們還是需要實現一個廣播接收者,來接收電話狀態改變的廣播,這裡使用在服務中動態註冊廣播接收者的方法來實現,主要好處在於便於控制廣播接收者的生命週期,同時也能比靜態註冊有更高的許可權
private static final String PHONE = "PHONE";private static final String BOTH = "BOTH";private static final String SMS = "SMS";private TelephonyManager tm;private BlacklistDao dao;private inCommingCallReceiver callReceiver;private PhoneStateListener listener;@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onCreate() {super.onCreate();tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);dao = new BlacklistDao(this, 1);callReceiver = new inCommingCallReceiver();IntentFilter filter = new IntentFilter();filter.addAction("android.intent.action.PHONE_STATE");filter.addAction("android.provider.Telephony.SMS_RECEIVED");filter.setPriority(Integer.MAX_VALUE);listener = new PhoneStateListener() {private BlacklistItem blacklistItem;@Overridepublic void onCallStateChanged(int state, String incomingNumber) {super.onCallStateChanged(state, incomingNumber);switch (state) {case TelephonyManager.CALL_STATE_RINGING:// 如果是來電的時候blacklistItem = dao.queryItem(incomingNumber);if (blacklistItem != null) {String type = blacklistItem.getType();if ((BOTH.equals(type) || PHONE.equals(type))) {System.out.println("掛斷電話");hangUpCallFromBlacklist(incomingNumber);//掛斷電話的方法}}break;default:break;}}};registerReceiver(callReceiver, filter);// 註冊廣播接收者}
在服務的onDestroy()方法中取消註冊廣播接收者:
@Overridepublic void onDestroy() {super.onDestroy();System.out.println("關閉黑名單服務");unregisterReceiver(callReceiver);// 取消監聽tm.listen(listener, PhoneStateListener.LISTEN_NONE);// listener = null;}
內部類廣播接收者,用於接收電話狀態改變的廣播:
/** * 監聽來電 * * @author Alex * */private class inCommingCallReceiver extends BroadcastReceiver {private BlacklistItem blacklistItem;@Overridepublic void onReceive(Context context, Intent intent) {if ("android.intent.action.PHONE_STATE".equals(intent.getAction())) {// 如果收到的是電話狀態的變化tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);}}}
下面我們重點來看hangUpCallFromBlacklist(incomingNumber);這個實現掛斷來電的方法,其中incomingNumber是來電的電話號碼。按照我的慣例,還是從安卓系統的源碼入手,由於endCall方法原來是在TelephoneManager中的,所以我們不妨從TelephoneManager的執行個體化方法getSystemService入手:
我們可以發現這個方法是定義在ContextWrapper中的,而ContextWrapper又是繼承自Context,我們不妨進入到Context的源碼中去一探究竟:
在Context中,我們只找到了一個getSystemService的抽象方法,那麼如何去找實現方法呢,在java中,抽象類別的實作類別一般名字都是在抽象類別名後面加上一個"Impl",於是我們去搜尋源碼中的ContextImpl.java,找到之後開啟發現:
我們發現getSystemService實際上返回了一個ServiceFetcher對象的一個getService方法的結果,我們來看看ServiceFetcher的getService方法:
在這裡我們可以看到,在定義了一個static塊中,註冊了很多不同的service服務,而這些gerService方法都是由ServiceManager來調用的,返回值是一個IBinder對象。接下來我們可以在檔案的匯入包的部分找到ServiceManager的位置是在android/os下的:
由於getService返回的是一個IBinder對象,我們只要找到這個getService方法的實現,就可以傳入TELEPONY_SERVICE從而拿到真正的TelephonyManager所代理的那個遠端服務繫結對象,從而調用隱藏在其中的endCall方法。
<喎?http://www.bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+0NLUy7XEysejrNTaU2VydmljZU1uYWdlci5qYXZh1tCjrM7Sw8fV0rW9wctnZXRTZXJ2aWNlt723qLXEyrXP1jo8L3A+CjxwPjxpbWcgc3JjPQ=="http://www.2cto.com/uploadfile/Collfiles/20140924/20140924090000134.png" alt="\">
我們可以發現,ServiceManager這個類也是一個隱藏類,我們無法在我們的代碼中直接拿到這個類來調用其中的getService方法來擷取IBinder對象,那麼我們要如何做呢?
這裡就只有使用反射的方法來處理:
Class clazz = CallSmsSafeService.class.getClassLoader().loadClass("android.os.ServiceManager");Method method = clazz.getMethod("getService", String.class);IBinder binder = (IBinder) method.invoke(null, TELEPHONY_SERVICE);通過上面的反射做法,我們拿到了對應於TelephonyManager的IBinder對象,下面我們需要做的利用aidl來調用遠程方法,既然是使用的TelephonyManager的IBinder對象,我們再進入到TelephonyManager的源碼中去看看:
我們發現,在TelephonyManager中,類似於getCallState()這類的方法基本都返回的是getITelephony()的返回值調用的方法,那麼這個getITelephy()是什麼呢:
我們發現,返回的實際上是一個ITelephony對象,而且是以一種調用遠程服務方法的形式返回的;
我們在檔案的頭部找到ITelephony的位置:
開啟上面的目錄,我們發現ITelephony是一個aidl檔案,進入其中,我們可以找到endCall方法:
由於在ITelephony.aidl的頭部有如下資訊:
我們想要通過aidl來調用遠程服務telephony的方法endCall(),我們需要將Telehpony.aidl拷貝到我們工程中建立的com.android.internal.telephony包中,同時將android.telephony.NeighboringCellInfo.aidl檔案拷貝到工程中建立的android.telephony包中,這樣在gen目中下就會自動產生一個對應的ITelephony.java檔案。至此,我們就可以使用下面的語句來調用遠程服務的endCall方法:
ITelephony.Stub.asInterface(binder).endCall();
最後,不要忘記在資訊清單檔中加入對應的許可權: