1
头图
Pay attention to WeChat public account: Brother K crawler, continue to share advanced crawler, JS/Android reverse engineering and other technical dry goods!

statement

All the content in this article is for learning and communication only. The captured content, sensitive URLs, and data interfaces have been desensitized, and it is strictly forbidden to use them for commercial or illegal purposes. Otherwise, all the consequences arising therefrom will have nothing to do with the author. Infringement, please contact me to delete it immediately!

Reverse target

  • Goal: W shop login interface UA parameter encryption, JS code is obfuscated by OB
  • Homepage: aHR0cHM6Ly9kLndlaWRpYW4uY29tLw==
  • Interface: aHR0cHM6Ly9zc28xLndlaWRpYW4uY29tL3VzZXIvbG9naW4=
  • Reverse parameters: Form Data: ua: H4sIAAAAAAAAA91ViZUbMQhtiVOIcnRRxRafr%2FGuN5ukgoyfLUZC...

Introduction to OB obfuscation

The full name of OB obfuscation is Obfuscator. Obfuscator actually means obfuscation. The official website: https://obfuscator.io/ . The author is a Russian JavaScript development engineer named Timofey Kachalov. The first version was released as early as 2016. .

A normal piece of code is as follows:

function hi() {
  console.log("Hello World!");
}
hi();

The result after obfuscation:

function _0x3f26() {
    var _0x2dad75 = ['5881925kTCKCP', 'Hello\x20World!', '600mDvfGa', '699564jYNxbu', '1083271cEvuvT', 'log', '18sKjcFY', '214857eMgFSU', '77856FUKcuE', '736425OzpdFI', '737172JqcGMg'];
    _0x3f26 = function () {
        return _0x2dad75;
    };
    return _0x3f26();
}

(function (_0x307c88, _0x4f8223) {
    var _0x32807d = _0x1fe9, _0x330c58 = _0x307c88();
    while (!![]) {
        try {
            var _0x5d6354 = parseInt(_0x32807d(0x6f)) / 0x1 + parseInt(_0x32807d(0x6e)) / 0x2 + parseInt(_0x32807d(0x70)) / 0x3 + -parseInt(_0x32807d(0x69)) / 0x4 + parseInt(_0x32807d(0x71)) / 0x5 + parseInt(_0x32807d(0x6c)) / 0x6 * (parseInt(_0x32807d(0x6a)) / 0x7) + -parseInt(_0x32807d(0x73)) / 0x8 * (parseInt(_0x32807d(0x6d)) / 0x9);
            if (_0x5d6354 === _0x4f8223) break; else _0x330c58['push'](_0x330c58['shift']());
        } catch (_0x3f18e4) {
            _0x330c58['push'](_0x330c58['shift']());
        }
    }
}(_0x3f26, 0xaa023));

function _0x1fe9(_0xa907e7, _0x410a46) {
    var _0x3f261f = _0x3f26();
    return _0x1fe9 = function (_0x1fe950, _0x5a08da) {
        _0x1fe950 = _0x1fe950 - 0x69;
        var _0x82a06 = _0x3f261f[_0x1fe950];
        return _0x82a06;
    }, _0x1fe9(_0xa907e7, _0x410a46);
}

function hi() {
    var _0x12a222 = _0x1fe9;
    console[_0x12a222(0x6b)](_0x12a222(0x72));
}

hi();

OB obfuscation has the following characteristics:

1. Generally consists of a large array or a function containing a large array, a self-executing function, a decryption function and an encrypted function;

2. Function names and variable names usually start with _0x or 0x , followed by a combination of 1 to 6 numbers or letters;

3. Self-executing function, shift operation, there are obvious push and shift keywords;

For example, in the above example, the _0x3f26() method defines a large array. There are push and shift keywords in the self-executing function, which is mainly used to shift the large array. _0x1fe9() is the decryption function, and hi() is the encrypted function.

Packet capture analysis

