0

I'm attempting to create an undetectable method of overriding the Error object in JavaScript without triggering standard detection mechanisms. My goal is to modify the Error prototype or constructor in a way that cannot be easily identified by typical inspection techniques.

I've explored multiple strategies for overriding the Error object:

  • Using Proxy
  • Arrow Function
  • Long Function(Error=function(){})
  • Short Function(Error={Error(){}}.Error)

I've developed a comprehensive test function to validate the stealth of the override:

function testError(){
    if(Object.hasOwn(Error,"caller") || Object.hasOwn(Error,"arguments") || Object.hasOwn(Error,"prepareStackTrace"))
        return false;
    try {
        new Error()
    } catch (error) {
        return false;
    }
    try {
        Error()
    } catch (error) {
        return false;
    }
    try {
        Object.create(Error).toString()
    } catch (error) {
        if(error.stack.includes("Object.toString"))
           return false
    }
    return Error.isError(new Error) && new Error() instanceof Error && new TypeError() instanceof Error && Error() instanceof Error && TypeError() instanceof Error
}

What techniques can successfully bypass these detection mechanisms and pass the provided testError() function?

Current challenges:

  • Most standard override techniques are easily detectable
  • Need a method that doesn't trigger standard inspection checks
9
  • 2
    What does a custom error type not solve that you need to monkey-patch Error or Error.prototype? Given something like ... class MyError extends Error { constructor(...args) { super(...args); /* custom implementation */ } } ... for instance is good enough for being detected via ... Error.isError(new MyError). And for the matter of monkey patching, in order to provide a good approach/solution one needs to know whether Error or Error.prototype or both are going to be patched. Commented May 27 at 9:23
  • Purpose is to hide the use of Error.prepareStackTrace, for this Error needs to be overridden, custom error solution is not. And the main problem here is that Error needs to be overridden. there is no problem in Error.prototype Commented May 27 at 10:00
  • Patching the built in Error type/constructor is going to break instanceof operator based detection of sub-error types like e.g. SyntaxError instances. What definitely was worth a try is augmenting/patching Error.prototype via Object.defineProperty. Commented May 27 at 10:01
  • 3
    You're way off the beaten path already if you're dealing with code that tries to verify Error has been tampered with. At this point there wouldn't really be a "standard" anything. It's exceptionally abnormal to not trust Error. And if the code already doesn't trust it, then there are a million things it might be trying to check. Including many that are flawed (no-ops or with high chance of producing false positives or false negatives) Commented May 27 at 10:01
  • As I understand from your comments, it seems impossible to make a hidden Error override. Is it possible to manipulate the error stack trace without overriding Error? Commented May 27 at 10:33

1 Answer 1

0

I found the solution below. It passes the testError successfully. Could you please share your opinion about this code?

       const originalError = globalThis.Error;
       //We used a class to generate a function without caller and arguments prototypes.
       const Error = (class {
           static Error = function(msg) {                   
                   const error = new originalError(msg);
                   Object.setPrototypeOf(error, Error.prototype);
                   return error;                   
           }
       }).Error;
       // Copy properties from original Error
       Object.getOwnPropertyNames(originalError.prototype).forEach((prop) => {
           if (prop !== 'constructor') {
               Object.defineProperty(Error.prototype, prop, Object.getOwnPropertyDescriptor(originalError.prototype, prop));
           }
       });
       // Copy static properties from original Error
       Object.getOwnPropertyNames(originalError).forEach((prop) => {
           if (prop !== 'prototype')
               Object.defineProperty(Error, prop, Object.getOwnPropertyDescriptor(originalError, prop));
       });

       const errorTypes = [
           'TypeError',
           'ReferenceError',
           'SyntaxError',
           'RangeError',
           'EvalError',
           'URIError',
           'AggregateError',
       ];
       //Set Error as the prototype of all error types
       errorTypes.forEach((errorType) => {
           const errorConstructor = globalThis[errorType];
           if (errorConstructor) {
               Object.setPrototypeOf(errorConstructor, Error);
               Object.setPrototypeOf(errorConstructor.prototype, Error.prototype);
           }
       });
       originalError.prepareStackTrace = function prepareStackTrace(err, stack) {
           if (Object.getPrototypeOf(Object.getPrototypeOf(err)) === originalError.prototype) {
               // We only set the prototype if it is not already set (Runtime Errors)
               Object.setPrototypeOf(Object.getPrototypeOf(err), Error.prototype);
           }
           const limit = Math.max(Error.stackTraceLimit,0);
           originalError.stackTraceLimit = limit;
           const stackArr = err.stack.split('\n');
           err.stack = stackArr.slice(0, limit + 1).join('\n');
           stack = stack.slice(0, limit);
           if (Error.prepareStackTrace) {
               err.stack = Error.prepareStackTrace(err, stack);
           }
           return err.stack;
       };
       Object.defineProperty(globalThis, 'Error', {
           value: Error,
           writable: true,
           configurable: true,
           enumerable: false,
       });

       function testError() {
           if (Object.hasOwn(Error, "caller") || Object.hasOwn(Error, "arguments") || Object.hasOwn(Error, "prepareStackTrace"))
               return false;
           try {
               new Error()
           } catch (error) {
               return false;
           }
           try {
               Error()
           } catch (error) {
               return false;
           }
           try {
               Object.create(Error).toString()
           } catch (error) {
               if (error.stack.includes("Object.toString"))
                   return false
           }
           return Error.isError(new Error()) && new Error() instanceof Error && new TypeError() instanceof Error && Error() instanceof Error && TypeError() instanceof Error
       }
       console.log(`Error constructor patched: ${testError() ? 'success' : 'failed'}`);

Sign up to request clarification or add additional context in comments.

4 Comments

Since the code lacks comments at places where it's needed I'm right now not really fond of doing a code review. The only thing that immediately is obvious is the implementation of Error.isError relying solely on instanceof which does entirely break the behavior of the built-in method which detects errors cross-realm and even rejects objects that are not clearly error objects despite featuring an error instance along the prototype chain.
I removed the isError override code and added comments for better understanding
Round 1 ... One does not need a weird class construct in order to create a concise generic method (a method which has only length and name as own properties). Such a function can be created via the the method definition shorthand ... const { Error } = { Error(msg) { if (this instanceof Error) { const error = new originalError(msg); Object.setPrototypeOf(error, Error.prototype); return error; } return new Error(msg); } };
However, the shorthand function definition does not create Error.prototype and cannot be used with new. Using class creates Error.prototype and can be used with new.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.