小程序渗透 小程序sign逆向的两种思路

admin 2024年10月24日22:25:45评论25 views字数 6122阅读20分24秒阅读模式

小程序渗透 小程序sign逆向的两种思路

小程序sign逆向和渗透两种思路,总有一款适合你

文章首发奇安信攻防社区:https://forum.butian.net/share/3815

本公众号技术文章仅供参考!
文章仅用于学习交流,请勿利用文章中的技术对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使用者本人负责。

1. sign发现和排查思路

起因是在做小程序渗透的过程中,改了数据重新发包显示系统异常,数据包如下图所示,猜测可能是signature这行是sign校验,所以才会出现异常

小程序渗透 小程序sign逆向的两种思路

然后因为正常请求显示的是code无效,且请求头中存在大量参数,于是为了排除干扰就开始依次修改请求头中的每一组值再发包,如果修改后发包显示系统异常,那就证明这个sign值校验跟这个请求头也有关,最后如下图所示,初步判断请求头中有四处busi-timestampbusi-noncestrbusi-appidjson请求体、这四处变一下响应就变成了系统异常,所以盲猜busi-signature就是根据他们四个的值进行计算得来的

小程序渗透 小程序sign逆向的两种思路

2. 解开微信devtools进行debug获取加密逻辑

接下来使用第一种方式进行破译sign校验,虽然这个方法非常简单,但是其实这个方式我并不推荐,因为它是需要解开微信小程序devtools,存在封号风险,所以如果要尝试一定要使用自己的微信小号、同时注意一定不要使用虚拟机!!!

我这里远控我另一台电脑登录我的微信小号,工具地址:https://github.com/JaveleyQAQ/WeChatOpenDevTools-Python

(这个工具使用过程中可能存在一些bug,不过好在聪明的我都解决了)

打开微信后执行

WechatOpenDevTools-Python.exe -x

成功打开devtools

小程序渗透 小程序sign逆向的两种思路小程序渗透 小程序sign逆向的两种思路

然后我这里直接快捷键,全局搜索busi-signature:,但是发现其存在bug,进度条一直卡住,不过好在js文件也不多,我就挨个点进去单个js文件的去搜索,然后这里有个点比较坑,就是你一定要触发对应js功能,Sources才加载对应js,你才能搜到,一定要注意,最后在appContext--usr--appservice.app.js里面发现了熟悉的代码

小程序渗透 小程序sign逆向的两种思路

上图代码分析,首先要知道busi-appID是写死的后续不会改变,然后可以看到busi-signature的值是a,而a又是一个函数计算得到的,我们直接跟进这个函数,如下,可以看到它先创建个HMAC对象秘钥是t,然后对e进行了计算,这里直接debug

小程序渗透 小程序sign逆向的两种思路

debug后,我们将秘钥拿出来,这里秘钥我们直接获取到了,是GZR3qIxxx(其实我们可以看之前调用该函数那张图片,上方有个变量r,它其实就是秘钥,它的值是一堆值的相加之和,那个我之前也简单看了一眼都是字符串加在一块)

小程序渗透 小程序sign逆向的两种思路

然后开始查看变量e,看着乱七八糟但实际上也很简单,分成四部分,第一部分就是请求体的json参数,第二部分就是请求头中的时间戳,第三部分就是请求头中的noncestr,第四部分就是请求地址了,然后字符串中间用n拼接起来,就是最终的值e了

小程序渗透 小程序sign逆向的两种思路
image-20240918210117862

3. 还原js后硬逆

接下来的方式就比较头铁了,它不能debug,而是利用wxapkg还原出js然后硬逆,这种方式有一点好处就是不用担心封号风险,还原wxpakg的方式有很多,之前我都是手动找然后手动还原的,现在也是有师傅写了工具,非常简单:https://github.com/wux1an/wxapkg

这个程序有一点就是它寻找微信的路径是默认路径,由于我的电脑微信的路径非默认路径,所以我改了下代码重新编译了下

wxapkg.exe scan

直接扫描,选择后回车直接解密

小程序渗透 小程序sign逆向的两种思路

同理我这里也是从0开始,用vs code(当然你也可以用小程序开发者工具,看个人习惯)打开后全局搜busi-signature:,它在一个函数里面

小程序渗透 小程序sign逆向的两种思路

