吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4085|回复: 23
上一主题 下一主题
收起左侧

[Web逆向] 某航司Reese84逆向分析-补环境篇

  [复制链接]
跳转到指定楼层
楼主
LiSAimer 发表于 2025-9-24 20:07 回帖奖励

声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!若有侵权,请联系作者删除。

逆向目标

目标网站:
aHR0cHM6Ly93d3cudGhhaWFpcndheXMuY29tL2VuLXVzLw==

抓包分析

进入页面后选择单程航班,填写完出发和到达地等基础信息后点击Search Flights搜索航班

此时会打开一个tab页跳转新的链接,速度将链接复制下来,后续直接访问这个链接抓包,就不用每次进首页填信息搜索了
链接我也贴下来吧,记得改下里面的航班日期

aHR0cHM6Ly93d3cudGhhaWFpcndheXMuY29tL3Bzc19yb3V0ZXIvcmVmeC9ib29raW5nP2FfYWlycG9ydD1UUEUmY2FiaW5fY2xhc3M9UEUmZF9kYXRlPTIwMjUtMDktMTYmZF9haXJwb3J0PVBFSyZsYW5nPWdiJm5fYWR1bHQ9MSZuX2NoaWxkPTAmbl9pbmZhbnQ9MCZ0cmlwX3R5cGU9TyZwb3NfY291bnRyeV9jb2RlPXVzJnNvdXJjZT13ZWIK

这个网站有两种请求链接的方式可以获取到目标加密参数

这里我使用抓包工具Reqable来展示(很好用的工具赶紧出3.0!!)

第一种方式:动态链接

搜索链接关键字:y-Almost-yet-know-Now-Son-ther-That-swearers-of-
发送了三次请求,一次get两次post

关键字后面的路径是动态更新的
我实际观察其实也就三种路径大概每几个小时轮换一下
3nOsRQ8irc_ZtcVNrFJG8gbVR_3mK-NK2L-00vw-NqY
1C6gqW7DCJxcjna2gXOHEpZ86Z_N1PlOqK21enI-F7g
OKH9SC3iqYKBQuit1BFulwBSToX748kxZM6TsL6An5E

get请求 ?s= 后面的是每次请求都会变更的
响应中返回了一个js,代码也是动态的
这是我们需要主要分析的,加密逻辑基本都在这个js里面

第一次post请求

固定载荷{"f":"gpc"},响应是一个base64编码的数据
解码后样式如下,包含一些字段参数,在主逻辑中需要用到

第二次post请求
url地址和第一次post一样,载荷变了

载荷中的 p 是最关键的加密参数
其他参数都可以用正则在js中提取到,有的可以随机生成
补环境返回完整的载荷直接用就行

响应中的token则是我们最终的目标,携带它即可获取接口数据

第二种方式:固定链接

搜索链接关键字:pplacked-bothe-right-eque-mine-in-him-aftend-Thi
发送了一次get请求,一次post请求

get请求和第一种方式get请求一样是获取动态js
post请求

可以看到载荷是第一种方式获取到的token
响应中的token并没有变化
一开始我以为是注册激活的接口,其实不然,怀疑是查询有效期
试着将local storage中存入的reese84 token删除掉

再重新请求航班搜索接口
或者不删除多等待一会看抓包内容
发现不会再请求动态链接的接口了

隔一段时间请求固定链接,载荷也和动态链接的一样
只是多个了old_token有值
old_token即上一次正确获取的token
实测固定链接post请求old_token为空也可以

巴拉巴拉说了一大段
总结就是用动态链接的js或者固定连接的js补环境都可以
js的核心逻辑基本一致
固定连接方便替换js并且少一次post请求

最后我们再分析看一下哪个接口返回了航班数据

search/air-bounds接口返回了航班数据
只需要请求头携带X-D-Token和Authorization
有的网站是cookie中存入reese84 token请求有的则是放在请求头X-D-Token中
Authorization请求token/initialization接口获取没啥好说的

解混淆

下面以动态链接方式作为补环境分析目标
(绝不是作者补完动态链接的才发现固态链接更好调)

把js拿到本地看看结构

第一部分主要用了大量的substr来做混淆

二三部分则是普通的ob混淆

所以咱们第一步先解混淆
先处理第一部分代码的混淆
获取第一部分源码,将其包裹在try-catch中安全的通过eval在全局作用域中执行,使顶层变量生效,随后识别并预计算所有 variable.substr(start, length) 调用,将其替换为静态字符串字面量

window = global;
const wrapTryBlock = template(`try{
 $BODY;
}catch(err){}`);

function extractEvalSource(ast) {
const initialStmt = ast.program.body[0].expression.callee.body;
const wrappedBlock = wrapTryBlock({ "$BODY": initialStmt });
const generatedSource = generator(wrappedBlock).code;
return generatedSource;
}

const compiledCode = extractEvalSource(ast);
eval(compiledCode);

