用Xposed框架拦截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 SubmitPdu 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;ichar) (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操作系统的短信接收_第1张图片
用Xposed框架拦截Android操作系统的短信接收_第2张图片

你可能感兴趣的:(Android)