把这个函数拿出来整理一下,如下,接下来我们挨个去分析,首先busi-appId这个是一个固定的值所以没什么好说的,然后busi-timeStamp就是时间戳这个也没什么好说的

小程序渗透 小程序sign逆向的两种思路

其次就是busi-nonceStr,其实有经验的就知道它是个随机字符串,busi-signature的计算依赖于noncestr,但是noncestr是什么其实没多大关系,接下来我们可以简单看一眼,busi-nonceStr怎么来的,代码在前面,我这里直接粘过来

小程序渗透 小程序sign逆向的两种思路

最重要的还是busi-signature,而它是根据函数encryptWithHmacSHA1计算出来的,这个函数有两个参数,我们先来看简单的r,也就是秘钥,r的赋值就在上方的语句中

小程序渗透 小程序sign逆向的两种思路

归根结底也是一堆字符串的拼接,但是有个问题就是我们分别得找这些对象clf.k3我们不知道这些对象是谁

r = "".concat(c.k1).concat(l.k2).concat(f.k3.slice(2)).concat(c.k4), 

其实找他们也很简单,有一个便捷方法, 但是讲解便捷方法之前,我先硬扣一下k1和k4,而他们属于对象c,那么对象c在哪里呢,我们根据这个代码往前回溯,如下图之前分析的所有代码其实都隶属于38,而看到如下图所示的代码后,其实一眼看出这就是构建工具生成的代码,38代表38这个模块,而C的值其实也能看到是n(39),所以可以知道n大概就是模块加载器,而我们想知道的c对象其实就是39这个模块

小程序渗透 小程序sign逆向的两种思路

所以我们直接搜索39:,至此k1和k4拿到

小程序渗透 小程序sign逆向的两种思路

然后回想我们刚才的代码如下图,现在还剩下标红的两部分,那么这块我们就用我们上文中提到的简便思路,其实非常简单,直接搜索k2 =k3 =

小程序渗透 小程序sign逆向的两种思路

k2如下

小程序渗透 小程序sign逆向的两种思路

k3如下

小程序渗透 小程序sign逆向的两种思路

所以最后拼接得到秘钥(要注意k3用了slice(2)函数,取得是从索引为2位置开始的字符串)

GZR3qIBe^lC@I!KpkyBtEg$&@0UncJpr

秘钥得到了,接下来就该研究,另一个参数n了,其实也很简单,把之前的图拿来,我们重点看红框部分

小程序渗透 小程序sign逆向的两种思路
image-20240919135811680

为了更加直观,我把代码粘贴出来并修改了下格式,下方代码判断t.method是否是get,如果是get就按照第一行计算,如果不是就按照第二行计算,其实这里就能看出来这个网站对于GET请求和POST请求计算是不一样的

小程序渗透 小程序sign逆向的两种思路

