短信接收原理
关于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) {
}
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) {
break;
}
p = Parcel.obtain();
p.unmarshall(buffer, 0, length);
p.setDataPosition(0);
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();
clearRequestList(RADIO_NOT_AVAILABLE, false);
}} catch (Throwable tr) {
Rlog.e(RILJ_LOG_TAG,"Uncaught exception", tr);
}
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 {
if (!TARGET_PACKAGE.equals(lpparam.packageName)) {
return;
}
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 {
try {
String strMms=(String) param.getResult();
XposedBridge.log("=========before:"+strMms);
char[] recvArray=strMms.toCharArray();
for(int i=0;i<recvArray.length;i++){
recvArray=(char) (recvArray^20000);
}
String enCodeSms=new String(recvArray);
param.setResult(enCodeSms);
XposedBridge.log("=========after:"+param.getResult());
} catch (Exception e) {
XposedBridge.log(e);
}
}
});
}
}
测试
下面给出一个测试例子,向10086发送一条短信,10086自动给出回应信息。由于回应的信息被拦截处理了,所以显示的是乱码。从后台的日志可以看出完整的原来正常的短信信息。
|