最新版wxunpacker
2023-3-11 11:22:56 Author: bbs.pediy.com(查看原文) 阅读量:29 收藏

2

0

文件调用方法:
python3 wx.py --help
usage: wx.py [-h] [-w W] [-i I] [-o O] [-b B]

optional arguments:
  -h, --help  show this help message and exit
  -w W        Wechat mini program ID.
  -i I        A <folder name> which contains multiple wxapkg files, or a single wxapkg <file name>.
  -o O        Output folder.
  -b B        True/False means whether to beautify the JS code, True will result in a poor performance.
3

0

from Crypto.Cipher import AES
import hashlib
import os
import json
import urllib
from helper import helper
import jsbeautifier
import execjs
import argparse
import os

# 微信小程序文件格式
# 文件头
# 1字节 一定是190
# 4字节 一定是0
# 4字节 索引段长度
# 4字节 数据段长度
# 1字节 一定是237
# 4字节 文件总个数
# 索引段
# 4字节 文件名长度
# N字节 文件名
# 4字节 文件在数据段中的位置(相对于header的0偏移,而不是如下数据段的0偏移)
# 4字节 文件长度
# 数据段

OUTPUT_FOLDER = "output"
NEED_BEAUTIFY_JS = False
iv = "the iv: 16 bytes"
salt = "saltiest"
global_wxml = ["",0]

def decrypt(wx_package, wxid):
    fsize = os.path.getsize(wx_package)

    f = open(wx_package, "rb")
    f.seek(6)
    wx_header = f.read(1024)
    wx_others = f.read(fsize - 6 - 1024)
    f.close()

    aes_key = hashlib.pbkdf2_hmac('sha1', wxid.encode(), salt.encode(), 1000, 32)
    cipher = AES.new(aes_key, AES.MODE_CBC, iv.encode())
    decrypted_wx_header = cipher.decrypt(wx_header)
    n = decrypted_wx_header[-1]
    if n > 0:
        decrypted_wx_header = decrypted_wx_header[:-n]

    xor_key = ord(str(wxid[-2]))
    decrypted_wx_others = []
    for b in wx_others:
        decrypted_wx_others.append(b ^ xor_key)

    return decrypted_wx_header + bytes(decrypted_wx_others)

def write_file(fname, buf, mode):
    items = fname.split("/")
    path = OUTPUT_FOLDER
    for i in range(0, len(items) - 1):
        path += "/" + items[i]
        md(path)

    f = open(path + "/" + items[-1], mode)
    f.write(buf)
    f.close()

def process_package(buf):
    index = 0
    # <editor-fold desc="处理微信头">
    print("magic number is " + str(ord(buf[index:1])))
    index += 1
    print("always o is {0}".format(int.from_bytes(buf[index:index + 4], "big")))
    index += 4
    index_seg_length = int.from_bytes(buf[index:index + 4], "big")
    print("index segments length is {0}".format(index_seg_length))
    index += 4
    body_seg_length = int.from_bytes(buf[index:index + 4], "big")
    print("body segments length is {0}".format(body_seg_length))
    index += 4
    print("last mask must be 237 ---> {0}".format(int.from_bytes(buf[index:index + 1], "big")))
    index += 1
    file_count = int.from_bytes(buf[index:index + 4], "big")
    print("file count is {0}".format(file_count))
    index += 4
    # </editor-fold">

    index_length = 0
    # <editor-fold desc="处理微信数据段">
    for fcount in range(0, file_count):
        if index_length + 4 >= index_seg_length:  # 如果用while true
            break
        filename_length = int.from_bytes(buf[index:index + 4], "big")
        index += 4
        fname = buf[index:index + filename_length].decode()
        index += filename_length
        offset_of_file_in_segment = int.from_bytes(buf[index:index + 4], "big")
        index += 4
        file_size = int.from_bytes(buf[index:index + 4], "big")
        index += 4

        print("File name length ={0}, file name = {1}, offset in segment = {2}, file size = {3}".format(filename_length,
                                                                                                        fname,
                                                                                                        offset_of_file_in_segment,
                                                                                                        file_size))

        content = buf[offset_of_file_in_segment:offset_of_file_in_segment + file_size]
        if fname.endswith(".json"):
            content = urllib.parse.unquote(json.dumps(json.loads(content.decode()), indent=4))
            write_file(fname, content, "w")
        elif fname.endswith(".js"):
            content = content.decode()
            if "app-service.js" not in fname and NEED_BEAUTIFY_JS is True:
                content = jsbeautifier.beautify(content)
            write_file(fname, content, "w")
        else:
            write_file(fname, content, "wb")
        index_length += 4 * 3 + filename_length
    # </editor-fold>

def md(dir):
    if os.path.exists(dir) is False:
        os.mkdir(dir)

