用Xposed框架攔截Android作業系統的簡訊接收
阿新 • • 發佈:2019-01-31
簡訊接收原理
關於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自動給出迴應資訊。由於迴應的資訊被攔截處理了,所以顯示的是亂碼。從後臺的日誌可以看出完整的原來正常的簡訊資訊。