12306 Ticket Snapping Series, as long as the source of RAIL_DEVICEID is found, ticket Snapping will not drop line from here (middle)

Go straight to focus

High-rise building for key clues

The most typical network request in Js file is asynchronous callback, which complicates the original simple operation. If you wait for me, I wait for him, he still waits for her.

The direct result is that the entire request process reverses, and assuming the normal process is A->B->C-D-E-F, asynchronous requests are likely to fall into this trap: F

So callback functions from one layer to another are really difficult to maintain, and this technology is slowly phasing out and updating to a more maintainable way, or is it no longer expanding, back to the main point, or find out when the program actually started calling first!

ja.prototype = {
  initEc: function(a) {
      var b = ""
        , c = this
        , d = void 0 != a && void 0 != a.localAddr ? a.localAddr : "";
      c.checkWapOrWeb();
      this.ec.get("RAIL_OkLJUJ", function(a) {
          b = a;
          c.getDfpMoreInfo(function() {
              if (!(9E5 < F("RAIL_EXPIRATION") - (new Date).getTime() & null != F("RAIL_DEVICEID") & void 0 != F("RAIL_DEVICEID") & !c.NeedUpdate())) {
                  for (var a = "", e = "", g = c.getpackStr(b), m = [], q = [], t = [], k = [], n = 0; n < g.length; n++)
                      "new" != g[n].value && -1 == Fb.indexOf(g[n].key) && (-1 != Gb.indexOf(g[n].key) ? q.push(g[n]) : -1 != Ib.indexOf(g[n].key) ? t.push(g[n]) : -1 != Hb.indexOf(g[n].key) ? k.push(g[n]) : m.push(g[n]));
                  g = "";
                  for (n = 0; n < q.length; n++)
                      g = g + q[n].key.charAt(0) + q[n].value;
                  q = "";
                  for (n = 0; n < k.length; n++)
                      q = 0 == n ? q + k[n].value : q + "x" + k[n].value;
                  k = "";
                  for (n = 0; n < t.length; n++)
                      k = 0 == n ? k + t[n].value : k + "x" + t[n].value;
                  m.push(new l("storeDb",g));
                  m.push(new l("srcScreenSize",q));
                  m.push(new l("scrAvailSize",k));
                  "" != d && m.push(new l("localCode",pb(d)));
                  e = c.hashAlg(m, a, e);
                  a = e.key;
                  e = e.value;
                  a += "\x26timestamp\x3d" + (new Date).getTime();
                  $a.getJSON("https://kyfw.12306.cn/otn/HttpZF/logdevice" + ("?algID\x3drblubbXDx3\x26hashCode\x3d" + e + a), null, function(a) {
                      var b = JSON.parse(a);
                      void 0 != lb && lb.postMessage(a, r.parent);
                      for (var d in b)
                          "dfp" == d ? F("RAIL_DEVICEID") != b[d] && (W("RAIL_DEVICEID", b[d], 1E3),
                          c.deviceEc.set("RAIL_DEVICEID", b[d])) : "exp" == d ? W("RAIL_EXPIRATION", b[d], 1E3) : "cookieCode" == d && (c.ec.set("RAIL_OkLJUJ", b[d]),
                          W("RAIL_OkLJUJ", "", 0))
                  })
              }
          })
      }, 1)
  }
}

The outermost function of the core code is the initEc function, which is obviously written as a property method of the traditional js, so judging the property methods mounted on the object should all accomplish some of the same functions.

For the time being, don't rush on to find out who is calling the initEc function, just figure out the outline of the whole function structure.

function ja() {
    this.ec = new evercookie;
    this.deviceEc = new evercookie;
    this.cfp = new aa;
    this.packageString = "";
    this.moreInfoArray = []
}

ja.prototype = {
  getScrWidth: function() {
      return new l("scrWidth",r.screen.width.toString())
  },
  ...
  ,
   checkWapOrWeb: function() {
      return "WindowsPhone" == Ha() || "iOS" == Ha() || "Android" == Ha() ? !0 : !1
  }
}

If you're familiar with web development, it's not difficult to see that this is standard object-oriented writing, that ja functions as constructors have a large set of member variables built into them, and that they inherit a lot of methods from the prototype chain.

What's more, there are three constructors with the new keyword in the object attributes, which are similar to ja's. It's not easy to restore the related algorithm when the tall building is flat.

But think about how hard it is to grab tickets and still get access to errors. Or do you want to research algorithms to work out the generation logic of RAIL_DEVICEID once and for all, and use them to compute and disguise your browser perfectly!

Now continue your search with the initEc function name to find out who called it, and it's easy to find a new function name: getFingerPrint

ja.prototype = {
  getFingerPrint: function() {
      var a = this;
      r.RTCPeerConnection || r.webkitRTCPeerConnection || r.mozRTCPeerConnection ? nb(function(b) {
          a.initEc(b)
      }) : a.initEc()
  }
}

Likewise, don't stop too much, continue searching for getFingerPrint keywords, find the Pa function, and finally stop being ja's method.

function Pa() {
    if (-1 == F("RAIL_EXPIRATION"))
        for (var a = 0; 10 > a; a++)
            G(function() {
                (new ja).getFingerPrint()
            }, 20 + 2E3 * Math.pow(a, 2));
    else
        (new ja).getFingerPrint();
    G(function() {
        r.setInterval(function() {
            (new ja).getFingerPrint()
        }, 3E5)
    }, 3E5)
}

At the same time, the Pa function is also the first line of code in the JS file, so you can have a look at the overall structure code of js.

(function() {
   
})();

The advantage of closures implemented by self-executing anonymous functions is that the variables inside the functions do not pollute other files, let alone the confused variable names are full of variables a,b,c,d,e,f and so on, so closures are not necessary.

Now continue your search with Pa as a clue and you'll find the function entry, but there's nothing else.

var mb = !1;
u.addEventListener ? u.addEventListener("DOMContentLoaded", function b() {
    u.removeEventListener("DOMContentLoaded", b, !1);
    Pa()
}, !1) : u.attachEvent && u.attachEvent("onreadystatechange", function c() {
    mb || "interactive" != u.readyState && "complete" != u.readyState || (u.detachEvent("onreadystatechange", c),
    Pa(),
    mb = !0)
})

js is a typical event-driven programming language. I'll do this after something happens, I'll start working when the page loads, the button is clicked, I'm going to log in, I'm going to leave work when the page closes, and so on.

The above code implements that the Pa() function is executed after the page element has been loaded successfully, and the Pa function executes (new ja).getFingerPrint(), followed by the initEc function.

Now that the basic process is generally clear, summarize the basic code logic as follows:

