Table of contents
foreword
I heard that this is a relatively strict website, so I won’t make a deep analysis here, just analyze some environmental points.
For details, you can see the big guy’s (playing is a risk transfer)
analysis of the new version of Xiaohongshu xs (expired on 2023-05-30)
1. Analysis
Look at the wave cover
The encryption point is here, no skills, just follow up and see
window._webmsxyw
This is just to pick out the whole js, make up the environment, pay attention to roughness!
l and a can be copied on the webpage, which is probably like this.
Let him run normally first, let’s talk about it, and try to run the results. To be on the safe side, use vm2 to run first to prevent it from detecting our node environment
const fs = require('fs')
const {
VM, VMScript } = require('vm2');
// console.time('myTimer');
const sandbox = {
require: require,
console: console
};
const codeFile = `${
__dirname}/test.js`;
const vm = new VM({
sandbox: sandbox,
require: {
external: true
}
});
const script = new VMScript(fs.readFileSync(codeFile), `${
__dirname}/我正在调试的代码.js`);
debugger;
result = vm.run(script);
console.log(result)
Let’s make up some basic objects first, and then add simple agents one by one (why not use your own chain agent, it’s too long, I’m bored, just be lazy and randomize the whole thing)
Then you can start building, the following is a simple proxy
better.proxy = function (o, callerName){
return new Proxy(o, {
set(target, property, value){
console.table([{
"类型":"set-->","调用者":callerName,"属性":property,"值":value}]);
return Reflect.set(...arguments);
},
get(target, property, receiver){
if (property!=="Math"){
console.table([{
"类型":"get<--","调用者":callerName,"属性":property,"值":target[property]}]);
}
return Reflect.get(...arguments);
},
}
);
};
Run it, and you can see that it has detected its Symbol (Symbol.toStringTag) attribute.
Open the browser and take a look. This attribute has value, so we can make it up. The above two attributes cannot be obtained. Value, Symbol(nodejs.util.inspect.custom) This property is the Symbol used in Node.js for the string representation of the custom object.
So we are going to make up this Symbol (Symbol.toStringTag) next, and make up his prototype chain by the way (here is a simple layer, not too deep, asking, I am lazy)
var Window = function Window(){
throw new TypeError("Illegal constructor");
};
Object.defineProperties(Window.prototype, {
[Symbol.toStringTag]:{
value:"Window",
configurable:false, // 通常为 false,不允许删除属性并修改其特性。
writable:true, // 通常为 true,允许重新赋值。
enumerable:true // 通常为 true,可以出现在 for...in 循环或 Object.keys() 方法中。
}
});
window.__proto__ = Window.prototype;
The above one is solved, but looking down, these three are missing again. Make up, navigator and window are the same way to make up.
Here is a small detail Navigator, you will encounter it later, just configure it normally first. There are two remaining methods, just add them directly. Don't give the code anymore, just fill it up honestly.
If you encounter this kind of situation later, everyone must have learned how to make it up. The next one will not take you to make up this toString. This is a parameter method that
needs to be made up after some toStrings above are done. AudioContext
The principle we adhere to is that if the object is an agent, just throw one up to him (the next operation related to the object must be done in this way) and then we have
reached this step. Many people have different ambiguities in this method, so
I will show it. , I understand that this method is on the prototype of Document, and its function is to create an element, that is, an object. As the saying goes, everything is an object, then I will return an object, and then proxy it to see if it is fetched Is it okay to have any value? Here we take canvas as an example to proxy this canvas_obj
object. (What to do if it is a div or span object, create it, return the object of the object in the past, and fill in the prototype if there is a detection prototype, and you’re done. Make up what is missing, don’t waste time to make up if you don’t detect it) Through my above
supplement Environmental thinking, just add a little bit casually, and the data will come out. At this time, it will definitely not work if you use it (I tried it). Everyone observes that the avatar at the beginning of ey after XYW does not look like base64 encryption. If it does, use it to decrypt it. See what is
the base64 decrypted URL
. You can see that only this payload needs us to figure out how it came from, and other parameters can almost be fixed. Then we need to go back to the webpage to see how it was encrypted.
Put the js first, and then you can see that the first point is where our XS comes out. _ace_dcca5[0]
The second point is the return value, then we need to do a hook operation on this at the first point
_ace_dcca5 = new Proxy(_ace_dcca5,{
get: function(target, prop) {
return target[prop];
// 返回属性的值
},
set: function(target, prop, value) {
// debugger; // 在这里设置断点或执行自定义逻辑
target[prop] = value;
// 设置属性的值
try {
if (value['_ace_4de55'] && value['_ace_4de55'].startsWith("XYW")) {
debugger ;
}
} catch (error) {
}
return true;
}
});
Be optimistic here. The first time was XYW.
The second time is XYW_, and the next time is the base64 encrypted thing we want.
Here, we will follow up step by step. If you keep following here, you can find that this base64 has appeared.
Let's follow up _ace_34d1(3, 49)
, don't forget to bring in the parameters.
Following this step, we can actually find some clues. I have already followed it. The effect is to assign _ace_66
a value to _ace_936
this value. It can be found that this _ace_66
is not an assignment, so what does it mean? This thing is global.
The subscript 49 in this is the base64 encrypted thing we want, then we will hook it next.
This is the hook code, and it won't work if you directly use it for a brainless hook. Follow up.
_ace_66 = new Proxy(_ace_66,{
get: function(target, prop) {
return target[prop];
// 返回属性的值
},
set: function(target, prop, value) {
if (prop === "49") {
debugger; // 在第 49 个下标被设置时设置断点或执行自定义逻辑
}
console.log(value)
target[prop] = value; // 设置属性的值
return true; // 表示设置成功
}
});
This is how it looks like successfully hooked. You are familiar with this data. It is the source data that was used as base64.
Apart from anything else, hook it
_ace_66 = new Proxy(_ace_66,{
get: function(target, prop) {
return target[prop];
// 返回属性的值
},
set: function(target, prop, value) {
try {
if (value && value.startsWith("cd")) {
debugger ;
}
} catch (error) {
}
if (prop === "49") {
debugger ;// 在第 49 个下标被设置时设置断点或执行自定义逻辑
}
console.log(value)
target[prop] = value;
// 设置属性的值
return true;
// 表示设置成功
}
});
Follow up, and you can find that it is this _ace_936 again, and you have come to the end here. After looking at this, it _ace_dcca5
is an overall situation. Good guy, continue to hook.
Keep going up
here again, boy
In fact, it can be found that we have followed a dead end again, then we will continue to hook and print out the value of this global variable directly. By printing the log, we can find that there is a sum. Good guy, this is the truth. It is based stackInput
on stackOutput
this
encryption x1=f060e018ee22aede6cdd2393dadd54f9;x2=0|0|0|1|0|0|1|0|0|0|1|0|0|0|0;x3=18995b7979bwbgf099rbj41o4mscuki8wn6a4ep3250000419053;x4=1690954928490;
.
Then decompose this parameter, you can get x1, x2, x3, x4, x4 is obviously the timestamp, don't worry about it, x3 is the a1 of the cookie, don't worry about it, only x1 and x2 don't know how to get it. But don’t panic, in the process of printing above, everyone must have seen x1, x2, x3, x4, that is, when _ace_66
hooking, then we will go back and hook _ace_66 again to see.
It can be found that x1 and the length of x1 appear only after the url string appears. Experienced readers should think of md5. Indeed, this is the encrypted md5 of the url string. Now x1 is Solved, only one x2 left.
MD5 encrypted address
Don’t worry, the printed information is not only x1, but also x2.
But looking at the above, x2 has no clue, then we can go back to our own nodejs environment to see what is different from the browser
Two, verification
There is no need to go back and hook our own program. According to the hook _ace_66
, we can find that they are all produced through this function. We can go back and print this function.
Remember to use cmd to read and filter the information, otherwise you won’t be able to see it. You
can find that only x2 in nodejs is different from the browser, then let’s look up and print the information, and you will find that after passing through this set->userAgent
x2 The value becomes 1. This is the small detail mentioned earlier. It is impossible to do this in the browser.
So we need to take some measures to directly set it as unacceptable set
, and then take a look
Object.defineProperty(Navigator.prototype, "userAgent", {
value:'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36',
writable: false
});
Look, this becomes 0, and there is another 1 that is different from the browser, let's look up again. The browser is 0|0|0|1|0|0|1|0|0|0|1|0|0|0|0
ours. 0|0|0|1|0|0|1|0|0|1|1|0|0|0|0
Look up, where is the different 1? Look, after calling window.Window, it becomes 1. There must be a problem. The window.Window of our nodejs is
var Window = function Window(){
throw new TypeError("Illegal constructor");
};
The browser is the window. Window is the window itself, so just operate it lightly window.Window=window
, the good guy is the same as the browser this time
, and then send the result to the request. At this point, the XS environment of Xiaohongshu is completed, and the quack kills. Remember to request data
and encrypt 一样
it!
There are about 300 lines in the js environment, and some of them are useless. It's very simple, you can try it yourself.
learn from
Analysis of the new version of Xiaohongshu xs (expired on 2023-05-30)