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