AI 时代,前端逆向的门槛已经低到离谱 — 以 Upwork 为例

我用 AI 逆向 Upwork 消息系统,2小时搞定数据层开发

前言

作为 Upwork 自由职业者,我一直觉得它的消息管理界面信息量太大,不够直观。我想做一个 Chrome 插件来简化消息管理,核心需求很简单:一眼看出哪些对话需要我回复,哪些在等对方。

传统做法是下载混淆后的 JS 文件慢慢分析,但这次我决定换个思路——全程和 AI 配合,看看能多快搞定。

结果远超预期。从零开始到完全摸清 API、认证方式、数据结构,总共不到 2 小时。

第一步:摸清技术栈(5分钟)

打开 Upwork 消息页面,F12 看 Sources 面板,从加载的 JS 文件名就能判断出技术栈:

ThunderNuxt/rooms.fdb6ff58.js ThunderNuxt/realtime.fa79131f.js ThunderNuxt/composer.9c0ad3d8.js

“Thunder” 是 Upwork 消息系统的内部代号,基于 Nuxt.js(Vue 2 SSR 框架)。实时通信用了两条 WebSocket 连接,都基于 Atmosphere.js 框架:

wss://tl.upwork.com/wp?app=thunder&... // 消息主通道 wss://tl.upwork.com/wp?app=global-dash-api&... // 通知/监控

同时还加载了 Forter、Incognia 两套反欺诈 SDK 和 OneTrust 隐私合规组件。整体架构很清晰。

关键发现:不需要下载和分析任何 JS 文件。 那些代码都是混淆压缩过的,变量名全是 o4YH1l 这种乱码。我需要的只是数据从哪来、长什么样。

第二步:找到 Vue 实例和 Store(15分钟)

Upwork 消息页面实际上有两个独立的 Vue 应用——顶部导航栏是一个微前端(spa_user.umd.js),消息主体是另一个。直接用 document.querySelector('#app').__vue__ 是找不到的。

最终通过遍历 DOM 定位到正确的入口:

void function() { let s = null; document.querySelectorAll('div').forEach(el => { if (el.__vue__ && el.__vue__.$store && !s) { s = el.__vue__.$store; } }); if (s) { console.log('模块:', Object.keys(s._modules.root._children)); console.log('State:', Object.keys(s.state)); console.log('Actions:', Object.keys(s._actions)); } }();

但发现了一个意外:Thunder 的 Vuex Store 里并没有消息数据模块。模块列表是 tracingcontextuserflagstheme 这些基础设施,消息数据完全走 REST API 获取。

这说明 Upwork 的消息内容不缓存在前端状态管理里,每次都是从服务端拉取。对我的插件来说反而更简单——直接调 API 就行。

第三步:抓取 API 端点(10分钟)

在 Network 面板筛选 Fetch/XHR,切换对话时可以看到所有请求。核心 API 一共就几个:

GET /api/v3/rooms/rooms/simplified?limit=20&callerOrgId={orgId} → 对话列表 GET /api/v3/rooms/rooms/{roomId}/stories/simplified?limit=20&callerOrgId={orgId} → 消息列表 GET /api/v3/rooms/rooms/{roomId}/users?limit=100&callerOrgId={orgId} → 对话参与者 GET /api/v3/rooms/users/messageCounts?callerOrgId={orgId} → 未读数统计

所有请求都挂在 /api/v3/rooms/ 路径下,参数结构统一,非常规整。

第四步:搞定认证(5分钟)

直接调 API 会返回 401 Unauthorized。Token 在哪?

遍历 localStorage 立刻找到:

localStorage.getItem('f60cac5f103c5518_api_token') // → "oauth2v2_int_36466ecdc9f06e6509a66b018cf9a60e"

加上 Authorization: Bearer 头就能正常请求:

const token = localStorage.getItem('f60cac5f103c5518_api_token'); const headers = { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }; const res = await fetch('/api/v3/rooms/rooms/simplified?limit=20&callerOrgId=' + orgId, { headers }); const data = await res.json();

对于 Chrome 插件来说,Content Script 运行在 Upwork 页面上下文中,用户已经登录,直接从 localStorage 读 Token 即可,不需要用户手动输入任何凭据。

第五步:解析数据格式(20分钟)

这是最有趣的部分。API 返回的不是常规 JSON,而是 Thrift 序列化的 JSON 格式,字段名全是数字编号:

{ "1": {"str": "room_f0ff6267bae1..."}, "2": {"str": "某某客户"}, "7": {"str": "项目标题"}, "8": {"map": ["str", "str", 26, {...}]}, "10": {"i32": 0}, "12": {"i64": 1771698648604}, "13": {"rec": {"1": {"str": "story_865b..."}, "8": {"str": "消息内容"}}} }