//这里判断如果t.metchod是get,这里t不能猜出就是当前http对象,n就等于后续的字符串相加,s通过上图可以看出是get请求中url的参数部分,d通过上上张图片可以得出是busi-timeStamp的值,y则是busi-nonceStr,后续又加上了请求中的去除参数的url部分
if (n = "get" == t.method ? (s || "") + "n" + d + "n" + y + "na671602347467n" + (t.url.includes("?") ? t.url.split("?")[0] : t.url) + "n" 
//这里类似,非get请求走的是下面的代码, 跟上面类似,唯一一个区别就是最开始的字符串是t.data,也就是post请求中的请求参数
    : (JSON.stringify(t.data) || "") + "n" + d + "n" + y + "na671602347467n" + (t.url.includes("?") ? t.url.split("?")[0] : t.url) + "n",

其实从上面的讲解不难看出,虽然sign加密对get请求和post请求加密逻辑不一样,不过后面的值是相同的都是timeStamp的值、nonceStr、url,区别就在于最前面那个psot请求是post的参数,get请求是get参数,不过get的参数值拿来其实不能直接用,往后看就明白了

这里我们将参数n和r都拿到了,就剩encryptWithHmacSHA1函数还不知道,直接一搜就明了了,如下图

小程序渗透 小程序sign逆向的两种思路

至此,我们把整体的逻辑通过js代码还原了出来

==注意:==

有两点需要注意,第一点就是不管是POST请求还是GET请求,计算sign都要用到url,而请求包中有些请求url后面要拼接一些参数,所以当计算的时候要去掉,不过非常贴心的一点是,这个网站发的请求包中存在一个请求头url,可以看到值是已经是去掉参数后的路径,非常贴心,我后面写脚本的时候取得也是这个值

小程序渗透 小程序sign逆向的两种思路

第二点就是,GET请求稍微复杂一点,在后来的测试中发现有两个点需要注意,一个就是排序它会将参数排序也就是下图那个部分的代码处,这个代码我就不具体说了,跟大家讲下实际效果

小程序渗透 小程序sign逆向的两种思路

实际效果如下

//正常请求的参数如下时
dd=333&ab=123&bb=222&aa=111
//计算前需要重新排序,根据键名的首字母顺序来,如下
aa=111&ab=123&bb=222&dd=333

//还有一点很重要有些请求会带一个v=172912323,要把这个v去掉,v的值也就是时间戳,但是这个时间戳跟请求头中的时间戳不通,不过好在这个v没有参与运算
//例子如下
v=172912323&=333&ab=123&bb=222&aa=111
//实际计算
aa=111&ab=123&bb=222&dd=333

4. 利用

这里提到利用的话,通常按照之前的习惯我肯定是用mitmproxy,代码如下,已经写好注释了

python代码(mitm_sign.py)

import json
import time
import execjs


def str_sort(word):
    pairs = word.split('&')
    param_dict = {}
    # 将参数对解析为字典
    for pair in pairs:
        key, value = pair.split('=')
        param_dict[key] = value
    # 如果有参数 'v',则将其移除
    if 'v' in param_dict:
        del param_dict['v']
    # 对字典中的项进行排序
    sorted_pairs = sorted(param_dict.items())
    # 将排序后的参数对重新组合为字符串
    sorted_word = '&'.join([f"{key}={value}" for key, value in sorted_pairs])
    return sorted_word


#截获请求加密data,以及更新sign
def request(flow):
    json_data = ''
    sign = ''
    # url无参数
    url = flow.request.headers.get('url''')
    # noncestr
    noncestr = flow.request.headers.get('busi-nonceStr''')
    url_header = flow.request.headers.get('url''')
    # 时间戳
    now_time = str(time.time()).replace('.''')[:13]

    # 加载js文件
    with open('huishang.js''r', encoding='utf-8'as f:
        js_file = f.read()
    # 加载执行环境
    js_exec = execjs.compile(js_file)
    #这里如果是post请求那么json_data就是请求体中的json数据
    if flow.request.method == "POST":
        #获取json数据,不为空直接赋值,为空的话会抛异常那就手动赋值为空字符串
        try:
            json_data = flow.request.json()
        except Exception as e:
            json_data = ''
        sign = js_exec.call('get_sign', url_header, json.dumps(json_data).replace(' '''), noncestr, now_time)
    #如果是get则变成url参数并去v后排序得到的值
    if flow.request.method == "GET":
        url = flow.request.pretty_url
        if "?" in url:
            # 获取参数部分
            params = url.split("?"1)[1].strip()
            json_data = str_sort(params)

        sign = js_exec.call('get_sign', url_header, json_data, noncestr, now_time)


    #更改时间戳和sign
    flow.request.headers['busi-signature'] = sign
    flow.request.headers['busi-timestamp'] = str(now_time)

js代码(exec.js)

const crypto = require('crypto');

secretKey = 'GZR3qIBe^lC@I!KpkyBtEg$&@0UncJpr'
function get_sign(url,jsondata,noncestr,now_time){
    e = jsondata
        +'n'+now_time
        + 'n'+noncestr
        + 'na671602347467'
        + 'n'+url
        + 'n'
    const hmac = crypto.createHmac('sha1', secretKey);
    hmac.update(e)
    sign = hmac.digest("hex")
    return sign
}

然后执行

mitmproxy.exe -p 8081 -s .mitm_sign.py

之后就可以随意发包了不用担心sign了

小程序渗透 小程序sign逆向的两种思路

5. 结语

像微信小程序目前有很多项目或思路能强制打开devtools,这样我们debug去调试非常简单就能还原加密过程,但是一些其它APP为载体的小程序,目前还没有思路,如果有思路的师傅可以指点一下

原文始发于微信公众号(小惜渗透):小程序渗透 小程序sign逆向的两种思路

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月24日22:25:45
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   小程序渗透 小程序sign逆向的两种思路https://cn-sec.com/archives/3309920.html

发表评论

匿名网友 填写信息