文章目录
- 概要
- 整体架构流程
- 技术细节
- 小结
概要
提示:仅供学习,不得用做商业交易,如有侵权请及时联系
逆向:Trip国际版携c
URL:aHR0cHM6Ly9oay50cmlwLmNvbS9ob3RlbHM=
目的:参数 Phantom-Token 补环境
接口:aHR0cHM6Ly9oay50cmlwLmNvbS9odGxzL2dldEhvdGVsTGlzdD94LXRyYWNlSUQ9MTczMzQ0OTExMjQ3OS4wOWJlblgyMXNGcEEtMTczMzQ1MDc3OTY5OS0xMTI5Mjc1NTM0
整体架构流程
提示:挂代理补环境并验证参数
1、找到参数加密位置–搜索–断点
2、分析加密入口和明文
加密内容就是载荷数据(表单数据)
3、进入方法内,扣下js
发现跟携程的token如出一辙,都是这种格式的vmp
4、将vmp复制到本地,挂上代理
function setProxyArr(proxyObjArr) {
for (let i = 0; i < proxyObjArr.length; i++) {
const handler = `{
get: function(target, property, receiver) {
console.log("方法:", "get ", "对象:", "${proxyObjArr[i]}", " 属性:", property, " 属性类型:", typeof property, ", 属性值:", target[property], ", 属性值类型:", typeof target[property]);
return target[property];
},
set: function(target, property, value, receiver) {
console.log("方法:", "set ", "对象:", "${proxyObjArr[i]}", " 属性:", property, " 属性类型:", typeof property, ", 属性值:", value, ", 属性值类型:", typeof target[property]);
return Reflect.set(...arguments);
}
}`;
eval(`try {
${proxyObjArr[i]};
${proxyObjArr[i]} = new Proxy(${proxyObjArr[i]}, ${handler});
} catch (e) {
${proxyObjArr[i]} = {};
${proxyObjArr[i]} = new Proxy(${proxyObjArr[i]}, ${handler});
}`);
}
}
5、接下来,缺啥补啥
缺window,我们知道浏览器的window表示全局,而node里面的全局是global和globalThis,那么我们到浏览器看看window有没有这俩个
我们发现有globalThis
CanvasRenderingContext2D = function () { };
HTMLCanvasElement = function () { };
navigator = {}
screen = {}
document = {}
setProxyArr(['window','navigator','screen','document','CanvasRenderingContext2D','HTMLCanvasElement'])
我们发现缺少createElement,在js中dom进行该操作实际上就是在创建标签,所以我们要捕抓它到底创建了什么标签
document = {
createElement: function (res) {
console.log("创建标签:", res);
}
}
补完这些我们发现基本环境就已经通了
6、接下来我们就可以调用signature方法生成Phantom-Token参数值,直接复制o.data表单
var data = {
"guideLogin": "T",
"search": {
"sessionId": "913eaa06-9980-ce5e-e3e2-5b2b46e3222c",
"preHotelCount": 12,
"preHotelIds": [
115996086,
456133,
345032,
6490490,
425224,
371241,
374627,
6398678,
474480,
447883,
109336017,
78037552
],
"checkIn": "20241206",
"checkOut": "20241207",
"sourceFromTag": "",
"filters": [
{
"filterId": "17|1",
"value": "1",
"type": "17",
"subType": "2",
"sceneType": "17"
},
{
"filterId": "80|0|1",
"value": "0",
"type": "80",
"subType": "2",
"sceneType": "80"
},
{
"filterId": "29|1",
"value": "1|2",
"type": "29"
}
],
"pageCode": 10320668148,
"location": {
"geo": {
"countryID": 1,
"provinceID": 0,
"cityID": 30,
"districtID": 0,
"oversea": false
},
"coordinates": []
},
"pageIndex": 2,
"pageSize": 10,
"needTagMerge": "T",
"roomQuantity": 1,
"orderFieldSelectedByUser": false,
"hotelId": 0,
"hotelIds": [],
"lat": 22.536674277945078,
"lng": 114.06165334380388,
"tripWalkDriveSwitch": "T",
"resultType": "CT",
"nearbyHotHotel": {},
"recommendTimes": 0,
"crossPromotionId": "",
"travellingForWork": false
},
"batchRefresh": {
"batchId": "",
"batchSeqNo": 0
},
"queryTag": "NORMAL",
"mapType": "MAPBOX",
"extends": {
"crossPriceConsistencyLog": "",
"NewTaxDescForAmountshowtype0": "B",
"TaxDescForAmountshowtype2": "T",
"MealTagDependOnMealType": "T",
"MultiMainHotelPics": "T",
"enableDynamicRefresh": "T",
"isFirstDynamicRefresh": "T",
"ExposeBedInfos": "F",
"TaxDescRemoveRoomNight": "",
"priceMaskLoginTip": "",
"NeedHotelHighLight": ""
},
"head": {
"platform": "PC",
"clientId": "1733449112479.09benX21sFpA",
"bu": "ibu",
"group": "TRIP",
"aid": "",
"sid": "",
"ouid": "",
"caid": "",
"csid": "",
"couid": "",
"region": "HK",
"locale": "zh-HK",
"timeZone": "8",
"currency": "HKD",
"p": "89018333664",
"pageID": "10320668148",
"deviceID": "PC",
"clientVersion": "0",
"frontend": {
"vid": "1733449112479.09benX21sFpA",
"sessionID": "1",
"pvid": "2"
},
"extension": [
{
"name": "cityId",
"value": "30"
},
{
"name": "checkIn",
"value": "2024/12/06"
},
{
"name": "checkOut",
"value": "2024/12/07"
},
{
"name": "region",
"value": "HK"
}
],
"tripSub1": "",
"qid": "275606394200",
"pid": "4bd353a1-5c1c-45a8-ab88-113ad7b1b866",
"hotelExtension": {},
"cid": "1733449112479.09benX21sFpA",
"traceLogID": "b9fc81848e5e2",
"ticket": "",
"href": "脱米处理",
"deviceConfig": "M"
}
}
console.log(window.signature(data));
继续运行
cookie:复制浏览器的cookie
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
crypto = {
randomUUID:function randomUUID() {
const hexDigits = '0123456789abcdef';
let uuid = '';
for (let i = 0; i < 36; i++) {
if (i === 8 || i === 13 || i === 18 || i === 23) {
uuid += '-';
} else if (i === 14) {
uuid += '4';
} else if (i === 19) {
uuid += hexDigits[(Math.floor(Math.random() * 4) + 8)];
} else {
uuid += hexDigits[Math.floor(Math.random() * 16)];
}
}
return uuid;
}
}
webdriver:false
这里我们看到,dom创建了一个canvas标签,那么为什么这里直接new HTMLCanvasElement对象呢,因为创建canvas对象的原型就是它
我们也给代理上,getContext原型链上的方法,创建上下文
HTMLCanvasElement = function () {
this.getContext = function () {
console.log('上下文:',arguments[0])
}
};
CanvasRenderingContext2D = function () { };
twod = new CanvasRenderingContext2D();
HTMLCanvasElement = function () {
this.getContext = function () {
console.log('上下文:', arguments[0])
if (arguments[0] === '2d') {
return twod
}
}
};
我们创建的2d对象的原型就是CanvasRenderingContext2D
canvas对象中的setAttribute在js中就是设置属性和属性值的
HTMLCanvasElement = function () {
this.getContext = function () {
console.log('上下文:', arguments[0])
if (arguments[0] === '2d') {
return twod
}
};
this.setAttribute = function () {
console.log('设置属性:', arguments[0], arguments[1])
};
};
该方法在js就拿到一个图片的base64的值,这个我们可以通过浏览器进行hook,拿到该值
HTMLCanvasElement.prototype.toDataURL_ = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function () {
console.log("toDataURL被劫持");
debugger;
return HTMLCanvasElement.prototype.toDataURL_.apply(this, arguments);
}
这里我就直接粘贴出来
number = 1
HTMLCanvasElement = function () {
this.getContext = function () {
console.log('上下文:', arguments[0])
if (arguments[0] === '2d') {
return twod
}
};
this.setAttribute = function () {
console.log('设置属性:', arguments[0], arguments[1])
};
this.toDataURL = function toDataURL() {
if (number === 1) {
number += 1
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANwAAAAeCAYAAABHenA+AAAAAXNSR0IArs4c6QAAAMNJREFUeF7t00ENAAAIAzHm3zQu7lUMLGm4nSNAIBNYtmSIAIETnCcgEAoILsQ2RUBwfoBAKCC4ENsUAcH5AQKhgOBCbFMEBOcHCIQCgguxTREQnB8gEAoILsQ2RUBwfoBAKCC4ENsUAcH5AQKhgOBCbFMEBOcHCIQCgguxTREQnB8gEAoILsQ2RUBwfoBAKCC4ENsUAcH5AQKhgOBCbFMEBOcHCIQCgguxTREQnB8gEAoILsQ2RUBwfoBAKCC4ENsUgQen2QAfWCC+5AAAAABJRU5ErkJggg=='
}
if (number === 2) {
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAAA5JREFUGFdjZIACRhgDAAAtAANNw4UTAAAAAElFTkSuQmCC'
}
}
};
这里我们再运行发现报错了,往上找发现CanvasRenderingContext2D去调用了prototype属性,CanvasRenderingContext2D.prototype也需要挂上代理
这里就出现了这个属性并没有
CanvasRenderingContext2D = function () {
this.fillRect = function fillRect() { };
this.fillText = function fillText() { };
};
CanvasRenderingContext2D.prototype.fillRect = function fillRect() { };
CanvasRenderingContext2D.prototype.fillText = function fillText() { };
运行发现有报错了,这里其实就是检测原型的toString方法
CanvasRenderingContext2D = function () {
this.fillRect = function fillRect() { };
this.fillText = function fillText() { };
};
CanvasRenderingContext2D.prototype.fillRect = function fillRect() { };
CanvasRenderingContext2D.prototype.fillText = function fillText() { };
CanvasRenderingContext2D.prototype.fillRect = function fillRect() { };
CanvasRenderingContext2D.prototype.fillText = function fillText() { };
CanvasRenderingContext2D.prototype.fillRect.toString = function toString() {
return 'function fillRect() { [native code] }'
}
CanvasRenderingContext2D.prototype.fillText.toString = function toString() {
return 'function fillText() { [native code] }'
}
twod = new CanvasRenderingContext2D();
number = 1
HTMLCanvasElement = function () {
this.getContext = function getContext() {
console.log('上下文:', arguments[0])
if (arguments[0] === '2d') {
return twod
}
};
this.setAttribute = function setAttribute() {
console.log('设置属性:', arguments[0], arguments[1])
};
this.toDataURL = function toDataURL() {
if (number === 1) {
number += 1
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANwAAAAeCAYAAABHenA+AAAAAXNSR0IArs4c6QAAAMNJREFUeF7t00ENAAAIAzHm3zQu7lUMLGm4nSNAIBNYtmSIAIETnCcgEAoILsQ2RUBwfoBAKCC4ENsUAcH5AQKhgOBCbFMEBOcHCIQCgguxTREQnB8gEAoILsQ2RUBwfoBAKCC4ENsUAcH5AQKhgOBCbFMEBOcHCIQCgguxTREQnB8gEAoILsQ2RUBwfoBAKCC4ENsUAcH5AQKhgOBCbFMEBOcHCIQCgguxTREQnB8gEAoILsQ2RUBwfoBAKCC4ENsUgQen2QAfWCC+5AAAAABJRU5ErkJggg=='
}
if (number === 2) {
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAAA5JREFUGFdjZIACRhgDAAAtAANNw4UTAAAAAElFTkSuQmCC'
}
}
};
HTMLCanvasElement.prototype.getContext = function getContext() { };
HTMLCanvasElement.prototype.setAttribute = function setAttribute() { };
HTMLCanvasElement.prototype.toDataURL = function toDataURL() { };
HTMLCanvasElement.prototype.getContext.toString = function toString() {
return 'function getContext() { [native code] }'
}
HTMLCanvasElement.prototype.setAttribute.toString = function toString(res, res1) {
return 'function setAttribute() { [native code] }'
}
HTMLCanvasElement.prototype.toDataURL.toString = function toString(res, res1) {
return 'function toDataURL() { [native code] }'
}
HTMLCanvasElement.prototype.toString = function toString() {
return '[object HTMLCanvasElement]'
}
HTMLCanvasElement.toString = function toString() {
return 'function HTMLCanvasElement() { [native code] }'
}
然后我们发现不报错了,继续
screen = {
colorDepth: 24,
width: 1536,
height: 864,
availWidth: 1536,
availHeight: 864,
pixelDepth: 24,
}
navigator = {
language: 'zh-CN',
platform: 'Win32',
webdriver:false,
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
}
7、最后我们运行看一下
好家伙居然失败了,肯定检测了啥,我们突然想到了它上面检测了原型链我方法,那么createElement是不是也被检测了
HTMLDocument = function HTMLDocument() { };
HTMLDocument.prototype.createElement = function createElement() {
if (res === "canvas") {
return canvas
}
}
HTMLDocument.prototype.createElement.toString = function toString() {
return 'function createElement() { [native code] }'
}
HTMLDocument.prototype.toString = function toString() { return '[object HTMLDocument]'; }
document = {cookie:浏览器打印}
8、我们发现成功了
技术细节
提示:细节
- 需要注意代理的对象不能漏
- 仔细观察缺少了什么
- 分析检测点
小结
提示:学习交流群:v:wzwzwz0613