吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3096|回复: 26
上一主题 下一主题
收起左侧

[Web逆向] ProcessOn完整破解思路附带 踩坑过程

  [复制链接]
跳转到指定楼层
楼主
a3341736201 发表于 2026-5-30 12:12 回帖奖励
闲的无聊,看到有这么一个Electron 桌面端,试试练练手

## 1. 环境与工具

目标软件 | ProcessOn v5.14.16 桌面端 |
框架 | Electron |
asar 路径 | `*\ProcessOn\resources\app.asar`

## 2. 第一步:解包 asar


npx asar extract


2.1解包后的目录结构:
processon_extracted/
├── node_modules/
│   ├── font-list/          # 原生模块(需 unpack)
│   └── jszip/              # 原生模块(需 unpack)
├── out/
│   ├── main/
│   │   └── index.js        # 主进程(74KB,含 RSA 密钥对)
│   ├── preload/
│   │   └── index.js        # preload 脚本
│   └── renderer/
│       ├── login.html       # 登录页(不要改!)
│       ├── index.html       # 主界面
│       └── src/
│           ├── assets/
│           │   ├── user.js           # ⭐ 核心:用户模块(173KB)
│           │   ├── js/pc/
│           │   │   ├── home.js       # ⭐ 首页:会员标签显示
│           │   │   ├── login.js       # 登录逻辑
│           │   │   ├── server/server.js  # API 通信
│           │   │   ├── saveWindow.js  # 保存窗口
│           │   │   └── ...
│           │   └── js/plug_in/
│           │       └── inject.js     # 权限对象定义
│           └── v6_editor/            # 编辑器
└── package.json

2.2 审计验证模块:

1.RSA 私钥硬编码在客户端 | main/index.js class C
2.Electron 安全配置缺失(nodeIntegration等) | main/index.js BrowserWindow
3.授权码本地明文存储 | %APPDATA%\ProcessOn\data\code\code.txt
4.用户 token localStorage 明文 | processon_token, processon_ws_token
5.IPC 无认证(任意 channel 可调) | preload/index.js
6.privilege 数据 localStorage 明文 | localStorage.privilege
7.CSP 配置宽松 | main/index.js



其中RSA 密钥对 main/index.js` 中 “class C” 包含完整的 RSA 密钥对(硬编码在客户端),用于:授权码的 RSA 加密,授权码的验证【*重大问题】

3.分析验证逻辑权限体系
ProcessOn 的会员权限通过 3次实现:




1: getUser() → 用户基础信息
API: /api/pc/user/info
返回: user.expireTime, user.member 等
消费方: home.js (首页标签), saveWindow.js

2: getUserPrivilege() → 权限详情
API: /api/pc/user/privilege
返回: privilege.member, fileCount 等
消费方: inject.js (编辑器功能限制)

3: inject.js 权限对象映射
memberPrivilege → 会员权限(100000图形、导出等)
noMemberPrivilege → 免费版限制(60图形、无导出等)
判断: window.isMember()



3.1 getUser(用户基础信息)
// user.js
getUser: function(userId, callback, options) {
    userId = userId || UserOperation.getUk();
    if (pcUtil.isOnline()) {
        ServerUtil.userInfo(function(res) {
            if (res && 200 == res.code && res.data.user) {
                let data = res.data;
                let user = {
                    id: data.user.userId,
                    ...data.user,               // 包含 expireTime, member 等
                    totalFileCount: data.auth.fileCount,
                    auth: data.auth
                };
                DBUtil.put("users", user, () => { callback(user) });
            }
        });
    } else {
        // 离线读本地 DB
        DBUtil.findOne("users", userId, (user) => { callback(user || false) });
    }
}

用户会员判断关键点
UserOperation.getUser(false, user => {
    window.user = user;
    pageContext.updataFileNum((data) => {
        const totalInfo = Object.assign(user, data);

        //  关键判断:终身会员?
        const isLongMember = user.expireTime
            && new Date(user.expireTime).getTime() > new Date('2100/01/01').getTime();

        if (isLongMember) {
            // → 显示 "终身版"
        } else if (user.member && expireTime <= 2100/01/01) {
            // → 显示 "个人版"
        } else {
            // → 显示 "免费版"
        }
    });
});


在这里我遇到一个坑,“isLongMember ”要求 expireTime > 2100/01/01`!用 `2099-12-31` 不够!这跟正常的判断有点出入  厚礼蟹!
3.2  getUserPrivilege(权限详情)

// user.js
getUserPrivilege: function(userId, callback) {
    if (!userId || userId == "") userId = UserOperation.getUk();
    if (pcUtil.isOnline()) {
        ServerUtil.userPrivilege(function(res) {
            let data = res.data;
            callback({...data.privilege, totalFileCount: data.privilege.fileCount})
        });
    } else {
        callback(JSON.parse(localStorage.getItem("privilege")))
    }
}

API 端点: GET /api/pc/user/privilege


3.3   inject.js 权限对象
// inject.js — 会员权限(功能不受限)
const memberPrivilege = {
    flowShapeNum: 100000,      // 图形数量
    mindShapeNum: Infinity,
    download: true,           // 导出
    AIGC: true,               // AI 功能
    canvasNum: 30,             // 画布数量
    historyNum: 999999,       // 历史版本
    // ...
}

// 免费版权限(功能受限)
const noMemberPrivilege = {
    flowShapeNum: 60,          // 只能 60 个图形!
    mindShapeNum: 80,
    download: false,          // 无导出
    AIGC: false,              // 无 AI
    canvasNum: 1,              // 单画布
    historyNum: 5,
    // ...
}

判断函数:

// isMember 检查 privilege 对象
window.isMember() → privilege.member || privilege.orgMember


