安全测试 分享一次微信小程序的接口破解

alienwu · 2020年09月23日 · 最后由 fdy 回复于 2023年12月07日 · 13461 次阅读

本人是一个游戏测试小菜鸡,平时十分喜欢玩腾讯的一款老端游 QQ 三国,有一次巧遇发现了一款对我体验十分友好的小程序《三国查询》,然后我想破解它。
先介绍一下这款小程序的功能:
1.提供快速查询游戏内道具摆摊/商店贩卖的具体坐标;
2.提供清晰知道该道具的详细属性;
我遇到了这款小程序后就爱上了它,它使我玩游戏变得很便捷。但是我也是个老破坏家了,遇到一款游戏,一款软件总的想弄它,于是就萌生出破解这款小程序的想法了。
接着来分享破解这款小程序的历程:
1).获取到该款小程序的包:
在夜神模拟器中下载微信并登录微信,然后搜索目标小程序,加载目标小程序。在加载完成后,你的本地就会生成一个微信小程序的包文件 xx.wxapkg,然后就讲该包文件发到电脑本地。具体路径在夜神模拟器的:/data/data/com.tencent.mm/MicroMsg/...(按时间排序,一般情况下是一个文件夹)/appbrand/pkg/xx.wxapkg图中有 4 个 wxapkg 文件,一般来说最大的那个就是目标的小程序,不太确定哪个是目标的包文件可以全部拉到 pc 本地后逐一反编译排除即可。
2).获取到源包文件后接下来就是对小程序包文件进行反编译:
关于微信小程序的反编译教程网上有很多,这里只作简单的介绍。
我这里是用的是 wxappUnpacker 来对小程序进行反编译。
node wuWxapkg.js [-d] 执行命令后即可反编译出微信小程序的源代码。

3).分析小程序源代码中的协议:
经过查看发现这款小程序的在 app-service.js 上,破解一个接口的步骤分为就是破解接口中的请求参数,请求头,返回的响应体内容。
首先,使用了 fiddler 进行抓包分析接口,经过分析发现接口的主要参数为:clientGuid,sign,token,破解这三个参数后即可构造请求。
请求体内容详情如下图:

这个作者对自己的作品做了很严谨的接口保护,响应体也进行了加密,可想而知接口数据的重要性。
响应内容详情如下图:

最后是打开 app-service.js 找到并分析具体参数的构造过程,接着就是分析请求的内容。这个过程就不作多介绍了,靠个人操作~
4).编写破解代码:
关于 clientGuid 请求参数:
从源码中我找到了 clientGuid 参数的具体生成过程:

function uuid() {
    for (var e = [], r = 0; r < 36; r++)
        e[r] = "0123456789abcdef".substr(Math.floor(16 * Math.random()), 1);
    e[14] = "4",
    e[19] = "0123456789abcdef".substr(3 & e[19] | 8, 1),
    e[8] = e[13] = e[18] = e[23] = "-";
    var t = e.join("");
    return t
}

该函数就是构建 clientGuid 的函数。
关于 token 请求参数:
这个毋庸置疑,是用户登录的令牌,这个没办法在客户端源码中获取,只有登录的用户才拥有,获取 token 最直接的办法就是抓包从 fiddler 中获取。

关于 sign 签名参数:

