[JS reverse hundred examples] the first question on the anti climbing practice platform of netizens: JS confusion encryption and anti hook operation

Time:2022-5-24

Follow wechat official account: brother K crawler, and continue to share advanced crawler, js/ Android reverse and other technical dry goods!

statement

All contents in this article are only for learning and communication. The packet capturing content, sensitive website and data interface have been desensitized. It is strictly prohibited to use them for commercial and illegal purposes, otherwise all the consequences have nothing to do with the author. If there is infringement, please contact me and delete them immediately!

Write in front

The topic itself is not very difficult, but there are many pits, mainly anti hook operation and local joint commissioning and compensation environment. This paper will introduce each pit in detail, not just a single stroke, but in great detail!

Through this article, you will learn:

  1. Hook function and timer to eliminate infinite debugger;
  2. Solve the anti hook problem and find the encryption parameters through hook_ signature;
  3. Analyze the differences between the browser and the local environment, how to find navigator, document, location and other objects, and how to supplement the local environment;
  4. How to use pycharm for local joint debugging to locate the difference between local and browser environment, so as to pass the detection.

Reverse target

  • Objective: WebTracker anti crawler practice platform question 1: JS confusion encryption, anti hook operation
  • Link:http://spider.wangluozhe.com/challenge/1
  • Introduction: the answer to this question is to add all the data on 100 pages. It is required to complete this question in the form of hook. Do not solve it in the form of AST and deduction code, and do not decrypt it with JS anti confusion tool. (the writing method and usage of hook code have been in brother K’s previous articles, so this article will not introduce it in detail)

01.png

Bypass infinite debugger

First of all, it is observed that the URL does not change when you click to turn the page, so it is generally an Ajax request. Some parameters of each request will change. Skillfully press F12 to find the encryption parameters. You will find that you will immediately stop, enter the infinite debugger state, and follow a stack up to display the word debugger, as shown in the following figure:

02.png

This is also the case in the previous case of brother K. at that time, we directly rewritten the JS and replaced the word debugger. However, this topic obviously hopes that we can use the hook method to get rid of the infinite debugger. In addition to the debugger, we noticed that there is a word constructor in front of us. In JavaScript, it is called construction method, which is generally called during object creation or instantiation, Its basic syntax is:constructor([arguments]) { ... }For details, please refer toMDN construction method, in this case, it is obvious that the debugger is the arguments parameter of the constructor, so we can write the following hook code to pass the infinite debugger:

//Keep the original constructor first
Function.prototype.constructor_ = Function.prototype.constructor;
Function.prototype.constructor = function (a) {
    //If the parameter is debugger, an empty method is returned
    if(a == "debugger") {
        return function (){};
    }
    //If the parameter is not debugger, the original method is returned
    return Function.prototype.constructor_(a);
};

There are also many ways to inject hook code, such as entering code directly on the browser developer tool console (refreshing the web page will fail), Fiddler plug-in injection, oil monkey plug-in injection, self writing browser plug-in injection, etc. these methods have been introduced in brother K’s previous articles and will not be repeated today.

This time we use Fiddler plug-in injection. After injecting the above hook code, we will find that it will enter infinite debugger and setinterval again. Obviously, the timer has two necessary parameters. The first is the method to be executed, and the second is the time parameter, that is, the time interval of calling the method periodically, in milliseconds. For details, please refer toRookie tutorial window setinterval ()Similarly, we can hook it out:

//Keep the original timer first
var setInterval_ = setInterval
setInterval = function (func, time){
    //If the time parameter is 0x7d0, an empty method is returned
    //Of course, you can also directly return null without judgment. There are many ways to write it
    if(time == 0x7d0)
    {
        return function () {};
    }
    //If the time parameter is not 0x7d0, the original method is returned
    return setInterval_(func, time)
}

03.png

Paste two sections of hook code into the browser plug-in, open the hook, refresh the page, and you will find that the infinite debugger has passed.

04.png

Hook parameter

After passing the infinite debugger, we click a page casually, and the packet capture can see that it is a post request in form data,pageIs the number of pages,countIs the amount of data per page,_signatureIt is the parameter we want to reverse, as shown in the figure below:

05.png