Click login to capture the packet, you can see that there is a ua parameter, which is encrypted and will change every time you log in, as shown in the following figure:

01.png

If you search for ua directly, there are too many results and it is not convenient to filter. It is easier to find the encrypted location through XHR breakpoints. As shown in the figure below, the last submitted r parameter contains the value of ua. If you look up, you can see that the value of i is passed After URL encoding, looking up, the value of i is obtained through window.getUa() , which is actually an anonymous function in uad.js.

02.png

Follow-up to uad.js, you can see the call window[_0x4651('0x710')] this method, the final return of _0x261229 is ua encrypted value, similar to the mouse _0x4651('0x710') , _0x4651('0x440') value selected, you can actually see some string, these characters Through direct search, the string can be found in a large array in the head, as shown in the following figure:

03.png

04.png

Confusion reduction and replacement

A large array, a self-executing function that performs shift operations with obvious push and shift keywords, is undoubtedly confused by OB. So what should we do to make it look more pleasing to the eye?

You can manually select the view value in the browser and replace it locally. Of course, you don’t need to replace all of them. Follow the stack and replace where you use them. Don’t be foolish to replace them all manually. Complex code.

If you encounter a lot of codes, it is recommended to use an anti-obfuscation tool to deal with it. The domestic OB confusion specialization tool and the foreign de4js . The reduction of the ape-man learning tools is very high, but some OB confusion restoration After running, it will report an error. The OB obfuscation in this case can not be run normally after being processed by the ape-man learning tool. You may need to pre-process it yourself. The de4js tool is developed by an author in Vietnam and is open source. You can deploy it to On its own machine, it supports a variety of obfuscation restoration, including Eval, OB, JSFuck, AA, JJ, etc. You can paste the code directly and automatically recognize the obfuscation method. In this case, de4js is recommended, as shown in the following figure:

05.png

We copy the restored result to a local file, and use Fiddler's Autoresponder function to replace the response, as shown in the following figure:

06.png

If you start the packet capture at this time and refresh the page, you will find that the request status status shows CORS error, the JS replacement is unsuccessful, and you can also see the error No 'Access-Control-Allow-Origin' header is present on the requested resource. in the console as shown in the following figure:

14.png

CORS cross-domain error

CORS (Cross-Origin Resource Sharing) is a W3C standard that uses additional HTTP headers to tell browsers to allow web applications running on one source to access resources located on a different source. Any difference between the protocol, domain name, and port of a request URL from the current page address is cross-domain. A common cross-domain problem is that the browser prompts that the API of domain B cannot be accessed under domain A. For further understanding of CORS, please refer to W3C CORS Enabled .

The brief process is as follows:

1. The consumer sends an Origin header to the provider: Origin: http://www.site.com;
2. The provider sends a Access-Control-Allow-Origin response header to the consumer. If the value is * or the site corresponding to Origin, it means that the resource is allowed to be shared with the consumer. If the value is null or does not exist, it means that the resource is not allowed to be shared to the consumer;
3. In addition to Access-Control-Allow-Origin , some sites may also detect Access-Control-Allow-Credentials , which is true to allow;
4. The browser judges whether to allow consumers to access the provider source across domains based on the provider's response message;

According to the previous error message in the console, we can know that it is Access-Control-Allow-Origin in the response header. There are two ways to add this parameter to the response header in Fiddler, which are introduced below:

The first is to use Fiddler's Filter function, set it in Response Headers, and fill in Access-Control-Allow-Origin and allowed domain names respectively, as shown in the following figure:

15.png

The second is to modify the CustomRules.js file, select Rules —> Customize Rules in static function OnBeforeResponse(oSession: Session) , and add the following code under the 061af0d8565805 module:

if(oSession.uriContains("要处理的 URL")){
    oSession.oResponse["Access-Control-Allow-Origin"] =  "允许的域名";
}

16.png

Choose one of the two methods. After the setting is completed, it can be replaced successfully. Refresh and debug again to see that it is the restored JS, as shown in the following figure:

07.png

Reverse analysis

Obviously window.getUa is the main encryption function, so let's analyze this function first:

window.getUa = function() {
    var _0x7dfc34 = new Date().getTime();
    if (_0x4a9622) {
        _0x2644f4();
    }
    _0x55b608();
    var _0x261229 = _0x1722c3(_0x2e98dd) + '|' + _0x1722c3(_0x420004) + '|' + _0x7dfc34.toString(0x10);
    _0x261229 = btoa(_0x570bef.gzip(_0x261229, {
        'to': 'string'
    }));
    return _0x261229;
};

_0x7dfc34 is a time stamp, then a judge if we can put the mouse down and see judge, the judge found _0x4a9622 is false, then _0x2644f4() will not be executed, and then perform a _0x55b608() method, _0x261229 value, mainly call _0x1722c3() According to the method, _0x2e98dd and _0x420004 are passed in successively. Obviously, these two values are more critical. If you search separately, you can find:

_0x2e98dd defines some headers, browser information, screen information, system font information, etc. These information can be directly passed in as fixed values, as shown in the following figure:

09.png

10.png

_0x420004 useful result of the search for 061af0d85659e7 is that only an empty object is defined. In the console output, you can see that it actually contains some keyboard and mouse clicks and movements. In fact, after testing, it is found that _0x420004 is not a strong check. You can use random number simulation to generate, or you can directly copy a fixed value.

11.png

_0x2e98dd and _0x420004 are not strongly checked and can be passed in as fixed values. Both values are in JSON format. We can use the copy statement to copy the value JSON.stringify() statement Copy the output result manually.

12.png

Local joint debugging

There are many functions that call each other. You can copy the entire JS directly. We noticed that the entire function is a self-executing function. When calling locally, we can define a global variable, and then in the window.getUa function, set the value of _0x261229 Assigning a value to a global variable is equivalent to exporting the value. Finally, just take this global variable. Another way is to window.getUa it from executing, rewrite it into a normal function, and then call the 061af0d8565a9b method to get the ua value.

First, we _0x420004 _0x2e98dd and 061af0d8565abb locally. Here is a small detail. You need to comment out the definition of these two values in the original JS code to prevent conflicts.

When local debugging, you will be prompted window , location , document is not defined, the definition of what an object can be empty, and then prompts attachEvent undefined search is _0x13cd5a a prototype object, in addition to attachEvent outside, there is a addEventListener , addEventListener() The method is used to add event handlers to the specified element. It is implemented in IE using the attachEvent() method. We bury a breakpoint in Google Chrome to debug it. Refreshing the page will directly enter the addEventListener() method. The event is keydown , that is, the keyboard is pressed. Just call the following _0x5cec90 method and output the this returned later. In fact, it did not produce any useful value, so _0x13cd5a.prototype.bind method directly, and the actual test has no effect.

08.png

Then the local debugging will prompt that btoa undefined, btoa and atob are two functions of the window object, among which btoa is binary to ascii, which is used to express the binary data in ascii code, that is, the encoding process of Base64, and 061af0d is atob ascii to binary, used to parse ascii code into binary data, that is, the decoding process of Base64.

In NodeJS, Buffer is provided, which can be used to perform Base64 encoding and decoding. I will not introduce it in detail here. You can use Baidu. The original btoa window.getUa method is like this:

_0x261229 = btoa(_0x570bef.gzip(_0x261229, {'to': 'string'}));

In NodeJS, we can write like this:

_0x261229 = Buffer.from(_0x570bef.gzip(_0x261229, {'to': 'string'}), "latin1").toString('base64');

Note: Buffer.from() passed in a latin1 parameter, this is because _0x570bef.gzip(_0x261229, {'to': 'string'}) is Latin1 (ISO-8859-1 alias) encoding, if you do not pass or pass in other parameters, the final result may be the btoa as the result obtained by the 061af0d8565c1a method Different!

