杨培文

Yang Peiwen

首先我们安装wiringPi

1
2
3
4
5
6

sudo apt-get install git-core
git clone git://git.drogon.net/wiringPi
cd wiringPi
./build

这样我们就安装成功了,如果失败请更新系统:

1
2
3
4

sudo apt-get update
sudo apt-get upgrade

然后我们可以开始使用C语言写一个简单的闪烁程序了:

1
2
3

nano blink.c

以下源码来自wiringPi的examples/blink.c:

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

#include <stdio.h>
#include <wiringPi.h>

// LED Pin - wiringPi pin 0 is BCM_GPIO 17.

#define LED 0

int main (void)
{
printf ("Raspberry Pi blink\n") ;

wiringPiSetup () ;
pinMode (LED, OUTPUT) ;

for (;;)
{
digitalWrite (LED, HIGH) ; // On
delay (500) ; // mS
digitalWrite (LED, LOW) ; // Off
delay (500) ;
}
return 0 ;
}


按Ctrl+X,然后输入Y,回车保存. 然后,我们利用gcc来编译刚才的源码,运行:

1
2
3
4

gcc blink.c -o blink -lwiringPi
sudo ./blink

这里记得,控制IO的时候需要sudo,不然会提示:
1
2
3

wiringPiSetup: Must be root. (Did you forget sudo?)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

+-----+-----+---------+------+---+-Model B2-+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| | | 3.3v | | | 1 || 2 | | | 5v | | |
| 2 | 8 | SDA.1 | ALT0 | 1 | 3 || 4 | | | 5V | | |
| 3 | 9 | SCL.1 | ALT0 | 1 | 5 || 6 | | | 0v | | |
| 4 | 7 | GPIO. 7 | IN | 1 | 7 || 8 | 1 | ALT0 | TxD | 15 | 14 |
| | | 0v | | | 9 || 10 | 1 | ALT0 | RxD | 16 | 15 |
| 17 | 0 | GPIO. 0 | IN | 0 | 11 || 12 | 0 | IN | GPIO. 1 | 1 | 18 |
| 27 | 2 | GPIO. 2 | IN | 0 | 13 || 14 | | | 0v | | |
| 22 | 3 | GPIO. 3 | IN | 0 | 15 || 16 | 0 | IN | GPIO. 4 | 4 | 23 |
| | | 3.3v | | | 17 || 18 | 0 | IN | GPIO. 5 | 5 | 24 |
| 10 | 12 | MOSI | IN | 0 | 19 || 20 | | | 0v | | |
| 9 | 13 | MISO | IN | 0 | 21 || 22 | 0 | IN | GPIO. 6 | 6 | 25 |
| 11 | 14 | SCLK | IN | 0 | 23 || 24 | 0 | IN | CE0 | 10 | 8 |
| | | 0v | | | 25 || 26 | 0 | IN | CE1 | 11 | 7 |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| 28 | 17 | GPIO.17 | ALT2 | 0 | 51 || 52 | 0 | ALT2 | GPIO.18 | 18 | 29 |
| 30 | 19 | GPIO.19 | ALT2 | 0 | 53 || 54 | 0 | ALT2 | GPIO.20 | 20 | 31 |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+-Model B2-+---+------+---------+-----+-----+

这里是openssl编译好的库,可以在iOS设备以及MacBook上使用:http://pan.baidu.com/s/1mgkjGs4

如果感兴趣的话可以自行编译openssl的源码,记得编译成arm使用的文件就行.

在xcode中导入

直接将文件拖入xcode目录中即可,如图:

然后,我们如果是swift,我们需要引入一个桥梁,操作如下: 1,File->New->File 2,新建一个C

3,输入名字

4,选择目录

5,最关键的,创建一个桥梁文件

6,创建成功,注意最后那个test-Bridging-Header.h

然后,我们把openssl拖进来

在.h头文件里加上这样两句话:

1
2
void rsajiami(const unsigned char* in,int size,unsigned char* out);
void rsajiemi(const unsigned char* in,unsigned char* out);

就像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//
// testc.h
// test
//
// Created by 杨培文 on 15/2/25.
// Copyright (c) 2015年 杨培文. All rights reserved.
//

#ifndef __test__testc__
#define __test__testc__

#include <stdio.h>

