標籤:xposed架構 sms接收流程 android
簡訊接收原理
關於Android作業系統簡訊的接收和發送流程的文章網上有一大堆,但是真正說得很清楚的不多,這篇blog寫得不錯。其實要想真正弄懂Android作業系統簡訊的流程,還是Linus的那句話: Read the fucking source code.呵呵
在Android作業系統中,大部分敏感資訊的傳遞過程都是基於binder機制的,當然SMS也不例外。對於SMS的接收流程的描述從Framework層和Application層這兩個層面進行介紹。
- Framework層
當簡訊到達Framework層後,會首先啟動RIL中的RILReceiver去接收簡訊,在RILReceiver中使用LocalSocket去讀簡訊,然後把讀到的簡訊放在一個Parcel對象中,然後調用processResponse(Parcel p)去處理,processResponse()中調用processUnsolicited(Parcel p)處理。
Android作業系統對應的源碼如下:
class RILReceiver implements Runnable { byte[] buffer; RILReceiver() { buffer = new byte[RIL_MAX_COMMAND_BYTES]; } @Override public void run() { int retryCount = 0; try {for (;;) { LocalSocket s = null; LocalSocketAddress l; try { s = new LocalSocket(); l = new LocalSocketAddress(SOCKET_NAME_RIL, LocalSocketAddress.Namespace.RESERVED); s.connect(l); } catch (IOException ex){ try { if (s != null) { s.close(); } } catch (IOException ex2) { //ignore failure to close after failure to connect } // don‘t print an error message after the the first time // or after the 8th time if (retryCount == 8) { Rlog.e (RILJ_LOG_TAG, "Couldn‘t find ‘" + SOCKET_NAME_RIL + "‘ socket after " + retryCount + " times, continuing to retry silently"); } else if (retryCount > 0 && retryCount < 8) { Rlog.i (RILJ_LOG_TAG, "Couldn‘t find ‘" + SOCKET_NAME_RIL + "‘ socket; retrying after timeout"); } try { Thread.sleep(SOCKET_OPEN_RETRY_MILLIS); } catch (InterruptedException er) { } retryCount++; continue; } retryCount = 0; mSocket = s; Rlog.i(RILJ_LOG_TAG, "Connected to ‘" + SOCKET_NAME_RIL + "‘ socket"); int length = 0; try { InputStream is = mSocket.getInputStream(); for (;;) { Parcel p; length = readRilMessage(is, buffer); if (length < 0) { // End-of-stream reached break; } p = Parcel.obtain(); p.unmarshall(buffer, 0, length); p.setDataPosition(0); //Rlog.v(RILJ_LOG_TAG, "Read packet: " + length + " bytes"); processResponse(p); p.recycle(); } } catch (java.io.IOException ex) { Rlog.i(RILJ_LOG_TAG, "‘" + SOCKET_NAME_RIL + "‘ socket closed", ex); } catch (Throwable tr) { Rlog.e(RILJ_LOG_TAG, "Uncaught exception read length=" + length + "Exception:" + tr.toString()); } Rlog.i(RILJ_LOG_TAG, "Disconnected from ‘" + SOCKET_NAME_RIL + "‘ socket"); setRadioState (RadioState.RADIO_UNAVAILABLE); try { mSocket.close(); } catch (IOException ex) { } mSocket = null; RILRequest.resetSerial(); // Clear request list on close clearRequestList(RADIO_NOT_AVAILABLE, false); }} catch (Throwable tr) { Rlog.e(RILJ_LOG_TAG,"Uncaught exception", tr); } /* We‘re disconnected so we don‘t know the ril version */ notifyRegistrantsRilConnectionChanged(-1); } }
- Application層
在App層,PrivilegedSmsReceiver在接收到簡訊來了的廣播之後,由SmsReceiver啟動SmsReceiverService來做具體的處理。接收簡訊的action為SMS_RECEIVED_ACTION,所以調用handleSmsReceived()處理,使用insertMessage()將簡訊插入資料庫,這裡首先會判斷簡訊是否為CLASS_0簡訊,如果是則直接顯示,不插入資料庫。如果不是則會進行訊息的替換或者插入資料庫,替換使用了SmsMessaged的isReplace()方法判斷,原則是簡訊協議標識mProtocolIdentifier的判斷。如果既不是CLASS_0簡訊也不需要替換,則將簡訊插入資料庫,然後使用MessagingNotification在StatusBar做一個notification,通知使用者簡訊來了。這裡就不附上Android作業系統的相關代碼了,感興趣的,可以自己在Grepcode或者AndroidXRef上自己查看。
編碼實現
上面已經弄清楚原理了,研究Android作業系統對應部分的源碼,不難找出相應的解決方案。這裡選擇一種比較簡單的hook,用xposed架構進行攔截。當然也是經過多次失敗嘗試後找到的一種比較有效方法。思路很簡單,就是針對簡訊接收流程中調用的函數,攔截該函數,擷取接收端的資訊流,對資訊流進行按位異或處理。下面給出核心部分的源碼:
package com.example.receiver;import de.robv.android.xposed.IXposedHookLoadPackage;import de.robv.android.xposed.XC_MethodHook;import de.robv.android.xposed.XposedBridge;import de.robv.android.xposed.XposedHelpers;import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;/** * @author Li Jiansong * @date:2015-7-27 上午11:15:48 * @version : * *Server端簡訊接收端的攔截,經過多次嘗試,最終有效是下面的方案 *攔截SmsMessage的內部類PduParser的getUserDataUCS2方法,該方法傳回型別為String *String getUserDataUCS2(int byteCount) * */public class RecvHooker implements IXposedHookLoadPackage{ private static final String TARGET_PACKAGE = "com.android.mms"; @Override public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable { // TODO Auto-generated method stub //XposedBridge.log("--------loaded app:"+lpparam.packageName);// if(!lpparam.packageName.equals("com.android.mms"))// return; if (!TARGET_PACKAGE.equals(lpparam.packageName)) { // XposedBridge.log("SendRawSMSMod: ignoring package: " + // lpparam.packageName); return; }// /**// * 攔截SmsMessage的內部類PduParser的getUserData方法,// * byte[] getUserData(){}// * 該方法不帶參數// */// final Class<?> recvClazz=XposedHelpers.findClass("com.android.internal.telephony.gsm"// +".SmsMessage$PduParser",lpparam.classLoader);// // XposedBridge.log("==========開始進入攔截----");// // XposedHelpers.findAndHookMethod(recvClazz, "getUserData",// new XC_MethodHook(){// // @Override// protected void afterHookedMethod(MethodHookParam param)// throws Throwable {// // TODO Auto-generated method stub// //super.beforeHookedMethod(param);// // XposedBridge.log("=========getUserData被調用");// byte[] recvByteSms=(byte[]) param.getResult();// String strRecvSms="";// strRecvSms+=new String(recvByteSms);// // //byte[] srtbyte = strRecvSms.getBytes();// //String lsx="6666666666666666666666666666666666";// param.setResult(strRecvSms.getBytes());// //SmsMessage msg=new SmsMessage();// // XposedBridge.log("========接收的簡訊內容為:"+strRecvSms);// return;// }// // // }); //XposedBridge.log("-------開始攔截");// findAndHookMethod("com.android.internal.telephony.gsm.SmsMessage",lpparam.classLoader,// "getSubmitPdu",String.class,// String.class, String.class, boolean.class, byte[].class,// int.class, int.class, int.class, new XC_MethodHook(){// // /**// * 攔截SmsMessage的getSubmitPdu方法,其有5個參數// * String scAddress,// * String destinationAddress, // * String message,// * boolean statusReportRequested, // * byte[] header// * // */// // /**// * Get an SMS-SUBMIT PDU for a destination address and a message// *// * @param scAddress Service Centre address. Null means use default.// * @return a <code>SubmitPdu</code> containing the encoded SC// * address, if applicable, and the encoded message.// * Returns null on encode error.// */// @Override// protected void beforeHookedMethod(MethodHookParam param)// throws Throwable {// // TODO Auto-generated method stub// // super.beforeHookedMethod(param);// XposedBridge.log("getSubmitPdu被調用");// if(param.args[2]==null){// return;// }// String message=(String) param.args[2];// XposedBridge.log("======before:SMS message:"+message);// SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// message+=df.format(new Date());// XposedBridge.log("========after SMS message:"+message);// SubmitPdu rawPdu=new SubmitPdu();// //StringTokenizer stringTokenizer=new StringTokenizer(string, delimiters, returnDelimiters)// param.setResult(rawPdu);// XposedBridge.log("=============hook替換成功");// // return;// // }// });// final Class<?> recvClazz=XposedHelpers.findClass("com.android.internal.telephony.gsm"// +".SmsMessage$PduParser",lpparam.classLoader); XposedBridge.log("=========開始進入攔截"); XposedHelpers.findAndHookMethod("com.android.internal.telephony.gsm"+".SmsMessage$PduParser", lpparam.classLoader,"getUserDataUCS2",int.class, new XC_MethodHook(){ /** * Interprets the user data payload as UCS2 characters, and * decodes them into a String. * * @param byteCount the number of bytes in the user data payload * @return a String with the decoded characters */ /** * 攔截SmsMessage的內部類PduParser的getUserDataUCS2方法,該方法傳回型別為String * String getUserDataUCS2(int byteCount) * */ @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { // TODO Auto-generated method stub // super.afterHookedMethod(param); try { String strMms=(String) param.getResult(); XposedBridge.log("=========before:"+strMms); //String after="666666666666666"; char[] recvArray=strMms.toCharArray(); for(int i=0;i<recvArray.length;i++){ recvArray[i]=(char) (recvArray[i]^20000); } String enCodeSms=new String(recvArray); param.setResult(enCodeSms); XposedBridge.log("=========after:"+param.getResult()); //return; } catch (Exception e) { // TODO Auto-generated catch block //e.printStackTrace(); XposedBridge.log(e); } } }); }}
測試
下面給出一個測試例子,向10086發送一條簡訊,10086自動給出回應資訊。由於回應的資訊被攔截處理了,所以顯示的是亂碼。從背景日誌可以看出完整的原來正常的簡訊資訊。
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
用Xposed架構攔截Android作業系統的簡訊接收