Function toString

JavaScript

众所周知,Sentry 在运行的时候,会改写原生的 console API,用于记录上下文相关的一些信息。然而,在一个有 Sentry 的页面上输入 console.log 并回车,会看到输出的内容是:

ƒ log() { [native code] }

但是如果真的输入 console.log('hello world') 执行一下,又会看到输出的文件来自于 breadcrumbs.ts 而不是常见的 VMxxx

这里,Sentry 确实重写了 console 的 API,而之所以会输出 ƒ log() { [native code] } 是因为 Sentry 通过改写 Function.prototype.toString 函数,再一次改写了输出结果,从而达到了迷惑的作用(有些代码会通过判断 toString 是否包含 native code 来判断当前的 API 是否被改写了)。

具体的代码可以在这里找到,大体如下:

originalFunctionToString = Function.prototype.toString;

Function.prototype.toString =
  function(this: WrappedFunction, ...args: any[]): string {
    const context = this.__sentry_original__ || this;
    // tslint:disable-next-line:no-unsafe-any
    return originalFunctionToString.apply(context, args);
  };

如果需要判断当前的 console.log 是否被改写了,针对 Sentry 的话只需要判断 console.log__sentry_original__ 是否存在就可以了。或者,看一下 console.log.toString.toString() 的值也是可以的,因为 Sentry 并没有对 Function.prototype.toString 也做一样的 toString 改写。

如果希望可以做更好的隐藏,那么可以考虑把 Function.prototype.toString 也改写掉:

function wrap(obj, api, f) {
  const original = obj[api];
  obj[api] = f;
  obj[api].__wrapped__ = true;
  obj[api].__wrapped_original__ = original;
}

wrap(Function.prototype, 'toString', function (...args) {
  const context = this.__wrapped__ ? this.__wrapped_original__ : this;
  return Function.prototype.toString.__wrapped_original__.apply(context, args);
});
wrap(console, 'log', function (...args) {
  return console.log.__wrapped_original__.apply(this, ['extra: '].concat(args));
});