码上爬 8~13关js逆向代码

查看 19|回复 3
作者:buzaizb   
各位好,这次给大家分享一下 码上爬 8~13 关的js逆向代码。  
  
在第11 关中 和前面不同的是使用了 WebAssembly(简称 Wasm)技术。
WebAssembly主要是借助Emscripten编译工具,使c/c++文件能转成wasm格式的文件,javaScript可以直接调用该文件执行其中的方法。从而可将一些api参数的加密逻辑隐藏其中。
对于逆向使用了这种技术的网站的情况下,我们可以借助 python 的 pywasm库来模拟执行 wasm文件。
通过对11关的请求的调用栈溯源可以得知,加密参数M 就是通过这个方法的来的,当前这个文件就是一个wasm文件。


Pasted image 20250926175816.png (97.91 KB, 下载次数: 1)
下载附件
2025-9-26 17:59 上传

因此我们将目标 .wasm 文件另存到本地,然后使用pywasm库来模拟执行加密方法即可:


Pasted image 20250925203202.png (120.15 KB, 下载次数: 1)
下载附件
2025-9-26 17:59 上传

wasm模拟执行,可以使用以下代码:
[Python] 纯文本查看 复制代码import pywasm  
def generate_param_m(num, ts):  
runtime = pywasm.core.Runtime()  
m = runtime.instance_from_file('./encrypt.wasm')  
r = runtime.invocate(m, 'encrypt', [num, ts])  
return r[0]
在第12关中,可以看到整个js 文件,除了发送请求和方法名解析的方法。就只剩下了一个 interpreter。这个方法的内部就是一个自定义的解释器,请求参数中的加密逻辑也在其中。
对于这种情况,我们挨个去debug 溯源比较麻烦,因此直接copy 这个方法下来。整个12关的逆向过程就是缺啥补啥。
在补环境的过程中,下面的这段代码可以更好的帮助我们理清调用关系:
[JavaScript] 纯文本查看 复制代码dtavm = {}
dtavm.log = console.log

function proxy(obj, objname, type) {
    function getMethodHandler(WatchName, target_obj) {
        let methodhandler = {
            apply(target, thisArg, argArray) {
                if (this.target_obj) {
                    thisArg = this.target_obj
                }
                let result = Reflect.apply(target, thisArg, argArray)
                if (target.name !== "toString") {
                    if (target.name === "addEventListener") {
                        dtavm.log(`调用者 => [${WatchName}] 函数名 => [${target.name}], 传参 => [${argArray[0]}], 结果 => [${result}].`)
                    } else if (WatchName === "window.console") {
                    } else {
                        dtavm.log(`调用者 => [${WatchName}] 函数名 => [${target.name}], 传参 => [${argArray}], 结果 => [${result}].`)
                    }
                } else {
                    dtavm.log(`调用者 => [${WatchName}] 函数名 => [${target.name}], 传参 => [${argArray}], 结果 => [${result}].`)
                }
                return result
            },
            construct(target, argArray, newTarget) {
                var result = Reflect.construct(target, argArray, newTarget)
                dtavm.log(`调用者 => [${WatchName}] 构造函数名 => [${target.name}], 传参 => [${argArray}], 结果 => [${(result)}].`)
                return result;
            }
        }
        methodhandler.target_obj = target_obj
        return methodhandler
    }

    function getObjhandler(WatchName) {
        let handler = {
            get(target, propKey, receiver) {
                let result = target[propKey]
                if (result instanceof Object) {
                    if (typeof result === "function") {
                        dtavm.log(`调用者 => [${WatchName}] 获取属性名 => [${propKey}] , 是个函数`)
                        return new Proxy(result, getMethodHandler(WatchName, target))
                    } else {
                        dtavm.log(`调用者 => [${WatchName}] 获取属性名 => [${propKey}], 结果 => [${(result)}]`);
                    }
                    return new Proxy(result, getObjhandler(`${WatchName}.${propKey}`))
                }
                if (typeof (propKey) !== "symbol") {
                    dtavm.log(`调用者 => [${WatchName}] 获取属性名 => [${propKey?.description ?? propKey}], 结果 => [${result}]`);
                }
                return result;
            },
            set(target, propKey, value, receiver) {
                if (value instanceof Object) {
                    dtavm.log(`调用者 => [${WatchName}] 设置属性名 => [${propKey}], 值为 => [${(value)}]`);
                } else {
                    dtavm.log(`调用者 => [${WatchName}] 设置属性名 => [${propKey}], 值为 => [${value}]`);
                }
                return Reflect.set(target, propKey, value, receiver);
            },
            has(target, propKey) {
                var result = Reflect.has(target, propKey);
                dtavm.log(`针对in操作符的代理has=> [${WatchName}] 有无属性名 => [${propKey}], 结果 => [${result}]`)
                return result;
            },
            deleteProperty(target, propKey) {
                var result = Reflect.deleteProperty(target, propKey);
                dtavm.log(`拦截属性delete => [${WatchName}] 删除属性名 => [${propKey}], 结果 => [${result}]`)
                return result;
            },
            defineProperty(target, propKey, attributes) {
                var result = Reflect.defineProperty(target, propKey, attributes);
                dtavm.log(`拦截对象define操作 => [${WatchName}] 待检索属性名 => [${propKey.toString()}] 属性描述 => [${(attributes)}], 结果 => [${result}]`)
                // debugger
                return result
            },
            getPrototypeOf(target) {
                var result = Reflect.getPrototypeOf(target)
                dtavm.log(`被代理的目标对象 => [${WatchName}] 代理结果 => [${(result)}]`)
                return result;
            },
            setPrototypeOf(target, proto) {
                dtavm.log(`被拦截的目标对象 => [${WatchName}] 对象新原型==> [${(proto)}]`)
                return Reflect.setPrototypeOf(target, proto);
            },
            preventExtensions(target) {
                dtavm.log(`方法用于设置preventExtensions => [${WatchName}] 防止扩展`)
                return Reflect.preventExtensions(target);
            },
            isExtensible(target) {
                var result = Reflect.isExtensible(target)
                dtavm.log(`拦截对对象的isExtensible() => [${WatchName}] isExtensible, 返回值==> [${result}]`)
                return result;
            },
        }
        return handler;
    }

    if (type === "method") {
        return new Proxy(obj, getMethodHandler(objname, obj));
    }
    return new Proxy(obj, getObjhandler(objname));
}