看起来像加密?其实不是。Thrift 是 Apache 的跨语言序列化框架,数字编号只是字段 ID。通过对照页面上显示的内容和返回数据,每个字段的含义很快就反推出来了:

对话(Room)结构:

字段类型含义
1strroomId
2str对话标题
7str项目名称
8map上下文信息(合同ID、金额、状态等)
10i32未读消息数
12i64最后更新时间戳
13rec最后一条消息
30str客户ID
31str客户组织ID
34str合同ID

消息(Story)结构:

字段类型含义
1strstoryId
2strroomId
3i64创建时间
5str发送者ID
8str消息正文
10str消息类型(系统消息)
12lst关联对象(里程碑等)
13tf是否已读
36str摘要文本

这些数字编号在 Upwork 内部的 .thrift 定义文件里有对应的字段名,但我们不需要那个文件,通过数据本身就能完全还原语义。

第六步:实现核心业务逻辑(5分钟)

有了数据结构,插件的核心逻辑就非常简单了。我最想要的功能是对话状态自动分类——判断"最后一条消息是谁发的"来决定状态:

function getRoomStatus(room, myId) { const lastStory = room['13']; if (!lastStory) return { label: '无消息', icon: '⚪' }; const senderId = lastStory['5']?.str; const unread = lastStory['13']?.tf === 0; const time = lastStory['3']?.i64; const daysSince = (Date.now() - time) / 86400000; if (senderId !== myId && unread) { return { label: '新消息', icon: '🔴', priority: 1 }; } if (senderId !== myId) { if (daysSince > 3) return { label: '急需回复', icon: '🟠', priority: 2 }; return { label: '需要回复', icon: '🟡', priority: 3 }; } if (senderId === myId) { if (daysSince > 7) return { label: '对方可能忘了', icon: '💤', priority: 4 }; return { label: '等对方回复', icon: '🟢', priority: 5 }; } }

五种状态,按优先级排序,一眼就知道该先处理哪个对话。

总结:AI 改变了前端逆向的游戏规则

整个过程没有下载任何 JS 文件,没有用反混淆工具,没有读一行压缩代码。

传统逆向流程:下载 JS → 格式化 → 反混淆 → 读代码 → 猜逻辑 → 试错。可能需要几天。

AI 辅助流程:告诉 AI 你看到什么 → AI 告诉你下一步查什么 → 把结果贴回来 → AI 分析含义。2 小时。

本质上 AI 充当了一个"有经验的逆向工程师搭档"。它知道 Nuxt 应用的 Vue 实例挂在哪里,知道 Atmosphere.js 是 WebSocket 框架,知道 Thrift 序列化长什么样,知道 OAuth Token 通常存在哪。这些经验以前需要人积累多年,现在一个对话窗口就搞定了。

前端 JS "加密"的门槛已经非常低了。只要数据要展示给用户,它就必须在浏览器里被解密和渲染,这个过程中的一切都是透明的。AI 只是让找到这些数据的过程变得极其高效。

Read more

LLaMA论文阅读笔记

论文:https://arxiv.org/pdf/2302.13971 1、为什么要做这个研究(理论走向和目前缺陷) ? 之前的效果的模型要么不开源,要么用了私有数据训练,完全开源的效果都不咋地。 2、他们怎么做这个研究 (方法,尤其是与之前不同之处) ? 用完全开源的数据(1.4万亿tokens),并没有大的离谱模型(7B到65B), 做了一系列前面研究做的优化,如RMSNorm的Pre-normalization减少计算量,FFN的激活函数替换为SwiGLU增强表现能力更好,旋转位置编码RoPE提升模型长度外推性。这些优化基本都成了后续大模型设计的标配。 3、发现了什么(总结结果,补充和理论的关系)? 算是首个完全开源且效果和闭源模型相当的LLM模型,候选qwen也是基于这个模型改进得到的。 摘要 发布LLaMA系列模型,参数量从7B到65B量级,训练数据在1.5万亿tokens,且全是开源的数据,13B参数量的比175B的GPT-3性能还要好,65B的模型达到当前最好的大语言模型效果。 1 引言 在算力固定的情况下,小模型+大数据比大模型+小数据效果更好,而且小模型

2026-04-03期 AI最新资讯

2026年4月3日 AI资讯日报 每日精选人工智能领域最新动态,带你快速掌握技术突破、产品发布与行业趋势。 🚀 技术突破 Meta 发布 Llama 4 系列开源大模型 Meta 今日正式推出 Llama 4 系列,包含三个版本:Llama 4 Mini、Llama 4 Base 和 Llama 4 Ultra。在 MMLU、HumanEval、GSM8K 等主流基准测试中,Llama 4 Ultra 的平均得分达到 89.7%,超越 GPT-4(88.5%),且推理速度提升约 30%。模型采用混合专家(MoE)架构,总参数量达 1.2