void rsajiami(const unsigned char* in,int size,unsigned char* out);
void rsajiemi(const unsigned char* in,unsigned char* out);

#endif /* defined(__test__testc__) */


然后在.c文件里引入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
33
34

//
// testc.c
// test
//
// Created by 杨培文 on 15/2/25.
// Copyright (c) 2015年 杨培文. All rights reserved.
//

#include "testc.h"
#include "openssl/rsa.h"
#include "openssl/bn.h"

static char n[] = "EA32BA96FCC395CC766EAFFEBC8EFE1F0886E99504CB7C3877548698793446BA7BA07CF915DBB5BE69337A3697B4DC354DA78ABAE17ED33EDAD87674D0D0D2B54D549E566AF0C016C276F327ADC3D4EE06E64EBC608E4AC9E3CE63416C246FD57DBEA8ADA036AA683F9A812CD8ECA705E019D6A943121CDDB2CF9BF1BCD0F5F9";
static char e[] = "10001";

void rsajiami(const unsigned char* in,int size,unsigned char* out){
RSA *a = RSA_new();
BIGNUM *bn = BN_new();BN_hex2bn(&bn, n);
BIGNUM *be = BN_new();BN_hex2bn(&be, e);
a->e=be;a->n=bn;
RSA_public_encrypt(size, in, out, a, RSA_PKCS1_PADDING);
}

void rsajiemi(const unsigned char* in,unsigned char* out){
RSA *a = RSA_new();
BIGNUM *bn = BN_new();
BN_hex2bn(&bn, n);
BIGNUM *be = BN_new();
BN_hex2bn(&be, e);
a->e=be;a->n=bn;
RSA_public_decrypt(128, in, out, a, RSA_PKCS1_PADDING);
}

然后我们就可以在swift中调用C里面的rsajiami函数:

1
2
3
4
5
func jiami(buf:[Byte])->[Byte]{
var re:[UInt8] = [UInt8](count:128,repeatedValue:0x0)
rsajiami(buf,CInt(countElements(buf)),&re)
return re
}

同样的也可以调用解密函数:

1
2
remain = [UInt8](count:20,repeatedValue:0x0)
rsajiemi(re,&remain)

很简单的多线程访问

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
import urllib
import socket
from threading import *

url = "http://www.baidu.com/s?ie=UTF-8&wd=ypw"
cishu = 0
socket.setdefaulttimeout(15)

def gethtml():
global cishu
cishu += 1
print cishu
urllib.urlopen(url).read()

def attack():
while True:
try:
gethtml()
except error:
pass

def run():
t = Thread(target=attack)
t.start()

for i in range(10):
run()

值得注意的地方有:全局变量要用global,需要用try和timeout来防止线程崩溃

这就是我见过的最小的单片机,只有八个脚,如下图:

这小单片机只有芯片大小,但是该有的功能还是有的,可以用C语言编程,不需要外接晶振就能实现准确的时钟.

那么这就是它的最小系统,其实就是在VCC和GND之间加了5V,然后加了两个电容滤波,无需晶振.

然后就可以实现一些简单的功能,比如串口的收发,IO口的控制,PWM输出等,甚至可以通过电容充电时间来实现ADC,也可以模拟各种通信协议来实现传感器的扩展,比如I2C模拟等.

下面是对PWM的模拟,非常简单. 程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

#include <REG2051.H>
void delay (unsigned char h)
{
int i;
while(h--)for(i=0;i<148;i++);
}
int liangdu=0,f=1;
void main(void)
{
int i;
for(i=0;i<255;i++)
{
if(i<liangdu)P3=0XFF;else P3=0x00;
delay(500);
}
liangdu+=f;
if(liangdu==255|liangdu==0)f=-f;
}

apt-get

1
2
3
4
5
6
7
apt-get update
apt-get update -o Acquire::http::No-Cache=True
apt-get upgrade
apt-get install php5
apt-get remove php5
apt-get purge vsftpd

更新源 更新所有包 安装包 卸载包 卸载并删除配置文件

ls

1
2
3
4
ls
ls -l
ls -a

列出文件 列出文件详细信息(权限,所有者等) 列出包括隐藏文件的所有文件