(function() {
   var mb = !1;
  u.addEventListener ? u.addEventListener("DOMContentLoaded", function b() {
      u.removeEventListener("DOMContentLoaded", b, !1);
      Pa()
  }, !1) : u.attachEvent && u.attachEvent("onreadystatechange", function c() {
      mb || "interactive" != u.readyState && "complete" != u.readyState || (u.detachEvent("onreadystatechange", c),
      Pa(),
      mb = !0)
  })

  function Pa() {
    if (-1 == F("RAIL_EXPIRATION"))
        for (var a = 0; 10 > a; a++)
            G(function() {
                (new ja).getFingerPrint()
            }, 20 + 2E3 * Math.pow(a, 2));
    else
        (new ja).getFingerPrint();
    G(function() {
        r.setInterval(function() {
            (new ja).getFingerPrint()
        }, 3E5)
    }, 3E5)
  }

  function ja() {
    this.ec = new evercookie;
    this.deviceEc = new evercookie;
    this.cfp = new aa;
    this.packageString = "";
    this.moreInfoArray = []
  }

  ja.prototype = {
    getFingerPrint: function() {
        var a = this;
        r.RTCPeerConnection || r.webkitRTCPeerConnection || r.mozRTCPeerConnection ? nb(function(b) {
            a.initEc(b)
        }) : a.initEc()
    },
    initEc: function(a) {
      var b = ""
        , c = this
        , d = void 0 != a && void 0 != a.localAddr ? a.localAddr : "";
      c.checkWapOrWeb();
      this.ec.get("RAIL_OkLJUJ", function(a) {
          b = a;
          c.getDfpMoreInfo(function() {
              if (!(9E5 < F("RAIL_EXPIRATION") - (new Date).getTime() & null != F("RAIL_DEVICEID") & void 0 != F("RAIL_DEVICEID") & !c.NeedUpdate())) {
                  for (var a = "", e = "", g = c.getpackStr(b), m = [], q = [], t = [], k = [], n = 0; n < g.length; n++)
                      "new" != g[n].value && -1 == Fb.indexOf(g[n].key) && (-1 != Gb.indexOf(g[n].key) ? q.push(g[n]) : -1 != Ib.indexOf(g[n].key) ? t.push(g[n]) : -1 != Hb.indexOf(g[n].key) ? k.push(g[n]) : m.push(g[n]));
                  g = "";
                  for (n = 0; n < q.length; n++)
                      g = g + q[n].key.charAt(0) + q[n].value;
                  q = "";
                  for (n = 0; n < k.length; n++)
                      q = 0 == n ? q + k[n].value : q + "x" + k[n].value;
                  k = "";
                  for (n = 0; n < t.length; n++)
                      k = 0 == n ? k + t[n].value : k + "x" + t[n].value;
                  m.push(new l("storeDb",g));
                  m.push(new l("srcScreenSize",q));
                  m.push(new l("scrAvailSize",k));
                  "" != d && m.push(new l("localCode",pb(d)));
                  e = c.hashAlg(m, a, e);
                  a = e.key;
                  e = e.value;
                  a += "\x26timestamp\x3d" + (new Date).getTime();
                  $a.getJSON("https://kyfw.12306.cn/otn/HttpZF/logdevice" + ("?algID\x3drblubbXDx3\x26hashCode\x3d" + e + a), null, function(a) {
                      var b = JSON.parse(a);
                      void 0 != lb && lb.postMessage(a, r.parent);
                      for (var d in b)
                          "dfp" == d ? F("RAIL_DEVICEID") != b[d] && (W("RAIL_DEVICEID", b[d], 1E3),
                          c.deviceEc.set("RAIL_DEVICEID", b[d])) : "exp" == d ? W("RAIL_EXPIRATION", b[d], 1E3) : "cookieCode" == d && (c.ec.set("RAIL_OkLJUJ", b[d]),
                          W("RAIL_OkLJUJ", "", 0))
                  })
              }
          })
      }, 1)
    }
  }
})();

From the above code analysis, I believe you will find that the relevant logic should be compatible with IE browser, set timer to update cookie values repeatedly, and there is remote RTC to maintain communication, you have to say it is doing really well, it is a national travel agent tool!

Limited effort, here is the simplest case to choose for the algorithm restore process. The browser chooses Google Chrome browser to block compatibility patch processing for IE, regardless of RTCPeerConnection, so the code logic is simplified as follows:

(function() {
  document.addEventListener("DOMContentLoaded", Pa,false)

  function Pa() {
    (new ja).getFingerPrint();
  }

  function ja() {
    this.ec = new evercookie;
    this.deviceEc = new evercookie;
    this.cfp = new aa;
    this.packageString = "";
    this.moreInfoArray = []
  }

  ja.prototype = {
    getFingerPrint: function() {
        this.initEc()
    },
    initEc: function(a) {
      var b = ""
        , c = this
        , d = void 0 != a && void 0 != a.localAddr ? a.localAddr : "";
      c.checkWapOrWeb();
      this.ec.get("RAIL_OkLJUJ", function(a) {
          b = a;
          c.getDfpMoreInfo(function() {
              if (!(9E5 < F("RAIL_EXPIRATION") - (new Date).getTime() & null != F("RAIL_DEVICEID") & void 0 != F("RAIL_DEVICEID") & !c.NeedUpdate())) {
                  for (var a = "", e = "", g = c.getpackStr(b), m = [], q = [], t = [], k = [], n = 0; n < g.length; n++)
                      "new" != g[n].value && -1 == Fb.indexOf(g[n].key) && (-1 != Gb.indexOf(g[n].key) ? q.push(g[n]) : -1 != Ib.indexOf(g[n].key) ? t.push(g[n]) : -1 != Hb.indexOf(g[n].key) ? k.push(g[n]) : m.push(g[n]));
                  g = "";
                  for (n = 0; n < q.length; n++)
                      g = g + q[n].key.charAt(0) + q[n].value;
                  q = "";
                  for (n = 0; n < k.length; n++)
                      q = 0 == n ? q + k[n].value : q + "x" + k[n].value;
                  k = "";
                  for (n = 0; n < t.length; n++)
                      k = 0 == n ? k + t[n].value : k + "x" + t[n].value;
                  m.push(new l("storeDb",g));
                  m.push(new l("srcScreenSize",q));
                  m.push(new l("scrAvailSize",k));
                  "" != d && m.push(new l("localCode",pb(d)));
                  e = c.hashAlg(m, a, e);
                  a = e.key;
                  e = e.value;
                  a += "\x26timestamp\x3d" + (new Date).getTime();
                  $a.getJSON("https://kyfw.12306.cn/otn/HttpZF/logdevice" + ("?algID\x3drblubbXDx3\x26hashCode\x3d" + e + a), null, function(a) {
                      var b = JSON.parse(a);
                      void 0 != lb && lb.postMessage(a, r.parent);
                      for (var d in b)
                          "dfp" == d ? F("RAIL_DEVICEID") != b[d] && (W("RAIL_DEVICEID", b[d], 1E3),
                          c.deviceEc.set("RAIL_DEVICEID", b[d])) : "exp" == d ? W("RAIL_EXPIRATION", b[d], 1E3) : "cookieCode" == d && (c.ec.set("RAIL_OkLJUJ", b[d]),
                          W("RAIL_OkLJUJ", "", 0))
                  })
              }
          })
      }, 1)
    }
  }
})();

So the core of the problem now is to find out the data flow direction of the initEc function, and the implementation of the restore algorithm is not a dream!

Breakpoint Debugging Trace Call Stack

After the static analyzer structure, start the power off debugging and observe the data flow direction, so as to have a clear idea, while maintaining the consistency of each operation environment for the process to be repeatable.

Specifically, first the Chrome browser is in seamless mode, then the site cache is emptied for each trial, and finally the current page can be refreshed happily and ready for the next round of power-off debugging.

Breakpoints are typed in advance at key points (left mouse click on line number), then wait for the program to enter debugging mode. After a while, breakpoints can be entered to see the value of the program running step by step, and variable values can be monitored in the debugging area.

Of course, there can also be a function call stack relationship, which is only an auxiliary means. The most important thing is to find out the calling sequence of the function by analyzing itself. In principle, large first, small first, whole then detail.

The function eventually sends an ajax request to get the cookie and write it locally and in the cookie, with the following parental data:

{"exp":"1582097104310","cookieCode":"FGH8SO9zGaWtwuld2jrurRzwmZKeXABx","dfp":"EKLLyLS1K7tqtcuZ6LEPYoUKsxmVNyrAlWNLDi3P-gA-tJMLkTxMuhsRNHEhbk7ntCFCsIpymD57I4AyfPUoWB4D_a_Fe5usS8sfJxP_OJjoun5QjAfgDBBmDLh_m2OeRVN2NnRK0-paM6dCSVKdjFGILKUOJYWT"}

