Today’s web site
aHR0cHM6Ly93d3cuemhpaHUuY29tL3NlYXJjaD90eXBlPWNvbnRlbnQmcT0lRTYlQkIlQjQlRTYlQkIlQjQ=
Encryption positioning
The request that needs to be analyzed is the following
The header of this request has the encrypted parameter X-ZSE -96
Today we are going to analyze the generation logic of this parameter
There are three ways to simply locate a request, as discussed in the previous article
The header encryption parameter has a special name that we can directly retrieve globally to locate the parameter
The results of the retrieval are as follows
There is only one result, and there are two results when you retrieve the result again
, respectively,
You hit all the breakpoints and then you refresh the request and you can see that the breakpoint breaks at this point down here
Indicating that we have located the value assigned, we can continue to analyze its logic.
Encryption analysis
So we’ve found the position of the parameter assignment, so how do we generate the parameter
As you can see from the page, the encryption logic for this parameter looks like this
T = (0,i.default)(t, b.body, {zse93: m,dc0: y,xZst81: E}); _ = T.signature; V. et (" x - zse - 96 ", "2.0 _ + _");Copy the code
Let’s put the break point on T and see, what we need is T sign
Currently unknown parameters/methods are T, I. default, B. body, M, Y, and E
Let’s take it one by one
You can see here that y is a string of encrypted gibberish
var y = (0,r.getDC0Cookie)()
Copy the code
Further analysis shows that y is the value of the current cookie with key d_c0
The parameter T is the URL of the current request
The parameter M is a fixed value
O = o.ZSE_83_VERSION.web
m = u + "_" + O
Copy the code
The E parameter is null and the B. body parameter is undefine
The only thing left is i.dedFault, so a step through the analysis shows that the i.dedfault method eventually returns signature, which is the required encryption value
The logic of this signature is as follows
signature = (0,o.default)((0,r.default)(d))
Copy the code
The d passed in here is just the concatenation of the arguments above
There are two more unknown methods, o.default and R. default
Let’s look at the first method, R.DeFault
The logic of a single step in is as follows
function m(e, t, n) {return t ? n ? O(t, e) : h(O(t, e)) : n ? v(e) : h(v(e))}
Copy the code
So here are some triplet expressions that return h(v(e))
This method is relatively simple, in fact, the above d to take the MD5 hash operation
Pass o. DeFault to the result of R.default
What goes into it is the following logic
var b = function(e) {return __g._encrypt(encodeURIComponent(e))};
Copy the code
The r() method is used here
To analyze this method, we can slowly dig out all the logic by ourselves, or copy the JS file to the local as I did, and we will find all the logic in a function.
Take this code and run it in the browser
It works normally, so let’s run this code in Node
Encryption to rewrite
I’ve changed the result of running it in Node, and I’ve changed it to make sure it runs without errors
The first thing to do is to copy the code directly and run it
> exports > window.exports
The atOB is undefined
I think everyone knows this, but it’s base64, and there’s a lot of ways you can complement it
Method 1:
_keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; function _utf8_encode (string) { var string = string.replace(/\r\n/g,"\n"); var utftext = ""; for (var n = 0; n < string.length; n++) { var c = string.charCodeAt(n); if (c < 128) { utftext += String.fromCharCode(c); } else if((c > 127) && (c < 2048)) { utftext += String.fromCharCode((c >> 6) | 192); utftext += String.fromCharCode((c & 63) | 128); } else { utftext += String.fromCharCode((c >> 12) | 224); utftext += String.fromCharCode(((c >> 6) & 63) | 128); utftext += String.fromCharCode((c & 63) | 128); } } return utftext; } function _utf8_decode (utftext) { var string = ""; var i = 0; var c = 0; var c1 = 0; var c2 = 0; var c3 = 0; while ( i < utftext.length ) { c = utftext.charCodeAt(i); if (c < 128) { string += String.fromCharCode(c); i++; } else if((c > 191) && (c < 224)) { c2 = utftext.charCodeAt(i+1); string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); i += 2; } else { c2 = utftext.charCodeAt(i+1); c3 = utftext.charCodeAt(i+2); string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); i += 3; } } return string; } var xazxBase64 = { 'decode': function (input){ output = ""; var chr1, chr2, chr3; var enc1, enc2, enc3, enc4; i = 0; input = input.replace(/[^A-Za-z0-9+/=]/g, ""); while (i < input.length) { enc1 = _keyStr.indexOf(input.charAt(i++)); enc2 = _keyStr.indexOf(input.charAt(i++)); enc3 = _keyStr.indexOf(input.charAt(i++)); enc4 = _keyStr.indexOf(input.charAt(i++)); chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); chr3 = ((enc3 & 3) << 6) | enc4; output = output + String.fromCharCode(chr1); if (enc3 ! == 64) { output = output + String.fromCharCode(chr2); } if (enc4 ! == 64) { output = output + String.fromCharCode(chr3); } } output = _utf8_decode(output); return output; }, 'encode': function (input){ output = ""; var chr1, chr2, chr3, enc1, enc2, enc3, enc4; i = 0; input = _utf8_encode(input); while (i < input.length) { chr1 = input.charCodeAt(i++); chr2 = input.charCodeAt(i++); chr3 = input.charCodeAt(i++); enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; if (isNaN(chr2)) { enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; } output = output + _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + _keyStr.charAt(enc3) + _keyStr.charAt(enc4); } return output; }};Copy the code
Method 2:
global.Buffer = global.Buffer || require('buffer').Buffer;
if (typeof btoa === 'undefined') {
global.btoa = function (str) {
return new Buffer.from(str, 'binary').toString('base64');
};
}
if (typeof atob === 'undefined') {
global.atob = function (b64Encoded) {
return new Buffer.from(b64Encoded, 'base64').toString('binary');
};
}
Copy the code
Method 3:
var atob = function(r) {
e = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var o = String(r).replace(/=+$/, "");
if (o.length % 4 == 1)
throw new t("'atob' failed: The string to be decoded is not correctly encoded.");
for (var n, a, i = 0, c = 0, d = ""; a = o.charAt(c++); ~a && (n = i % 4 ? 64 * n + a : a,
i++ % 4) ? d += String.fromCharCode(255 & n >> (-2 * i & 6)) : 0)
a = e.indexOf(a);
return d
}
Copy the code
The JSDOM version generates the correct encryption values
This is the most popular version on the Internet, in fact, there is no problem, directly use jSDOM set an environment on the end
It’s also very simple to use
npm install jsdom
Copy the code
Add the following at the beginning of the code
const jsdom = require("jsdom"); const { JSDOM } = jsdom; const dom = new JSDOM(`<! DOCTYPE html><p>Hello world</p>`); window = dom.window; document = window.document; XMLHttpRequest = window.XMLHttpRequest;Copy the code
Running it directly yields the following results
# # input value 127927 b6d4c1814afa22cdea9c7d7be9 aHt0c6Lyn9Ox28S8K0OqNJuqb0FYoXYBG8F0b7uySRYf # jsdom results correctly aHt0c6Lyn9Ox28S8K0OqNJuqb0FYoXYBG8F0b7uySRYfCopy the code
The Node version generates the correct encryption values
If you want to use the encryption generated by the node result
Recommended method 2, you can directly get the result, but the result is not the same, more last 4, lazy a bit directly cut off the last four
C06829267e17d3941f5c4cf33db9d509 # correct results aHt0c6Lyn9Ox28S8K0OqNJuqb0FYoXYBG8F0b7uySRYf # # the input value our own results AHt0c6Lyn9Ox28S8K0OqNJuqb0FYoXYBG8F0b7uySRYf9Tuw # removed after four is finishedCopy the code
A one-step approach requires a little bit of analysis of his encryption
Skip the next section if you don’t want to analyze it
And then the main thing that I’m going to tell you is where is the point of analyzing the pile
Let’s start with the encrypted entry
__g._encrypt(encodeURIComponent(e))
Copy the code
In this case, __g._encrypt is r()
R is called down here
I’m using the o.v here and the o.v here is generated by new G.v
It’s a long string of Base64 encodings in the code
D and g.rototype. v jump back and forth, and make some judgments in these two methods, and shift the operation to produce the final result
Where is the point at which you can see the message?
Retrieve var k globally
Print out the results of charCodeAt here and you get the following results
__g
_encrypt
window
undefined
window
navigator
Object
name
nodejs
userAgent
headless
userAgent
toLowerCase
indexOf
callPhantom
_phantom
__phantomas
buffer
Buffer
emit
spawn
webdriver
domAutomation
domAutomationController
getOwnPropertyDescriptor
userAgent
getOwnPropertyDescriptor
webdriver
getOwnPropertyDescriptor
[native code]
getOwnPropertyDescriptor
Function
prototype
toString
call
indexOf
length
RuPtXwxpThIZ0qyz_9fYLCOV8B1mMGKs7UnFHgN3iDaWAJE-Qrk2ecSo6bjd4vl5
length
charCodeAt
..
charAt
...
charCodeAt
..
charAt
...
charCodeAt
..
charAt
...
charCodeAt
...
charAt
...
charCodeAt
...
charAt
...
charCodeAt
...
charAt
...
charCodeAt
...
charAt
...
charCodeAt
...
charAt
...
charCodeAt
...
charAt
...
charCodeAt
...
charAt
...
charCodeAt
...
charAt
...
Copy the code
Except for the dot up here is the printed value of this.c
The value here can be observed is the number of times the loop traversal, there is a need to note the value of their own record, next time you add a judgment debugger directly broken at this position
In addition to the two positions above, there are two positions that you need to be aware of, that you don’t need a break point to be aware of when you’re analyzing
The first one is where the eval code is, and that’s where the code is going to be executed, so you need to be careful what code is being executed
Just like __g at the beginning and window and Navigator and all of that goes through here
If the value you generate is completely different from the value the page generated, or if it ends up with an empty string, congratulations. That’s why I wrote this post.
I always thought there was no environment detection in Zhihu, especially when I used the above code to run out of almost the same code, I felt that the encryption was too simple. Later, when I studied it carefully, I found that I was naive.
The first one is that the code is completely different
What you need to focus on is when this.c is 99
“
At the 99th pass you can print the value of this.c to confirm the position
If there is no Buffer, it will skip 101-105 and directly assign this.c to 106
If there is a Buffer in the window, perform steps 101-105 in order to add a character to the hash value that is passed in, so that the value is different and the result is different
Another is that the output is a null value, this detection is more
The thing to notice is that this method code checks the toString of the method and the getOwnPropertyDescriptor
This test is broad, ranging from this.C equals 106 to 199
If you don’t want to bother, just use the code above to generate the value and run
That’s all for today. See you next time