rustenium-identity 0.1.12

A versatile stealth overlay for rustenium
Documentation
// Native-shaped property spoofing.
//
// Detectors (CreepJS's `queryLies` is the reference implementation) do not check
// whether a *value* is plausible — they check whether the function object backing
// it is still shaped like a native one. A native method or accessor has:
//
//   * own property names exactly ['length', 'name']  (no prototype/arguments/caller)
//   * a correct `length` and `name`
//   * no [[Construct]]  -> `new fn()` and `class X extends fn {}` both TypeError
//   * `Function.prototype.toString` reporting "[native code]"
//   * a receiver brand check -> `Navigator.prototype.platform` throws TypeError
//
// A plain `function () {}` fails five of those at once, which is why every naive
// spoof lights up ~10 distinct lie types per property. Object-literal getters and
// method shorthand are the only forms that match natively, so everything below is
// built from those two.
var PropertyModifier = (function () {
    var origToString = Function.prototype.toString;
    var registry = new WeakMap();

    // Detectors run their probes from a nested about:blank iframe and call *that*
    // realm's Function.prototype.toString on our functions. Realms don't share a
    // WeakMap, so a child realm asks its parent before falling back to real source.
    // about:blank inherits the parent origin, so this reach is always permitted.
    var parentToString = null;
    try {
        if (typeof window !== 'undefined' && window.parent && window.parent !== window) {
            parentToString = window.parent.Function.prototype.toString;
        }
    } catch (e) {}

    // Must be method shorthand, not `toString: function toString(){}`. A function
    // expression carries own prototype/arguments/caller and is constructible, which
    // trips six separate probes; a method definition never has any of them.
    var patchedToString = ({
        toString() {
            var name = registry.get(this);
            if (name !== undefined) return 'function ' + name + '() { [native code] }';
            if (parentToString !== null) {
                try { return parentToString.call(this); } catch (e) {}
            }
            // Re-throws the native TypeError with a native stack for non-functions,
            // which is what the `at Function.toString` frame probes look for.
            return origToString.call(this);
        },
    }).toString;

    registry.set(patchedToString, 'toString');
    Object.defineProperty(Function.prototype, 'toString', {
        value: patchedToString, writable: true, enumerable: false, configurable: true,
    });

    function nativeGetter(prop, impl) {
        var holder = { get [prop]() { return impl.call(this); } };
        var fn = Object.getOwnPropertyDescriptor(holder, prop).get;
        registry.set(fn, 'get ' + prop);
        return fn;
    }

    function nativeMethod(name, arity, impl) {
        var holder = { [name](...args) { return impl.apply(this, args); } };
        var fn = holder[name];
        // Rest params report length 0; restore the real arity without adding a key.
        if (fn.length !== arity) Object.defineProperty(fn, 'length', { value: arity });
        registry.set(fn, name);
        return fn;
    }

    return {
        nativeGetter: nativeGetter,
        nativeMethod: nativeMethod,

        /// Register `fn` so Function.prototype.toString reports it as native.
        markNative: function (fn, name) { registry.set(fn, name); return fn; },

        /// Replace a readonly accessor with one returning `value`.
        spoofProperty: function (target, prop, value) {
            var desc = Object.getOwnPropertyDescriptor(target, prop);
            var origGet = desc && desc.get;
            var getter = nativeGetter(prop, function () {
                // Delegating to the real getter reproduces the native brand check
                // exactly: wrong receivers get the genuine "Illegal invocation"
                // TypeError, which is what the `obj.prototype[name]` probe wants.
                if (origGet) origGet.call(this);
                return value;
            });
            Object.defineProperty(target, prop, {
                get: getter,
                set: desc ? desc.set : undefined,
                enumerable: desc ? desc.enumerable : true,
                configurable: true,
            });
        },

        /// Derive a readonly accessor from the real one: `transform(realValue, self)`.
        /// Use this instead of stamping a value onto the returned object — natively
        /// these live on the prototype, so an own property on the instance shows up
        /// in Object.getOwnPropertyNames() where a real one yields nothing.
        spoofAccessor: function (target, prop, transform) {
            var desc = Object.getOwnPropertyDescriptor(target, prop);
            if (!desc || !desc.get) return;
            var origGet = desc.get;
            var getter = nativeGetter(prop, function () {
                return transform(origGet.call(this), this);
            });
            Object.defineProperty(target, prop, {
                get: getter, set: desc.set, enumerable: desc.enumerable, configurable: true,
            });
        },

        /// Replace a method. `wrap` receives the original and returns the new body.
        spoofMethod: function (target, name, wrap) {
            var orig = target[name];
            if (typeof orig !== 'function') return;
            var fn = nativeMethod(name, orig.length, wrap(orig));
            Object.defineProperty(target, name, {
                value: fn, writable: true, enumerable: true, configurable: true,
            });
        },

        /// Genuinely remove a property. WebIDL members are configurable, so a real
        /// delete works and leaves nothing to hide — no descriptor bookkeeping, and
        /// no need to patch getOwnPropertyDescriptor/hasOwn/hasOwnProperty (each of
        /// which would be another native function to fake).
        deleteProperty: function (target, prop) {
            try { delete target[prop]; } catch (e) {}
        },
    };
})();