We search directly_signature, there is only one result, of whichwindow.get_sign()The way is to set_signatureAs shown in the figure below:

06.png

Here comes the problem!!! Let’s take a look at the title of this topic. JS confusion encryption and anti hook operation. The author has repeatedly stressed that this topic is to test hook ability! And so far, it seems that we haven’t encountered any anti hook means, so we can search directly_signatureObviously, it’s too simple. It must be obtained through hook_signatureAnd the subsequent hook operation will not be smooth!

Don’t say much. Let’s write a hook directlywindow._signatureThe code of is as follows:

(function() {
    //Rigorous mode checks for all errors
    'use strict';
    //Window is the object to hook. Here is the object of hook_ signature
	var _signatureTemp = "";
    Object.defineProperty(window, '_signature', {
		//Hook set method is the method of assignment 
		set: function(val) {
				console. Log ('hook captures _signaturesetting - > ', Val);
                debugger;
				_signatureTemp = val;
				return val;
		},
		//The hook get method is also the method to get the value 
		get: function()
		{
			return _signatureTemp;
		}
    });
})();

Two hook codes that bypass the infinite debugger, and this hook_signatureUse the fiddler plug-in to inject the code together with the debugger (note that the code bypassing the debugger should be placed in the hook)_signatureAfter the code, otherwise it may not work, which may be the bug of the plug-in). Refresh the web page again, and you can find that the buttons on the front row of pages are missing. Open the developer tool and you can see two errors in the upper right corner. Click to jump to the wrong code, and you can also see the error message on the console, as shown in the figure below:

07.png

Whole 1 JS code has been confused by sojson jsjiami V6 version. We output some confused code on the console, and then restore this code manually. There are two variablesi1I1i1liandillllli1, it seems laborious. Use it directlyaandbReplace with the following:

(function() {
    'use strict';
    var a = '';
    Object["defineProperty"](window, "_signature", {
        set: function(b) {
            a = b;
            return b;
        },
        get: function() {
            return a;
        }
    });
}());

Are you familiar with it? There are get and set methods. This is a hookwindow._signatureOperation? The whole logic is when the set method is set_signatureWhen, assign it to a and get it_signatureWhen you return a, this operation is actually for_signatureWithout any impact, what is the meaning of this code? Why do we report errors when we add our own hook code?

Look at the error message:Uncaught TypeError: Cannot redefine property: _signature, cannot be redefined_signature? Our hook code runs as soon as the page is loadedObject.defineProperty(window, '_signature', {}), wait until the JS of the website starts againdefinePropertyIt will report an error when it is. That’s very simple. Since we don’t want to redefine it, and the website’s own JS hook code will not be affected_signature, just delete it! This place is probably anti hook operation.

Save original 1 JS to the local, delete its hook code, use Fiddler’s autoresponder function to replace the response (there are many replacement methods, which are also described in brother K’s previous articles), refresh again, find that the exception is removed, and the hook arrives successfully_signature

08.png

09.png

Reverse parameter

After a successful hook, you can directly follow the stack and expose the method:window._signature = window.byted_acrawler(window.sign())

10.png

Let’s have a look firstwindow.sign()In fact, if you select it, you can see that it is a 13 bit millisecond timestamp. We follow up 1 JS to see his implementation code:

11.png

We will restore some confused codes manually:

window["sign"] = function sign() {
    try {
        div = document["createElement"];
        return Date["parse"](new Date())["toString"]();
    } catch (IIl1lI1i) {
        return "123456789abcdefghigklmnopqrstuvwxyz";
    }
}

It should be noted here that there is a pit buried for us. If you skip it directly and think that a time stamp is not good-looking, you are very wrong! Note that this is a try catch statement, including onediv = document["createElement"];, there is an HTML DOM document object that creates a div tag. If this code is put into the browser for execution, there is no problem. Go directly to the try statement and return the timestamp. If it is executed in our local node, it will be captureddocument is not defined, and then use the catch statement to return the string of numbers plus letters. The final result must be incorrect!

The solution is also very simple. In the local code, either remove the try catch statement and directly return the timestamp, or define the document at the beginning, or comment out the line of code that creates the div tag directly. But brother K recommends defining the document directly here, because who can guarantee that there are similar holes in other places? If you hide deeply and don’t find it, isn’t it in vain?

