0x00 前记

 

之所以突然搞一发事是因为闪讯又掏出了新玩意(强行推送的新版本客户端里新增的扫码登录),实现了类似于扫码支付的一步扫码直接登录的功能。去年刚了一年闪讯把这东西摸了个透,所以我就想拉下来搞个远程一键登录应该挺好玩的(只是因为到了周末贼无聊)。
所以,这就是故事的开端。


0x00 开始

 

0x000 桌面端闪讯探秘

既然是扫码,我们知道二维码就是一个字符串转成的图像而已(我觉得闪讯不会用什么非常高级的参数传递方式,于是就猜是这个了)。然后试着刷新了几个二维码,得到的结果是这样的
open1
sc1 provider=1FAAAF890D9D88D518C504F0A94FBDBD&key=6197712867475996672
sc2 provider=1FAAAF890D9D88D518C504F0A94FBDBD&key=6197712110651588608
open2
sc3 provider=1FAAAF890D9D88D518C504F0A94FBDBD&key=6197712406765256704
sc4 provider=1FAAAF890D9D88D518C504F0A94FBDBD&key=6197712563456065536
sc5 provider=1FAAAF890D9D88D518C504F0A94FBDBD&key=6197712712983003136
sc6 provider=1FAAAF890D9D88D518C504F0A94FBDBD&key=6197712867475996672

provider这东西是固定的(然而并没有什么卵用,后面就知道了),然后key貌似除了前几位是固定的之外后面都没什么规律,而且闪讯客户端三分钟刷新一个二维码,好像都不太一样,大概是跟密码pin计算一样的套路,有时间作为种子的。所以这肯定是重点。到此好像就没什么信息了,而且laj闪讯貌似这个二维码服务器瞬间爆炸,已经刷不出来了,所以进一步的抓包和key的生成算法分析放到之后更新吧。

 

0x000 Android端闪讯探秘