const stringLiteralOptimizer = {
  CallExpression(path) {
    const { node } = path;
    if (!node.callee.object || typeof node.callee.object.name !== 'string') return;
    if (!node.callee.property || node.callee.property.name !== 'substr') return;
    const sourceVar = node.callee.object.name;
    const startIndex = node.arguments[0].extra.rawValue;
    const lengthParam = node.arguments[1].extra.rawValue;
    const computedValue = eval(`${sourceVar}.substr(${startIndex}, ${lengthParam})`);
    path.replaceWith(types.StringLiteral(computedValue));
  }
};

traverse(ast, stringLiteralOptimizer);

看看处理后的效果对比

接着处理二三部分ob混淆
先遍历所有 VariableDeclarator(变量声明节点)
检查变量是否由 a1_0x89f8 或其“别名变量”初始化
将符合条件的变量名加入 trackedIdentifiers 集合收集起来

const trackedIdentifiers = new Set();
const declarationVisitor = {
  VariableDeclarator(path) {
    const { node } = path;
    if (!node.id?.name || !node.init?.name) return;
    const declaredName = node.id.name;
    const initializerName = node.init.name;
    if (initializerName === 'a1_0x89f8' || trackedIdentifiers.has(initializerName)) {
      trackedIdentifiers.add(declaredName);
    }
  }
};
traverse(ast, declarationVisitor)

然后将第二部分代码执行,声明出解密环境

obEnv = `第二部分代码`
eval(obEnv)

再将符合条件的表达式进行函数调用解密

const callExpressionVisitor = {
  CallExpression(path) {
    const { node } = path;
    const calleeName = node.callee.name;
    if (
      node.arguments.length !== 1 ||
      !node.arguments[0].value ||
      typeof node.arguments[0].value !== 'string' && typeof node.arguments[0].value !== 'number'
    ) {
      return;
    }
    const argValue = node.arguments[0].value;
    if (trackedIdentifiers.has(calleeName)) {
      if (typeof a1_0x89f8 !== 'function') {
        console.warn(`Function a1_0x89f8 is not defined.`);
        return;
      }
      try {
        const result = a1_0x89f8(argValue);
        path.replaceWith(types.stringLiteral(String(result)));
      } catch (err) {
        console.error(`Error evaluating ${calleeName}(${argValue}):`, err);
      }
    }
  }
};
traverse(ast, callExpressionVisitor);

最后再来个常量折叠

const constantFold = {
"BinaryExpression|UnaryExpression"(path) {
      if(path.isBinaryExpression({operator:"/"})||
         path.isUnaryExpression({operator:"-"}) || 
         path.isUnaryExpression({operator:"void"}))
    {
      return;
    }
      const {confident, value} = path.evaluate();
      if (!confident)
          return;
      if (typeof value == 'number' && (!Number.isFinite(value))) {
          return;
      }
      path.replaceWith(types.valueToNode(value));
  },
}

traverse(ast, constantFold);

看看效果对比

到此解混淆差不多了,能搜索关键字就够了,当然,还有继续优化的空间,比如优化下三目运算,删除无用死代码,一些美化格式啥的,感兴趣可以继续去研究

定位加密位置

先将我们上一步解混淆的文件替换到浏览器中
(如何替换这里不多赘述)

替换有一个关键点需要注意,解混淆文件中有一个路径代码如下

需要改为和当前时间段的请求路径一致
比如,我现在当前请求路径如下

那么解混淆文件中的路径就需要改为
3nOsRQ8irc_ZtcVNrFJG8gbVR_3mK-NK2L-00vw-NqY
不然替换后会报错,报错看控制台提示如下就说明得改路径了

替换完后打上关键xhr断点执行到如下位置

在抓包分析中说过的第一次post请求传一个固定载荷
放开断点继续又跳到这个位置

第二次的post请求,有我们需要的关键载荷
所以我们补环境能到这个位置就算成功了

继续分析
js内搜索一下关键字 aih 定位到如下位置

能看到一些参数,往下看 return 了一个 Promise
下个断点到这看看
清空一下cookie和storage缓存(建议每次重写访问都清一下)
这里有个关键点,动态链接断点断不住怎么办
直接在js文件第一行加一行debuuger;

进入js文件第一行了,再定位到Promise位置下断点发现还是跳不过去
仔细一点能发现它进入的路径变了

进入了一个cachebuster
搜索一下发现其生成位置

往上找这个函数的进入位置

发现是在一个reloadScript方法里进行了脚本重载
这里直接说原因
搜索关键字 reese84interrogatorconstructor

就是这个时间戳导致的
校验这个时间戳发现超时了
修改为取当前时间即可

this["st"] = Math.floor(Date.now() / 1000);

重新再来一遍终于可以断点到Promise了

Promise中return了一个interrogate执行函数
传入包含aih的一些参数和两个函数
跟着往下走

眼熟吗,这不就是刚才改时间戳位置的st吗
再往下走,就进入了最主要的加密逻辑方法里了

