首先我用一道CTF题目来说说微信小程序逆向是什么,以及如何去解密,拆包

题目的附件是这两个
- WXML:微信标记语言,用于描述页面结构,类似 HTML。
- WXSS:微信样式表,类似 CSS,用于定义页面样式。
- JS:业务逻辑代码,处理交互和数据处理。
- JSON:配置文件,描述页面配置、路由、全局配置等。
- 开发阶段:开发者编写上述文件,通过微信开发者工具进行调试。
- 编译阶段:开发者点击上传或预览时,微信开发者工具将源代码经过压缩、混淆、整合等处理,生成一个或多个
.wxapkg
文件。
- JS 文件会被压缩合并到
app-service.js
中; - WXML 文件与 WXSS 文件经过处理整合到
page-frame.html
或其他文件中; - 配置文件合并为
app-config.json
等。
加密与混淆手段
压缩与混淆:代码经过压缩,变量名缩短,格式被压缩成一行。
模板转换:WXML 模板会被编译为一段 JavaScript 代码,通过特殊的模板解析函数(例如 $gwx 等)还原页面视图。
加密算法:部分逻辑(如 CTF 题目)可能嵌入自定义加密算法,例如使用类似 RC4 的 S-box 生成、异或操作等。
https://github.com/Angels-Ray/UnpackMiniApp
首先下载这个工具

之后会看到这个应用程序,首次使用会有点卡,之后双击选择你要解密的小程序

因为这个题目是没有加密的,所以会显示无需解密
https://github.com/Fickley/wxappUnpacker
之后下载这个工具,将wxapkg程序包复制到反编译脚本目录wxappUnpacker里面,依次安装以下依赖
这是用来拆包的可以理解,但是首次使用需要配置一下
npm install esprima
npm install css-tree
npm install cssbeautify
npm install vm2
npm install uglify-es
npm install js-beautify
之后就可以开始反编译了
node wuWxapkg.js 123.wxapkg
使用这个命令之后会在这个目录里面看到一个同名文件夹,打开就会看到代码了

之后咱们就可以来做题了
首先打开这个wxml的文件
<!-- index.wxml -->
<navigation-bar title="Weixin" back="{{false}}" color="black" background="#FFF"></navigation-bar>
<scroll-view class="scrollarea" scroll-y="true" scroll-with-animation="true" style="height: 100vh;">
<view class="container">
<view animation="{{animationData}}" class="input-container">
<input
class="cool-input"
placeholder="请输入内容..."
bindinput="onInputChange"
value="{{inputValue}}"
/>
<icon type="search" class="input-icon"></icon>
</view>
<button animation="{{animationData}}" class="check-button" bindtap="onCheck" style="position: relative; left: -1rpx; top: -981rpx">Check</button>
</view>
</scroll-view>
发现逻辑在index.js代码下面,之后我们把刚刚的文件夹拖进webstorm

但是大家可以发现这个js代码的格式好像不太对,这里可以用
https://beautifier.io/
这个工具进行美化处理

可以看到十分的清晰明了,之后分析发现就是一个rc4加密,直接上脚本
def generate_sbox(key):
# 初始化数组 a 和辅助数组 e
a = list(range(256))
n = len(key)
e = [ord(key[i % n]) for i in range(256)]
i = 0
for r in range(256):
i = (i + a[r] + e[r]) % 256
# 交换 a[r] 和 a[i]
a[r], a[i] = a[i], a[r]
# 同时交换 a[(r+1)%256] 和 a[(i+1)%256]
r1 = (r + 1) % 256
i1 = (i + 1) % 256
a[r1], a[i1] = a[i1], a[r1]
return a
def keystream(sbox, length):
# 复制 sbox 以避免改变原数组
n = sbox[:]
e = 0
o = 0
stream = []
for i in range(length):
o = (o + n[i % 256]) % 256
e = (e + n[o]) % 256
# 交换 n[o] 与 n[e]
n[o], n[e] = n[e], n[o]
s = n[(n[o] + n[e]) % 256]
stream.append(s)
return stream
if __name__ == '__main__':
key = "NSSCTF2025"
# 固定的目标数组
fixed = [216, 156, 159, 86, 8, 143, 254, 92, 113, 3, 228, 74, 37, 80, 146, 68, 71, 42, 137, 132, 170, 85, 13, 196, 226, 152, 120, 176, 184, 36, 195, 233, 123, 230, 89, 10, 121, 180, 5, 219]
L = len(fixed)
# 生成 S-box
sbox = generate_sbox(key)
# 生成 keystream 对应长度
stream = keystream(sbox, L)
# 根据公式: input_char = output_char XOR keystream_value
flag_chars = [chr(c ^ s) for c, s in zip(fixed, stream)]
flag = "".join(flag_chars)
print("Flag is:", flag)
#Flag is: NSSCTF{c6e67111c1aadd1bdc4dad6d99c254e7}
[注意]看雪招聘,专注安全领域的专业人才平台!