素人がプログラミングを勉強していたブログ

プログラミング、セキュリティ、英語、Webなどのブログ since 2008

setTimeout, setIntervalを乗っ取って爆速にする

setTimeout() vs ハッカー、仁義なき戦いによると

function isNativeFunction(func, name)
{
    for (var o in func) {
        if (o === "toString") return false;
    }
    var match = func.toString().match(/^function (\S+)\(\)\s*{\s*\[native code\]\s*}$/);
    return (match && match[1] === name);
}

setInterval = function(){};
isNativeFunction(setInterval, 'setInterval'); // false

でsetIntervalが偽装されているか調べられると書いてあるが、そんなことはない。

自分が普段使っているブラウザはSafariなので他のブラウザではsetIntervalが上書きできないから下記コードは無効だが、setIntervalを書き換えたいと思う時など特定のときしかありえないので、Safariを使えばいいと思う

ほとんど全てのチェックを素通りするフェイクのsetIntervalの書き方をここに載せる(ネイティブかどうかを判別する方法を発見したら教えて欲しい)。ここではsetIntervalだけ上書きしているが、少しコードを追加すればsetTimeoutも上書きできる。

このコードを使うと、具体的には、setInterval(f, 1000);とすると、setInterval(f, 1)となり、スクリプト等でdocument-on時に走らせると全てが爆速になる。

(function () {

var nativeSetInterval = setInterval;
var nativeToString = Function.prototype.toString;

// ネイティブはfunction () {であるがsetInterval.length = 1で、Object.definePropertyで上書き不可能なので仮の引数を入れてごまかす
// この関数のtoString()については後から偽装するので大丈夫
var fakeSetInterval = function setInterval(_) {
  var fn = arguments[0];
  var ms = arguments[1];
  return nativeSetInterval.call(this, fn, ms / 1000);
}

// setInterval.toString()の結果を偽装 DontEnum等もネイティブ同様になるようにする
Object.defineProperty(Function.prototype, 'toString', {
  configurable: true,
  enumerable: false,
  value: function toString() {
    if (this == fakeSetInterval) {
      return 'function setInterval() {\n    [native code]\n}';
    }
    
    if (this == Function.prototype) {
      // Function.prototype.toString()対策
      return 'function () {\n    [native code]\n}';
    }
    
    // Function.prototype.toString.toString()対策
    if (this == Function.prototype.toString) {
      return 'function toString() {\n    [native code]\n}';
    }

    // 偽装する必要のないものは元のtoStringを呼ぶ
    return nativeToString.apply(this, arguments);
  },
  writable: true
});

// 改造済みのsetIntervalを注入

Object.defineProperty(Window.prototype, 'setInterval', {
  configurable: true,
  enumerable: true,
  value: fakeSetInterval,
  writable: true
});

})();

このコードを実行すると冒頭で引用したisNativeFunction(setInterval, 'setInterval') がtrueを返す。唯一考えられる対策はiframeなどの外部ページからFunction.prototype.toStringを取ってきてそれを使うことだが、

var toString = iframe.contentWindow.Function.prototype.toString;
toString.call(setInterval) == 'function setInterval() {\n  [native code]\n}';

このコードもiframe内で、ユーザスクリプトでdocument-start時にFunction.prototype.toStringを書き換えてしまえばいいので、意味がない。クライアント側で実行するコードは、全てユーザのコントロール下にあると思って書かないと、脆弱性の原因になるハッカーの勝利である。

引用元のサイトにあるように、ダウンロードサイトの待ち時間やゲームなどで、どうしても時間によるチェックをいれたい場合、サーバ側でタイマーを使って、待ち時間が経過するまではAPIがエラーを返すように作るしかないと思う。