Finger
一只孤独的代码狗/程序猿/攻城狮

前言

去年一次jd购物为了凑单花了6块钱买了个京东来点(一个类似亚马逊DashButton的小东西) 本来琢磨着反正这么便宜,改吧改吧可以当个无线门铃或者呼叫器,无奈一直没时间下手就丢在了角落。前几天收拾屋子又翻了出来,正好折腾一下。

很多人可能都没听说过京东来点这个东西,先拖一段官网的介绍:
> Q:京东来点是什么?
> A: 来点是为对特定商品有重复性购买需求的客户而开发的京东下单硬件;
>        它可以放置在家中任何你需要的位置,连接wifi的情况下,下单时只需轻轻一点;
>        来点集合了农夫山泉、蒙牛、伊利、联合利华、金龙鱼等日销类品牌,满足消费者大部分的日常需求。专属的优惠价格,让您享受便捷的同时为您节省更多花销

什么是光波入网

跟DashButton一样,京东来点只有在第一次匹配绑定和修改配置的时候需要跟手机app配合,其他日常使用购买的时候是独立完成连接wifi并下单这个动作的。但跟DashButton通过蓝牙+wifi方式连接不同的是,京东来点采用的是光波入网,听起来很屌有没有。先来看看这个很屌的光波入网到底是怎么操作的。

首先扫描盒子说明书中的二维码下载app,登录京东账号后点击绑定就开始了光波入网操作,输入Wi-Fi名称和密码然后按住来点的按钮直到蓝灯闪烁 最后放到屏幕中央特定区域点击下一步

最后两张图就是入网时的状态,屏幕中央区域会不规律的黑白闪烁。等屏幕闪完来点上的灯就变成了绿色,然后app提示拿下来点继续操作,接着就是绑定购买商品。等所有操作完成之后,只要按一下来点上的按钮,就会自动帮你下单(或者放入购物车,app内设置) 无需再开启app,甚至手机都不用开。很神奇是不是,手机屏幕闪一闪这个小东西就能连上wifi直接下单买东西。

背面的这个小孔应该就是用来接收光波信号的。

光波入网的猜想

光波入网完成后,app第一时间就能连接到来点,所以大概可以猜到手机app在屏幕闪烁的时候至少传递了以下信息给来点

  1. wifi名
  2. wifi密码
  3. 很大可能还包括了手机的IP地址

因为如果没有传递IP地址的话,扫描整个网段特定开放端口再连接应该不会那么快。

屏幕的黑和白闪烁很容易联想到二进制的1和0,因为闪烁的时间有长有短,所以大胆假设一下:

  • 黑白两色分别为1和0
  • 显示的时间长度为数据长度

比如白色为1 黑色为0 黑色显示1秒是一个0 白色显示一秒是一个1(当然没有1秒那么长,只是定义一个时间)显示2秒3秒就是两个 三个 依次类推。

验证猜想

为了验证上面的假设,首先我们需要一个能把光信号转换成数字信号的东西。毕竟闪成那样盯着看不瞎就不错了,别指望还能分辨出闪烁的时间长短。这里有一段优酷的光波入网视频 直接拖到2:25就能看到入网过程的闪烁。

一开始我是打算把闪烁录下来然后放慢了记录间隔,看了几百帧后果断放弃了……还是把合适的任务交给合适的人去做吧。低成本方案是光敏电阻,大概长这样:

万能的淘宝上有进一步封装的光敏电阻模块,长这样:

是的你没看错 2块钱不到就能买到如此精致的小玩意儿。图片上这款带有DO数字开关量输出和AO模拟电压输出。
DO和AO的区别是:DO只会输出高低电平来表示光照强度是否大于某个阀值(可以通过图中蓝色电位器调整)而AO会输出光照强度的模拟电压值,通俗一点就是DO只会告诉你还是,而AO会告诉你一个数字来表示有多亮

我手里这块只有DO输出,不过没关系,我们不用知道有多亮。只要知道是亮还是暗就行了。黑和白的差距还是很明显的,足够通过阀值判断了。

这模块可没有和电脑常见接口通信的能力,所以我们还需要一块arduino(或者树莓派之类的板子)来读取模块数据并传输给电脑。我这里用的是arduino pro micro,买来玩badusb的。当时嫌碍事没让卖家发带插针的,现在只能自己焊了。别问我为什么只焊一边- -。

连接很简单 VCC->VCC GND->GND DO->D10 然后烧入程序

int stat= HIGH;
unsigned long startTime;
void setup() {
  Serial.begin(9600);
  pinMode(10, INPUT); //定义D10为输入
  startTime = millis();
}
void loop() {
  unsigned long curTime;
  int value = digitalRead(10);
  if(value != stat){   //如果光照发生变化就输出间隔时间
    curTime = millis();
    stat = value;
    if(stat == HIGH){ //高电平 表示光照没有达到阀值 黑
      Serial.print("0\t");
      Serial.println(curTime-startTime);
      startTime = curTime;
    }else{            //低电平 表示光照超过阀值 白
      Serial.print("1\t");
      Serial.println(curTime-startTime);
      startTime = curTime;
    }
  }

}

