ƒ parse() { [native code] }
ƒ stringify() { [native code] }
基于这个概念,我们在来想想我们的jshook,我们的hook不就是把底层C/C++实现的函数改成我们的js去实现吗?保留一份原始函数,然后重写原生函数的逻辑.
那么我们的hook真的不会被检测吗?真的安全吗?没有不漏风的窗,茅和盾是彼此彼此的,你hook肯定会留下一些痕迹,我可以去检测你有没有这些痕迹进而制止你篡改我的原生函数.
当我们对一个原生函数进行重写的时候,我在控制台输出这个函数的名称
ƒ (params){
var _parse=JSON.parse;
console.log(params);
return _parse(params);
}
这个函数的实现从C/C++层面变成了js层面的,输出的是我的hook代码,与没有重写的做个对比,也不难发现,你重写了我的原生函数后,不外加处理,肯定没有native code这样的关键字,那我的思路就是先把函数转成字符串在通过正则去匹配有没有native code这样的关键字,如果没有则返回true,有的话则返回false.
我们接下来直接看demo
function Ce(func){
if(typeof func === "function"){
var Sd=func["toString"]()["replace"](new RegExp("\\s", "g"), "");
var N_="{[nativecode]}";
return Sd["substring"](Sd["length"] - N_["length"]) === N_;
}
}
console.log(Ce(console["debug"]));
console.log(Ce(JSON.parse));
这份检测的函数Ce的具体逻辑来自于Caplusa(reese84)的某个代码段,我是对其进行反混淆和精简处理过的.
Ce函数的大体逻辑就是先判断是不是函数类型,如果是函数类型,就将这个函数转成字符串正则去掉字符串多余的空格,在截取字符串的总长-关键字长度为索引判断截取的内容是否为关键字,如果是就返回true不是就false.
toString检测一般的作用在防止函数被篡改和浏览器环境检测
所以,我们平时写hook脚本也好,补环境补函数也好,都要对做toString保护对抗toString检测.
对抗toString检测的思路有很多,我就讲一个比较简单的吧
竟然你是通过调用函数的toString属性获取值,那我不可以覆盖toString属性的值来返回关键字native code进行绕过吗,这是我们的一个思路
我们认识一下一个新的方法Object.defineProperty,他的作用是用于直接在对象上定义一个新属性,或者修改对象的现有属性,并返回该对象.
这个方法有三个入参Object.defineProperty(targetObject, propertyName, descriptor)
分别是
targetObject 要操作的目标对象
propertyName 要定义/修改的属性名(符串或 Symbol)
descriptor 属性描述符对象,控制属性行为
描述符又有两种类型:
[ol]
{
value: any, // 属性值
writable: boolean, // 是否可修改(默认 false)
enumerable: boolean, // 是否可枚举(for-in/Object.keys,默认 false)
configurable: boolean // 是否可删除/修改描述符(默认 false)
}
{
get: function() { / 获取值时调用 / },
set: function(newVal) { / 设置值时调用 / },
enumerable: boolean,
configurable: boolean
}
[/ol]
我们这里主要用到第一种描述符,当调用目标函数的toString属性时,我通过Object.defineProperty方法修改原有对象的toString属性也叫覆盖他原有的属性为native code关键字,不就可以进行绕过了嘛,在配置一下不可修改,不可删除防止被额外的覆盖.
Object.defineProperty(funcs, 'toString', {
value: function() {
return `function ${this.name}() {
[native code]
}`;
},
writable: false,
configurable: false,
enumerable: false
});