Then take a lookwindow.byted_acrawler(), the return statement mainly usessign()that iswindow.sign()Methods andIIl1llI1()Method, we follow upIIl1llI1()Method, you can see that the try catch statement is also used,nav = navigator[liIIIi11('2b')];Similar to the previous div, it is also recommended to define navigator directly, as shown in the following figure:

14.png

15.png

The methods used here are basically analyzed. After defining the window, document and navigator, run them locally and you will be promptedwindow[liIIIi11(...)] is not a function

16.png

When we go to the website, we will find that this method is actually a timer, which has no great effect. Just comment it out directly:

17.png

Pycharm local joint commissioning

After the above operations, run it locally again and you will be promptedwindow.signs is not a function, the error is an eval statement. Let’s go to the browser and have a look at the eval statement and find that it iswindow.sign(), why does local becomewindow.signs(), what about an extra s for no reason?

18.png

19.png

There is only one reason for this. That is the difference between the local environment and the browser environment. There must be environment detection in the confused code. If it is not the browser environment, the code in Eval will be modified and an s will be added. Here, if you directly delete the whole function containing Eval sentences and the setinterval timer above, the code can also run normally. However, brother K always pursues details! We must find out the reason for adding more s!

We use pycharm locally for debugging to see where an S is added. The error is the eval statement. We click this line, the next breakpoint, and right-click debug to run, Enter the debugging interface (PS: the original code has an infinite debugger. If it is not processed, the debugging in pycharm will also enter the infinite debugger. You can directly add the previous hook code to the local code or delete the corresponding function or variable):

20.png

On the left is the call stack and on the right is the variable value. On the whole, it is similar to the developer tools in chrome. For detailed usage, please refer toOfficial JetBrains documentation, mainly introduce the 8 buttons in the figure below:

  1. Show execution point (ALT + F10): if your cursor is on other lines or other pages, click this button to jump to the line where the current breakpoint is located;
  2. Step over (F8): step over and go down line by line. If there is a method on this line, it will not enter the method;
  3. Step into (F7): step in. If there is a method in the current line, you can enter the method. It is generally used to enter the user-defined method written by the user, and will not enter the official class library;
  4. Force step into (ALT + Shift + F7): forced entry. You can enter any method. When viewing the underlying source code, you can use this method to enter the official class library;
  5. Step out (Shift + F8): step out, exit from the entered method to the method call. At this time, the method has been executed, but the assignment has not been completed;
  6. Restart frame: abandon the current breakpoint and re execute the breakpoint;
  7. Run to cursor (ALT + F9): when running to the cursor, the code will run to the cursor line without breaking points;
  8. Evaluate expression (ALT + F8): To evaluate an expression, you can run the expression directly without entering it on the command line.

We click the step into button to enter thefunction IIlIliii(), the try catch statement is also used here. If you continue to the next step, you will find that an exception is caught and promptCannot read property 'location' of undefined, as shown in the figure below:

21.png

Let’s output the value of each variable and restore the code manually, as follows:

function IIlIliii(II1, iIIiIIi1) {
    try {
        href = window["document"]["location"]["href"];
        check_screen = screen["availHeight"];
        window["code"] = "gnature = window.byted_acrawler(window.sign())";
        return '';
    } catch (I1IiI1il) {
        window["code"] = "gnature = window.byted_acrawlers(window.signs())";
        return '';
    }
}

In this way, we found a clue. Locally, we do not have document, location, href and availheight objects, so we will use the catch statement to becomewindow.signs(), an error will be reported. The solution here is also very simple. You can directly delete the redundant code and directly define it as the string of statements without s, or you can choose to supplement the environment. Look at the values of href and screen in the browser and define it:

var window = {
    "document": {
        "location": {
            "href": "http://spider.wangluozhe.com/challenge/1"
        }
    },
}

var screen = {
    "availHeight": 1040
}

Then run it again and you will be prompted againsign is not defined, heresign()Actuallywindow.sign(), which is the followingwindow[liIIIi11('a')]Method, any one of the following can be changed:

22.png

