Android 4.4 之后,开发者不能直接往短信数据库添加短信了,只有系统默认的短信应用才能在收件箱中添加短信,除非将自己的应用设置为默认短信应用,当这种方法不太实用,因为没有那个用户愿意修改自己的默认短信应用,即便是临时的也不靠谱。爬了一堆坑之后勉强找到一个行之有效的方法。
思路
在不改变默认短信的情况下,通过发送广播,告诉短信应用收到短信了,然后再通过xposed劫持短信内容intent里的参数,将参数修改为系统接收到短信时的参数格式,这样便能成功伪造短信了。当然,你也可以直接在发送广播时将系统接收到短信时的参数格式带进去,但这样得事先编辑好参数,而且参数内容会非常长,很麻烦,尤其是在终端直接发送广播时,这种方法就不适用了。
发送广播
下面开始具体的过程
首先,发送广播必须通过命令行来发送,直接通过代码会出现没有权限的错误,并且通过命令行发送前,必须先输入“su”来获取root权限,否则短信会接收不到广播
am broadcast -a android.provider.Telephony.SMS_DELIVER -n com.android.messaging/.receiver.SmsDeliverReceiver //后面接具体参数
我这里是先发送广播给SmsDeliverReceiver 类,因为短信最先接收到广播的就是这个类,然后才会发送给 receiver.SmsReceiver 类。参数有三个:sender、receiver、message,参数名可以自己定义,分别代表发送人手机号,接收人手机号和短信内容。
xposed劫持
finalClass aClass = XposedHelpers.findClass(
"com.android.messaging.receiver.SmsReceiver", lpparam.classLoader);
XposedBridge.hookAllMethods(aClass,"deliverSmsIntent",newXC_MethodHook() {
@Override
protected voidbeforeHookedMethod(MethodHookParam param)throwsThrowable {
super.beforeHookedMethod(param);
Log.e("hock_sms","hookAllMethods--------开始劫持接收到的短信");
Intent it = (Intent) param.args[1];
byte[] pdu =new byte[0];
try{
String sender =null;
try{
sender = it.getStringExtra("sender");
}catch(Exception e) {
e.printStackTrace();
}
String receiver =null;
try{
receiver = it.getStringExtra("receiver");
}catch(Exception e) {
e.printStackTrace();
}
String message =null;
try{
message = it.getStringExtra("message");
}catch(Exception e) {
return;
}
pdu = SmsUtils.createPduSms(sender, receiver, message);//将参数转换为pdu byte数组
}catch(Exception e) {
e.printStackTrace();
}
Intent intent =newIntent();
intent.setAction(ACTION);
Bundle bundle =newBundle();
byte[][] b =new byte[1][1];
b[0] = pdu;
bundle.putString("format","3gpp");
bundle.putSerializable("pdus", b);
bundle.putString("slot","0");
bundle.putString("phone","0");
bundle.putString("subscription","2");
intent.putExtras(bundle);
param.args[1] = intent;
}
});
短信的参数中 "format","slot","phone","subscription" 一般没什么变化,具体作用没细究,可以写死。而最重要的一个参数便是 "pdus",它是一个二维byte数组,其中每个数组代表一条短信,pdus里包含一个或多个pdu byte数组。伪造短信最核心的就是如何将发送人、接收人和短信内容转化成固定格式的byte数组了,直接采用getByte() 方法是行不通的,必须采用特定格式的方法才行。转化方式如下:
public static byte[] createPduSms(String sender,String receiver, String body) {
//Source: http://stackoverflow.com/a/12338541
//Source: http://blog.dev001.net/post/14085892020/android-generate-incoming-sms-from-within-your-app
sender =phoneTpye(sender);
receiver =phoneTpye(receiver);
byte[] pdu =null;
byte[] scBytes = PhoneNumberUtils
.networkPortionToCalledPartyBCD(receiver);
byte[] senderBytes = PhoneNumberUtils
.networkPortionToCalledPartyBCD(sender);
intlsmcs = scBytes.length;
byte[] dateBytes =new byte[7];
Calendar calendar =newGregorianCalendar();
dateBytes[0] =reverseByte((byte) (calendar.get(Calendar.YEAR)));
dateBytes[1] =reverseByte((byte) (calendar.get(Calendar.MONTH) +1));
dateBytes[2] =reverseByte((byte) (calendar.get(Calendar.DAY_OF_MONTH)));
dateBytes[3] =reverseByte((byte) (calendar.get(Calendar.HOUR_OF_DAY)));
dateBytes[4] =reverseByte((byte) (calendar.get(Calendar.MINUTE)));
dateBytes[5] =reverseByte((byte) (calendar.get(Calendar.SECOND)));
dateBytes[6] =reverseByte((byte) ((calendar.get(Calendar.ZONE_OFFSET) + calendar
.get(Calendar.DST_OFFSET)) / (60*1000*15)));
try{
ByteArrayOutputStream bo =newByteArrayOutputStream();
bo.write(lsmcs);
bo.write(scBytes);
bo.write(0x04);
bo.write((byte) sender.length());
bo.write(senderBytes);
bo.write(0x00);
try{
String sReflectedClassName ="com.android.internal.telephony.GsmAlphabet";
Class cReflectedNFCExtras = Class.forName(sReflectedClassName);
Method stringToGsm7BitPacked = cReflectedNFCExtras.getMethod("stringToGsm7BitPacked",newClass[] { String.class});
stringToGsm7BitPacked.setAccessible(true);
byte[] bodybytes = (byte[]) stringToGsm7BitPacked.invoke(null, body);
bo.write(0x00);// encoding: 0 for default 7bit
bo.write(dateBytes);
bo.write(bodybytes);
}catch(Exception e) {
try{
// try UCS-2
byte[] bodybytes =encodeUCS2(body,null);
bo.write(0x08);// encoding: 0x08 (GSM_UCS2) for UCS-2
bo.write(dateBytes);
bo.write(bodybytes);
}catch(UnsupportedEncodingException uex) {
Log.e(TAG, String.format("String '%s' encode unknow", body));
}
}
pdu = bo.toByteArray();
}catch(IOException e) {
}
returnpdu;
}
private static bytereverseByte(byteb) {
return(byte) ((b &0xF0) >>4| (b &0x0F) <<4);
}
private staticString phoneTpye(String phone){
if(TextUtils.isEmpty(phone)){
return"00000000000";
}
returnphone.length()==11?"+86"+phone:phone;
}
private static byte[] encodeUCS2(String message,byte[] header)
throwsUnsupportedEncodingException {
byte[] userData, textPart;
textPart = message.getBytes("utf-16be");
if(header !=null) {
// Need 1 byte for UDHL
userData =new byte[header.length+ textPart.length+1];
userData[0] = (byte)header.length;
System.arraycopy(header,0, userData,1, header.length);
System.arraycopy(textPart,0, userData, header.length+1, textPart.length);
}
else{
userData = textPart;
}
byte[] ret =new byte[userData.length+1];
ret[0] = (byte) (userData.length&0xff);
System.arraycopy(userData,0, ret,1, userData.length);
returnret;
}
具体怎么实现的我就不多说了,直接套用就行,只要调用createPduSms() 就可以成功转化,其他几个方法只是辅助方法而已。
结束
到此,便可以成功伪造短信了,插件安装重启手机后,就可以通过在终端发送广播或者在应用中通过命令行发送广播就可以实现伪造接收到短信了。