1
2
3
4
5
6
7
8
9
10
11
12
Last login: Fri Feb 27 12:39:25 on ttys001
YPW-MacBook-Pro:~ yangpeiwen$ cd Desktop/hello/
YPW-MacBook-Pro:hello yangpeiwen$ ls
hello hello.c
YPW-MacBook-Pro:hello yangpeiwen$ ls -l
total 32
-rwxr-xr-x 1 yangpeiwen staff 8536 2 27 12:39 hello
-rw-rw-r-- 1 yangpeiwen staff 94 2 26 12:48 hello.c
YPW-MacBook-Pro:hello yangpeiwen$ ls -a
. .. .hide hello hello.c
YPW-MacBook-Pro:hello yangpeiwen$

cat

1
2
cat /etc/passwd

查看文件内容

uname

1
2
uname -a

列出系统信息 比如:Darwin YPW-MacBook-Pro.local 14.1.0 Darwin Kernel Version 14.1.0: Mon Dec 22 23:10:38 PST 2014; root:xnu-2782.10.72~2/RELEASE_X86_64 x86_64

mkdir

创建文件夹

1
2
3
mkdir /var/blog
mkdir -p tmp/a/b/c

创建一个目录 创建多层目录

wget

下载文件

1
2
3
wget http://baidu.com
cat index.html

效果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
YPW-MacBook-Pro:~ yangpeiwen$ wget http://baidu.com
--2015-02-27 12:44:45-- http://baidu.com/
正在解析主机 baidu.com (baidu.com)... 123.125.114.144, 220.181.111.85, 220.181.57.217
正在连接 baidu.com (baidu.com)|123.125.114.144|:80... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度:81 [text/html]
正在保存至: “index.html”

index.html 100%[=====================>] 81 --.-KB/s 用时 0.002s

2015-02-27 12:44:45 (33.4 KB/s) - 已保存 “index.html” [81/81])

YPW-MacBook-Pro:~ yangpeiwen$ cat index.html
<html>
<meta http-equiv="refresh" content="0;url=http://www.baidu.com/">
</html>

tar

1
2
3
tar -zcvf /var/www/1.zip /var/www
tar -xzvf 1.0.14.10.10.-release.tar.gz

zcvf是压缩 xzvf是解压

mv,rm,cp

1
2
3
4
5
mv * /var/blog
rm -rf build
cp * /var/blog
mv index.html index.php

移动,删除,复制,重命名

pwd

显示当前目录

useradd,passad

1
2
3
4
useradd -g www-data -d /var/www ftp
passwd ftp


useradd -g www-data -d /var/www ftpypw 添加用户 -g 用户组 -d 用户目录 用户名称 设置密码

cat

1
2
cat /etc/passwd

查看文件内容

chmod,chown

1
2
3
chmod -R 755 /var/www
chown ftpypw /var/www

设置目录权限 设置目录所有者

du

1
2
3
4
5
du -sh /var/www # 查看文件夹大小
du -h /var/www --max-depth=1 # 只看一层文件夹
du -h /var/www # 查看所有文件夹大小
du -ah /var/www # 查看所有文件大小

export

1
2
export PATH=$PATH:/usr/local/lib/python2.7/dist-packages

添加环境变量

df

1
2
df -h

列出U盘或硬盘

mount

1
2
3
mkdir /mnt/upan
mount -t auto /dev/sda1 /mnt/upan

挂载U盘

mkfs

1
2
3
4
apt-get install dosfstools
umount /dev/sda1
mkfs.vfat /dev/sda1

格式化U盘为fat32格式

screen

1
2
3
screen -X ypw  # 进入一个screen并命名为ypw
screen -r ypw # 恢复ypw这个窗口

Ctrl+D退出这个窗口

ln

1
2
ln -s /mnt/upan/ img

创建一个链接

a2enmod

1
2
3
4
a2enmod headers
a2enmod expires
service apache2 restart

最开始的时候我们的上网登录是通过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);
}

之后学校封了HTTP(80端口),只留下了HTTPS(443端口),于是我们将协议修改为HTTPS.不过这里有一个证书问题,我们选择信任所有证书来解决这个问题.引用了一个库里面的类:SSLSocketFactoryEx.下面是这个类的源码:
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
88
package 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;}

@Override
public void checkClientTrusted(
java.security.cert.X509Certificate[] chain, String authType)
throws java.security.cert.CertificateException {}

@Override
public void checkServerTrusted(
java.security.cert.X509Certificate[] chain, String authType)
throws java.security.cert.CertificateException {}
};
sslContext.init(null, new TrustManager[] { tm }, null);
}

@Override
public Socket createSocket(Socket socket, String host, int port,boolean autoClose) throws IOException, UnknownHostException {
return sslContext.getSocketFactory().createSocket(socket, host, port,autoClose);
}

@Override
public Socket createSocket() throws IOException {
return sslContext.getSocketFactory().createSocket();
}
}

利用这个东西,我们实现了https:
1
2
3
4
HttpPost hPost = /*跟之前一样*/;
HttpClient httpclient = SSLSocketFactoryEx.getNewHttpClient();
HttpResponse hResponse = httpclient.execute(hPost);

不过,神奇的网络中心干脆取消了https,全面取消网页上网方式,仅留下了电脑端登录的方式. 而神奇的我们则再一次研究了他们的电脑端.

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
15
public 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;
}

这里的outputStream和inputStream都是全局变量,用来输入输出的. 连上之后我们要发送一个空包给服务器.

读取返回数据:

读取方式很简单,我们通过刚才client得到的inputStream即可读出数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public 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
15
public 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
    17
    public 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
    2
    char[] 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
33
public 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
26
public 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
29
public 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
18
public 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
16
public 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;
}

利用之前的读取函数,我们在发送完毕之后马上读取数据:char[] rec = read();

解封装

那么发送过去登录数据之后,就要接收数据,不过接收到的数据依然是封装好的,我们需要对其解封装:

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
public 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;
}

这里解封装有两种可能,一种是解出来纯文本数据,一种是解出来登录成功之后的保持在线数据,这里我们通过命令码判断,如果是1就代表登录成功,那么就进行RSA解密,并赋值给全局变量remain,否则就是string,我们对其进行GBK解码.

RSA解密

RSA解密如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public 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);
}

解密是DECRYPT_MODE,其余和加密没有差别.

获取到remiain(通常是20字节)这个量之后就可以进行各项操作了,比如保持在线,断开等.

断开连接

下面是断开的操作(断开需要发送三次断开连接的数据):
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
Runnable duankaiRunnable = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
DatagramSocket datagramSocket = null;
try {
datagramSocket = new DatagramSocket();
print("建立断开socket");
}catch (Exception e) {
e.printStackTrace();
}
InetAddress ip = null;
try {
ip = InetAddress.getByName("192.168.8.8");
char[] data = getcmd(1);
print("断开发送数据:"+charsToHexString(data));
DatagramPacket datagramPacket = new DatagramPacket(charToByte(data), remain.length+7, ip, 21099);
datagramSocket.send(datagramPacket);
datagramSocket.send(datagramPacket);
datagramSocket.send(datagramPacket);
remain=null;
show("断开成功");
}catch(NullPointerException e){
show("你还没有登录");
}catch (Exception e) {
e.printStackTrace();
print("断开出错:"+e.getMessage());
}
}
};

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
17
public 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
9
Pattern 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+)");

安装LAMP

1
2
3
apt-get update
apt-get upgrade
apt-get install apache2 mysql-server mysql-client php5 php5-mysql

选配:

phpmyadmin

1
2
apt-get install phpmyadmin
ln -s /usr/share/phpmyadmin/ /var/www/

vsftpd

1
2
3
4
5
6
7
apt-get install vsftpd
useradd -d /var/www ftpypw
chown ftpypw /var/www
passwd ftpypw #设置你的密码

nano /etc/vsftpd.conf #按照下面的设置,禁止匿名,允许本地用户
/etc/init.d/./vsftpd restart

anonymous_enable=NO local_enable=YES write_enable=YES

wordpress

https://cn.wordpress.org/下载好最新版,解压后通过ftp上传即可

创建新的ftp用户:

1
2
3
4
mkdir /var/wzq
useradd -d /var/wzq ftpwzq
chown ftpypw /var/www
ls -l # 查看所有者

php相关配置

1
nano /etc/php5/apache2/php.ini
display_errors = On
upload_max_filesize = 8M
max_execution_time = 300
memory_limit = 128M

配置多个站点:apache2:

1
2
cd /etc/apache2/sites-enabled
ls
1
2
3
4
5
6
7
<VirtualHost *:80>
ServerName ypw.hk
ServerAlias ypw.hk
DocumentRoot "/var/www/"
ErrorLog "/var/log/apache2/edunuke_errors.log"
CustomLog "/var/log/apache2/edunuke_accesses.log" common
</VirtualHost>

配置mysql编码问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
mysql -u root -proot
set character_set_client = utf8;
set character_set_server = utf8;
set character_set_connection = utf8;
set character_set_database = utf8;
set character_set_results = utf8;
set collation_connection = utf8_general_ci;
set collation_database = utf8_general_ci;
set collation_server = utf8_general_ci;
show variables like 'character_set_%';
nano /etc/mysql/my.cnf #按照下面的内容修改

service mysql restart
1
2
3
4
5
6
7
8
[client]
default-character-set=utf8

[mysql]
default-character-set=utf8

[mysqld]
character-set-server=utf8

编码还有问题的话:

php每次查询之前加这句话:mysql_query("SET NAMES UTF8"); 就像下面一样:

1
2
3
4
$con = mysql_connect(SAE_MYSQL_HOST_M.':'.SAE_MYSQL_PORT,SAE_MYSQL_USER,SAE_MYSQL_PASS);
$db=mysql_select_db("app_ypw2", $con);
mysql_query("SET NAMES UTF8");
$count = mysql_query("SELECT COUNT(*) FROM content");

wordpress配置缓存(WP Super Cache):

1
2
3
4
a2enmod rewrite
a2enmod headers
a2enmod expires
/etc/init.d/apache2 force-reload

去掉工具栏wordpress.org:

在wp-includes/default-widgets.php中修改,搜索wordpress.org然后删除

wordpress配置gravatar头像:

将以下内容添加到主题文件的functions.php里:

1
2
3
4
5
function get_avatar_deadwood( $avatar ) {
$avatar = str_replace(array("gravatar.proxy.ustclug.org","gravatar.proxy.ustclug.org","gravatar.proxy.ustclug.org","gravatar.proxy.ustclug.org"),"gravatar.duoshuo.com",$avatar);
return $avatar;
}
add_filter( 'get_avatar', 'get_avatar_deadwood' );

php配置memcached:

1
2
apt-get install memcached php5-memcache php5-memcached
/etc/init.d/apache2 restart

wordpress配置memcached:

首先去插件安装Memcached Object Cache 2.0.2 然后:

1
2
cd /var/www
cp wp-content/plugins/memcached/object-cache.php wp-content/

然后删了之前那个memcached... 复制的那个content目录下的插件会在Drop-in高级插件中显示. 安装成功之后去WP Super Cache 设置的高级设置中开启 使用对象缓存系统来存储缓存文件。 (实验室功能)

memcached命中率查看:

1
2
3
telnet 127.0.0.1 11211
stats
quit

其中我感兴趣的数据就是这些:

1
2
3
4
5
6
STAT cmd_get 919
STAT cmd_set 281
STAT cmd_flush 0
STAT cmd_touch 0
STAT get_hits 746
STAT get_misses 173

给wordpress添加查询次数,加载时间:

1
2
cd /var/www/wp-content/themes/twentyfourteen
nano footer.php

在最底部添加:

1
本次查询<?php echo get_num_queries();?>次,耗时<?php timer_stop(1);?>

就像这样:

1
2
3
4
5
6
7
8
9
10
11

<p style="float:right;">
本次查询<?php echo get_num_queries();?>次,耗时<?php timer_stop(1);?>
</p>
</div><!-- .site-info -->
</footer><!-- #colophon -->
</div><!-- #page -->
<?php wp_footer(); ?>
</body>
</html>

硬件需要:树莓派摄像头(我的是500W像素的那种),人体红外热释电模块,LED(可选) 需要:python,RPi.GPIO库 安装方式:

1
2
3
4
sudo apt-get install python-dev
sudo apt-get install python-pip
sudo pip install rpi.gpio

[caption id="attachment_386" align="alignnone" width="546"][](/images/gpio3.jpg) gpio3[/caption]

安装好之后就可以用python来使用GPIO了. 根据这个图,我们知道P0和P1分别是11和12.

我们就可以写出程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import RPi.GPIO as GPIO
import time
import os
GPIO.setmode(GPIO.BOARD)
GPIO.setup(11,GPIO.OUT)
GPIO.setup(12,GPIO.IN)
GPIO.output(11,GPIO.LOW)