Run again and there is no error. We can write a method to get it ourselves_signature: you can choose one of the following expressions:

function getSign(){
    return window[liIIIi11('9')](window[liIIIi11('a')]())
}

function getSign(){
    return window.byted_acrawler(window.sign())
}

//Test output
console.log(getSign())

We run it and find that there is no output in pychar. Similarly, we output it on the console of the topic pageconsole.log, it is found to be empty, as shown in the figure below:

23.png

Looks like he’s rightconsole.logIn fact, this situation is not a problem. We directly use Python script to call what we wrote earliergetSign()Method can be obtained_signatureBut again, brother K always pursues details! I have to find a way to deal with itconsole.logMake it normal!

Here, we still use pycham to debug and get more familiar with local joint debuggingconsole.log(getSign())The next breakpoint at the statement. Follow up step by step and you will find that the statement has been enteredvar IlII1li1 = function() {};, view the variable value at this time, and findconsole.logconsole.warnAnd other methods have been left blank, as shown in the figure below:

24.png

Follow up the next step and find that it is returned directly. Here, it is possible that the method related to console will be null when JS is run for the first time. Therefore, first lay down several breakpoints in the suspected method of console processing, and then re debug. You will find that the else statement will be reached, and then directly assign ilii1li1, that is, the null method, to the related commands of console, as shown in the following figure:

25.png

When the problem is found, we can comment out the if else statement without leaving it empty, and then debug it again. It is found that the result can be output directly:

26.png

Call Python to carry_ Signature calculates the data of each page one by one, and finally submits it successfully:

2.png

Complete code

GitHub pays attention to brother K crawler and continues to share crawler related codes! Welcome, star!https://github.com/kgepachong/

The following only demonstrates some key codes, which cannot be run directly!Full code warehouse address:https://github.com/kgepachong/crawler/

JavaScript encryption key code architecture

var window = {
    "document": {
        "location": {
            "href": "http://spider.wangluozhe.com/challenge/1"
        }
    },
}

var screen = {
    "availHeight": 1040
}
var document = {}
var navigator = {}
var location = {}

//Keep the original constructor first
Function.prototype.constructor_ = Function.prototype.constructor;
Function.prototype.constructor = function (a) {
    //If the parameter is debugger, an empty method is returned
    if(a == "debugger") {
        return function (){};
    }
    //If the parameter is not debugger, the original method is returned
    return Function.prototype.constructor_(a);
};

//Keep the original timer first
var setInterval_ = setInterval
setInterval = function (func, time){
    //If the time parameter is 0x7d0, an empty method is returned
    //Of course, you can also directly return null without judgment. There are many ways to write it
    if(time == 0x7d0)
    {
        return function () {};
    }
    //If the time parameter is not 0x7d0, the original method is returned
    return setInterval_(func, time)
}

var iil = 'jsjiami.com.v6'
  , iiIIilii = [iil, '\x73\x65\x74\x49\x6e\x74\x65\x72\x76\x61\x6c', '\x6a\x73\x6a', ...];
var liIIIi11 = function(_0x11145e, _0x3cbe90) {
    _0x11145e = ~~'0x'['concat'](_0x11145e);
    var _0x636e4d = iiIIilii[_0x11145e];
    return _0x636e4d;
};
(function(_0x52284d, _0xfd26eb) {
    var _0x1bba22 = 0x0;
    for (_0xfd26eb = _0x52284d['shift'](_0x1bba22 >> 0x2); _0xfd26eb && _0xfd26eb !== (_0x52284d['pop'](_0x1bba22 >> 0x3) + '')['replace'](/[fnwRwdGKbwKrRFCtSC=]/g, ''); _0x1bba22++) {
        _0x1bba22 = _0x1bba22 ^ 0x661c2;
    }
}(iiIIilii, liIIIi11));
// window[liIIIi11('0')](function() {
//     var l111IlII = liIIIi11('1') + liIIIi11('2');
//     if (typeof iil == liIIIi11('3') + liIIIi11('4') || iil != l111IlII + liIIIi11('5') + l111IlII[liIIIi11('6')]) {
//         var Ilil11iI = [];
//         while (Ilil11iI[liIIIi11('6')] > -0x1) {
//             Ilil11iI[liIIIi11('7')](Ilil11iI[liIIIi11('6')] ^ 0x2);
//         }
//     }
//     iliI1lli();
// }, 0x7d0);
(function() {
    var iiIIiil = function() {}();
    var l1liii11 = function() {}();
    window[liIIIi11('9')] = function byted_acrawler() {};
    window[liIIIi11('a')] = function sign() {};
    (function() {}());
    // (function() {
    //     'use strict';
    //     var i1I1i1li = '';
    //     Object[liIIIi11('1f')](window, liIIIi11('21'), {
    //         '\x73\x65\x74': function(illllli1) {
    //             i1I1i1li = illllli1;
    //             return illllli1;
    //         },
    //         '\x67\x65\x74': function() {
    //             return i1I1i1li;
    //         }
    //     });
    // }());
    var iiil1 = 0x0;
    var l11il1l1 = '';
    var ii1Ii = 0x8;
    function i1Il11i(iiIll1i) {}
    function I1lIIlil(l11l1iIi) {}
    function lllIIiI(IIi1lIil) {}

    //N functions are omitted here
    
    window[liIIIi11('37')]();
}());