一切就绪,先在电脑上监听串口 然后手机重新打开app,点击绑定,输入wifi密码11111111然后下一步直到提示放上来点。这个时候把光敏电阻模块的感光头对准屏幕中心区域,开始入网后马上屏幕上就开始刷数据了。将所有输出保存到data.txt,然后我们拉一段出来分析。

1    50
0    49
1    201
0    48
1    200
0    49
1    50
0    49
1    53
0    49
1    205
0    43
1    210
0    40
1    52
0    65
1    50
0    49
1    212
0    40
1    203
0    49
1    50
0    53
1    49
0    44
1    201
0    47
1    51
0    49
1    50
0    50

很明显的两种长度 一种是50毫秒左右 一种是200毫秒左右。而且有个很奇怪的现象,200毫秒左右的数据只会出现在1的时候 而0全部都是50毫秒左右。如果按照我们上面的假设 两种颜色分别代表1和0, 那么所有0都是50毫秒这也太不正常了。
而且1只有50或者200毫秒两种情况也不对劲。
直接看数据有点不够直观,想办法转换成图形:

phpgd库直接画的 凑合看吧 图中横坐标是时间 纵坐标是数据(0|1)。我们原先的推测是这样的:

显然有问题, 再看上面的图片想到另一种推测:会不会1的宽度才是实际数据,而0只是做为一个间隔。

  • 4个1代表1
  • 1个1代表0

如下图:

写个脚本把刚才保存的data.txt按这个思路处理一下

$data   = array();
$i      = 0;
$ret    = '';
$handle = @fopen("data.txt", "r");
while (!feof($handle)) {
    $data[$i] = fgets($handle, 100);
    $i++;
}
fclose($handle);

foreach ($data as $data) {
    $arr = explode("\t", $data);
    if ($arr[0] == "1") {
        if ($arr[1] > 150) { //150应该足够分辨50和200了
            $ret .= "1";
        } else {
            $ret .= "0";
        }
    }
}
echo "len:"strlen($ret)."\n".$ret;

得到数据:
>len:256
>1010110000001110010101000101000000101101010011000100100101001110010010110101111101000011001100110011001001000001010001000100010100001000001100010011000100110001001100010011000100110001001100010011000111000000101010000000000110001001001000101010000000000001

这次看起来就正常多了。数据长度是256正好可以被8整除,8位一行分割一下得到

10101100
00001110
01010100
01010000
00101101
01001100
01001001
01001110
01001011
01011111
01000011
00110011
00110010
01000001
01000100
01000101
00001000
00110001
00110001
00110001
00110001
00110001
00110001
00110001
00110001
11000000
10101000
00000001
10001001
00100010
10100000
00000001

一眼就瞄到了那连续的8个00110001 转换成16进制就是31,也就是ascii字符1 这不就是我们刚才在来点app设置的wifi密码么。
在刚才的脚本后面再加上一段

$str = '';
$hex = '';
$bin = '';
for ($i = 0; $i < strlen($ret) / 8; $i++) {
    $tmp = substr($ret, $i * 8, 8);
    $bin .= bindec($tmp) . " ";
    $str .= chr(bindec($tmp)) . " ";
    $hex .= dechex(bindec($tmp)) . " ";
}
echo $str . "\n" . $hex . "\n" . $bin;

重新运行得到输出:

?? T P - L I N K _ C 3 2 A D E 1 1 1 1 1 1 1 1 ??? ?" ??

ac e 54 50 2d 4c 49 4e 4b 5f 43 33 32 41 44 45 8 31 31 31 31 31 31 31 31 c0 a8 1 89 22 a0 1  

172 14 84 80 45 76 73 78 75 95 67 51 50 65 68 69 8 49 49 49 49 49 49 49 49 192 168 1 137 34 160 1  

是不是发现了很多好东西 有我们的wifi名TP-LINK_C32ADE wifi密码11111111 十进制部分还能看到192 168 1 137


剩下还有4位未知数据,分析未知数据主要就是提交不同的payload去收集结果,从结果中找出跟payload相关的部分。这里包括app版本 系统类型(ios,android) wifi名 wifi密码 ip mac 甚至还有像 时间 网络流量 这些变量

经过不同设备(android,ios)多次抓取+网卡抓包,基本上明确了剩下的4位数据:

  • 第一位的ac在不同设备不同wifi名不同密码各种情况下始终未改变 猜测是标志位 来点读取到这个标志位开始解析接下来的数据并入网
  • 后面的22 a0 其实是app本地监听的端口(8864), 来点入网成功后会通过光波读取数据中的ip和端口连接上app。每次入网app本地监听的端口会+2,但是如果app关闭了再打开 就会从22a0(8864)重新开始。
  • 最后一位应该是数据校验位,当数据相同时这一位也相同(每次重启app可以重置端口为8864),当其他任意数据改变时这一位也改变。具体算法嘛,嘿嘿……(尴尬一笑)

后记

好了,光波入网的分析差不多就这样了。来点跟亚马逊DashButton在设计思路上非常相似,很多DashButton的玩法都能用来点实现。可以翻墙搜一下DashButton hack看看老外是怎么玩的。

这篇文章有人发言喽~

  1. :?:

    finger 扁担w

    2017/10/17

    @Ta
    #1

发表评论