Skip to content
On this page

房*天下小程序pseusign参数分析

请求包分析

使用 Charles 软件进行抓包,详细过程就不细说了,直接贴上抓包结果

python
headers = {  
    'Host': 'miniapp.fang.com',  
    'pseusign': 'p10120230411162220f9a1195df01c47a6065f53b22d1c4100',  
    'openid': 'oxBn60ADRc6eURhDDkYReN4MjFik',  
    'content-type': 'application/json',  
    'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_1_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.33(0x18002129) NetType/WIFI Language/zh_CN',  
    'Referer': 'https://servicewechat.com/wxffbb41ec9b99a969/758/page-frame.html',  
}  
  
params = {  
    'appname': 'fangx',  
    'v': '1.3.4',  
    'miniplat': 'weixin',  
    'scene': '1027',  
}  
  
response = requests.get('https://miniapp.fang.com/pingyin/cityList', params=params, headers=headers, verify=False)  

经过来回删除恢复参数的方式,得出 pseusign 参数尤为重要,直接分析小程序代码。

获取小程序代码

之前写过一篇 [小程序逆向]PC端VX小程序代码提取 来获取小程序的代码,不过后面在使用 wxappUnpacker 接包的时候出了问题,会提示以下错误

TypeError: subPackage.pages is not iterable

不过又发现了一个新工具 unveilr ,使用过程也很简单,简单演示下

前提还是需要使用 PC微信小程序包解密工具 对在 PC 端拿到的小程序文件进行解密,参考上述文章即可。

直接使用 NPM 安装(首先电脑上得确保安装 NodeJS)

# npm
npm i unveilr -g
# yarn
yarn global add unveilr

unveilr --help
# 简称
uvr -h
# 当 'unveilr' 不是内部或外部命令,也不是可运行的程序或批处理文件。
# 尝试在命令前面加一个 npx, 例如:
npx unveilr --help

在对小程序文件进行解密后直接使用 unveilr /path/to/wxapkg/dir/ 就可以获取到源文件了。

还有很多使用实例具体参考unveilr 文档。

运行小程序

使用微信小程序开发工具导入解包后的代码,直接编译运行,会得到以下错误信息

[插件 wx1db9e5ab1149ea03] provider:wx1db9e5ab1149ea03, version:2.1.1, INVALID_LOGIN,access_token expired
插件文档: https://mp.weixin.qq.com/wxopen/plugindevdoc?appid=wx1db9e5ab1149ea03&token=&lang=zh_CN(env: macOS,mp,1.06.2303060; lib: 2.30.3)

可以大概看懂是插件过期的意思,那么就找到 app.jsonplugins 删除就好了,就想下面这样

js
"plugins": {
	"echarts": {
	"version": "2.1.1",
	"provider": "wx1db9e5ab1149ea03",
	"subpackage": "__APP__"
	}
},
js
"plugins": {
},

继续编译允许,继续报错,内容如下

WAServiceMainContext.js?t=wechat&s=1681204008944&v=2.30.3:1 TypeError: _typeof3 is not a function

解决办法,找到 @babel/runtime/helpers/typeof.js,把下面内容替换到文件中

js
function _typeof2(o) {
    "@babel/helpers - typeof";
    return (_typeof2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
        return typeof o;
    } : function (o) {
        return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
    })(o);
}

function _typeof(o) {
    return "function" == typeof Symbol && "symbol" === _typeof2(Symbol.iterator) ? module.exports = _typeof = function (o) {
        return _typeof2(o);
    } : module.exports = _typeof = function (o) {
        return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : _typeof2(o);
    }, _typeof(o);
}
module.exports = _typeof;

继续编译运行,通过了。

分析代码

直接全局搜索 pseusign 找到下面代码处