function iliI1lli(lil1I1) {
    function lili11I(l11I11l1) {
        if (typeof l11I11l1 === liIIIi11('38')) {
            return function(lllI11i) {}
            [liIIIi11('39')](liIIIi11('3a'))[liIIIi11('8')](liIIIi11('3b'));
        } else {
            if (('' + l11I11l1 / l11I11l1)[liIIIi11('6')] !== 0x1 || l11I11l1 % 0x14 === 0x0) {
                (function() {
                    return !![];
                }
                [liIIIi11('39')](liIIIi11('3c') + liIIIi11('3d'))[liIIIi11('3e')](liIIIi11('3f')));
            } else {
                (function() {
                    return ![];
                }
                [liIIIi11('39')](liIIIi11('3c') + liIIIi11('3d'))[liIIIi11('8')](liIIIi11('40')));
            }
        }
        lili11I(++l11I11l1);
    }
    try {
        if (lil1I1) {
            return lili11I;
        } else {
            lili11I(0x0);
        }
    } catch (liIlI1il) {}
}
;iil = 'jsjiami.com.v6';

// function getSign(){
//     return window[liIIIi11('9')](window[liIIIi11('a')]())
// }

function getSign(){
    return window.byted_acrawler(window.sign())
}

console.log(getSign())

Python computing key code

# ==================================
# --*-- coding: utf-8 --*--
# @Time    : 2021-12-01
#@author: wechat official account: brother K crawler
# @FileName: challenge_1.py
# @Software: PyCharm
# ==================================


import execjs
import requests

challenge_api = "http://spider.wangluozhe.com/challenge/api/1"
headers = {
    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
    "Cookie": "change the cookie value to your own!",
    "Host": "spider.wangluozhe.com",
    "Origin": "http://spider.wangluozhe.com",
    "Referer": "http://spider.wangluozhe.com/challenge/1",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36",
    "X-Requested-With": "XMLHttpRequest"
}


def get_signature():
    with open('challenge_1.js', 'r', encoding='utf-8') as f:
        ppdai_js = execjs.compile(f.read())
    signature = ppdai_js.call("getSign")
    print("signature: ", signature)
    return signature


def main():
    result = 0
    for page in range(1, 101):
        data = {
            "page": page,
            "count": 10,
            "_signature": get_signature()
        }
        response = requests.post(url=challenge_api, headers=headers, data=data).json()
        for d in response["data"]:
            result += d["value"]
    Print ("the result is:", result)


if __name__ == '__main__':
    main()

Recommended Today

Oozie5. 2.1 + Hadoop 3 compilation

Compiling oozie5.0 based on Hadoop 3 two point one system requirements Java JDK 1.8+ Maven 3.0.1+ Hadoop 3.0.0+ Compilation summary git clone https://github.com/apache/oozie.git #If you are building for Hadoop 3, you must activate the profile. The following attributes should be specified when publishing hadoop-3 builds: -Dgeneratedocs: force oozie document generation -Dskiptests: skip tests -Dvc. Revision […]