Cookies are successfully generated after one request is completed and written to the local cache. If not emptied, the process to enter the next breakpoint will be different from this one, so for repeatable operation, the operating environment needs to be restored when the breakpoint is debugged again.

Variable a has no value at the first load. When you accidentally go to the next process, a has already been value at this time. After many tests, you can find out the data flow direction and how to restore the operating environment, so as to keep the consistency of the results.

After repeated tests, the basic data flow direction is restored as follows:

(function() {
  ja.prototype = {
    // C:initEc
    initEc: function(a) {
      this.ec.get("RAIL_OkLJUJ", function(a) {
          c.getDfpMoreInfo(function() {

          })
      }, 1)
    },
    // c.getDfpMoreInfo:A
    getDfpMoreInfo: function(a) {
        
    }
  }

  // this.ec.get("RAIL_OkLJUJ":B
  window.evercookie = window.Evercookie = function(a) {
      this.get = function(a, b, c) {

      }
  }
})();

Asynchronously request C this.ec.get("RAIL_OkLJUJ" function, call the c.getDfpMoreInfo function when window.evercookie.get is finished, and call the function core key code when getDfpMoreInfo function is finished.

In addition to the callback between asynchronous requests in general, there are a lot of callback functions in many details. Take the getDfpMoreInfo function as an example, you have to collect so much information to start doing your own thing!

  ja.prototype = {
    getDfpMoreInfo: function(a) {
        var b = this;
        this.moreInfoArray = [];
        b.cfp.get(function(c, d) {
            b.moreInfoArray.push(b.getCanvansCode(c + ""));
            for (var e in d) {
                c = d[e].key;
                var f = d[e].value + "";
                switch (c) {
                case "session_storage":
                    b.moreInfoArray.push(b.getSessionStorage(f));
                    break;
                case "local_storage":
                    b.moreInfoArray.push(b.getLocalStorage(f));
                    break;
                case "indexed_db":
                    b.moreInfoArray.push(b.getIndexedDb(f));
                    break;
                case "open_database":
                    b.moreInfoArray.push(b.getOpenDatabase(f));
                    break;
                case "do_not_track":
                    b.moreInfoArray.push(b.getDoNotTrack(f));
                    break;
                case "ie_plugins":
                    b.moreInfoArray.push(b.getPlugins(f));
                    break;
                case "regular_plugins":
                    b.moreInfoArray.push(b.getPlugins());
                    break;
                case "adblock":
                    b.moreInfoArray.push(b.getAdblock(f));
                    break;
                case "has_lied_languages":
                    b.moreInfoArray.push(b.getHasLiedLanguages(f));
                    break;
                case "has_lied_resolution":
                    b.moreInfoArray.push(b.getHasLiedResolution(f));
                    break;
                case "has_lied_os":
                    b.moreInfoArray.push(b.getHasLiedOs(f));
                    break;
                case "has_lied_browser":
                    b.moreInfoArray.push(b.getHasLiedBrowser(f));
                    break;
                case "touch_support":
                    b.moreInfoArray.push(b.getTouchSupport(f));
                    break;
                case "js_fonts":
                    b.moreInfoArray.push(b.getJsFonts(f))
                }
            }
            "function" == typeof a && a()
        })
      }
  }

  function aa(a) {
      if (!(this instanceof aa))
          return new aa(a);
      this.options = this.extend(a, {
          detectScreenOrientation: !0,
          swfPath: "flash/compiled/FontList.swf",
          sortPluginsFor: [/palemoon/i],
          swfContainerId: "fingerprintjs2",
          userDefinedFonts: []
      });
      this.nativeForEach = Array.prototype.forEach;
      this.nativeMap = Array.prototype.map
  }

  aa.prototype = {
    get: function(a) {
        var b = []
          , b = this.userAgentKey(b)
          , b = this.languageKey(b)
          , b = this.colorDepthKey(b)
          , b = this.pixelRatioKey(b)
          , b = this.screenResolutionKey(b)
          , b = this.availableScreenResolutionKey(b)
          , b = this.timezoneOffsetKey(b)
          , b = this.sessionStorageKey(b)
          , b = this.localStorageKey(b)
          , b = this.indexedDbKey(b)
          , b = this.addBehaviorKey(b)
          , b = this.openDatabaseKey(b)
          , b = this.cpuClassKey(b)
          , b = this.platformKey(b)
          , b = this.doNotTrackKey(b)
          , b = this.pluginsKey(b)
          , b = this.canvasKey(b)
          , b = this.webglKey(b)
          , b = this.adBlockKey(b)
          , b = this.hasLiedLanguagesKey(b)
          , b = this.hasLiedResolutionKey(b)
          , b = this.hasLiedOsKey(b)
          , b = this.hasLiedBrowserKey(b)
          , b = this.touchSupportKey(b)
          , c = this;
        this.fontsKey(b, function(b) {
            var d = [];
            c.each(b, function(a) {
                var b = a.value;
                "undefined" !== typeof a.value.join && (b = a.value.join(";"));
                d.push(b)
            });
            var f = c.x64hash128(d.join("~~~"), 31);
            return a(f, b)
        })
    }
  }

The getDfpMoreInfo function first runs the b.cfp.get function, which is found to be a aa.prototype.get function by searching the root cause. This function not only obtains simple browser information, but also involves font encryption: var f = c.x64hash128 (d.join("~"), 31);

However, to continue analyzing the getDfpMoreInfo function, you need to first understand what the callback function parameters are in b.cfp.get(function(c, d) {and getDfpMoreInfo: function(a) {, so you must analyze them from beginning to end, which is also a trap for asynchronous requests.

It seems that the request logic is complete at once, but it is really difficult to maintain. To analyze C, you must first analyze B. Unexpectedly, B depends on A.

So the next step is to grope through the breakthrough initEc function, find the initial function A, and then debug the breakpoint to see how to call back the initEc function step by step, that is, to transform the asynchronous request into a synchronous request.

Slowly turn synchronization step by step

(function() {
  ja.prototype = {
    // C:initEc
    initEc: function(a) {
      this.ec.get("RAIL_OkLJUJ", function(a) {
          c.getDfpMoreInfo(function() {

          })
      }, 1)
    },
    // c.getDfpMoreInfo:A
    getDfpMoreInfo: function(a) {
        
    }
  }

  // this.ec.get("RAIL_OkLJUJ":B
  window.evercookie = window.Evercookie = function(a) {
      this.get = function(a, b, c) {

      }
  }
})();

Continuing along this path to analyze the call process for getDfpMoreInfo adds more call details, so now it's complete with the following code:

The convention is that the synchronous call order marked as alphabet A,B,C corresponds to the asynchronous request C

To show hierarchy, the second tier C,B,A can be represented as AC,AB,AA, and so on.

The ultimate skill forms a call tree through hierarchical invocation relationships, with roughly the following results:

.
├── C
│   ├── CC
│   ├── CB
│   └── CA
├── B
│   ├── BC
│   ├── BB
│   └── BA
└── A
    ├── AC
    ├── AB
    └── AA

Call Stack Tree to browse the sequence of asynchronous function calls at a glance. Design it following the stack structure of the data structure, and then find that C also depends on B,B depends on A, execution returns to the previous level to continue execution. This design feels great. Praise yourself!

(function() {
  ja.prototype = {
    // C
    initEc: function(a) {
      this.ec.get("RAIL_OkLJUJ", function(a) {
          c.getDfpMoreInfo(function() {

          })
      }, 1)
    },
    // A
    getDfpMoreInfo: function(a) {
        
    }
  }

  // AC
  aa.prototype = {
    get: function(a) {
        
    }
  }

  // B
  window.evercookie = window.Evercookie = function(a) {
      this.get = function(a, b, c) {

      }
  }

})();

However, in the actual analysis process, it is found that peer requests are not always in the form of three-level ABC. Here, according to the actual situation, according to this idea, we can analyze and verify conjectures by ourselves and debugging with breakpoints.

  • step 1: Get basic information and callback after getting encrypted fonts
aa.prototype = {
    get: function(a) {
        var b = []
          , b = this.userAgentKey(b)
          , b = this.languageKey(b)
          , b = this.colorDepthKey(b)
          , b = this.pixelRatioKey(b)
          , b = this.screenResolutionKey(b)
          , b = this.availableScreenResolutionKey(b)
          , b = this.timezoneOffsetKey(b)
          , b = this.sessionStorageKey(b)
          , b = this.localStorageKey(b)
          , b = this.indexedDbKey(b)
          , b = this.addBehaviorKey(b)
          , b = this.openDatabaseKey(b)
          , b = this.cpuClassKey(b)
          , b = this.platformKey(b)
          , b = this.doNotTrackKey(b)
          , b = this.pluginsKey(b)
          , b = this.canvasKey(b)
          , b = this.webglKey(b)
          , b = this.adBlockKey(b)
          , b = this.hasLiedLanguagesKey(b)
          , b = this.hasLiedResolutionKey(b)
          , b = this.hasLiedOsKey(b)
          , b = this.hasLiedBrowserKey(b)
          , b = this.touchSupportKey(b)
          , c = this;
        this.fontsKey(b, function(b) {
            var d = [];
            c.each(b, function(a) {
                var b = a.value;
                "undefined" !== typeof a.value.join && (b = a.value.join(";"));
                d.push(b)
            });
            var f = c.x64hash128(d.join("~~~"), 31);
            return a(f, b)
        })
    }
  }

Var f = c.x64hash128 (d.join ("~~"), 31); involves a series of cryptographic operations that are left unattended for the moment. The most important sentence below, return a(f, b), executes a callback function to continue with the next logic.

  • step 2: Get more information from the browser and call back at the end
  ja.prototype = {
    getDfpMoreInfo: function(a) {
        var b = this;
        this.moreInfoArray = [];
        b.cfp.get(function(c, d) {
            b.moreInfoArray.push(b.getCanvansCode(c + ""));
            for (var e in d) {
                c = d[e].key;
                var f = d[e].value + "";
                switch (c) {
                case "session_storage":
                    b.moreInfoArray.push(b.getSessionStorage(f));
                    break;
                case "local_storage":
                    b.moreInfoArray.push(b.getLocalStorage(f));
                    break;
                case "indexed_db":
                    b.moreInfoArray.push(b.getIndexedDb(f));
                    break;
                case "open_database":
                    b.moreInfoArray.push(b.getOpenDatabase(f));
                    break;
                case "do_not_track":
                    b.moreInfoArray.push(b.getDoNotTrack(f));
                    break;
                case "ie_plugins":
                    b.moreInfoArray.push(b.getPlugins(f));
                    break;
                case "regular_plugins":
                    b.moreInfoArray.push(b.getPlugins());
                    break;
                case "adblock":
                    b.moreInfoArray.push(b.getAdblock(f));
                    break;
                case "has_lied_languages":
                    b.moreInfoArray.push(b.getHasLiedLanguages(f));
                    break;
                case "has_lied_resolution":
                    b.moreInfoArray.push(b.getHasLiedResolution(f));
                    break;
                case "has_lied_os":
                    b.moreInfoArray.push(b.getHasLiedOs(f));
                    break;
                case "has_lied_browser":
                    b.moreInfoArray.push(b.getHasLiedBrowser(f));
                    break;
                case "touch_support":
                    b.moreInfoArray.push(b.getTouchSupport(f));
                    break;
                case "js_fonts":
                    b.moreInfoArray.push(b.getJsFonts(f))
                }
            }
            "function" == typeof a && a()
        })
      }
  }

The b.cfp.get(function(c, d) {} function is the aa.prototype.get() function from the previous step.

  • step 3: Get the browser machine code before packaging parameters
ja.prototype = {
    getMachineCode: function() {
          return [this.getUUID(), this.getCookieCode(), this.getUserAgent(), this.getScrHeight(), this.getScrWidth(), this.getScrAvailHeight(), this.getScrAvailWidth(), this.md5ScrColorDepth(), this.getScrDeviceXDPI(), this.getAppCodeName(), this.getAppName(), this.getJavaEnabled(), this.getMimeTypes(), this.getPlatform(), this.getAppMinorVersion(), this.getBrowserLanguage(), this.getCookieEnabled(), this.getCpuClass(), this.getOnLine(), this.getSystemLanguage(), this.getUserLanguage(), this.getTimeZone(), this.getFlashVersion(), this.getHistoryList(), this.getCustId(), this.getSendPlatform()]
      }
  }

This function comes from c.getDfpMoreInfo in the initEc function (g = c.getpackStr(b) in the callback function), this.getMachineCode() has a deep heart and a hidden secret.

  • step 4: Combine more information and reorder before packing parameters
ja.prototype = {
    getpackStr: function(a) {
          var b = []
            , b = []
            , b = this.getMachineCode()
            , b = b.concat(this.moreInfoArray);
          null != a && void 0 != a && "" != a && 32 == a.length && b.push(new l("cookieCode",a));
          b.sort(function(a, b) {
              var c, d;
              if ("object" === typeof a && "object" === typeof b && a && b)
                  return c = a.key,
                  d = b.key,
                  c === d ? 0 : typeof c === typeof d ? c < d ? -1 : 1 : typeof c < typeof d ? -1 : 1;
              throw "error";
          });
          return b
      }
  }

This function also comes from c.getDfpMoreInfo in the initEc function (g = c.getpackStr(b) in the callback function), which is worth studying!

  • step 5: Reclassify browser information and encrypt generate request parameters
ja.prototype = {
    initEc: function(a) {
        var b = ""
          , c = this
          , d = void 0 != a && void 0 != a.localAddr ? a.localAddr : "";
        c.checkWapOrWeb();
        this.ec.get("RAIL_OkLJUJ", function(a) {
            b = a;
            c.getDfpMoreInfo(function() {
                if (!(9E5 < F("RAIL_EXPIRATION") - (new Date).getTime() & null != F("RAIL_DEVICEID") & void 0 != F("RAIL_DEVICEID") & !c.NeedUpdate())) {
                    for (var a = "", e = "", g = c.getpackStr(b), m = [], q = [], t = [], k = [], n = 0; n < g.length; n++)
                        "new" != g[n].value && -1 == Fb.indexOf(g[n].key) && (-1 != Gb.indexOf(g[n].key) ? q.push(g[n]) : -1 != Ib.indexOf(g[n].key) ? t.push(g[n]) : -1 != Hb.indexOf(g[n].key) ? k.push(g[n]) : m.push(g[n]));
                    g = "";
                    for (n = 0; n < q.length; n++)
                        g = g + q[n].key.charAt(0) + q[n].value;
                    q = "";
                    for (n = 0; n < k.length; n++)
                        q = 0 == n ? q + k[n].value : q + "x" + k[n].value;
                    k = "";
                    for (n = 0; n < t.length; n++)
                        k = 0 == n ? k + t[n].value : k + "x" + t[n].value;
                    m.push(new l("storeDb",g));
                    m.push(new l("srcScreenSize",q));
                    m.push(new l("scrAvailSize",k));
                    "" != d && m.push(new l("localCode",pb(d)));
                    e = c.hashAlg(m, a, e);
                    a = e.key;
                    e = e.value;
                    a += "\x26timestamp\x3d" + (new Date).getTime();
                    $a.getJSON("https://kyfw.12306.cn/otn/HttpZF/logdevice" + ("?algID\x3drblubbXDx3\x26hashCode\x3d" + e + a), null, function(a) {
                        var b = JSON.parse(a);
                        void 0 != lb && lb.postMessage(a, r.parent);
                        for (var d in b)
                            "dfp" == d ? F("RAIL_DEVICEID") != b[d] && (W("RAIL_DEVICEID", b[d], 1E3),
                            c.deviceEc.set("RAIL_DEVICEID", b[d])) : "exp" == d ? W("RAIL_EXPIRATION", b[d], 1E3) : "cookieCode" == d && (c.ec.set("RAIL_OkLJUJ", b[d]),
                            W("RAIL_OkLJUJ", "", 0))
                    })
                }
            })
        }, 1)
    }
  }

e = c.hashAlg(m, a, e); the encryption process is more complex than the encryption of basic information, but overall it is not very difficult, mainly involving string inversion, piecewise assembly, hash algorithm, base64 encoding, and so on.

Skip details when you encounter a bottleneck

After reading through the full text and repeatedly validating guesses with breakpoint debugging, we found that we were requesting https://kyfw.12306.cn/otn/HttpZF/logdevice When the parameters are really exhaustive, try your best!

Overall, access to browser information and also involves various patches compatible with IE browsers. It is not enough to encrypt the original information with a unique encryption algorithm for a long time. There are also logical judgments about whether to forge browser parameters. It is really capitalized!

It's easy to get information about your browser by setting it up in conjunction with code that identifies whether or not you're lying, such as the following common settings for browser user code:

  aa.prototype = {
    userAgentKey: function(a) {
        this.options.excludeUserAgent || a.push({
            key: "user_agent",
            value: this.getUserAgent()
        });
        return a
    },
    getUserAgent: function() {
        var a = g.userAgent;
        return a = a.replace(/\&|\+|\?|\%|\#|\/|\=/g, "")
    }
  }

  ja.prototype = {
    getUserAgent: function() {
        var a = g.userAgent
          , a = a.replace(/\&|\+/g, "");
        return new l("userAgent",a.toString())
    }
  }

Different objects have different logic to handle the same user agent. There are many examples like the above. They are simple and full of traps. You don't even know the source code. After reading, will you still spit out 12306 technical garbage?

The next three encryption algorithms are the initial basic information encryption, the font information encryption and the final classification information encryption.

  function ba(a) {
      for (var b = [], c = (1 << ca) - 1, d = 0; d < a.length * ca; d += ca)
          b[d >> 5] |= (a.charCodeAt(d / ca) & c) << d % 32;
      a = a.length * ca;
      b[a >> 5] |= 128 << a % 32;
      b[(a + 64 >>> 9 << 4) + 14] = a;
      a = 1732584193;
      for (var c = -271733879, d = -1732584194, e = 271733878, f = 0; f < b.length; f += 16) {
          var h = a
            , p = c
            , g = d
            , m = e;
          a = D(a, c, d, e, b[f + 0], 7, -680876936);
          e = D(e, a, c, d, b[f + 1], 12, -389564586);
          d = D(d, e, a, c, b[f + 2], 17, 606105819);
          c = D(c, d, e, a, b[f + 3], 22, -1044525330);
          a = D(a, c, d, e, b[f + 4], 7, -176418897);
          e = D(e, a, c, d, b[f + 5], 12, 1200080426);
          d = D(d, e, a, c, b[f + 6], 17, -1473231341);
          c = D(c, d, e, a, b[f + 7], 22, -45705983);
          a = D(a, c, d, e, b[f + 8], 7, 1770035416);
          e = D(e, a, c, d, b[f + 9], 12, -1958414417);
          d = D(d, e, a, c, b[f + 10], 17, -42063);
          c = D(c, d, e, a, b[f + 11], 22, -1990404162);
          a = D(a, c, d, e, b[f + 12], 7, 1804603682);
          e = D(e, a, c, d, b[f + 13], 12, -40341101);
          d = D(d, e, a, c, b[f + 14], 17, -1502002290);
          c = D(c, d, e, a, b[f + 15], 22, 1236535329);
          a = C(a, c, d, e, b[f + 1], 5, -165796510);
          e = C(e, a, c, d, b[f + 6], 9, -1069501632);
          d = C(d, e, a, c, b[f + 11], 14, 643717713);
          c = C(c, d, e, a, b[f + 0], 20, -373897302);
          a = C(a, c, d, e, b[f + 5], 5, -701558691);
          e = C(e, a, c, d, b[f + 10], 9, 38016083);
          d = C(d, e, a, c, b[f + 15], 14, -660478335);
          c = C(c, d, e, a, b[f + 4], 20, -405537848);
          a = C(a, c, d, e, b[f + 9], 5, 568446438);
          e = C(e, a, c, d, b[f + 14], 9, -1019803690);
          d = C(d, e, a, c, b[f + 3], 14, -187363961);
          c = C(c, d, e, a, b[f + 8], 20, 1163531501);
          a = C(a, c, d, e, b[f + 13], 5, -1444681467);
          e = C(e, a, c, d, b[f + 2], 9, -51403784);
          d = C(d, e, a, c, b[f + 7], 14, 1735328473);
          c = C(c, d, e, a, b[f + 12], 20, -1926607734);
          a = A(c ^ d ^ e, a, c, b[f + 5], 4, -378558);
          e = A(a ^ c ^ d, e, a, b[f + 8], 11, -2022574463);
          d = A(e ^ a ^ c, d, e, b[f + 11], 16, 1839030562);
          c = A(d ^ e ^ a, c, d, b[f + 14], 23, -35309556);
          a = A(c ^ d ^ e, a, c, b[f + 1], 4, -1530992060);
          e = A(a ^ c ^ d, e, a, b[f + 4], 11, 1272893353);
          d = A(e ^ a ^ c, d, e, b[f + 7], 16, -155497632);
          c = A(d ^ e ^ a, c, d, b[f + 10], 23, -1094730640);
          a = A(c ^ d ^ e, a, c, b[f + 13], 4, 681279174);
          e = A(a ^ c ^ d, e, a, b[f + 0], 11, -358537222);
          d = A(e ^ a ^ c, d, e, b[f + 3], 16, -722521979);
          c = A(d ^ e ^ a, c, d, b[f + 6], 23, 76029189);
          a = A(c ^ d ^ e, a, c, b[f + 9], 4, -640364487);
          e = A(a ^ c ^ d, e, a, b[f + 12], 11, -421815835);
          d = A(e ^ a ^ c, d, e, b[f + 15], 16, 530742520);
          c = A(d ^ e ^ a, c, d, b[f + 2], 23, -995338651);
          a = E(a, c, d, e, b[f + 0], 6, -198630844);
          e = E(e, a, c, d, b[f + 7], 10, 1126891415);
          d = E(d, e, a, c, b[f + 14], 15, -1416354905);
          c = E(c, d, e, a, b[f + 5], 21, -57434055);
          a = E(a, c, d, e, b[f + 12], 6, 1700485571);
          e = E(e, a, c, d, b[f + 3], 10, -1894986606);
          d = E(d, e, a, c, b[f + 10], 15, -1051523);
          c = E(c, d, e, a, b[f + 1], 21, -2054922799);
          a = E(a, c, d, e, b[f + 8], 6, 1873313359);
          e = E(e, a, c, d, b[f + 15], 10, -30611744);
          d = E(d, e, a, c, b[f + 6], 15, -1560198380);
          c = E(c, d, e, a, b[f + 13], 21, 1309151649);
          a = E(a, c, d, e, b[f + 4], 6, -145523070);
          e = E(e, a, c, d, b[f + 11], 10, -1120210379);
          d = E(d, e, a, c, b[f + 2], 15, 718787259);
          c = E(c, d, e, a, b[f + 9], 21, -343485551);
          a = N(a, h);
          c = N(c, p);
          d = N(d, g);
          e = N(e, m)
      }
      b = [a, c, d, e];
      a = rb ? "0123456789ABCDEF" : "0123456789abcdef";
      c = "";
      for (d = 0; d < 4 * b.length; d++)
          c += a.charAt(b[d >> 2] >> d % 4 * 8 + 4 & 15) + a.charAt(b[d >> 2] >> d % 4 * 8 & 15);
      return c
  }

  aa.prototype = {
    x64hash128: function(a, b) {
          a = a || "";
          b = b || 0;
          for (var c = a.length % 16, d = a.length - c, e = [0, b], f = [0, b], h, p, g = [2277735313, 289559509], m = [1291169091, 658871167], l = 0; l < d; l += 16)
              h = [a.charCodeAt(l + 4) & 255 | (a.charCodeAt(l + 5) & 255) << 8 | (a.charCodeAt(l + 6) & 255) << 16 | (a.charCodeAt(l + 7) & 255) << 24, a.charCodeAt(l) & 255 | (a.charCodeAt(l + 1) & 255) << 8 | (a.charCodeAt(l + 2) & 255) << 16 | (a.charCodeAt(l + 3) & 255) << 24],
              p = [a.charCodeAt(l + 12) & 255 | (a.charCodeAt(l + 13) & 255) << 8 | (a.charCodeAt(l + 14) & 255) << 16 | (a.charCodeAt(l + 15) & 255) << 24, a.charCodeAt(l + 8) & 255 | (a.charCodeAt(l + 9) & 255) << 8 | (a.charCodeAt(l + 10) & 255) << 16 | (a.charCodeAt(l + 11) & 255) << 24],
              h = this.x64Multiply(h, g),
              h = this.x64Rotl(h, 31),
              h = this.x64Multiply(h, m),
              e = this.x64Xor(e, h),
              e = this.x64Rotl(e, 27),
              e = this.x64Add(e, f),
              e = this.x64Add(this.x64Multiply(e, [0, 5]), [0, 1390208809]),
              p = this.x64Multiply(p, m),
              p = this.x64Rotl(p, 33),
              p = this.x64Multiply(p, g),
              f = this.x64Xor(f, p),
              f = this.x64Rotl(f, 31),
              f = this.x64Add(f, e),
              f = this.x64Add(this.x64Multiply(f, [0, 5]), [0, 944331445]);
          h = [0, 0];
          p = [0, 0];
          switch (c) {
          case 15:
              p = this.x64Xor(p, this.x64LeftShift([0, a.charCodeAt(l + 14)], 48));
          case 14:
              p = this.x64Xor(p, this.x64LeftShift([0, a.charCodeAt(l + 13)], 40));
          case 13:
              p = this.x64Xor(p, this.x64LeftShift([0, a.charCodeAt(l + 12)], 32));
          case 12:
              p = this.x64Xor(p, this.x64LeftShift([0, a.charCodeAt(l + 11)], 24));
          case 11:
              p = this.x64Xor(p, this.x64LeftShift([0, a.charCodeAt(l + 10)], 16));
          case 10:
              p = this.x64Xor(p, this.x64LeftShift([0, a.charCodeAt(l + 9)], 8));
          case 9:
              p = this.x64Xor(p, [0, a.charCodeAt(l + 8)]),
              p = this.x64Multiply(p, m),
              p = this.x64Rotl(p, 33),
              p = this.x64Multiply(p, g),
              f = this.x64Xor(f, p);
          case 8:
              h = this.x64Xor(h, this.x64LeftShift([0, a.charCodeAt(l + 7)], 56));
          case 7:
              h = this.x64Xor(h, this.x64LeftShift([0, a.charCodeAt(l + 6)], 48));
          case 6:
              h = this.x64Xor(h, this.x64LeftShift([0, a.charCodeAt(l + 5)], 40));
          case 5:
              h = this.x64Xor(h, this.x64LeftShift([0, a.charCodeAt(l + 4)], 32));
          case 4:
              h = this.x64Xor(h, this.x64LeftShift([0, a.charCodeAt(l + 3)], 24));
          case 3:
              h = this.x64Xor(h, this.x64LeftShift([0, a.charCodeAt(l + 2)], 16));
          case 2:
              h = this.x64Xor(h, this.x64LeftShift([0, a.charCodeAt(l + 1)], 8));
          case 1:
              h = this.x64Xor(h, [0, a.charCodeAt(l)]),
              h = this.x64Multiply(h, g),
              h = this.x64Rotl(h, 31),
              h = this.x64Multiply(h, m),
              e = this.x64Xor(e, h)
          }
          e = this.x64Xor(e, [0, a.length]);
          f = this.x64Xor(f, [0, a.length]);
          e = this.x64Add(e, f);
          f = this.x64Add(f, e);
          e = this.x64Fmix(e);
          f = this.x64Fmix(f);
          e = this.x64Add(e, f);
          f = this.x64Add(f, e);
          return ("00000000" + (e[0] >>> 0).toString(16)).slice(-8) + ("00000000" + (e[1] >>> 0).toString(16)).slice(-8) + ("00000000" + (f[0] >>> 0).toString(16)).slice(-8) + ("00000000" + (f[1] >>> 0).toString(16)).slice(-8)
      }
  }

  ja.prototype = {
    hashAlg: function(a, b, c) {
          a.sort(function(a, b) {
              var c, d;
              if ("object" === typeof a && "object" === typeof b && a && b)
                  return c = a.key,
                  d = b.key,
                  c === d ? 0 : typeof c === typeof d ? c < d ? -1 : 1 : typeof c < typeof d ? -1 : 1;
              throw "error";
          });
          for (var d = 0; d < a.length; d++) {
              var e = a[d].key.replace(RegExp("%", "gm"), "")
                , f = ""
                , f = "string" == typeof a[d].value ? a[d].value.replace(RegExp("%", "gm"), "") : a[d].value;
              "" !== f && (c += e + f,
              b += "\x26" + (void 0 == hb[e] ? e : hb[e]) + "\x3d" + f)
          }
          a = c;
          c = "";
          d = a.length;
          for (e = 0; e < d; e++)
              f = a.charAt(e).charCodeAt(0),
              c = 127 === f ? c + String.fromCharCode(0) : c + String.fromCharCode(f + 1);
          a = c;
          c = a.length;
          d = 0 == c % 3 ? parseInt(c / 3) : parseInt(c / 3) + 1;
          3 > c || (e = a.substring(0, 1 * d),
          f = a.substring(1 * d, 2 * d),
          a = a.substring(2 * d, c) + e + f);
          a = Qa(a);
          a = Qa(a);
          c = R.SHA256(a).toString(R.enc.Base64);
          c = R.SHA256(c).toString(R.enc.Base64);
          return new l(b,c)
      }
  }

These three algorithms seem scary. In fact, as long as you patiently debug them, they can be slowly restored, but only in the order of letters. Who can let us see the source code without encryption? It's hard for intelligent developers to just replace the names of variables. What's more, this part and business logic are of little concern, let's skip it for now.

Algorithmic Replication

The algorithm uses the object-oriented programming style of closure design as a whole, implements the encryption logic of the original object based on the prototype chain characteristics, adds special methods to temporarily modify browser-related information, and finally mounts the custom object chromeHelper directly on the window property for easy external invocation.

/**
 * chrome The simplified version of the browser mainly restores the basic process of loading RAIL_OkLJUJ and RAIL_DEVICEID for the first time. Updating RAIL_DEVICEID so that it needs to be encrypted together with RAIL_OkLJUJ only adds one more cookieCode parameter, there is nothing special about it, and it will not be described in detail.
 * @author: snowdreams1006
 * @wechat: snowdreams1006(Snow Dream Technology Post Station)
 */
(function(window) {

  /**
   * Default empty constructor
   */
  function chromeHelper() {

  }

  /**
   * Set up user agent, detect by: navigator.userAgent
   */
  chromeHelper.setUserAgent = function(userAgent) {
    if (!userAgent) {
      userAgent = "Mozilla5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit537.36 (KHTML, like Gecko) Chrome80.0.3987.87 Safari537.36";
    }
    Object.defineProperty(navigator, "userAgent", {
      value: userAgent,
      writable: false
    });
  }
   
  /**
   * Inheritance of Object-Oriented Programming Based on Prototype Chain
   */
  chromeHelper.prototype = {
    /**
     * Obtain information about the initialization browser device from e = c.hashAlg(k, a, e) in initEc;
     */
    encryptedFingerPrintInfo: function() {
      // Get categorized browser fingerprint information
      classifiedBrowserFingerPrintInfoArr = this.getClassifiedBrowserFingerPrintInfo();
      encryptedFingerPrintInfoMap = this.hashAlg(classifiedBrowserFingerPrintInfoArr, "", "");

      return encryptedFingerPrintInfoMap;
    }
  }

  /**
   * Mount directly on the global variable window object for easy direct invocation.
   */
  window.chromeHelper = chromeHelper;
})(window);

step 1: Get basic information

chromeHelper.prototype = {
    /**
     * Get basic browser information from b.cfp.get and aa in getDfpMoreInfo
     */
    getBasicInfoArr: function() {
      // Basic information, returns void 0 if the data is invalid, undefined
      var basicInfoArr = [];

      // user agent 
      basicInfoArr.push(this.getUserAgentKeyAndValue(1));
      // language
      basicInfoArr.push(this.getLanguageKeyAndValue(1));
      // color depth 
      basicInfoArr.push(this.getColorDepthKeyAndValue(1));
      // Pixel scale
      basicInfoArr.push(this.getPixelRatioKeyAndValue(1));
      // Screen resolution
      basicInfoArr.push(this.getScreenResolutionKeyAndValue(1));
      // Available Screen Resolution
      basicInfoArr.push(this.getAvailableScreenResolutionKeyAndValue(1));
      // Time zone offset
      basicInfoArr.push(this.getTimezoneOffsetKeyAndValue(1));
      // Session Storage
      basicInfoArr.push(this.getSessionStorageKeyAndValue(1));
      // Local Storage
      basicInfoArr.push(this.getLocalStorageKeyAndValue(1));
      // IndexedDb Storage
      basicInfoArr.push(this.getIndexedDbKeyAndValue(1));
      // websql storage
      basicInfoArr.push(this.getOpenDatabaseKeyAndValue(1));
      // cpu type
      basicInfoArr.push(this.getCpuClassKeyAndValue(1));
      // Platform type
      basicInfoArr.push(this.getPlatformKeyAndValue(1));
      // Backtracking
      basicInfoArr.push(this.getDoNotTrackKeyAndValue(1));
      // Plug-in unit
      basicInfoArr.push(this.getPluginsKeyAndValue(1));
      // TODO Canvas
      basicInfoArr.push(this.getCanvasKeyAndValue(0));
      // webgl canvas
      basicInfoArr.push(this.getWebglKeyAndValue(1));
      // adBlock Advertising Interception
      basicInfoArr.push(this.getAdBlockKeyAndValue(1));
      // Lying Language
      basicInfoArr.push(this.getHasLiedLanguagesKeyAndValue(1));
      // Lying Resolution
      basicInfoArr.push(this.getHasLiedResolutionKeyAndValue(1));
      // Lying Operating System
      basicInfoArr.push(this.getHasLiedOsKeyAndValue(1));
      // Lying Browser
      basicInfoArr.push(this.getHasLiedBrowserKeyAndValue(1));
      // Touch support
      basicInfoArr.push(this.getTouchSupportKeyAndValue(1));
      // Typeface
      basicInfoArr.push(this.getFontsKeyAndValue(1));

      return basicInfoArr;
    }
}

step 2: Encrypt basic information

chromeHelper.prototype = {
    /**
     * Encrypt basic browser information, from aa get var f = c.x64hash128 (d.join("~~"), 31);
     */
    encryptedBasicInfoArr: function(basicInfoArr) {
      // Remove invalid undefined data and process array objects
      concatArr = [];
      for (i = 0; i < basicInfoArr.length; i++) {
        var basicInfoValue = basicInfoArr[i].value;

        // Values can also correspond to arrays, which are also converted to strings.
        if ("undefined" !== typeof basicInfoValue) {
          if (Object.prototype.toString.call(basicInfoValue) === '[object Array]') {
            basicInfoValue = basicInfoValue.join(";");
          }
          concatArr.push(basicInfoValue);
        }
      }

      // Encrypt basic information
      return this.x64hash128(concatArr.join("~~~"), 31);
    }
}

step 3: Get more information

chromeHelper.prototype = {
    /**
     * Get more information about your browser, from b = b.concat(this.moreInfoArray) in getpackStr;
     */
    getDfpMoreInfo: function(basicInfoArr, encryptedStr) {
      // More information
      var moreInfoArr = [];

      // Add Canvas Information
      moreInfoArr.push(this.getCanvansCode(encryptedStr + ""));

      // Add Browser Local Storage Accumulated and Language Plug-in Class Information
      for (var index in basicInfoArr) {
        var name = basicInfoArr[index].key;
        var value = basicInfoArr[index].value + "";

        switch (name) {
          case "session_storage":
            moreInfoArr.push(this.getSessionStorageCode(value));
            break;
          case "local_storage":
            moreInfoArr.push(this.getLocalStorageCode(value));
            break;
          case "indexed_db":
            moreInfoArr.push(this.getIndexedDbCode(value));
            break;
          case "open_database":
            moreInfoArr.push(this.getOpenDatabaseCode(value));
            break;
          case "do_not_track":
            moreInfoArr.push(this.getDoNotTrackCode(value));
            break;
          case "regular_plugins":
            moreInfoArr.push(this.getPluginsCode());
            break;
          case "adblock":
            moreInfoArr.push(this.getAdblockCode(value));
            break;
          case "has_lied_languages":
            moreInfoArr.push(this.getHasLiedLanguagesCode(value));
            break;
          case "has_lied_resolution":
            moreInfoArr.push(this.getHasLiedResolutionCode(value));
            break;
          case "has_lied_os":
            moreInfoArr.push(this.getHasLiedOsCode(value));
            break;
          case "has_lied_browser":
            moreInfoArr.push(this.getHasLiedBrowserCode(value));
            break;
          case "touch_support":
            moreInfoArr.push(this.getTouchSupportCode(value));
            break;
          case "js_fonts":
            moreInfoArr.push(this.getJsFontsCode(value));
            break;
        }
      }

      return moreInfoArr;
    }
}

step 4: Get machine information

chromeHelper.prototype = {
    /**
     * Machine code information from this.getMachineCode() in getpackStr
     */
    getMachineCode: function() {
      // Machine code information, returns new if data is invalid
      var machineCodeArr = [];

      // uuid code
      machineCodeArr.push(this.getUUIDCode());
      // cookie code
      machineCodeArr.push(this.getCookieCode());
      // User Agent Code
      machineCodeArr.push(this.getUserAgentCode(1));
      // Source height code
      machineCodeArr.push(this.getScrHeightCode(1));
      // Source width code
      machineCodeArr.push(this.getScrWidthCode(1));
      // Available height codes
      machineCodeArr.push(this.getScrAvailHeightCode(1));
      // Available width codes
      machineCodeArr.push(this.getScrAvailWidthCode(1));
      // Color Depth Code
      machineCodeArr.push(this.getMd5ScrColorDepthCode(1));
      // Source Device XDPI Code
      machineCodeArr.push(this.getScrDeviceXDPICode());
      // app code name code
      machineCodeArr.push(this.getAppCodeNameCode(1));
      // app name code
      machineCodeArr.push(this.getAppNameCode(1));
      // Is Java code enabled
      machineCodeArr.push(this.getJavaEnabledCode(1));
      // Media Type Code
      machineCodeArr.push(this.getMimeTypesCode(1));
      // Platform Code
      machineCodeArr.push(this.getPlatformCode(1));
      // app minor version code
      machineCodeArr.push(this.getAppMinorVersionCode());
      // Browser Language Code
      machineCodeArr.push(this.getBrowserLanguageCode(1));
      // Is Cookie Code Enabled
      machineCodeArr.push(this.getCookieEnabledCode(1));
      // Cpu Type Code
      machineCodeArr.push(this.getCpuClassCode());
      // Is Online Code
      machineCodeArr.push(this.getOnLineCode(1));
      // System Language Code
      machineCodeArr.push(this.getSystemLanguageCode());
      // User Language Code
      machineCodeArr.push(this.getUserLanguageCode());
      // Time zone offset code
      machineCodeArr.push(this.getTimeZoneCode(1));
      // flash version code
      machineCodeArr.push(this.getFlashVersionCode(1));
      // History Bar Code
      machineCodeArr.push(this.getHistoryListCode(1));
      // Custom ID Code
      machineCodeArr.push(this.getCustIdCode());
      // Send Platform Code
      machineCodeArr.push(this.getSendPlatformCode());

      return machineCodeArr;
    }
}

step 5: Synthetic fingerprint information

chromeHelper.prototype = {
    /**
     * Get the browser's original fingerprint information, from l = c.getpackStr(b) in initEc
     */
    getOriginBrowserFingerPrintInfo: function() {
      // Browser Fingerprint Information
      var originBrowserFingerPrintArr = [];

      // Basic information for generating more information
      var basicInfoArr = this.getBasicInfoArr();
      // Essential Information Encryption Summary
      var encryptedStr = this.encryptedBasicInfoArr(basicInfoArr);
      // More information for combining machine code information
      var moreInfoArr = this.getDfpMoreInfo(basicInfoArr, encryptedStr);
      // Machine Code Information
      var machineCodeArr = this.getMachineCode(moreInfoArr);
      // Combine information and reorder
      originBrowserFingerPrintArr = this.concatMachineCodeAndDfpMoreInfo(machineCodeArr, moreInfoArr);

      return originBrowserFingerPrintArr;
    }
}

chromeHelper.prototype = {
    /**
     * Combine machine code and browser information to form the original fingerprint, from getpackStr in getpackStr
     */
    concatMachineCodeAndDfpMoreInfo: function(machineCodeArr, moreInfoArr) {
      // Machine Code Merge More Information
      var tempArr = machineCodeArr.concat(moreInfoArr);
      // Reorder
      tempArr.sort(function(a, b) {
        var c, d;
        if ("object" === typeof a && "object" === typeof b && a && b)
          return c = a.key,
            d = b.key,
            c === d ? 0 : typeof c === typeof d ? c < d ? -1 : 1 : typeof c < typeof d ? -1 : 1;
        throw "error";
      });
      return tempArr;
    }
}

step 6: Reclassify fingerprints

chromeHelper.prototype = {
    /**
     * Obtain the browser fingerprint information from k.push(new p("scrAvailSize",h)) in initEc;
     */
    getClassifiedBrowserFingerPrintInfo: function() {
      // Browser Fingerprint Information
      var originBrowserFingerPrintArr = this.getOriginBrowserFingerPrintInfo();

      // Category key name
      var Gb = "appCodeName appMinorVersion appName cpuClass onLine systemLanguage userLanguage historyList hasLiedLanguages hasLiedResolution hasLiedOs hasLiedBrowser".split(" "),
        Hb = ["scrAvailWidth", "scrAvailHeight"],
        Ib = ["scrDeviceXDPI", "scrColorDepth", "scrWidth", "scrHeight"],
        Jb = ["sessionStorage", "localStorage", "indexedDb", "openDatabase"];

      // Local storage class, key name corresponding to Jb
      var storeDbArr = [];
      // Screen real size class, key name corresponding to Ib
      var srcScreenSizeArr = [];
      // Screen Available Size Class, Key Name Corresponds to Hb
      var scrAvailSizeArr = [];
      // Other classes are also categorized browser fingerprint information
      var classifiedBrowserFingerPrintArr = []

      // Extract local storage classes, actual screen size classes, available screen size classes, and other classes
      for (var i = 0; i < originBrowserFingerPrintArr.length; i++) {
        var browserFingerPrint = originBrowserFingerPrintArr[i];
        var name = browserFingerPrint.key;
        var value = browserFingerPrint.value;
        "new" != value && -1 == Gb.indexOf(name) && (-1 != Jb.indexOf(name) ? storeDbArr.push(browserFingerPrint) : -1 != Hb.indexOf(name) ? scrAvailSizeArr.push(browserFingerPrint) : -1 != Ib.indexOf(name) ? srcScreenSizeArr.push(browserFingerPrint) : classifiedBrowserFingerPrintArr.push(browserFingerPrint));
      }

      // Local Storage
      storeDb = "";
      for (i = 0; i < storeDbArr.length; i++) {
        storeDb = storeDb + storeDbArr[i].key.charAt(0) + storeDbArr[i].value;
      }

      // Actual screen size
      srcScreenSize = "";
      for (i = 0; i < srcScreenSizeArr.length; i++) {
        srcScreenSize = 0 == i ? srcScreenSize + srcScreenSizeArr[i].value : srcScreenSize + "x" + srcScreenSizeArr[i].value;
      }

      // Screen Available Sizes
      scrAvailSize = "";
      for (i = 0; i < scrAvailSizeArr.length; i++) {
        scrAvailSize = 0 == i ? scrAvailSize + scrAvailSizeArr[i].value : scrAvailSize + "x" + scrAvailSizeArr[i].value;
      }

      // Add to other classes to form complete fingerprint information
      classifiedBrowserFingerPrintArr.push({
        "key": "storeDb",
        "value": storeDb
      });
      classifiedBrowserFingerPrintArr.push({
        "key": "srcScreenSize",
        "value": srcScreenSize
      });
      classifiedBrowserFingerPrintArr.push({
        "key": "scrAvailSize",
        "value": scrAvailSize
      });

      return classifiedBrowserFingerPrintArr;
    }
}

step 7: Encrypted taxonomy fingerprint

chromeHelper.prototype = {
    /**
     * Obtain information about the initialization browser device from e = c.hashAlg(k, a, e) in initEc;
     */
    encryptedFingerPrintInfo: function() {
      // Get categorized browser fingerprint information
      classifiedBrowserFingerPrintInfoArr = this.getClassifiedBrowserFingerPrintInfo();
      encryptedFingerPrintInfoMap = this.hashAlg(classifiedBrowserFingerPrintInfoArr, "", "");

      return encryptedFingerPrintInfoMap;
    }
}

Not Ended to Continue

In fact, it's almost done with the last two articles, but for the sake of the rigor of the tutorial, we decided to update the last one. The next one will show you how to use it and look back on the whole process. Thank you for reading.

Pay attention to the snow dream technology post station and don't get lost. Start small and wait for the last one!

If you feel this has helped you, please click on the approval or follow the public number "Snow Dream Technology Post" to update the quality articles regularly.

115 original articles were published, 118 were praised, 20,000 visits+
Private letter follow

Keywords: JSON Programming IE network

Added by joshuamd3 on Mon, 17 Feb 2020 07:22:03 +0200