EventTarget = function EventTarget(){}
//这里放你要代理的对象 --->
window = globalThis


//document
document = {}

// navigator
navigator = {}

//location
location = {}


//history
history = {}


//screen
screen = {}

//localStorage
localStorage = {}


localStorage = proxy(localStorage, 'localStorage')
screen = proxy(screen, 'screen')
location = proxy(location, 'location')
history = proxy(history, 'history')
window = proxy(window, 'window')
document = proxy(document, 'document')
navigator = proxy(navigator, 'navigator')
最后第13关:
我们发送网络请求,看他的调用链,点进 loadPage 方法:


Pasted image 20250925205955.png (117.9 KB, 下载次数: 1)
下载附件
2025-9-26 17:58 上传

发现者整个文件中就只有一个方法:


Pasted image 20250925210027.png (184.64 KB, 下载次数: 1)
下载附件
2025-9-26 17:58 上传

但是,按照之前关卡的逻辑,他会使用一个请求发送前的拦截器。
所以我们全局搜索‘$’。最后在jqueryxhr.js文件中可以找到这个请求发送前的拦截器:


Pasted image 20250925210202.png (232.65 KB, 下载次数: 1)
下载附件
2025-9-26 17:58 上传

这里可以将整个文件的js 都拉下来。不过在复制到本地之前可以先反混淆一下,这是我用的工具网址 https://obf-io.deobfuscate.io/。


Pasted image 20250925210806.png (155.55 KB, 下载次数: 1)
下载附件
2025-9-26 17:58 上传

复制代码后,我们在本地加上打印方法运行,发现没有任何输出。这是因为他重写了log 方法,我们注掉就好了:


Pasted image 20250925210954.png (137.75 KB, 下载次数: 1)
下载附件
2025-9-26 17:58 上传

最后我们再次运行可以发现了。
到此这关的请求参数加密逆向完成了, 最后我们组装一下方法,让其方便python调用就行了。
最后的最后,我们再发送请求的时候还有个小坑,就是他的请求入参是一个字符串且一点不能改动。
[Python] 纯文本查看 复制代码data = '{"page":"'+str(num)+'"}'
本文到此就结束了,非常感谢各位能看到这里。8~13关的代码我放在附件了。

下载次数, 方法

ZZ730605   

感谢分享。
millylee   

感谢分享,学习了
walykyy   

不错,感谢分享
您需要登录后才可以回帖 登录 | 立即注册

返回顶部