def process_json(fname):
    if os.path.exists(fname) is False:
        return

    f = open(fname, "r")
    all_lines = f.readlines()
    f.close()

    token = ".json'] = {"
    jsons = ''.join(all_lines).split("__wxAppCode__[")

    for j in jsons:
        if token in j:
            index = j.index(token)
            fname = j[1:index] + ".json"
            print("Processing " + fname)
            content, index = helper.get_string_by_seperators(j[index + len(token) - 1:], "{", "};", 0)
            content = json.dumps(json.loads("{" + content + "}"), indent=4)
            write_file(fname, content, "w")

def process_js(js_file):
    fname = OUTPUT_FOLDER + "/" + js_file
    if os.path.exists(js_file) is False:
        return

    f = open(js_file, "r")
    all_lines = f.readlines()
    f.close()

    items = ''.join(all_lines).split('define("')
    for i in range(1,len(items)):
        line = items[i]
        index = line.index(",")
        fname = line[0:index].replace('"','')

        index = line.index("{")
        line = line[index+1:].strip()

        index = line.rindex("});")
        line = line[0:index]

        if "}" in line:
            index = line.rindex("}")
            line = line[0:index]

        if line.startswith("'use strict';") or line.startswith('"use strict";'):
            line = line[13:]
        elif (line.startswith('(function(){"use strict";') or line.startswith(
                "(function(){'use strict';")) and line.endswith("})();"):
            line = line[25:][:-5]

        print("Processing " + fname)
        if NEED_BEAUTIFY_JS:
            write_file(fname, jsbeautifier.beautify(line), "w")
        else:
            write_file(fname, line, "w")

def process_wxss(fname):
    fname = OUTPUT_FOLDER + "/"+fname
    if os.path.exists(fname) is False:
        return

    f = open(fname, "r")
    all_lines = f.readlines()
    f.close()

    token = ".wxss"
    jsons = ''.join(all_lines).split("setCssToHead(")

    for j in jsons:
        if token in j:
            index = j.find('",],', 0)
            if index == -1:
                continue
            buf = j[0:index] + ";}"
            content = buf[:-2].replace('",[1],"', '').replace("\\n", '\n')
            items = content.split("\n")

            content = ""
            for item in items:
                if item == ";}":
                    continue
                if item.startswith('["'):
                    item = item[2:]
                c2 = item.replace("{", " {\n\t").replace(";", ";\n\t").replace("}", "\n}\n\n").replace("\\x3d",
                                                                                                       "=").replace(
                    "\\x22", '"').replace('",[0,', "").replace('],"', 'px').replace(":", ": ").replace("wx-", "")
                c3 = c2.replace("\n}\n\n", ";\n}\n\n")
                if c3.startswith(";"):
                    c3 = c3[1:]
                content += c3

            fname = helper.get_string_by_seperators(j, ',{path:"', '"})', 0)[0].replace("./", "")
            print("Processing " + fname)
            write_file(fname, content.replace("body {", "page {").replace(": : ", "::"), "w")

def process_wxml_nodes(nodes):
    wxml = ""
    if global_wxml[1]>0:
        wxml = "\t"*global_wxml[1]

    if type(nodes).__name__ != "dict":
        global_wxml[0] += str(nodes)
        return

    tag = nodes["tag"].replace("wx-", "")
    wxml += "<" + tag

    if nodes.get("attr") is not None:
        for attr in nodes["attr"].keys():
            wxml += " " + attr + "=\"" + str(nodes["attr"][attr]) + "\""
    wxml += ">"
    wxml += "\n"

    global_wxml[0] += wxml

    global_wxml[1] += 1
    for child in nodes["children"]:
        process_wxml_nodes(child)
    global_wxml[1] -= 1

    global_wxml[0] += "</" + tag + ">\n"

    return

def process_wxml_remove_useless(wxml_source):
    source = wxml_source

    tmp = helper.get_string_by_seperators(wxml_source,"<script>","</script>",0)[0]
    if len(tmp)>0:
        index = tmp.find("var setCssToHead")
        index2 = tmp.rindex(");")
        source = tmp[0:index]+tmp[index2+2:]

    else:
        first_token = 'if (!noCss)'
        last_token = 'var __subPageFrameEndTime__ = Date.now();'
        index = wxml_source.find(first_token)
        index2 = wxml_source.find(last_token, index)

        if index>-1 and index2>-1:
            source = wxml_source[0:index] + wxml_source[index2:]

    return source

