The application of Sekiro in web scraping and JavaScript reverse engineering

First, let’s pick a lucky one and find a login interface. sekiro1-1.png You can see the request parameters as follows. img.png Where the password is obviously encrypted in some way (the source of other parameters in the request is not the focus). So, the main content of this article is:

How to find the encryption process in the frontend How to use Sekiro for remote RPC calls Part One: Finding the encryption process in the frontend Regarding how the password is encrypted in the frontend, the results are listed first:

Obtain salt + public key through the previous API Concatenate salt + plaintext password Encrypt them together using the public key Perform encoding Now, let’s take a closer look. img.png Enter the encrypt() method (the plaintext password here is 222333, the preceding part is the salt). img.png We won’t look at getKey(), which gets the public key. Let’s focus on encrypt() and c().

encrypt() img_1.png Inside, doPublic is used to encrypt using the public key. c() img.png The resulting r is the password we want. Comparing the final password in the request parameters, it indeed matches. img.png Part Two: Using Sekiro for remote RPC calls First of all, the password encryption in this example is not just a single function; it involves many steps and also involves the salt + public key from the previous request, which can be a bit tricky to implement.

Let’s simplify it. Assume the frontend code is utility.getPwdEncrypt(), which returns an encrypted string. First, here is the official documentation of Sekiro. If the target website is using HTTPS, please download the root certificate and install it according to the official documentation.

Process: Inject the JavaScript code into the browser environment (the following is an example from the official website)

function SekiroClient(e){if(this.wsURL=e,this.handlers={},this.socket={},!e)throw new Error("wsURL can not be empty!!");this.webSocketFactory=this.resolveWebSocketFactory(),this.connect()}SekiroClient.prototype.resolveWebSocketFactory=function(){if("object"==typeof window){var e=window.WebSocket?window.WebSocket:window.MozWebSocket;return function(o){function t(o){this.mSocket=new e(o)}return t.prototype.close=function(){this.mSocket.close()},t.prototype.onmessage=function(e){this.mSocket.onmessage=e},t.prototype.onopen=function(e){this.mSocket.onopen=e},t.prototype.onclose=function(e){this.mSocket.onclose=e},t.prototype.send=function(e){this.mSocket.send(e)},new t(o)}}if("object"==typeof weex)try{console.log("test webSocket for weex");var o=weex.requireModule("webSocket");return console.log("find webSocket for weex:"+o),function(e){try{o.close()}catch(e){}return o.WebSocket(e,""),o}}catch(e){console.log(e)}if("object"==typeof WebSocket)return function(o){return new e(o)};throw new Error("the js environment do not support websocket")},SekiroClient.prototype.connect=function(){console.log("sekiro: begin of connect to wsURL: "+this.wsURL);var e=this;try{this.socket=this.webSocketFactory(this.wsURL)}catch(o){return console.log("sekiro: create connection failed,reconnect after 2s:"+o),void setTimeout(function(){e.connect()},2e3)}this.socket.onmessage(function(o){e.handleSekiroRequest(o.data)}),this.socket.onopen(function(e){console.log("sekiro: open a sekiro client connection")}),this.socket.onclose(function(o){console.log("sekiro: disconnected ,reconnection after 2s"),setTimeout(function(){e.connect()},2e3)})},SekiroClient.prototype.handleSekiroRequest=function(e){console.log("receive sekiro request: "+e);var o=JSON.parse(e),t=o.__sekiro_seq__;if(o.action){var n=o.action;if(this.handlers[n]){var s=this.handlers[n],i=this;try{s(o,function(e){try{i.sendSuccess(t,e)}catch(e){i.sendFailed(t,"e:"+e)}},function(e){i.sendFailed(t,e)})}catch(e){console.log("error: "+e),i.sendFailed(t,":"+e)}}else this.sendFailed(t,"no action handler: "+n+" defined")}else this.sendFailed(t,"need request param {action}")},SekiroClient.prototype.sendSuccess=function(e,o){var t;if("string"==typeof o)try{t=JSON.parse(o)}catch(e){(t={}).data=o}else"object"==typeof o?t=o:(t={}).data=o;(Array.isArray(t)||"string"==typeof t)&&(t={data:t,code:0}),t.code?t.code=0:(t.status,t.status=0),t.__sekiro_seq__=e;var n=JSON.stringify(t);console.log("response :"+n),this.socket.send(n)},SekiroClient.prototype.sendFailed=function(e,o){"string"!=typeof o&&(o=JSON.stringify(o));var t={};t.message=o,t.status=-1,t.__sekiro_seq__=e;var n=JSON.stringify(t);console.log("sekiro: response :"+n),this.socket.send(n)},SekiroClient.prototype.registerAction=function(e,o){if("string"!=typeof e)throw new Error("an action must be string");if("function"!=typeof o)throw new Error("a handler must be function");return console.log("sekiro: register action: "+e),this.handlers[e]=o,this};
var client = new SekiroClient("wss://sekiro.iinti.cn:5612/business/register?group=test_web&clientId=" + Math.random());
client.registerAction("testAction", function (request, resolve, reject) {
    resolve("ok");
});

In the resolve section, you can write specific operations. For example, upon receiving a message, you can directly call the frontend code function utility.getPwdEncrypt() to obtain the encrypted parameters and send them to the server.

Benefits of using Sekiro

  1. Without using Sekiro, you would need to write your own server, open a specific port, and establish a WebSocket connection, which can be quite cumbersome and not easily scalable.
  2. Currently, Sekiro’s capabilities are not limited to web scenarios; it can be used in various other scenarios such as:
    • Frida environment
    • Android (Xposed)
    • Browser JavaScript environment
    • iOS environment
    • Python language
    • Go language
  3. Sekiro currently provides a rich set of features, including:
    • Viewing the currently registered groups and mounted queues in the system
    • Flattened parameter-based call forwarding
    • Various access control mechanisms
    • Multiple call forwarding strategies