事件循环与异步——加密链的断点时机

本节目标

JS 是单线程,靠「事件循环」调度。网络请求、定时器都是异步,结果在「未来某个时刻」才就绪。逆向时如果算法依赖异步返回的数据(如先 fetch 一个 token 再加密),断点时机就很重要。

// 运行环境:Node.js 16+(无需依赖)
// 运行:node jsr-l6.cjs

console.log('A')                  // 同步,立刻执行
setTimeout(() => console.log('B'), 0)  // 宏任务,下一轮
Promise.resolve().then(() => console.log('C')) // 微任务,本轮末尾
console.log('D')                  // 同步
// 输出顺序:A D C B
// 因为:同步(A,D) → 微任务(C) → 宏任务(B)

// 逆向启示:如果加密依赖「先拿到服务器下发的随机数」,
// 那加密函数一定在「那个异步回调里」执行,断点要下在回调里
async function getTokenThenEncrypt() {
  const token = await fetchToken()   // 异步:等网络
  return encryptWith(token, 'data')  // token 到位后才加密
}
function fetchToken() { return Promise.resolve('SERVER_NONCE') }
function encryptWith(token, data) { return token + ':' + data }
getTokenThenEncrypt().then((r) => console.log(r))

断点时机:下在 encryptWith 内部,触发请求后程序会「暂停在它真正执行的那一刻」,此时 token 已经就绪——这就是你要的现场。

名词解释

事件循环(Event Loop):JS 单线程下,用来「排队执行任务」的机制。同步代码先跑,遇到异步(网络/定时器)就挂起,等主线程空了再按规则回来执行。它像银行柜台:先办完眼前人(同步),叫号的后台任务(异步)轮流上。

宏任务 / 微任务:异步的两类队列。微任务(Promise 回调)优先级高于宏任务(setTimeout),所以「本轮同步结束后、下一轮宏任务前」先清空微任务。知道这个顺序,你才能预判「加密结果到底在第几步出现」,从而把断点下对地方。

课后练习

练习 1:输出顺序是什么?

console.log(1); setTimeout(()=>console.log(2),0); Promise.resolve().then(()=>console.log(3)); console.log(4)

答案:1 4 3 2(同步 1、4 → 微任务 3 → 宏任务 2)。

练习 2:为什么加密依赖「服务器下发的 nonce」时,断点要下在异步回调里而不是请求发出前?

答案:因为 nonce 是异步返回的,请求发出前它还没值。只有下在「拿到 nonce 之后、加密执行时」的回调里,才能看到完整的输入(nonce + 明文),否则断点早了看到的是 undefined。

总结

事件循环表面是「底层原理」,实则是「断点时机指南」。我的观点:逆向遇到「一会儿有值、一会儿没有」的诡异现象,十有八九是异步时序没理清。很多新手在「请求刚发出」时下断点,发现密钥还是空的,就以为算法没找到——其实是下早了。记住铁律:加密依赖的每一个输入,都必须在断点那一刻已经就绪。如果输入来自网络/定时器,断点就老老实实下在那个异步回调内部。把事件循环顺序画在纸上,逆向的时序问题能解决一大半。