既然发送端没货了,那我们就跳到接收端来玩耍一番(<毕竟Sino我技能点最大一部分还是点在了各种安卓相关的内容上,来到主场心情十分激动
高兴地下载“校园闪讯”APP,然后装上手机先踩个点。WTF?什么鬼一扫码出来立刻提示成功,这网络速度有点感人啊(其实并不是这样 一会就知道了),发现并没有什么彩蛋,还是直接进入她去看一看比较实在。
提取classes.dex,运行dex2jar转成jar包(这个工具只是解包,并没有做任何的反编译工作),然后先用jd-gui初步分析一下,结果一进去就看到了彩蛋
private String aesKey = "1234567812345678"; //com.singlenet.bsl.AppClient.class
喂喂,这aes的key就有点不讲道理的吧。好的,你闪讯垃圾就不跟你计较了,继续往下看,找到扫码部分的core。因为在里面看到了zxing这个包,所以没跑就是直接activityResult里了,在那一翻,果然找到了

paramIntent = paramIntent.getExtras().getString("result");
    if ((!TextUtils.isEmpty(paramIntent)) && (paramIntent.contains("&")))
    {
      paramIntent = paramIntent.split("&");
      paramInt2 = paramIntent.length;
      paramInt1 = 0;
      while (paramInt1 < paramInt2)
      {
        String[] arrayOfString = paramIntent[paramInt1];
        if (arrayOfString.contains("="))
        {
          arrayOfString = arrayOfString.split("=");
          localHashMap.put(arrayOfString[0], arrayOfString[1]);
        }
        paramInt1 += 1;
      }
    }
    paramIntent = getSelectedAccount();
    new Thread()
    {
      public void run()
      {
        String str = new AppClient("1234567812345678", "zjbsl.singlenet.cn", 6016).sendLoginCmdToServer(this.val$key, paramIntent.getUsername(), paramIntent.getPassword());
        Message localMessage = new Message();
        localMessage.what = 2;
        localMessage.obj = str;
        SinglenetFragment.this.handler.sendMessage(localMessage);
      }
    }.start();

简单来说就是从结果里解析(用&,=分割,亏我开始还以为是把这个加到url后面直接get,对方的开发人员写成这样脑子也是有洞),然后带上你选择的用户名密码发送出去 host:zjbsl.singlenet.cn port:6016 。仔细的同学这里可能发现了,发送参数里只有key,没有provider!这也就是我开始为什么说provider毫无卵用的原因了(滑稽)。接下来探寻一发发送部分(什么?前面这就解决了?这么简单? 简单你别问我,你去问闪讯的开发人员 讲道理这真是我见过最低级别防护的网络提供商)。
通过sendLoginCmdToServer函数找过去,恩,大概是因为写的太乱了,Decompile失败了,手动分析字节码吧(嗨呀 好气),

// Byte code:
    //   0: aconst_null
    //   1: astore 4
    //   3: aconst_null
    //   4: astore_1
    //   5: aconst_null
    //   6: astore_3
    //   7: new 169	java/net/Socket
    //   10: dup
    //   11: aload_0
    //   12: getfield 22	com/singlenet/bsl/AppClient:server	Ljava/lang/String;
    //   15: aload_0
    //   16: getfield 25	com/singlenet/bsl/AppClient:port	I
    //   19: invokespecial 172	java/net/Socket:	(Ljava/lang/String;I)V
    //   22: astore_2
    //   23: aload_2
    //   24: ldc -83
    //   26: invokevirtual 177	java/net/Socket:setSoTimeout	(I)V
    //   29: aload_2
    //   30: invokevirtual 181	java/net/Socket:getInputStream	()Ljava/io/InputStream;
    //   33: astore_1
    //   34: sipush 1024
    //   37: newarray 
    //   39: astore_3
    //   40: aload_1
    //   41: aload_3
    //   42: invokevirtual 187	java/io/InputStream:read	([B)I
    //   45: pop
    //   46: aload_0
    //   47: aload_3
    //   48: invokevirtual 191	com/singlenet/bsl/AppClient:getWithResultStringByties	([B)Ljava/lang/String;
    ..........

看到了几个关键函数的调用

  public byte[] getByteStreamForSetAccount(String paramString1, String paramString2, String paramString3)
    throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException, BadPaddingException, IllegalBlockSizeException
  {
    return getByteStreamWithCmdString(String.format("SETACCOUNT %s %s %s", new Object[] { paramString1, paramString2, paramString3 }));
  }
  
  public byte[] getByteStreamWithCmdString(String paramString)
    throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException, BadPaddingException, IllegalBlockSizeException
  {
    Object localObject1 = new SecretKeySpec(this.aesKey.getBytes("utf-8"), "AES");
    Object localObject2 = Cipher.getInstance("AES/ECB/PKCS5Padding");
    ((Cipher)localObject2).init(1, (Key)localObject1);
    paramString = ((Cipher)localObject2).doFinal(paramString.getBytes("utf-8"));
    localObject1 = Utils.intToBytes(paramString.length);
    localObject2 = new byte[localObject1.length + paramString.length];
    System.arraycopy(localObject1, 0, localObject2, 0, localObject1.length);
    System.arraycopy(paramString, 0, localObject2, localObject1.length, paramString.length);
    return (byte[])localObject2;
  }

大概调用过程就是 调用这个返回一个编码好的byte然后Socket过去直接write了 连返回值都不接收(懵逼 这意思是 登上了就是成功,登不上就是失败? 好tm。。。),然后直接返回一个OK,前面接收到这个OK之后就会弹框提示成功(这就是为什么那么快就提示成功的原因,大写的服气。)
然后因为那边二维码产生的服务器挂了,这个接口没法测试,只能先鸽着了,下次有新进度再更新吧(顺便抓到了几个其他接口 去试一试 /滑稽)


发表评论

电子邮件地址不会被公开。