js
v.pseusign = h(e.url, i), o || (o = l("openid") || ""), v.openid = o, wx.request({
url: e.url,
data: "POST" === e.method ? e.data : i,
header: v,
method: e.method || "GET",
dataType: e.dataType || "json",
responseType: e.responseType || "text",

可以看到 pseusign 是由 h 函数生成,跟进 h 来看下

js
function h(e, n) {
    var o = "",
        a = getApp();
    if (a) {
        var i = a.vars.headerSgin;
        try {
            if (!n) return "";
            var c = {};
            if ("string" == typeof n) n.split("&").forEach((function (e) {
                var t = e.split("=");
                2 === t.length && (c[t[0]] = t[1])
            }));
            else c = n;
            if (e.includes("?")) e.substr(e.indexOf("?") + 1).split("&").forEach((function (e) {
                var t = e.split("=");
                2 === t.length && (c[t[0]] = t[1])
            }));
            var u = i.encodeNumber,
                s = i.password,
                g = function (e, t) {
                    var n = new Date(e.getUTCFullYear(), e.getUTCMonth(), e.getUTCDate(), e.getUTCHours(), e.getUTCMinutes(), e.getUTCSeconds()),
                        r = new Date(n.setHours(e.getUTCHours() + 8)),
                        o = {
                            "M+": r.getMonth() + 1,
                            "d+": r.getDate(),
                            "h+": r.getHours(),
                            "m+": r.getMinutes(),
                            "s+": r.getSeconds(),
                            "q+": Math.floor((r.getMonth() + 3) / 3),
                            S: r.getMilliseconds()
                        };
                    for (var a in /(y+)/.test(t) && (t = t.replace(RegExp.$1, String(r.getFullYear()).substr(4 - RegExp.$1.length))), o) new RegExp("(" + a + ")").test(t) && (t = t.replace(RegExp.$1, 1 === RegExp.$1.length ? o[a] : ("00" + o[a]).substr(String(o[a]).length)));
                    return t
                }(new Date, "yyyyMMddhhmmss"),
                l = "p".concat(u).concat(g),
                f = t(t({}, c), {}, {
                    micalltime: g,
                    miversion: i.miversgin,
                    misource: u
                }),
                h = Object.keys(f);
            h.sort((function (e, t) {
                return e.toLowerCase().localeCompare(t.toLowerCase())
            }));
            var d = "";
            h.forEach((function (e) {
                d += "&".concat(e, "=").concat(f[e])
            })), d = d.substr(1);
            var p = r.hex_md5("".concat(d).concat(s));
            o = "".concat(l).concat(p)
        } catch (e) {
            o = ""
        }
    }
    return o
}

打上 debugger 进行调试

js
var i = a.vars.headerSgin;

a.vars 则声明在 app.js 是一个常量对象,那么 headerSginheaderSgin: {encodeNumber: "101",password: "4f0c6725d917f8fe4e2791921187fd7d",miversgin: "p"}

js
c = n;

n 为函数的第 2 个参数,是一个对象,多调试几次就知道是接口的请求参数

js
if (e.includes("?")) e.substr(e.indexOf("?") + 1).split("&").forEach((function (e) {
        var t = e.split("=");
        2 === t.length && (c[t[0]] = t[1])
      }));

e 为函数的第 1 个参数,是一个 URL 地址

js
var u = i.encodeNumber,

u = 101

js
s = i.password,

s = 4f0c6725d917f8fe4e2791921187fd7d

js
g = function (e, t) {
          var n = new Date(e.getUTCFullYear(), e.getUTCMonth(), e.getUTCDate(), e.getUTCHours(), e.getUTCMinutes(), e.getUTCSeconds()),
            r = new Date(n.setHours(e.getUTCHours() + 8)),
            o = {
              "M+": r.getMonth() + 1,
              "d+": r.getDate(),
              "h+": r.getHours(),
              "m+": r.getMinutes(),
              "s+": r.getSeconds(),
              "q+": Math.floor((r.getMonth() + 3) / 3),
              S: r.getMilliseconds()
            };
          for (var a in /(y+)/.test(t) && (t = t.replace(RegExp.$1, String(r.getFullYear()).substr(4 - RegExp.$1.length))), o) new RegExp("(" + a + ")").test(t) && (t = t.replace(RegExp.$1, 1 === RegExp.$1.length ? o[a] : ("00" + o[a]).substr(String(o[a]).length)));
          return t
        }(new Date, "yyyyMMddhhmmss"),

这块代码就是取当前时间然后转化格式,g = 20230411175542

js
l = "p".concat(u).concat(g),

l = "p" + 101 + 20230411175542 = "p10120230411175542"

js
f = t(t({}, c), {}, {
  micalltime: g,
  miversion: i.miversgin,
  misource: u
}),

f 就是把上文中的 g、i.miversgin、u 合并为一个对象

js
h = Object.keys(f);

hf 对象的所有 key 的数组

js
h.sort((function (e, t) {
return e.toLowerCase().localeCompare(t.toLowerCase())
}));

h 数组中的元素全部转小写

js
var d = "";
h.forEach((function (e) {
d += "&".concat(e, "=").concat(f[e])
})), d = d.substr(1);

声明一个空字符串 d ,然后把 h 中的元素全部使用 & 拼接起来

js
 var p = r.hex_md5("".concat(d).concat(s));
o = "".concat(l).concat(p)

d 和之前的 s 拼接起来使用 md5 加密之后,在和 l 拼接,结果就出来了

o = "p10120230411175542fab22e3db8c2d5a523289a398d920647"

部分最终代码

js
function getSign(e, n) {
    var i = {
        encodeNumber: "101",
        password: "4f0c6725d917f8fe4e2791921187fd7d",
        miversgin: "p"
    };
    s = i.password;
    var c = {};
    if ("string" == typeof n) {
        n.split("&").forEach((function (e) {
            var t = e.split("=");
            2 === t.length && (c[t[0]] = t[1])
        }));
    } else {
        c = n;
        if (e.includes("?")) e.substr(e.indexOf("?") + 1).split("&").forEach((function (e) {
            var t = e.split("=");
            2 === t.length && (c[t[0]] = t[1])
        }));
    }
    var u = i.encodeNumber;
    g = function (e, t) {
        var n = new Date(e.getUTCFullYear(), e.getUTCMonth(), e.getUTCDate(), e.getUTCHours(), e.getUTCMinutes(), e.getUTCSeconds()),
            r = new Date(n.setHours(e.getUTCHours() + 8)),
            o = {
                "M+": r.getMonth() + 1,
                "d+": r.getDate(),
                "h+": r.getHours(),
                "m+": r.getMinutes(),
                "s+": r.getSeconds(),
                "q+": Math.floor((r.getMonth() + 3) / 3),
                S: r.getMilliseconds()
            };
        for (var a in /(y+)/.test(t) && (t = t.replace(RegExp.$1, String(r.getFullYear()).substr(4 - RegExp.$1.length))), o) new RegExp("(" + a + ")").test(t) && (t = t.replace(RegExp.$1, 1 === RegExp.$1.length ? o[a] : ("00" + o[a]).substr(String(o[a]).length)));
        return t
    }(new Date(), "yyyyMMddhhmmss");
    l = "p".concat(u).concat(g);
    f = _objectSpread2(_objectSpread2({}, c), {}, {
        micalltime: g,
        miversion: i.miversgin,
        misource: u
    });
    h = Object.keys(f);
    h.sort((function (e, t) {
        return e.toLowerCase().localeCompare(t.toLowerCase())
    }));
    var d = "";
    h.forEach((function (e) {
        d += "&".concat(e, "=").concat(f[e])
    })), d = d.substr(1);
    var p = hs.hex_md5("".concat(d).concat(s));
    o = "".concat(l).concat(p)
    return o;
}

// let url = 'https://miniapp.fang.com/pingyin/cityList';
// let params = {
//     'appname': 'fangx',
//     'v': '1.3.4',
//     'miniplat': 'weixin',
//     'scene': '1027',
// }
// console.log(getSign(url, params));

完整代码

关注公众号【趣码周】回复ftx即可获得。

关注我们

微信公众号:【趣码周

Last updated:

Released under the MIT License.