Life without Hook is not worth jsHook and simulated execution

1. Goals

Boss Li: Fenfei, the app http://91fans.com.cn/post/bankdataone/ that I analyzed last time is not enough to debug, and the js in the webpage can’t use Frida, I still want to hook its functions , What are you doing? And there is an app that can perform RPC to execute the signature. How can I use this js? Can't all the code be changed to js to make the request?

Fen Fei: Boss, you made so many demands all at once, isn’t it obvious that you want us to work overtime? The overtime pay will be doubled this time.

Two, steps

The most simple and easy js Hook - console.log

main.png

Our purpose is to hook the encryptSm4ECB function, and then print out its input parameters and return values.

Set breakpoints at appropriate locations (usually function entry and exit). Then right-click on the breakpoint -> Modify Breakpoint, and then enter the variable to be printed in the pop-up window.

TIP: In fact, this function is a conditional breakpoint, which can trigger a breakpoint when a condition is met, but it can be used to print variable values. After the modification is successful, the breakpoint icon will change color.

rc1.png

Run it, and the input parameters and results we want are all printed out.

TamperMonkey Injection

TamperMonkey is commonly known as the oil monkey, and you can understand that he is the Frida of the browser. However, in this sample, I did not find out how to hook the encryptSm4ECB, but using it to hook the global function can be successful. Brothers who have successfully used the oil monkey Hook to encryptSm4ECB can leave me a message for exchange.

Fiddler plugin injection

Fiddler can use plug-ins to inject js code while capturing packets. This looks more complicated, and I have no idea.

Chrome enables local substitution

Wouldn't it be great if we could directly modify the ArticleDetail.js and add the code for printing variables.

Chrome actually provides this function, which can be regarded as a file-level Hook, that is, when the request ArticleDetail.js is executed , it does not send a request to the server, but directly uses your local replacement js. That way you can change it however you want.

replace1.png

Select Replace on the source code page , and then check Enable local replacement . At this time, the browser will prompt you to give permission, and then select a local directory to store the js to be replaced.

replace2.png

Go back to the network page, select the js you want to replace, right click -> save and overwrite .

Go back to the source code page and find this js file. In fact, it has been saved in the directory we specified at the beginning.

At this time, find the specified function location and write the hook code.

TIP: xxx.js This kind of link replacement is no problem, and the hook code can also be activated. ArticleDetail.js?v=ab4f0b37a4a90050d429 The js in this mode has not been replaced successfully. The reason is unknown, and the successful brothers also leave a message to exchange.

The first step of simulation execution is to run through with Nodejs

Zi once said: reverse engineering is miscellaneous learning, and you need to understand a little bit of AZ language. js was originally running on the server side, and when Nodejs comes out, who will compete with it.

Ask Du Niang and Gu Ge, match VSCode + NodeJs well, Hello World runs through, let’s get started.

The code of the sample ArticleDetail.js is still very kind, there is basically no confusion, and it is clear at a glance.

The eight-character mantra of running through the code is step by step , divide and conquer .

One piece of code, one function after another to run through, you don't just copy the entire code up, and then give up treatment after seeing a bunch of error reports.

encryptSm4ECB: function(t) {
	var e = s("string" == typeof t ? t : JSON.stringify(t))
  ...
}

Execute the value of e first, e calls the function s, the parameter is t, but it is judged whether t is a string, what we directly printed when we hooked before is console.log(JSON.stringify(t));

So the code here can be written in Nodejs as:

var n = "dro";
var o = [20320, 25105, 20182, 30340, 22320, 30334, 21315, 19975, 20986, 20837, 19978, 19979, 21069, 21518, 25307, 38134, 22269, 26085, 26376, 23545, 38169, 22909, 22351];

function s(t) {
    var e, i, n = new Array;
    e = t.length;
    for (var r = 0; r < e; r++)
        (i = t.charCodeAt(r)) >= 65536 && i <= 1114111 ? (n.push(i >> 18 & 7 | 240),
            n.push(i >> 12 & 63 | 128),
            n.push(i >> 6 & 63 | 128),
            n.push(63 & i | 128)) : i >= 2048 && i <= 65535 ? (n.push(i >> 12 & 15 | 224),
                n.push(i >> 6 & 63 | 128),
                n.push(63 & i | 128)) : i >= 128 && i <= 2047 ? (n.push(i >> 6 & 31 | 192),
                    n.push(63 & i | 128)) : n.push(255 & i);
    return n
}

var t = '{"parentId":"f6be7358-f906-4087-b387-69cc17a9ebf8","parentType":"ARTICLE","pageIndex":1,"time":"2022-02-23T10:05:34.760","pageSize":5}';
var e = s(t);
console.log(e);

Here, the values ​​of n, t, and e can all be printed out through the previous hook scheme. For comparison, the value of e is ok, indicating that the s function is available.

var encryptSm4ECB = function (t) {
    var e = s(t)
    , i = (new Date).getTime()
    , r = (i + "").split("")
    , o = [r[5], r[10]].join("")
    , c = s("CFKt03X9Ufk" + n + o);	

The value of c is a bit complicated, but we can print out the values ​​​​of n and o when we hook, so in fact, we can write c first when debugging, which is equivalent to

var cStr = 'CFKt03X9Ufkdro88';
var c = s(cStr);

TIP: There is actually a pit buried here. The value of c is related to the final timestamp timestamp , so it should be matched.

keep going

var CMBSM4EncryptWithECB = function (t, e) {
        // if (!e || !t)
        //    return y.failed(c);
    // if ("object" != s(e) || "object" != s(t))
    //    return y.failed(F);

    // if (e.length <= 0)
    //    return y.failed(h);

    // if (16 != t.length)
    //    return y.failed(f);
    var i = encodeWithPKCS5(e, 16)
        , n = encryptWithECB(i, t);
    return n;

    // , r = new C;
    // return r.set("result", n),
    // y.success(r)
}

The y class seems to be used to output error messages, so don't use it at all.

The return value r encapsulates n , which is not elegant enough, so let's return n directly.

var encryptWithECB = function (t, e) {
    // l(void 0 !== t && t.length % 16 == 0, "illegal plaintext:the length of plaintext must be the multiple of 16."),
    // l(void 0 !== e && 16 === e.length, "illegal key:the length of sm4Key must be 16 bytes.");
    for (var i = vt(e), n = t.length, r = new Array(n), a = 0; a < n;)
        bt(t, a, r, a, i, 0),
            a += 16;
    return r
}

This l function seems to be an error message, kill it.

Then copy all the dependent vt , bt and other functions in, and it seems to be able to run, and there is another error report that is the return value.

Since we returned n directly, we have to change it

var encryptSm4ECB = function (t) {
    var e = s(t)
    , i = (new Date).getTime()
    , r = (i + "").split("")
    , o = [r[5], r[10]].join("")
    , c = s("CFKt03X9Ufk" + n + o);

    // var cStr = 'CFKt03X9Ufkdro88';
    // var c = s(cStr);

    try {
        var l = CMBSM4EncryptWithECB(c, e);

        for (var u = "", h = 0; h < l.length; h++)
            u += String.fromCharCode(l[h]);

        console.log(i);   
        return base64encode(u);    
        /*    
        return {
					      data: window.btoa(u),
            timestamp: i
        }
        // */

    } catch (d) { }
    return t instanceof Object ? null : ""
}

I was cheated by this window.btoa here . I asked Brother Gu, and he said that this is the Base64 transcoding provided by the browser. NodeJs also provides a Base64 function, but the transfer is different...

Fortunately, Brother Gu is still reliable, and found a Base64 written in js

var base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
    base64DecodeChars = new Array((-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), 62, (-1), (-1), (-1), 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, (-1), (-1), (-1), (-1), (-1), (-1), (-1), 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, (-1), (-1), (-1), (-1), (-1), (-1), 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, (-1), (-1), (-1), (-1), (-1));
var base64encode = function (e) {
    var r, a, c, h, o, t;
    for (c = e.length, a = 0, r = ''; a < c;) {
        if (h = 255 & e.charCodeAt(a++), a == c) {
            r += base64EncodeChars.charAt(h >> 2),
                r += base64EncodeChars.charAt((3 & h) << 4),
                r += '==';
            break
        }
        if (o = e.charCodeAt(a++), a == c) {
            r += base64EncodeChars.charAt(h >> 2),
                r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
                r += base64EncodeChars.charAt((15 & o) << 2),
                r += '=';
            break
        }
        t = e.charCodeAt(a++),
            r += base64EncodeChars.charAt(h >> 2),
            r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
            r += base64EncodeChars.charAt((15 & o) << 2 | (192 & t) >> 6),
            r += base64EncodeChars.charAt(63 & t)
    }
    return r
}

After a comparison, it is first-class, which is consistent with the results from Chrome Hook.

So how to use this result? You can use NodeJs to start a web server, and then rpc to execute.

Let's introduce an elegant method to execute js directly with python

Js simulation library introduction

There are many JavaScript execution engines written in Python.

PyV8

https://pypi.org/project/PyV8

It is said that it is old and in disrepair, and the latest version is from 2010. It is not recommended to use it.

But in fact, it was updated in 2013. Lian is quite old, can I still eat? I think it is worth trying just because of the name V8.

Js2Py

https://github.com/PiotrDabkowski/Js2Py

I also think it is too old. In fact, it was updated 5 months ago, so the potential of older programmers cannot be underestimated.

PyExecJS

https://pypi.org/project/PyExecJS/

A library originally born in Ruby and later ported to Python.

It is relatively active, and the latest update is in 2018. There are many examples of its use in the world. Many people recommend using

PyminiRacer

https://github.com/sqreen/PyMiniRacer

The author claims that this is a newer library that succeeds PyExecJS. It depends on fate. Fei Ge found it for the first time, so I use it today.

Pippet tea

https://github.com/pyppeteer/pyppeteer

You can also try this. In fact, many old libraries that are despised by people are still trying to update them.

Selenium

https://www.selenium.dev/

  • A web automation testing framework that can drive various browsers to simulate manual operations
  • Used to render pages to facilitate data extraction or pass captcha
  • It can also directly drive the browser to execute JS

Selenium can drive the browser, so it is easy to execute a js, which is used as the final killer.

PyminiRacer simulates the execution of encryptSm4ECB

Let's start with a Hello World

from py_mini_racer import py_mini_racer
jsSource = '''
var ffdemo = function(str){
	return str;
}

'''
ctx = py_mini_racer.MiniRacer()
ctx.eval(jsSource)
print(ctx.call("ffdemo", "Hello World"))

Yes, it is so handsome, just 3 lines of code.

Follow the gourd drawing, copy the code that NodeJs ran through just now, and execute print(ctx.call(“encryptSm4ECB”, strFF))

The result came out.

3. Summary

After NodeJs is executed, don't copy the entire page of code from the beginning, divide and conquer, and run through the functions one by one.

There is only one way JavaScript protection can go, and that is obfuscation. Next time we find a suitable sample, we will analyze it together.

ffshow.jpeg

Lian Po is old. He still eats a bucket of rice and ten catties of meat. His life is endless and his coding is endless.

Guess you like

Origin blog.csdn.net/fenfei331/article/details/123174604