壹影博客.
我在下午4点钟开始想你
Js函数native化-Js函数封装保护教程
  • 2023-2-12日
  • 2评论
  • 14965围观

Js函数native化

大家都知道Js函数在正常情况下可以用toString()方法打印出来,以本博客为例演示(https://bk.yyge.net)

如上图所示 我们在控制台输入window.get_menu_nav_open.toString()
获取全局对象下的get_menu_nav_open函数,并且利用toString()将其转换为字符串

针对这种直接明文获取的情况对开发者来说非常不友好,那么我们应该如何应对这种问题呢
我们不想让函数明文打印应该怎么做呢?

将函数native化就可以实现我们想要的效果 。我们之间看看最终的保护效果吧

那么这是如何做到的呢?我直接将函数贴出来大家可以参考学习

//函数native化
!function (){
 const $toString = Function.prototype.toString;
 const symbol=Symbol();
 const myToString = function (){
 	return typeof this === 'function' && this[symbol] || $toString.call(this);
 };


 function set_native(func, key, value){
 	Object.defineProperty(func,key,{
 	enumerable:false,
 	configurable:true,
 	Twritable:true,
 	value:value
  });
 };

 delete Function.prototype.toString;
 set_native(Function.prototype, "tostring", myToString);
 set_native(Function.prototype.toString, symbol,"function tostring() { [native code] }");
 globalThis.setNative = function (func, funcname) {
 	set_native(func, symbol, `function ${funcname || func.name || ''}() { [native code] }`);}
 }();

//使用函数

setNative(get_menu_nav_open,"get_menu_nav_open")
//参数1 函数本体
//参数2 函数名称-字符串
//设置完了之后 就完成了函数native化

除此之外,我还在网上搜到一种简单的写法,与上面的代码大同小异,如下

//写法2
function set_native(func) {
    func.toString = function () { return `function ${func.name}(){ [native code] }` }
    func.prototype.toString = toString
    return func
}

//直接把自己函数自身native吧
set_native(set_native)

第二种方式Proxy代理,提到重写、切面等概念时,不得不联想到一个功能强大的 API —— Proxy,不少黑魔法都借助它实现。

缺点:在 Safari 浏览器上有明显的破绽,函数转成字符串后变成:

function ProxyObject() {
    [native code]
}

完整保护代码 

console.log('before:', alert + '')      // "function alert() { [native code] }"
console.log('before:', alert.name)      // "alert"
console.log('before:', alert.length)    // 0

alert = (function() {
  function alert(...args) {
    console.log('test:', ...args)
  }
  return new Proxy(alert, {})
})()

console.log('after:', alert + '')       // "function () { [native code] }"
console.log('after:', alert.name)       // "alert"
console.log('after:', alert.length)     // 0

alert('hello proxy')

当然还有一种方式WebAssembly

由于 JS 程序是文本格式的,因此函数 toString 的结果自然能包含相应的代码。如果是二进制程序,那么 toString 的结果又会是什么?这个问题,可以用 WebAssembly 的导出函数来验证。

MDN 文档 中提到,WebAssembly 导出的函数会显示成 native code。

我们构造一个精简的 WebAssembly 程序,将导入的 x.y 函数导出成 z 函数:

(module
  (func (export "z") (import "x" "y") (param externref))
)

使用 wat2wasm 将其转成二进制数据,并进行封装:

function genNativeFunction(callback) {
  const buf = new Uint8Array([
    0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 1, 111, 0, 2, 7, 1, 1, 120, 1, 121, 0, 0, 7, 5, 1, 1, 122, 0, 0
  ])
  const mod = new WebAssembly.Module(buf)
  const obj = new WebAssembly.Instance(mod, {x: {y: callback}})
  return obj.exports.z
}

alert = (function() {
  function alert(...args) {
    console.log('test:', ...args)
  }
  return genNativeFunction(alert)
})()

console.log(alert + '')     // "function 0() { [native code] }"
console.log(alert.name)     // "0"
console.log(alert.length)   // 1

alert('hello wasm')

WebAssembly 导出函数转成字符串后确实包含 native code,不过破绽也非常明显,其中的函数名居然是一个数字!正常的 JS 函数显然不可能以数字命名。破绽 +1。

同样 name 属性也变成了数字。破绽 +2。

并且 WebAssembly 函数的形参数量是固定的,因此 length 属性也难以伪造。破绽 +3。

因此这个方案虽然有趣,但并不隐蔽。

当然在奇葩的 Safari 上,返回结果并不包含函数名,并且 name 属性是个空字符串。不过这依然是个大破绽。

此外在 Chrome 上,调试器单步断点(F11)无法进入 WebAssembly 的导出函数:

debugger
alert(123)

今天的教程就到这里,希望对你有的开发有所帮助
By 壹影~~

发表评论

呆毛飘啊飘

Lv.1 @回复 板凳

这个真的是66666666了

xvm

Lv.1 @回复 沙发

这个666

渝ICP备19011465号 | 渝ICP备19011465号-1