创建一个隐藏的 iframe 元素
并为其绑定一个 load 事件监听器
当 iframe 加载完成后,会执行这个回调函数
分析函数结构

向re数组里push了22个函数(不同站点可能不一样)
最后执行QN方法去遍历执行数组里的每个方法
在push的最后一个方法里能看到 p 值生成

再往上可以看到p值是由nt这个数组加密而成的

而nt里面所有的参数都是那22个函数生成存进来的

补环境

补环境有两种方式
第一种
只拿第一部分代码来补,因为核心加密方法都在第一部分
只需要如下构造interrogate方法和包含aih的参数即可
注意!这是一个异步函数!

第二种
一二三部分代码全要
不需要构造interrogate方法
只要成功hook到fetch请求做处理即可

两种方式要补的环境差不多,第二种方式稍微多一点
主要还是那22个函数里的环境
下面我列举一下这22个函数都大概校验了哪些环境

函数一:
通过document.addEventListener 添加很多的监听事件

函数二:
通过window.OfflineAudioContext的Web Audio API离线音频处理方法
渲染计算一些值

函数三:
通过document.addEventListener 添加很多的监听事件
通过document.__selenium_evaluate检测一些自动化环境

函数四:
随机数组生成字符串

函数五:
检验navigator、screen、window窗口的一些参数、生成四个canvas链接

函数六:
拿上一步的一个canvas链接做处理

函数七:
检测window.WebAssembly计算出一些值

函数八:
再拿函数五生成的一个canvas链接做处理

函数九:
再拿函数五生成的一个canvas链接做处理

函数十:
创建一个canvas,获取WebGL 上下文

函数十一:
拿上一步的webgl生成一些值,检测了大量的WebGL 扩展列表属性值和着色器
数值,还生成了两个canvas链接

函数十二:
拿函数十一生成的一个canvas链接生成hash值

函数十三:
随机数组生成字符串

函数十四:
再拿一个canvas链接处理

函数十五:
检测了window.WebGLRenderingContext
检测了navigator一些属性
document.createEvent主动报错
window.ontouchstart
document.createElement('video')
document.createElement('audio')
window.chrome
history.length
检测了一些自动化环境
window.PERSISTENT
window.TEMPORARY
检测了PerformanceObserver
创建一个canvas并获取其2D渲染上下文
document.documentElement.children
document.head.children
document.body.children
document中的一些函数

函数十六:
随机数组生成字符串

函数十七:
向nt数组push一个value为true的参数

函数十八:
检测WebAssembly
winodw.BigInt

函数十九:
document.createElement
navigator一些属性
new window.Audio()

函数二十:
检测了一些原型链如
Function.prototype.toString
Function.prototype.call
Function.prototype.apply
Function.prototype.bind
还有一些window和navigator等环境

函数二十一:
检测了window.performance.now()
window.Object.getPrototypeOf()校验一些属性

函数二十二:
没啥校验了,生成p值

二十二个函数的大概环境说完,再说一些注意点
canvas相关
创建了很多次的链接需要注意顺序

window.OfflineAudioContext
调用它的方法模拟生成一些数值

document补的东西最多,主要是canvas和webgl相关

其它都还简单,最后注意toString保护函数就行

结果验证

补了差不多1千行代码
最后将环境单独打包成一个文件,每次和请求的js代码合并一下执行就行
因为js是异步执行的
所以我是python执行js文件后将生成的参数再写入本地文件读取
纯算法的话最近比较忙等我抽空看有时间再写吧

动态链接的验证

固定连接的验证

免费评分

参与人数 9吾爱币 +10 热心值 +8 收起 理由
Akihi6 + 1 + 1 我很赞同!
outdoorreadbook + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
qiaoyong + 1 + 1 热心回复!
fengbolee + 2 + 1 用心讨论,共获提升!
klop + 1 + 1 用心讨论,共获提升!
chedapao + 1 我很赞同!
SherlockProel + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
allspark + 1 + 1 用心讨论,共获提升!
buluo533 + 1 + 1 用心讨论,共获提升!

查看全部评分

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

沙发
buluo533 发表于 2025-9-25 07:42
学习了,大佬
3#
dhsfb 发表于 2025-9-25 08:09
4#
onetwo888 发表于 2025-9-25 09:10
5#
吃饱奶粉干活 发表于 2025-9-25 10:01
牛逼,学习了!!!!!!!!!!!
6#
韩梓语 发表于 2025-9-25 12:00
向大佬学习!!想看看AST代码
7#
hfhskf2005 发表于 2025-9-25 12:35
值得学习,感谢分享
8#
NewhopeBCY 发表于 2025-9-25 12:40
看了LZ的帖子,我只想说一句很好很强大!
9#
LKE380 发表于 2025-9-25 13:02
不明觉厉,向牛人学习
10#
dandan946 发表于 2025-9-25 15:34
大佬,太强了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2025-12-5 08:52

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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