V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
azh7138m
V2EX  ›  微信

微信乘车卡是怎么判断手机是否 ROOT 的?

  •  
  •   azh7138m · 2019-04-12 23:14:28 +08:00 · 5439 次点击
    这是一个创建于 1812 天前的主题,其中的信息可能已经有所发展或是发生改变。

    以下为我观察到的行为,不保证正确。

    乘车卡小程序下载

    tldr

    接口 https://wlx.tenpay.com/cgi-bin/wx/hce/ccm_hce_account_info.cgi 返回的数据存在 retcode: "912320107",也就是 DEVICE_IS_ROOT: "912320107"

    代码节选

    节选自 pages/index/index.js

    {
      var e = require("../../utils/config.js"),
        t = require("../../utils/util.js"),
        o = require("../../common/request.js"),
        a = require("../../utils/lib.js"),
        n = requirePlugin("vfcPlugin").vfc,
        i = require("../../logic/login.js"),
        c = require("../../logic/uiHelper.js"),
        r = require("../../logic/uiShare.js"),
        s = require("../../logic/report"),
        d = require("../../common/token.js"),
        l = require("../../logic/location.js"),
        u = require("../../logic/elementData.js"),
        g = require("../../utils/thirdparty/aes.js").CryptoJS,
        _ = require("../../utils/thirdparty/rsa.js"),
        f = require("../../utils/thirdparty/md5.js"),
        p = getApp(),
        request = o.request,
        h = { RIGHT: "/img/right.png", FALSE: "/img/false.png" },
        w = { BAL: "余额不足", FAIL: "该城市暂不支持使用乘车卡" },
        y = { normal: "刷新", refresh: "已刷新" },
        C = {};
      Page({
        requestCardInfo: function() {
          arguments.length > 0 && void 0 !== arguments[0] && arguments[0];
          var e = this;
          t.compare(C.SDKVersion, "2.6.2", !0) >= 0
            ? wx.getWxSecData({
                complete: function(t) {
                  e.qryAccInfo({
                    sec_data_enc: t.encryptedData,
                    sec_data_enc_iv: t.iv
                  });
                }
              })
            : e.qryAccInfo({
                sec_data_enc: "",
                sec_data_enc_iv: "",
                wx_version: C.version,
                sdk_version: C.SDKVersion
              });
        },
        qryAccInfo: function() {
          var request_raw_data =
              arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
            o = this,
            request_data = Object.assign(
              {
                ykt_id: o.data.ykt_id,
                city_code: o.data.city_code,
                card_id: i.getCardId() || ""
              },
              request_raw_data
            );
          request({
            url: e.GET_CARD_INFO,
            showLoading: !0,
            data: request_data,
            success: function(resp) {
              var resp_data = resp.data;
              if ((console.log(resp), "0" === resp_data.retcode))
                if ("0" === resp_data.open_state) {
                  var n = {
                    cloud_card_id: resp_data.cloud_card_id,
                    vfc_card_id: resp_data.vfc_card_id,
                    vfc_user_id: resp_data.vfc_user_id,
                    vfc_user_name: resp_data.vfc_user_name,
                    acc_no: resp_data.acc_no,
                    card_id: resp_data.card_id
                  };
                  wx.setStorageSync(e.USER_INFO_KEY, n);
                  var i = resp_data.cloud_card_id || "";
                  o.setData({
                    loading: !1,
                    acc_no: resp_data.acc_no,
                    cloudCardId: i,
                    formatCloudCardId: i.replace(/(\d{4})/g, "$1 ")
                  }),
                    p.setGlobalData({ acc_no: resp_data.acc_no }),
                    wx.getStorageSync(e.HCETOKEN)
                      ? wx.getStorageSync(e.CERT_NO)
                        ? o.updateCardInfo()
                        : o.reqCertNo()
                      : o.requestToken();
                } else
                  wx.redirectTo({
                    url: "/pages/card/pre/pre?cardImg=" + o.data.cardImg
                  });
              else
                switch (resp_data.retcode) {
                  case e.RETCODE.USER_NOT_EXIST:
                  case e.RETCODE.USER_STOPPED:
                  case e.RETCODE.NEW_USER_NOT_NEED_PHONE_AUTH:
                    wx.redirectTo({
                      url:
                        "/pages/card/pre/pre?retcode=" +
                        resp_data.retcode +
                        "&cardImg=" +
                        o.data.cardImg
                    });
                    break;
                  case e.RETCODE.OPEN_NOT_IN_WHITELIST:
                  case e.RETCODE.OPEN_NOT_ALLOWED:
                    o.setData({ loading: !1, InWhiteList: !1 });
                    break;
                  case e.RETCODE.DEVICE_IS_ROOT:
                    wx.redirectTo({
                      url: "/pages/notSupport/notSupport?type=isRoot"
                    });
                    break;
                  default:
                    c.commonModal({
                      confirmText: "重试",
                      retcode: resp_data.retcode,
                      success: function(e) {
                        e.confirm && o.render();
                      }
                    });
                }
            },
            fail: function(e) {
              wx.showModal({
                title: "提示",
                content: "系统繁忙,请重试",
                confirmText: "重试",
                success: function(e) {
                  e.confirm && o.render();
                }
              });
            }
          });
        }
      });
    }
    
    

    节选自 common/request.js

    function request(user_query) {
      function isLoginUrl() {
        return real_query.url === n.LOGIN_CGI;
      }
      var real_query = Object.assign(
        {
          retry: !0,
          method: "POST",
          header: { "content-type": "application/x-www-form-urlencoded" },
          isReport: !0
        },
        user_query
      );
      real_query.data || (real_query.data = {}),
        (real_query.data.s_tk = token.getSysInfoToken()),
        (real_query.data.g_tk = token.getACSRFToken()),
        (real_query.data.version = n.APP_VERSION);
      var l = new Date().getTime().toString();
      (real_query.data.timestamp = l.substr(0, 10)),
        real_query.showLoading && wx.showLoading({ title: "加载中..." });
      var login_data = logic_login.getLoginData();
      if (login_data && "POST" == real_query.method)
        for (var d in login_data)
          ("wlx_app_id" !== d &&
            "wlx_open_id" !== d &&
            "wlx_skey" !== d &&
            "wlx_skey_type" !== d) ||
            (real_query.data[d] = login_data[d]);
      real_query.success = function(resp) {
        console.log(resp);
        var a = resp.data || {};
        200 == resp.statusCode
          ? !1 === isLoginUrl() &&
            real_query.retry &&
            "object" == (void 0 === a ? "undefined" : t(a)) &&
            logic_login.needAutoLogin(a.retcode)
            ? !0 !== request.retryMap[user_query.url]
              ? ((request.retryMap[user_query.url] = !0),
                o({
                  fromCache: !1,
                  showLoading: user_query.showLoading,
                  success: function(o) {
                    request(user_query);
                  }
                }))
              : wx.showModal({
                  title: "提示",
                  content: "登录失效,请重新登录?",
                  showCancel: !0,
                  success: function(t) {
                    t.confirm
                      ? o({
                          fromCache: !1,
                          success: function(o) {
                            request(user_query);
                          }
                        })
                      : wx.navigateBack({ delta: 5 });
                  }
                })
            : (!1 === isLoginUrl() &&
                !0 === request.retryMap[user_query.url] &&
                (request.retryMap[user_query.url] = !1),
              user_query.success(resp))
          : user_query.fail && user_query.fail();
      };
      (real_query.complete = function(e) {
        real_query.showLoading && wx.hideLoading(),
          user_query.complete && user_query.complete(e);
      }),
        console.log(real_query),
        wx.request(real_query);
    }
    
    

    节选自 logic/login.js

    function getLoginData() {
      try {
        var e = wx.getStorageSync(r.LOGIN_DATA_KEY);
        if (
          "" !== e.wlx_app_id &&
          "" !== e.wlx_open_id &&
          "" !== e.wlx_skey &&
          "" !== e.wlx_skey_type
        ) {
          var n = getApp();
          return n && n.globalData && (n.globalData.wlx_open_id = e.wlx_open_id), e;
        }
        return null;
      } catch (e) {
        return null;
      }
    }
    
    

    节选自 common/token.js

    {
      function e() {
        try {
          var e = wx.getSystemInfoSync(),
            t = {
              model: e.model,
              pixelRatio: e.pixelRatio,
              screenWidth: e.screenWidth,
              screenHeight: e.screenHeight
            };
          return r(JSON.stringify(t));
        } catch (e) {
          console.log("getSysInfoMd5Old");
        }
      }
      function t() {
        var e = o.getOpenIdFromStorage();
        return e ? r(e + n.SYSINFO_SALT) : (console.log("getSysInfoMd5"), "");
      }
      var r = require("../utils/thirdparty/md5.js"),
        n = require("../utils/config.js"),
        o = require("../logic/login.js");
      module.exports = {
        getSysInfoToken: function() {
          return e().substr(-4);
        },
        getACSRFToken: function() {
          try {
            var e = wx.getStorageSync(n.LOGIN_DATA_KEY);
            if (e) {
              for (var t = 5381, r = e.wlx_skey, o = 0, i = r.length; o < i; ++o)
                t += (t << 5) + r.charAt(o).charCodeAt();
              return 2147483647 & t;
            }
          } catch (e) {
            return "";
          }
          return "";
        },
        getSysInfoMd5: t,
        getGuidMd5: function() {
          return t();
        },
        md5: r
      };
    }
    
    

    这判断很有趣,清空数据后第一次打开就能用,再次打开就不行了。

    最后

    腾讯能不能因为我 ROOT 了设备就不让我用呢?

    四 服务中止 /终止

    1、财付通有权基于业务调整或风险管控的需要,暂停、中断或终止向您提供本服务的全部或部分功能。

    ----- 乘车卡使用协议

    12 条回复    2019-10-08 14:32:59 +08:00
    lpd0155
        1
    lpd0155  
       2019-04-12 23:26:22 +08:00 via Android   ❤️ 1
    小白问一句,压缩包里的东西怎么打开?
    azh7138m
        2
    azh7138m  
    OP
       2019-04-12 23:30:13 +08:00
    @lpd0155 那我补充下...
    "_-"开头这个是主包,剩下俩是分包
    解包的方法比较多了,比如 http://bfy.tw/NCNX
    tony601818
        3
    tony601818  
       2019-04-13 04:00:33 +08:00 via Android
    安卓系统有 SafetyNet
    lpd0155
        4
    lpd0155  
       2019-04-13 07:53:47 +08:00 via Android
    @tony601818 没用的,实测过了 SAFETY NET 依然被检测到 ROOT
    kokutou
        5
    kokutou  
       2019-04-13 09:38:56 +08:00 via Android
    magisk+edxposed。。。
    乘车码我昨天试了下开通了,今天还是可以打开。。。
    azh7138m
        6
    azh7138m  
    OP
       2019-04-13 10:09:40 +08:00 via Android
    @kokutou 我清空数据后第一次也是可以用的,过一会就不行了,不知道微信是怎么判断的,这里只是说乘车卡是怎么判断用户是否 root,这个接口的数据是怎么来的就不清楚了。
    kokutou
        7
    kokutou  
       2019-04-13 11:51:17 +08:00 via Android
    @azh7138m
    用支付宝的不就行了。。。

    还没听说支付宝因为 root 不让用什么东西的吧。。。

    说白了就是腾讯懒得担责任,一刀切,技术不行
    azh7138m
        8
    azh7138m  
    OP
       2019-04-13 11:57:51 +08:00
    @kokutou 我不用乘车卡(也不支持杭州),只是好奇一个小程序怎么判断手机是不是 root 了
    kokutou
        9
    kokutou  
       2019-04-13 12:47:05 +08:00 via Android
    @azh7138m
    从接口返回的,可能是微信本身检测的发回服务器,小程序再获取。
    fyooo
        10
    fyooo  
       2019-05-29 09:22:47 +08:00
    @tony601818 国内的手机不支持 GMS,应该是没有 SafetyNet API 的。估计是小程序调用微信的接口检查是否 root 的,https://stackoverflow.com/questions/27291676/root-detection-methodology-in-android-which-cannot-be-bypassed
    UchihaJay
        11
    UchihaJay  
       2019-08-23 13:17:55 +08:00
    绝对坑,用之前不说,充值开通了告诉我不能用,我为你个乘车吗换手机?行我不要了,告诉我哪里退钱
    sadfQED2
        12
    sadfQED2  
       2019-10-08 14:32:59 +08:00
    @UchihaJay #11 我特么也是,充完钱跟我说不能用,用了面具的隐藏功能也不行
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2714 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 15:43 · PVG 23:43 · LAX 08:43 · JFK 11:43
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.