function n(n, r) {
    var t = (65535 & n) + (65535 & r);
    return (n >> 16) + (r >> 16) + (t >> 16) << 16 | 65535 & t
}
function r(n, r) {
    return n << r | n >>> 32 - r
}
function t(t, e, u, o, c, f) {
    return n(r(n(n(e, t), n(o, f)), c), u)
}
function e(n, r, e, u, o, c, f) {
    return t(r & e | ~r & u, n, r, o, c, f)
}
function u(n, r, e, u, o, c, f) {
    return t(r & u | e & ~u, n, r, o, c, f)
}
function o(n, r, e, u, o, c, f) {
    return t(r ^ e ^ u, n, r, o, c, f)
}
function c(n, r, e, u, o, c, f) {
    return t(e ^ (r | ~u), n, r, o, c, f)
}
function f(r) {
    for (var t = 1732584193, f = -271733879, i = -1732584194, a = 271733878, h = 0; h < r.length; h += 16) {
        var l = t
          , g = f
          , v = i
          , d = a;
        f = c(f = c(f = c(f = c(f = o(f = o(f = o(f = o(f = u(f = u(f = u(f = u(f = e(f = e(f = e(f = e(f, i = e(i, a = e(a, t = e(t, f, i, a, r[h + 0], 7, -680876936), f, i, r[h + 1], 12, -389564586), t, f, r[h + 2], 17, 606105819), a, t, r[h + 3], 22, -1044525330), i = e(i, a = e(a, t = e(t, f, i, a, r[h + 4], 7, -176418897), f, i, r[h + 5], 12, 1200080426), t, f, r[h + 6], 17, -1473231341), a, t, r[h + 7], 22, -45705983), i = e(i, a = e(a, t = e(t, f, i, a, r[h + 8], 7, 1770035416), f, i, r[h + 9], 12, -1958414417), t, f, r[h + 10], 17, -42063), a, t, r[h + 11], 22, -1990404162), i = e(i, a = e(a, t = e(t, f, i, a, r[h + 12], 7, 1804603682), f, i, r[h + 13], 12, -40341101), t, f, r[h + 14], 17, -1502002290), a, t, r[h + 15], 22, 1236535329), i = u(i, a = u(a, t = u(t, f, i, a, r[h + 1], 5, -165796510), f, i, r[h + 6], 9, -1069501632), t, f, r[h + 11], 14, 643717713), a, t, r[h + 0], 20, -373897302), i = u(i, a = u(a, t = u(t, f, i, a, r[h + 5], 5, -701558691), f, i, r[h + 10], 9, 38016083), t, f, r[h + 15], 14, -660478335), a, t, r[h + 4], 20, -405537848), i = u(i, a = u(a, t = u(t, f, i, a, r[h + 9], 5, 568446438), f, i, r[h + 14], 9, -1019803690), t, f, r[h + 3], 14, -187363961), a, t, r[h + 8], 20, 1163531501), i = u(i, a = u(a, t = u(t, f, i, a, r[h + 13], 5, -1444681467), f, i, r[h + 2], 9, -51403784), t, f, r[h + 7], 14, 1735328473), a, t, r[h + 12], 20, -1926607734), i = o(i, a = o(a, t = o(t, f, i, a, r[h + 5], 4, -378558), f, i, r[h + 8], 11, -2022574463), t, f, r[h + 11], 16, 1839030562), a, t, r[h + 14], 23, -35309556), i = o(i, a = o(a, t = o(t, f, i, a, r[h + 1], 4, -1530992060), f, i, r[h + 4], 11, 1272893353), t, f, r[h + 7], 16, -155497632), a, t, r[h + 10], 23, -1094730640), i = o(i, a = o(a, t = o(t, f, i, a, r[h + 13], 4, 681279174), f, i, r[h + 0], 11, -358537222), t, f, r[h + 3], 16, -722521979), a, t, r[h + 6], 23, 76029189), i = o(i, a = o(a, t = o(t, f, i, a, r[h + 9], 4, -640364487), f, i, r[h + 12], 11, -421815835), t, f, r[h + 15], 16, 530742520), a, t, r[h + 2], 23, -995338651), i = c(i, a = c(a, t = c(t, f, i, a, r[h + 0], 6, -198630844), f, i, r[h + 7], 10, 1126891415), t, f, r[h + 14], 15, -1416354905), a, t, r[h + 5], 21, -57434055), i = c(i, a = c(a, t = c(t, f, i, a, r[h + 12], 6, 1700485571), f, i, r[h + 3], 10, -1894986606), t, f, r[h + 10], 15, -1051523), a, t, r[h + 1], 21, -2054922799), i = c(i, a = c(a, t = c(t, f, i, a, r[h + 8], 6, 1873313359), f, i, r[h + 15], 10, -30611744), t, f, r[h + 6], 15, -1560198380), a, t, r[h + 13], 21, 1309151649), i = c(i, a = c(a, t = c(t, f, i, a, r[h + 4], 6, -145523070), f, i, r[h + 11], 10, -1120210379), t, f, r[h + 2], 15, 718787259), a, t, r[h + 9], 21, -343485551),
        t = n(t, l),
        f = n(f, g),
        i = n(i, v),
        a = n(a, d)
    }
    return [t, f, i, a]
}
function i(n) {
    for (var r = "", t = 0; t < 4 * n.length; t++)
        r += "0123456789abcdef".charAt(n[t >> 2] >> t % 4 * 8 + 4 & 15) + "0123456789abcdef".charAt(n[t >> 2] >> t % 4 * 8 & 15);
    return r
}
function a(n) {
    for (var r = 1 + (n.length + 8 >> 6), t = new Array(16 * r), e = 0; e < 16 * r; e++)
        t[e] = 0;
    for (e = 0; e < n.length; e++)
        t[e >> 2] |= (255 & n.charCodeAt(e)) << e % 4 * 8;
    return t[e >> 2] |= 128 << e % 4 * 8,
    t[16 * r - 2] = 8 * n.length,
    t
}