4.实施 Patch 修改三个点位
修改点 1:getUserPrivilege(user.js)— 编辑器功能权限


// 原始:
getUserPrivilege:function(e,t=function(){}){e&&""!=e||(e=UserOperation.getUk()),pcUtil.isOnline()?ServerUtil$1.userPrivilege((function(e){let i=e.data;t({...i.privilege,totalFileCount:i.privilege.fileCount})})):t(JSON.parse(localStorage.getItem("privilege")))}

// Patch 后:
getUserPrivilege:function(e,t=function(){}){e&&""!=e||(e=UserOperation.getUk()),pcUtil.isOnline()?ServerUtil$1.userPrivilege((function(e){let i=e.data;i.privilege.expireTime='2099-12-31 23:59:59',i.privilege.orgExpireTime='2100-01-01 23:59:59',i.privilege.inActivity=true,i.privilege.createChartStatus=true,i.privilege.member=true,i.privilege.orgMember=true,t({...i.privilege,totalFileCount:i.privilege.fileCount})})):t(JSON.parse(localStorage.getItem("privilege")))}

注意* 踩坑点 :保留 `ServerUtil$1.userPrivilege` API 调用,在回调拿到真实数据后再覆盖。这样 `privilege` 对象的其他字段(如 `fileCount` 的真实值、`userId` 等)不会丢失

修改点 2:getUser(user.js)— 用户基础信息
// 原始:
DBUtil.put("users",n,(()=>{t(n)}))

// Patch 后:
n.member=true;n.orgMember=true;n.expireTime="2099-12-31 23:59:59";n.orgExpireTime="2099-12-31 23:59:59";n.fileCount=999999;n.totalFileCount=999999;DBUtil.put("users",n,(()=>{t(n)}))

修改点 3:home.js — totalInfo 合并后覆盖
// 原始:
const totalInfo = Object.assign(user, data)

// Patch 后:
const totalInfo = Object.assign(user, data);totalInfo.member=true;totalInfo.orgMember=true;totalInfo.expireTime="2099-12-31 23:59:59";totalInfo.fileCount=999999;totalInfo.totalFileCount=999999


注意* 踩坑点 :因为 `Object.assign(user, data)` 中 `data` 可能覆盖 `user` 的属性(API 返回的 `data` 是最新的),所以需要在合并后再覆盖一次。

5 打包

打包
npx asar pack "***** app.asar“

-----------------------------------
成品效果图:


看了一下功能都齐全,ai功能也是正常的!

这里说一下,其实Electron 真的很简单,只要会点 JS 玩这个东西 不是手拿把掐? 拿到文件就先解包,解了包就 DEV tools

如果你有搞不懂的程序,可以发我,抽空 帮你研究写个教程,记得给我加评分!


免费评分

参与人数 10威望 +1 吾爱币 +29 热心值 +10 收起 理由
涛之雨 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
18809828790 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Mint_Grass + 1 + 1 用心讨论,共获提升!
mylove90 + 1 谢谢@Thanks!
52pojieyangyi + 1 + 1 我很赞同!
百年抉择 + 1 + 1 我很赞同!
如果我是DJ? + 1 + 1 用心讨论,共获提升!
zhanglei1371 + 2 + 1 热心回复!
stone-liu + 1 + 1 我很赞同!
ly31415926535 + 1 + 1 我很赞同!

查看全部评分

发帖前要善用【论坛搜索】功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
涛之雨 发表于 2026-5-30 19:45
和ProcessOn破解思路
https://www.52pojie.cn/thread-2110217-1-1.html
(出处: 吾爱破解论坛)
一样,
为什么不用draw.io呢

当然,练手除外
推荐
涛之雨 发表于 2026-5-31 11:09
此外,
其实Electron 真的很简单

其实不准确,大部分electron都是直接asar解包就行
但是有部分例外:v8 字节码,asar加密然后拦截加载,甚至更离谱的,魔改定制electron

但是其实后面两种理论上都可以dump出源码,然后v8目前是比较稳的
沙发
stone-liu 发表于 2026-5-30 12:29
3#
孤狼微博 发表于 2026-5-30 14:42
我按照教程弄的显示的是个人版2099-12-31 导出高清pdf没问题  png不行,创建文件会提示文件可用数量不足,不知道大佬们遇到了没 我只按照教程修改了 4.实施 Patch 修改三个点位
4#
孤狼微博 发表于 2026-5-30 14:49
本帖最后由 孤狼微博 于 2026-5-30 14:57 编辑
孤狼微博 发表于 2026-5-30 14:42
我按照教程弄的显示的是个人版2099-12-31 导出高清pdf没问题  png不行,创建文件会提示文件可用数量不足,不 ...

发现少改了一个判断,有成功的大佬吗
5#
如果我是DJ? 发表于 2026-5-30 15:30
牛,按思路去试试
6#
666888tzq 发表于 2026-5-30 15:45
感谢分享,研究研究Electron。
7#
 楼主| a3341736201 发表于 2026-5-30 15:48 |楼主
孤狼微博 发表于 2026-5-30 14:49
发现少改了一个判断,有成功的大佬吗

已经尽量写的很详细了,具体是那个没改?
8#
 楼主| a3341736201 发表于 2026-5-30 15:54 |楼主
孤狼微博 发表于 2026-5-30 14:49
发现少改了一个判断,有成功的大佬吗

expireTime=2100-01-01 ,看看是不是时间这个问题
9#
孤狼微博 发表于 2026-5-30 15:55
a3341736201 发表于 2026-5-30 15:54
expireTime=2100-01-01 ,看看是不是时间这个问题

这个后面改了时间,还是不行,现在不知道什么错误,等一会再找个电脑试试
10#
zdye 发表于 2026-5-30 18:45
大神好人啊
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-10-16 10:11

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表