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

2019-04-12 23:14:28 +08:00
 azh7138m

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

乘车卡小程序下载

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

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

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

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

说白了就是腾讯懒得担责任,一刀切,技术不行
azh7138m
2019-04-13 11:57:51 +08:00
@kokutou 我不用乘车卡(也不支持杭州),只是好奇一个小程序怎么判断手机是不是 root 了
kokutou
2019-04-13 12:47:05 +08:00
@azh7138m
从接口返回的,可能是微信本身检测的发回服务器,小程序再获取。
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
2019-08-23 13:17:55 +08:00
绝对坑,用之前不说,充值开通了告诉我不能用,我为你个乘车吗换手机?行我不要了,告诉我哪里退钱
sadfQED2
2019-10-08 14:32:59 +08:00
@UchihaJay #11 我特么也是,充完钱跟我说不能用,用了面具的隐藏功能也不行

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/554654

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX