[JS reverse hundred examples] W store UA, OB anti confusion, packet capture and replacement CORS cross domain error analysis

Focus on official account dry cargo WeChat public: K brother crawler, keep sharing crawler advance, JS/ Android reverse technology dry goods!


All contents in this article are for learning and communication only. 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!

Reverse target

  • Objective: the UA parameters of the W store login interface are encrypted, and the JS code has been obfuscated
  • Home page: aHR0cHM6Ly9kLndlaWRpYW4uY29tLw==
  • Interface: ahr0chm6ly9zc28xlndlawrpyw4uy29tl3vzxivbg9naw4=
  • Reverse parameter: Form Data: UA: h4siaaaaaa91vizubmqhtivoicnrxrafr% 2fgun5ukgoyfluzc

Introduction to OB confusion

Obfuscator is the full name of obfuscator. Obfuscator actually means confusion. Official website: https://obfuscator.io/ , the author is a Russian JavaScript development engineer named Timofey Kachalov, who released the first version as early as 2016.

A normal code is as follows:

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

Results after OB confusion:

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) {
}(_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;


OB confusion has the following characteristics:

1. It is generally composed of a large array or a function containing a large array, a self executing function, a decryption function and an encrypted function;

2. Function and variable names are usually_ 0x or 0x, followed by 1 ~ 6 digits of numbers or letter combinations;

3. Self executing function for shift operation, with obvious push and shift keywords;

For example, in the above example_ The 0x3f26() method defines a large array. The self executing function contains the push and shift keywords, which are mainly used to shift the large array_ 0x1fe9() is the decryption function, and hi() is the encrypted function.

Packet capture analysis

Click log in to capture the package. You can see that there is a ua parameter, which is encrypted and will change each time you log in, as shown in the following figure:

If you directly search for ua, there are too many results and it is inconvenient to filter. It is easier to find the encrypted location through the XHR breakpoint, as shown in the figure below. The last submitted r parameter contains the ua value. Looking up, you can see that the value of i has been URL encoded. Looking up, the value of i is obtained through window.getUa(), which is actually an anonymous function in uad.js.

Following up with uad.js, you can see that the method window[_0x4651('0x710 ')] is called, and finally the returned_ 0x261229 is the encrypted ua value, which is similar with the mouse handle_ 0x4651('0x710'),_ If the value of 0x4651('0x440 ') is selected, you can see that it is actually some strings. These strings can be found in a large array in the header through direct search, as shown in the following figure:

Confusing restore and replace

A large array and a self executing function for shift operation with obvious push and shift keywords are undoubtedly obfuscated. So how should we deal with it 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 it all. Just follow the stack and replace it where it is used. Don't be silly to replace it all manually one by one. This method is suitable for less complex code.

If you encounter a lot of code, it is recommended to use anti obfuscation tools to deal with it. Domestic ones are recommended here Ape anthropology OB confusion solution tool And foreign de4js , the ape anthropological tool has a high degree of restoration, but some OB confusions will report errors after restoration. The measured OB confusions in this case will not work normally after being processed by the ape anthropological tool, and may need to be handled in advance. de4js this tool is developed by a manufacturer in Vietnam and is open source. You can deploy it to your own machine, It supports a variety of confusion restoration, including Eval, OB, jsluck, AA, JJ, etc. it can paste code directly and automatically identify confusion methods. de4js is recommended in this case, as shown in the following figure:

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

If you start capturing packets and refresh the page, you will find that the request status status shows CORS error, 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:

CORS cross domain error

CORS (cross origin resource sharing) is a W3C standard that uses an additional HTTP header to tell the browser that it allows Web applications running on one source to access resources from another source. Any difference between the protocol, domain name and port of a request URL and the current page address is cross domain. The 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 an access control allow Origin response header to the consumer. If the value is * or the site corresponding to Origin, it means that resources are allowed to be shared with the consumer. If the value is null or does not exist, it means that resources are not allowed to be shared with the consumer;
3. In addition to access control allow origin, some sites may also detect access control allow credentials. If it is true, it means it is allowed;
4. The browser judges whether to allow consumers to access the provider source across domains according to the provider's response message;

According to the previous error messages on the console, we can know that the response header lacks access control allow origin. In Fiddler, there are two methods to add this parameter to the response header, as described below:

The first is to use the Filter function of Fiddler to set it in Response Headers, and fill in access control allow origin and allowed domain names respectively, as shown in the following figure:

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

if(oSession.uriContains("To process URL")){
    oSession.oResponse["Access-Control-Allow-Origin"] =  "Allowed domain names";

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

backward 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) {
    var _0x261229 = _0x1722c3(_0x2e98dd) + '|' + _0x1722c3(_0x420004) + '|' + _0x7dfc34.toString(0x10);
    _0x261229 = btoa(_0x570bef.gzip(_0x261229, {
        'to': 'string'
    return _0x261229;

_ 0x7dfc34 is the time stamp, followed by an if judgment. We can mouse into the judgment to find the judgment_ 0x4a9622 is false, then_ 0x2644f4() will not be executed, and then it will be executed_ 0x55b608() method_ The value of 0x261229 mainly calls_ 0x1722c3() method, passed in sequence_ 0x2e98dd and_ 0x420004, it is obvious that these two values are key. Search them respectively and you can find:

_ 0x2e98dd defines some header, browser information, screen information, system font information, etc., which can be directly transmitted as fixed values, as shown in the following figure:

_ 0x420004 the useful result of the search is that only an empty object is defined. You can see that it actually contains some data of keyboard and mouse clicks and movements on the console. In fact, it is found through testing_ The value of 0x420004 is not strongly verified. It can be generated by random number simulation, or a fixed value can be copied directly.

_ 0x2e98dd and_ 0x420004 these two parameters are not subject to strong verification, and can be passed in as fixed values. These two values are in JSON format. We can directly copy their values with copy statement on the console, or output the results with JSON.stringify() statement and then copy them manually.

Local joint commissioning

There are many functions calling each other. You can directly copy the whole JS. We notice that the whole function is a self executing function. When calling locally, we can define a global variable, and then in the window.getua function_ The value of 0x261229 is assigned to the global variable, which is equivalent to the derived value. Finally, the global variable can be obtained. Another way is not to let it execute itself, to rewrite it into a normal general function, and then call the window.getUa method to get the ua value.

First we put_ 0x2e98dd and_ The value of 0x420004 is defined locally. Here is a small detail. You need to comment out the definition of these two values in the original JS code to prevent conflict.

During local debugging, you will be prompted that window, location and document are not defined. Just define an empty object, and then you will be prompted that attachEvent is not defined. Search yes_ In addition to attachEvent, a prototype object of 0x13cd5a also has an addEventListener. The addEventListener() method is used to add an event handle to the specified element. It is implemented in IE using attachEvent() method. We bury a breakpoint in Google Chrome for debugging. Refreshing the page will directly enter the addEventListener() method, where the event is keydown, That is, press the keyboard to call the following_ 0x5cec90 method outputs this returned later, which actually does not produce any useful value, so_ 0x13cd5a.prototype.bind method can be commented out directly, and the actual test has no impact.

After local debugging, it will be prompted that btoa is undefined. Btoa and atob are two functions of window objects. Btoa is binary to ascii, which is used to represent binary data with ascii code, that is, the encoding process of Base64, while atob is ascii to binary, which is used to parse ascii code into binary data, that is, the decoding process of Base64.

In NodeJS, a local module called Buffer is provided, which can be used to perform Base64 encoding and decoding. It is not described in detail here, but can be used by Baidu. The original btoa statement in the window.getUa method is as follows:

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

In NodeJS, we can write as follows:

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

Note: Buffer.from() passes in a Latin1 parameter because_ The result of 0x570bef.gzip (_0x261229, {'to':'string '}) is Latin1 (alias of ISO-8859-1). If you do not pass or pass in other parameters, the final result may be different from that obtained by btoa method!

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

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 part of the key code and cannot be run directly! Full code warehouse address: https://github.com/kgepachong...

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 functions are omitted here

var _0x2e98dd = {
    // Object specific value omitted
    "basic": {},
    "header": {},
    "navigator": {},
    "screenData": {},
    "sysfonts": [],
    "geoAndISP": {},
    "browserType": {},
    "performanceTiming": {},
    "canvasFp": {},
    "visTime": [],
    "other": {}
var _0x420004 = {
    // Object specific value omitted
    "keypress": true,
    "scroll": true,
    "click": true,
    "mousemove": true,
    "mousemoveData": [],
    "keypressData": [],
    "mouseclickData": [],
    "wheelDeltaData": []

window.getUa = function () {
    var _0x7dfc34 = new Date().getTime();
    if (_0x4a9622) {
    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;

// Test output
// console.log(window.getUa())

Python login key code

# ==================================
# --*-- coding: utf-8 --*--
# @Time    : 2021-11-15
# @Author: WeChat official account: K brother crawler
# @FileName: weidian_login.py
# @Software: PyCharm
# ==================================

import execjs
import requests
from urllib import parse

index_url = "Desensitization, complete code attention GitHub: https://github.com/kgepachong/crawler"
login_url = "Desensitization, complete code attention 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": "Desensitization, complete code attention GitHub: https://github.com/kgepachong/crawler",
        "referer": "Desensitization, complete code attention 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)

def main():
    phone = input("Please enter your login mobile number: ")
    password = input("Please enter the login password: ")
    ua = get_encrypted_ua()
    wd_token = get_wd_token()
    login(phone, password, ua, wd_token)

if __name__ == '__main__':

Tags: Python

Posted on Thu, 02 Dec 2021 22:21:47 -0500 by Eddyon