while True:
if(GPIO.input(12)):
GPIO.output(11,GPIO.HIGH)
time.sleep(0.1)
GPIO.output(11,GPIO.LOW)
os.system('/root/Desktop/yeelink.sh')
time.sleep(12)
else:
GPIO.output(11,GPIO.LOW)
time.sleep(0.1)


Python中的11实际上就是树莓派的P0口,我们在P0口接了一个LED灯. 12就是P1口,我们在P1接了一个人体红外热释电模块.

其中的yeelink.sh是利用yeelink提供的云服务,具体内容如下:

1
2
3
raspistill -t 500 -o "/root/Desktop/a.jpg" -w 640 -h 480
curl --request POST --data-binary @"/root/Desktop/a.jpg" --header "U-ApiKey:yourkey" --url http://api.yeelink.net/yoururl

其中的yourkey和yoururl需要申请得到,raspistill是树莓派拍照的命令,这里我们考虑到图像大小,将尺寸定在640x480.

摄像头大概是这样接的:

下图是拍到的样张。

首先我们需要安装wiringPi,具体步骤如下:

1
2
3
4
5
6
7
sudo apt-get install git-core
sudo apt-get update
sudo apt-get upgrade
git clone git://git.drogon.net/wiringPi
cd wiringPi
git pull origin
./build

安装成功之后执行

1
gpio readall

如果显示如下图就代表安装成功:

然后我们插入一个LED在P1口(严格意义上来说是应该给LED接限流电阻的,220欧姆):

根据GPIO的定义呢,我们可以知道GPIO .1口在wiringPi中对应的口是1:

然后我们在终端中输入如下命令:

1
2
gpio mode 1 OUTPUT
gpio write 1 1

其中第一句话是定义引脚1输出,第二句话是给引脚1输出1(高电平) 我们可以看到灯会亮.

如果我们再输入

1
gpio write 1 0

就能看到灯熄灭了. 好了,我们学会了怎么去控制灯的亮和灭.接下来就应该去通过php来控制了. 首先需要在树莓派上装好php,apache2.

1
2
sudo apt-get install apache2
sudo apt-get install php5

然后访问你的树莓派的IP地址,如果出现It works则代表你安装成功了. 由于apache默认目录在/var/www/,如果你像我一样不习惯你可以按照以下步骤将路径改到/home/pi/www/

1
sudo nano /etc/apache2/sites-enabled/000-default

DocumentRoot 就是apache2的默认目录,我改成了/home/pi/www,Ctrl+X再Y回车保存. 重启apache2服务

1
sudo /etc/init.d/apache2 restart

目录就成功的改好了.

然后我们到/home/pi/www目录下新建一个index.php

1
2
cd /home/pi/www
nano index.php

php的内容也很简单,让灯闪一下吧.

1
2
3
4
5
6
7
<?php
$pin = 1;
system("gpio mode $pin OUTPUT");
system("gpio write $pin 1");
system("gpio write $pin 0");
echo "blink~";
?>

然后我们去访问树莓派的IP地址,会发现灯闪了一下.

pi.zip

上面是我写的一个三个网页,index.php是主页,通过ajax方式实现后台访问php,control.php是控制GPIO的php,访问方式是http://192.168.0.107/control.php?pin=1&state=1 还有一个blink.php就是闪一下.

这是control的代码:

1
2
3
4
5
6
7
<?php
$pin = $_GET['pin'];
$state = $_GET['state'];
echo "pin:$pin, state:$state";
echo system("gpio mode $pin OUTPUT");
echo system("gpio write $pin $state");
?>

这里是index.php对control.php发送命令的代码(部分),

1
2
3
4
5
6
7
8
9
10
11
12
13
<script type="text/javascript">
function light(state)
{
xmlhttp=new XMLHttpRequest();
xmlhttp.open("GET","/control.php?pin="+$("#io").val()+"&state="+state,true);
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4)
if(xmlhttp.status==200)$("#returndata").html(xmlhttp.responseText);
}
xmlhttp.send();
}
</script>

然后实际上你看到的网页是这个样子:

通过点击不同的按钮就可以实现远程控制灯的亮灭了. 同样在手机上也可以控制:

0%