上一篇文章发现Asterisk板卡无法接收彩信之后,我就在寻找在不购买新的设备(如GPRS猫等)的情况下能使电脑接收到彩信的方法,毕竟我要做的只是想用彩信在自己架设的微博平台上发个微博而已。
重新分析彩信的收发过程,发送彩信的手机首先将彩信内容打包成mime格式,然后通过标准的http协议封装,指定http包的host域为彩信中心,使用post方法,直接将这个Http包发送给彩信网关(通常是10.0.0.172:80),当然彩信网关和彩信中心地址是在手机上用户自己设置的,然后彩信网关根据http包里面的彩信中心的url,查找dns后把这个http包送到彩信中心(这段是我猜的),彩信中心收到后发个短信给目标手机,主要包含一个url(当然还有其它内容),告诉目标手机:你有一条彩信,自己到url这个地方来取。然后目标手机构造一个标准的http请求包,其中Host域填写的是url的host,然后将这个包发送给彩信网关,由彩信网关将http包发送给彩信中心,获取响应后再把数据返回给目标手机,目标手机接到的数据时一个http响应包,包含mime数据,内容几乎和发送手机构造的mime数据一样,但略有不同,比如from字段的值貌似就是数据包走彩信中心或者彩信网关的时候加上去的,刚发送的时候抓包并没有发现from字段。
把tcpdump弄到手机上,然后使用adb shell登录进去抓了几个包,然后再pull出来用wirkshark打开截图如下:
发送:
接收:
既然接收端手机根据url直接通过标准http协议请求内容,所以我就想可否使用asterisk板卡接收pdu,然后我通过我的手机当服务器,把彩信内容从mmsc里拉出来,再通过我的手机上传到微博网站。
首先是从pdu中提取url的问题,把一个Pdu使用winhex打开,发现url地址非常明显,以http开头并使用/0结尾,很容易就提取出来。
其次是如何提取url内容的问题。
首先我直接在浏览器里打开,发现找不到地址,然后ping那个主机,也就是11.75.9.6,同时ping不通。这也算是意料之内,处于彩信网关之后的gprs网当然通过internet访问不到。
其次我尝试ping彩信网关,10.0.0.172,发现也ping不通,说明彩信网关只能通过手机的网络访问。
使用手机,进入adb shell,ping网关依然ping不通。然后发现接入点选的是cmnet,换到cmwap发现ip地址变到了10.*.*.*,这时候ping彩信网关能ping通,ping彩信网关后面的host也就是(11.75.9.6)也能ping通。说明彩信只能使用wap网进行发送。
编写程序使用java的http请求类直接请求那个url ,结果什么都没返回,猜测请求的并不是标准的http包。
然后根据wireshark里的包自己构造一个请求包,并在java里使用socket连接彩信网关80端口并发送,的确有结果返回,但返回的是401,即鉴权失败。
为什么会这样?
请求包里没有任何鉴权信息,是个单纯的http请求包而已,为什么mmsc不让我提取信息?
猜测1:当前ip地址和手机号有一个绑定关系,mmsc发现你的ip地址和收信人不对应,就不让你提取。但这个想法有个问题,就是移动互联网你的ip会随时变,这个绑定关系也就随时变,变了之后mmsc根据ip地址鉴权想想就会很麻烦。
猜测2:在进行抓包的时候神秘的发现,即使在wap网下,在短信接收和发送的时候网络要进行切换,从wap还是切换到wap,ip地址有一个改变的过程。也许只有在切换过的ip地址才有权进行彩信内容的提取。如何切换的我把文本复制下来了:
# ifconfig rmnet0
rmnet0: ip 10.58.198.145 mask 255.255.255.252 flags [up broadcast running multicast]
# ifconfig rmnet0
rmnet0: ip 10.58.198.145 mask 255.255.255.252 flags [up broadcast running multicast]
# ifconfig rmnet0
rmnet0: ip 10.58.198.145 mask 255.255.255.252 flags [down broadcast multicast]
# ifconfig rmnet0
rmnet0: ip 10.58.198.145 mask 255.255.255.252 flags [down broadcast multicast]
# ifconfig rmnet0
rmnet0: ip 10.86.10.72 mask 255.255.255.252 flags [up broadcast running multicast]
# ifconfig rmnet0
rmnet0: ip 10.86.10.72 mask 255.255.255.252 flags [up broadcast running multicast]
# ifconfig rmnet0
rmnet0: ip 10.86.10.72 mask 255.255.255.252 flags [up broadcast running multicast]
# ifconfig rmnet0
rmnet0: ip 10.86.10.72 mask 255.255.255.252 flags [up broadcast running multicast]
我打开wap网(用wap当网络接入点),然后让同学给我发彩信,我就不断的用ifconfig查看手机网络接口情况。我不懂电信网的东西,从我的角度来说,我只能看到发现我的rmnet0接口的ip发生了变化,接收完彩信之后又变化了回去,所以我就猜测可否是这个变化后的ip地址才能接收彩信呢?
但这个想法我没法验证,因为我不知道这个网络的切换到底是什么,并且也不知道如何显示的控制手机进行这个切换。
要写的就是这么多,用另一个手机接收板卡的彩信我暂时没有走通。
最后附上根据pdu提取url和构造http包的java代码,如果有需要的拿去用就行:
package roger.mmsTest.bLL; import java.io.*; import java.net.*; public class MmsTools { private static MmsTools single; private static String MMSGATEWAY="10.0.0.172"; public static MmsTools getInstance() { if(single==null) { single=new MmsTools(); } return single; } private MmsTools() { } public String getMMSXml(String pdu) { StringBuffer bf=new StringBuffer(); try { Socket socket=new Socket("10.222.26.7",8080); InputStreamReader in= new InputStreamReader(socket.getInputStream()); PrintWriter out=new PrintWriter(socket.getOutputStream()); String reqpkt=getMMSReqPkt(pdu); out.print(reqpkt); out.flush(); System.out.print(reqpkt); System.out.flush(); while(true) { int c=in.read(); if(c==-1) { break; } bf.append((char)c); } } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return bf.toString(); } private String getMMSReqPkt(String pdu) { StringBuffer bf=new StringBuffer(); String url=getPDUUrl(pdu); System.out.println(url); //bf.append("GET http://10.222.26.7:8080/was/bKjDhoPf4dRA HTTP/1.1\r\n"); bf.append("GET "); bf.append(url); bf.append(" HTTP/1.1\r\n"); bf.append("Accept: */*, application/vnd.wap.mms-message, application/vnd.wap.sic\r\n"); bf.append("x-wap-profile: http://www.htcmms.com.tw/Android/Common/DesireZ/ua-profile.xml\r\n"); bf.append("Accept-Language: en-GB, en-US\r\n"); //bf.append("Host: 10.222.26.7:8080\r\n"); bf.append("Host: "); bf.append(getHostFromURL(url)); bf.append("\r\n"); bf.append("Connection: Keep-Alive\r\n"); bf.append("User-Agent: HTC_DesireZ_A7272/1.0\r\n"); bf.append("\r\n"); return bf.toString(); } private String getHostFromURL(String url) { int start=url.indexOf(":"); start+=3; int end=url.indexOf("/",start); return url.substring(start,end); } private String getPDUUrl(String pdu) { String pdustr=getPDUString(pdu); StringBuffer sb=new StringBuffer(); boolean start=false; for(int i=0;i<=pdustr.length()-1;i++) { char c=pdustr.charAt(i); // System.out.println(c); if(c=='h' && i+3<=pdustr.length()-1 && pdustr.charAt(i+1)=='t' && pdustr.charAt(i+2)=='t' && pdustr.charAt(i+3)=='p') { start=true; } if(start==true) { if(c=='\0') { break; } else { sb.append(c); } } } // int end=pdustr.indexOf("\0", start); // String url=pdustr.substring(start,end); // return url; return sb.toString(); } private String getPDUString(String pdu) { StringBuffer bf=new StringBuffer(); for(int i=0;i<=pdu.length()-2;i+=2) { bf.append((char)(getIntegerFrom2Char(pdu.charAt(i),pdu.charAt(i+1)))); } return bf.toString(); } private int getIntegerFrom2Char(char a,char b) { return Integer.valueOf(String.valueOf(a),16)*16+Integer.valueOf(String.valueOf(b),16); } }
ps:我能吐槽为啥这个系列第二篇阅读量那么高么。。。。