//具体调用生成sign方法
var str="clientGuid=" + clientGuid + "&clientTimestamp=" + clientTimestamp + "&key=6JFzFFN5527IYdDf16VlBxErt96NTX18"
var sign =i(f(a(str)))

这里解释一下,sign 的生成是调用一个重重调用的复杂的计算函数,这个不用多管,其目标是接收一个字符串,该字符串的形成为:
var str="clientGuid=" + clientGuid + "&clientTimestamp=" + clientTimestamp + "&key=6JFzFFN5527IYdDf16VlBxErt96NTX18"
然后调用方法 var sign =i(f(a(str))) 即可生成 sign。我只能说这个作者有点呆萌,我第一次见人这么直白的生成 sign,毫无破解压力。

请求参数经全部破解,这时可以构造请求了,但是这里有个坑,就是作者对请求头作了检测(不太确定是微信自带的检测,还是作者自己设定的),请求头必须带字段 type:wechat 方可访问接口。
请求头详情如下:

POST /qqsg/boothList HTTP/1.1
Host: qqsg.pc9527.vip:3001
Connection: keep-alive
Content-Length: 366
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36 MicroMessenger/7.0.9.501 NetType/WIFI MiniProgramEnv/Windows WindowsWechat
content-type: application/json
type: weChat
Accept-Encoding: gzip, deflate, br

关于响应体 data 的破解:
请求成功后以为一切大功告成,其实不然,作者太爱惜自己的接口了,对响应体也进行了加密,经分析源码发现原来用的 AES 加密,于是后面就是一连串的破解编码:
最后对 data 解码成功:

到这里整个破解过程已经结束了~

结语:
记一个爱捣蛋爱搞破坏的游戏点点点菜鸡的一次破解微信小程序接口分享~
(最后我的账号已经被作者永久封禁了┭┮﹏┭┮。哼,你也是吃灰色产业的钱,你别逞强)
破解接口的好处这里就不多说了,最直接的一个操作就是套壳,伪造同款软件。直接盗用接口数据来进行低价出售服务来盈利,对方再怎么作流量监控,访问频率控制都是没用的,多购买几个对方的账号,IP,+redis 缓存访问即可解决这个问题。当然我并没有这个心思做这个事情,也坚持自己的底线不会做这个事情。毕~

QQ 三国真好玩,(●'◡'●)

破解项目链接:
https://github.com/alienwu2018/Crack-QQSGSearch.git

共收到 4 条回复 时间 点赞
仅楼主可见

老哥 这个人的按你计算方式貌似变了。。我之前也想破解来着(抚琴 dd

上面有一个响应体截图,” code“:501 不用管,因为这是我临时构造出来的,我的账号被作者封了,不舍得花钱买第二个账号进行演示,所以临时贴了个响应体,没注意修改 code 值~

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册