def process_wxml(pageframe):
    pageframe = OUTPUT_FOLDER + "/" + pageframe
    if os.path.exists(pageframe) is False:
        return

    source = open(pageframe).read()
    if source=="/* This file is left intentionally blank */":
        return

    flist = "\n"
    patch = 'var window={};var navigator={};navigator.userAgent="iPhone";window.screen={};document={};function define(){};function require(){};function setCssToHead(file, _xcInvalid, info){};'

    items = source.split("else __wxAppCode__[")
    x = []
    index = 0
    for item in items:
        func = helper.get_string_by_seperators(item,"=",";",0)[0]
        if "$" not in func:
            continue
        flist += "fuck_{0}={1};\n".format(index,func.strip())
        index+=1
        x.append(helper.get_string_by_seperators(item,"'","'",0)[0])

    source = process_wxml_remove_useless(source)
    patched_source = patch + source + flist
    # patched_source,x = process_wxml_method_1(source,patch)
    # if len(x) == 0:
    #     patched_source,x = process_wxml_method_2(source,patch)
    #     if len(x)==0:
    #         patched_source, x = process_wxml_method_3(source, patch)
    #         if len(x) == 0:
    #             raise Exception("Need more analysis for $gwx wxml format.")
    #
    js = execjs.compile(patched_source)


    for func_no in range(0, len(x)):
        try:
            nodes = js.call("fuck_{0}".format(func_no),[],[],[],[])
        except Exception as e:
            print(e)
            continue

        fname = x[func_no].replace("./", "")

        print("Processing "+fname)
        if len(nodes["children"])==0:
            continue
        global global_wxml
        process_wxml_nodes(nodes["children"][0])
        write_file(fname,global_wxml[0],"w")

        global_wxml = ["",0]

def process(flist,func):
    for f in flist:
        func(OUTPUT_FOLDER + f)

        with open(OUTPUT_FOLDER + "/app.json", "r") as fs:
            j = json.load(fs)
            for sub in j["subPackages"]:
                func(OUTPUT_FOLDER + "/" + sub["root"] + f)

def get_package_content(wx_package, wxid):
    fsize = os.path.getsize(wx_package)

    f = open(wx_package, "rb")
    data = f.read(fsize)
    f.close()

    buf = data

    if int(data[0]) != 190 and data[0:6].decode() == "V1MMWX":
        buf = decrypt(wx_package, wxid)
    if int(buf[0]) != 190:
        buf = None

    return buf

def init():
    parser = argparse.ArgumentParser()
    parser.add_argument("-w", help="Wechat mini program ID.")
    parser.add_argument("-i", help="A <folder name> which contains multiple wxapkg files, or a single wxapkg <file name>.")
    parser.add_argument("-o", help="Output folder.")
    parser.add_argument("-b",default=False, help="True/False means whether to beautify the JS code, True will result in a poor performance.")

    args = parser.parse_args()

    global OUTPUT_FOLDER
    OUTPUT_FOLDER = args.o
    if args.o is None:
        OUTPUT_FOLDER = "output"

    global NEED_BEAUTIFY_JS
    NEED_BEAUTIFY_JS = args.b

    wxid = args.w
    input = args.i

    wxapkg = []

    if input is None:
        input = "."

    if ".wxapkg" in input and os.path.exists(input):
        wxapkg.append(input)
    elif os.path.exists(input):
        for fp,dirs,fs in os.walk(input):
            for f in fs:
                if ".wxapkg" in f:
                    wxapkg.append(os.path.join(fp,f))

    return wxid,wxapkg

def main():
    wxid, wxapkg = init()
    if wxid is None or len(wxapkg)==0:
        print("Error wxid or input files. Type < python3 wx.py --help > to get more information.")
        return

    md(OUTPUT_FOLDER)

    print("Unpacking package...")
    for apkg in wxapkg:
        buf = get_package_content(apkg,wxid)
        if buf is not None:
            process_package(buf)

    print("================================================================")
    print("")
    print("Unpacking JSON files...")
    process_json(OUTPUT_FOLDER+"/app-service.js")

    print("================================================================")
    print("")
    print("Unpacking JS files...")
    process(["/app-service.js"], process_js)


    print("================================================================")
    print("")
    print("Unpacking WXSS files...")
    process(["/page-frame.js", "/page-frame.html"], process_wxss)

    print("================================================================")
    print("")
    print("Unpacking WXML files...")
    process(["/app-service.js", "/page-frame.js", "/page-frame.html"], process_wxml)

if __name__ == "__main__":
    main()

最后于 1天前 被今天是星期五编辑 ,原因:

活跃值: 活跃值 (2404)

能力值:

( LV2,RANK:10 )

在线值:

4

0

谢谢分享

活跃值: 活跃值 (30)

能力值:

( LV2,RANK:10 )

在线值:

5

0

不会用 

活跃值: 活跃值 (617)

能力值:

( LV2,RANK:10 )

在线值:

6

0

测试了一下,貌似还是有点问题


文章来源: https://bbs.pediy.com/thread-276426.htm
如有侵权请联系:admin#unsafe.sh