Since then, after the local joint debugging is completed, the correct ua value can be obtained!

Complete code

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

only part of the key code is demonstrated and cannot be run directly! complete code warehouse address: https://github.com/kgepachong/crawler/

JavaScript encryption key code architecture

var window = {};
var location = {};
var document = {};
var _0x5a577d = function () {}();
var _0xe26ae = function () {}();
var _0x3204b9 = function () {}();
var _0x3c7e70 = function () {}();
var _0x4a649b = function () {}();
var _0x21524f = function () {}();
var _0x2b0d61 = function () {}();
var _0x53634a = function () {}();
var _0x570bef = function () {}();
var _0xd05c32 = function (_0x5c6c0c) {};
window.CHLOROFP_STATUS = 'start';

// 此处省略 N 个函数

var _0x2e98dd = {
    // 对象具体的值已省略
    "basic": {},
    "header": {},
    "navigator": {},
    "screenData": {},
    "sysfonts": [],
    "geoAndISP": {},
    "browserType": {},
    "performanceTiming": {},
    "canvasFp": {},
    "visTime": [],
    "other": {}
}
var _0x420004 = {
    // 对象具体的值已省略
    "keypress": true,
    "scroll": true,
    "click": true,
    "mousemove": true,
    "mousemoveData": [],
    "keypressData": [],
    "mouseclickData": [],
    "wheelDeltaData": []
}

window.getUa = function () {
    var _0x7dfc34 = new Date().getTime();
    if (_0x4a9622) {
        _0x2644f4();
    }
    _0x55b608();
    var _0x261229 = _0x1722c3(_0x2e98dd) + '|' + _0x1722c3(_0x420004) + '|' + _0x7dfc34.toString(0x10);
    // _0x261229 = btoa(_0x570bef.gzip(_0x261229, {'to': 'string'}));
    _0x261229 = Buffer.from(_0x570bef.gzip(_0x261229, {'to': 'string'}), "latin1").toString('base64');
    return _0x261229;
};

// 测试输出
// console.log(window.getUa())

Python login key code

# ==================================
# --*-- coding: utf-8 --*--
# @Time    : 2021-11-15
# @Author  : 微信公众号:K哥爬虫
# @FileName: weidian_login.py
# @Software: PyCharm
# ==================================


import execjs
import requests
from urllib import parse


index_url = "脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler"
login_url = "脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler"
UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36"
session = requests.session()


def get_encrypted_ua():
    with open('get_encrypted_ua.js', 'r', encoding='utf-8') as f:
        uad_js = f.read()
    ua = execjs.compile(uad_js).call('window.getUa')
    ua = parse.quote(ua)
    return ua


def get_wd_token():
    headers = {"User-Agent": UserAgent}
    response = session.get(url=index_url, headers=headers)
    wd_token = response.cookies.get_dict()["wdtoken"]
    return wd_token


def login(phone, password, ua, wd_token):
    headers = {
        "user-agent": UserAgent,
        "origin": "脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler",
        "referer": "脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler",
    }
    data = {
        "phone": phone,
        "countryCode": "86",
        "password": password,
        "version": "1",
        "subaccountId": "",
        "clientInfo": '{"clientType": 1}',
        "captcha_session": "",
        "captcha_answer": "",
        "vcode": "",
        "mediaVcode": "",
        "ua": ua,
        "scene": "PCLogin",
        "wdtoken": wd_token
    }
    response = session.post(url=login_url, headers=headers, data=data)
    print(response.json())


def main():
    phone = input("请输入登录手机号: ")
    password = input("请输入登录密码: ")
    ua = get_encrypted_ua()
    wd_token = get_wd_token()
    login(phone, password, ua, wd_token)


if __name__ == '__main__':
    main()


K哥爬虫
166 声望154 粉丝

Python网络爬虫、JS 逆向等相关技术研究与分享。