instabtbu-详解上网登录
最开始的时候我们的上网登录是通过HTTP的POST实现的. 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 public String POST(String url,String postdata)
{
String result = "";
HttpPost hPost=new HttpPost(url);
List params=new ArrayList();
String posts[]=postdata.split("&");
String posts2[];
int i;
for(i=0;i<posts.length;i++)
{
posts2=posts[i].split("=");
if(posts2.length==2)
params.add(new BasicNameValuePair (posts2[0],posts2[1]));
else params.add(new BasicNameValuePair (posts2[0],""));
}
try{
HttpEntity hen=new UrlEncodedFormEntity(params,"gb2312");
hPost.setEntity(hen);
myClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 30000);
//请求超时
myClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 30000);
//读取超时
HttpResponse hResponse;
hResponse = myClient.execute(hPost);
if(hResponse.getStatusLine().getStatusCode()==200)
{
result = EntityUtils.toString(hResponse.getEntity());
//result = new String(result.getBytes("ISO_8859_1"),"gbk");
//转码
}
}catch(Exception e){
if(dialog2.isShowing())dialog2.dismiss();
show("连接BTBU失败。n请确认信号良好再操作。");
}
return(result);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88package hk.ypw.instabtbu;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.http.HttpVersion;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;
public class SSLSocketFactoryEx extends SSLSocketFactory {
public static DefaultHttpClient getNewHttpClient() {
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
SSLSocketFactoryEx sf = new SSLSocketFactoryEx(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", sf, 443));
ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
return new DefaultHttpClient(ccm, params);
} catch (Exception e) {
return new DefaultHttpClient();
}
}
SSLContext sslContext = SSLContext.getInstance("TLS");
public SSLSocketFactoryEx(KeyStore truststore)
throws NoSuchAlgorithmException, KeyManagementException,
KeyStoreException, UnrecoverableKeyException {
super(truststore);
TrustManager tm = new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {return null;}
public void checkClientTrusted(
java.security.cert.X509Certificate[] chain, String authType)
throws java.security.cert.CertificateException {}
public void checkServerTrusted(
java.security.cert.X509Certificate[] chain, String authType)
throws java.security.cert.CertificateException {}
};
sslContext.init(null, new TrustManager[] { tm }, null);
}
public Socket createSocket(Socket socket, String host, int port,boolean autoClose) throws IOException, UnknownHostException {
return sslContext.getSocketFactory().createSocket(socket, host, port,autoClose);
}
public Socket createSocket() throws IOException {
return sslContext.getSocketFactory().createSocket();
}
}1
2
3
4HttpPost hPost = /*跟之前一样*/;
HttpClient httpclient = SSLSocketFactoryEx.getNewHttpClient();
HttpResponse hResponse = httpclient.execute(hPost);
instabtbu2上网登录原理:
电脑端的原理就和HTTP完全不沾边了. 电脑端需要的前置技能有:十六进制数据处理,转义,TCP通信协议,UDP通信协议,CRC16校验,RSA加密.
电脑端登录的过程比较复杂.
建立TCP连接
首先我们要与192.168.8.8的21098端口建立TCP连接. 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public boolean connect() {
boolean f = false;
try{
client = new Socket("192.168.8.8",21098);
outputStream=client.getOutputStream();
inputStream = client.getInputStream();
client.setSoTimeout(30000);
f=true;
}catch (Exception e){
e.printStackTrace();
print("错误:"+e.getMessage());
}
return f;
}
读取返回数据:
读取方式很简单,我们通过刚才client得到的inputStream即可读出数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public char[] read(){
char[] read2=null;
try{
byte[] buffer = new byte[1024];
int len = inputStream.read(buffer);
read2 = new char[len];
int j;
for(j=0;j<len;j++)read2[j]=(char)buffer[j];
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
print("错误:"+e.getMessage());
}
print("读到数据:"+charsToHexString(read2)+",长度:"+read2.length);
return read2;
}
数据包结构
BTBU网络服务器的数据包结构是这样的:7E | 命令 | 长度 低位 | 长度 高位 | 数据 | CRC 低位 | CRC 高位 | 7E |
7E | 11 | 00 | 00 | 54 | 01 | 7E |
好,现在我们将这个数据(7E 11 00 00 54 01 7E)发送给服务器,服务器返回给我们的就是这样的数据(这是例子) 7E 11 10 00 E7 55 98 4A 70 7D 3E 74 EE 44 95 E4 88 FA F5 58 37 E1 D1 7E 7E 11 10 00 B7 12 5F E6 60 5B A7 69 3E 18 06 16 9D 57 CC 81 9C 93 7E 这时候你发现了一个问题,长度10 00明明是16位,第一行的数据却出现了17位,这是怎么回事呢? 其实这只是数据的转义.由于我们的数据中可能包含7E,而它的意义是数据的结束,如果数据中出现了7E,我们必须对它进行转义. E7 55 98 4A 70 7D 3E 74 EE 44 95 E4 88 FA F5 58 37中的7D 3E实际上就是7E转义以后的数据.
反转义
反转义规则: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public char[] fanzhuan(char[] buf){
char[] rec = new char[1024];
int i,j=0;
for(i=0;i<buf.length;i++)
{
if(buf[i]==0x7D){
rec[j++]=(char) (buf[++i]^0x40);
}else rec[j++]=buf[i];
}
char[] rec2 = new char[j];
for(i=0;i<j;i++)rec2[i]=rec[i];
return rec2;
}
构建数据包
OK我们理解转义之后,就能解析出服务器给我们发的固定长度的16字节数据.然后,我们需要构建出登录数据包来. 登录数据包(82字节)格式如下:
- 23字节用户名
- 23字节密码
- 20字节IP
- 16字节验证数据 验证数据就是服务器发过来的那一串.
那么这一部分用java生成登录数据包就是这样的: 通过这样的方式,我们可以获得登录数据包:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public char[] user(String number ,String password){
char[] msg = new char[82];
String ip = getIp();
try{
int i;
for(i=0;i<number.length();i++)msg[i]=number.charAt(i);
for(i=0;i<password.length();i++)msg[i+23]=password.charAt(i);
for(i=0;i<ip.length();i++)msg[i+23+23]=ip.charAt(i);
for(i=0;i<verify.length;i++)msg[i+23+23+20]=verify[i];
}catch(Exception e)
{
e.printStackTrace();
print("错误:"+e.getMessage());
}
return msg;
}1
2char[] msg = user(editText_num.getText().toString(), editText_psw.getText().toString());
RSA加密
数据包生成之后,我们需要对它进行RSA加密,RSA加密是一种非对称加密方式,具体代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33public char[] jiami(char[] data){
PublicKey key = getPublicKey();
Cipher cipher;
byte[] buf = null;
try {
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
buf=cipher.doFinal(charToByte(data));
}catch(Exception e)
{
e.printStackTrace();
print("错误:"+e.getMessage());
}
return byteToChar(buf);
}
public static PublicKey getPublicKey() {
PublicKey publicKey = null;
String modulus = "EA32BA96FCC395CC766EAFFEBC8EFE1F0886E99504CB7C3877548698793446BA7BA07CF915DBB5BE69337A3697B4DC354DA78ABAE17ED33EDAD87674D0D0D2B54D549E566AF0C016C276F327ADC3D4EE06E64EBC608E4AC9E3CE63416C246FD57DBEA8ADA036AA683F9A812CD8ECA705E019D6A943121CDDB2CF9BF1BCD0F5F9";
String publicExponent = "65537";
BigInteger m = new BigInteger(modulus,16);
BigInteger e = new BigInteger(publicExponent);
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(m, e);
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
publicKey = keyFactory.generatePublic(keySpec);
} catch (Exception e1) {
e1.printStackTrace();
}
return publicKey;
}
封装数据包
加密数据包之后,就可以封装成服务器需要的那个格式了:7E | 命令 | 长度 低位 | 长度 高位 | 数据 | CRC 低位 | CRC 高位 | 7E |
命令是1(msg=feng(msg, 0x01);),具体封装方式可以看到下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public char[] feng(char[] buf,int cmd){
char[] rec = new char[1024];
char[] jiami = jiami(buf);
char[] crc = new char[jiami.length+3];
char[] rec2 = new char[crc.length+4];
rec[0]=0x7E;
rec[1]=(char)cmd;
int len = jiami.length;
rec[2]=(char) (len&0xFF);
rec[3]=(char) (len>>8);
int i;
for(i=0;i<jiami.length;i++)rec[i+4]=jiami[i];
for(i=1;i<jiami.length+4;i++)crc[i-1]=rec[i];
int c = getCRC16(charToByte(crc));
rec[crc.length+1]=(char) (c&0xFF);
rec[crc.length+2]=(char) (c>>8);
rec[crc.length+3]=0x7E;
for(i=0;i<crc.length+4;i++)rec2[i]=rec[i];
return rec2;
}
CRC-16校验
其中有用到getCRC16()这个命令,多项式它是这样的X16+X15+X2+1,下面是查表法获得CRC-16的程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29public int getCRC16(byte[] bytes) {
int[] table = {0x0000,0x8005,0x800F,0x000A,0x801B,0x001E,0x0014,0x8011,0x8033,0x0036,0x003C,0x8039,0x0028,0x802D,0x8027,
0x0022,0x8063,0x0066,0x006C,0x8069,0x0078,0x807D,0x8077,0x0072,0x0050,0x8055,0x805F,0x005A,0x804B,0x004E,0x0044,
0x8041,0x80C3,0x00C6,0x00CC,0x80C9,0x00D8,0x80DD,0x80D7,0x00D2,0x00F0,0x80F5,0x80FF,0x00FA,0x80EB,0x00EE,0x00E4,
0x80E1, 0x00A0,0x80A5,0x80AF,0x00AA,0x80BB,0x00BE,0x00B4,0x80B1,0x8093,0x0096,0x009C,0x8099,0x0088,0x808D,0x8087,
0x0082,0x8183,0x0186,0x018C,0x8189,0x0198,0x819D,0x8197,0x0192,0x01B0,0x81B5,0x81BF,0x01BA,0x81AB,0x01AE,0x01A4,
0x81A1,0x01E0,0x81E5,0x81EF,0x01EA,0x81FB,0x01FE,0x01F4,0x81F1,0x81D3,0x01D6,0x01DC,0x81D9,0x01C8,0x81CD,0x81C7,
0x01C2,0x0140,0x8145,0x814F,0x014A,0x815B,0x015E,0x0154,0x8151,0x8173,0x0176,0x017C,0x8179,0x0168,0x816D,0x8167,
0x0162,0x8123,0x0126,0x012C,0x8129,0x0138,0x813D,0x8137,0x0132,0x0110,0x8115,0x811F,0x011A,0x810B,0x010E,0x0104,
0x8101,0x8303,0x0306,0x030C,0x8309,0x0318,0x831D,0x8317,0x0312,0x0330,0x8335,0x833F,0x033A,0x832B,0x032E,0x0324,
0x8321,0x0360,0x8365,0x836F,0x036A,0x837B,0x037E,0x0374,0x8371,0x8353,0x0356,0x035C,0x8359,0x0348,0x834D,0x8347,
0x0342,0x03C0,0x83C5,0x83CF,0x03CA,0x83DB,0x03DE,0x03D4,0x83D1,0x83F3,0x03F6,0x03FC,0x83F9,0x03E8,0x83ED,0x83E7,
0x03E2,0x83A3,0x03A6,0x03AC,0x83A9,0x03B8,0x83BD,0x83B7,0x03B2,0x0390,0x8395,0x839F,0x039A,0x838B,0x038E,0x0384,
0x8381,0x0280,0x8285,0x828F,0x028A,0x829B,0x029E,0x0294,0x8291,0x82B3,0x02B6,0x02BC,0x82B9,0x02A8,0x82AD,0x82A7,
0x02A2,0x82E3,0x02E6,0x02EC,0x82E9,0x02F8,0x82FD,0x82F7,0x02F2,0x02D0,0x82D5,0x82DF,0x02DA,0x82CB,0x02CE,0x02C4,
0x82C1,0x8243,0x0246,0x024C,0x8249,0x0258,0x825D,0x8257,0x0252,0x0270,0x8275,0x827F,0x027A,0x826B,0x026E,0x0264,
0x8261,0x0220,0x8225,0x822F,0x022A,0x823B,0x023E,0x0234,0x8231,0x8213,0x0216,0x021C,0x8219,0x0208,0x820D,0x8207,
0x0202};
int i = 0;
int len = bytes.length;
int crc = 0;
while(i<len){
int index = (crc>>8)^bytes[i++];
if(index<0)index+=256;
crc = ((crc&0xFF)<<8) ^ table[index];
}
return crc;
}
转义
封装好了之后,我们还需要转义一次,以免出现7E: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public char[] zhuan(char[] buf){
char[] rec = new char[1024];
int i=0,j=0;
for(i=0;i<buf.length;i++)
{
if((buf[i]==0x7D||buf[i]==0x7E)&&i!=0&&i!=buf.length-1){
rec[j++]=0x7D;
rec[j++]=(char) (buf[i]^0x40);
}else{
rec[j++]=buf[i];
}
}
char[] rec2 = new char[j];
for(i=0;i<j;i++)rec2[i]=rec[i];
return rec2;
}
发送
转义好了之后,就可以发送了: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public boolean send(char[] buf){
boolean f = false;
try {
byte[] send = charToByte(buf);
outputStream.write(send);
outputStream.flush();
f=true;
}catch(Exception e)
{
e.printStackTrace();
print("错误:"+e.getMessage());
}
print("发送数据:"+charsToHexString(buf)+",长度:"+buf.length);
return f;
}
解封装
那么发送过去登录数据之后,就要接收数据,不过接收到的数据依然是封装好的,我们需要对其解封装:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public char[] jiefeng(char[] buf){
char[] rec = null;
try{
int len=0;
int buf2=buf[2],buf3=buf[3];
if(buf2>256)buf2=0x100-0x10000+buf2;
if(buf3>256)buf3=0x100-0x10000+buf3;
len=buf2+buf3*0x100;
buf=fanzhuan(buf);
rec = new char[len];
for(int i=0;i<len;i++)rec[i]=buf[i+4];
if(buf[1]==1){
rec=jiemi(rec);
print("解密数据:"+charsToHexString(rec)+",长度:"+rec.length);
remain=rec;
}else{
recString = new String(charToByte(rec), "GBK");
rec=null;
}
}catch(Exception e){
e.printStackTrace();
}
return rec;
}
RSA解密
RSA解密如下: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public char[] jiemi(char[] data){
PublicKey key = getPublicKey();
Cipher cipher;
byte[] buf = null;
try {
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, key);
buf=cipher.doFinal(charToByte(data));
}catch(Exception e)
{
e.printStackTrace();
print("错误:"+e.getMessage());
}
return byteToChar(buf);
}
获取到remiain(通常是20字节)这个量之后就可以进行各项操作了,比如保持在线,断开等.
断开连接
下面是断开的操作(断开需要发送三次断开连接的数据):1 | Runnable duankaiRunnable = new Runnable() { |
7E | 命令 | 长度 低位 | 长度 高位 | 数据 | CRC 低位 | CRC 高位 | 7E |
断开连接数据包例子: 7E 01 14 00 61 A1 06 AD 35 04 4B 23 0C DC 98 9C
B6 6B D0 4B B3 16 01 00 03 D4 7E
断开中的getcmd函数相当于封装那20字节的remain: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public char[] getcmd(int cmd){
char[] data = new char[remain.length+7];
data[0]=0x7E;
data[1]=(char)cmd;
data[2]=0x14;
data[3]=0x00;
int i;
for(i=0;i<remain.length;i++)data[i+4]=(char) remain[i];
char[] crc = new char[remain.length+3];
for(i=0;i<remain.length+3;i++)crc[i]=data[i+1];
int c = getCRC16(charToByte(crc));
data[crc.length+1]=(char) (c&0xFF);
data[crc.length+2]=(char) (c>>8);
data[crc.length+3]=0x7E;
return data;
}
保持在线
保持在线的话就这样获取数据包:getcmd(0);,其余的数据不变.
查流量
查流量的话与登录不同,查流量发送的数据第一发送的命令码是3,第二发送的数据不含IP 也就是说查流量的格式是这样的:
- 23字节用户名
- 23字节密码
- 16字节验证数据 没错,长度只有62字节. 其他的RSA,CRC都是一样的操作. 返回的数据是纯文本的,储存在recString全局变量中.
取出流量数据
得到文本之后,我们通过正则表达式得到剩余流量: 1
2
3
4
5
6
7
8
9Pattern p=Pattern.compile("([0-9]+)(兆)");
Matcher m=p.matcher(result);
int liuliang=0;
while(m.find()){
String tempString = m.group(1);
liuliang+=Integer.valueOf(tempString).intValue();
}
liuliangString=String.valueOf(liuliang);1
Pattern.compile("剩余网费.*?(\\w+.\\w*元)");
1
Pattern.compile("在线:(\\d+)");