Protected file produced different result.

Issues related to VMProtect
weloveayaka
Posts: 58
Joined: Wed Jul 05, 2023 6:21 am

Protected file produced different result.

Post by weloveayaka »

Minimal code:
target .net framework 4.8.1

In fact, this is the minimal implementation code for a part of a more complex project.
Due to the type being changed to System.Object, code encountered issues in build 1879.

Actually, I heavily rely on reflection and proxies.


Code: Select all

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;

namespace test1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var service = new Service();
            var proxy = new Proxy(service);
            var interfaceProxy = (IInterface)proxy.GetTransparentProxy();
            interfaceProxy.DoSomething();
            Console.Read();
        }


        public class Proxy : RealProxy
        {
            private IInterface _target;

            public Proxy(IInterface target) : base(typeof(IInterface))
            {
                _target = target;
            }

            public override IMessage Invoke(IMessage msg)
            {
                if (msg is IMethodCallMessage methodCall)
                {
                    Console.WriteLine("Proxy is intercepting the method call.");

                    Console.WriteLine(methodCall.TypeName.ToString());
                    var result = methodCall.MethodBase.Invoke(_target, methodCall.InArgs);
                    Console.WriteLine("Proxy has called the service class method.");

                    return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
                }

                return null;
            }
        }

    }



    public class Service : MarshalByRefObject, IInterface
    {
        public void DoSomething()
        {
            Console.WriteLine("Service is doing something.");
        }
    }

    public interface IInterface
    {
        void DoSomething();
    }
}
config:

Code: Select all

<?xml version="1.0" encoding="UTF-8" ?>
<Document Version="2">
    <Protection InputFileName="test1.exe" Options="70400" VMCodeSectionName=".???" VMComplexity="20">
        <Messages />
        <Folders />
        <Procedures>
            <Procedure MapAddress="test1.Program::Main(string[])" Options="0" CompilationType="2" />
            <Procedure MapAddress="test1.Program::.ctor()" Options="0" CompilationType="2" />
            <Procedure MapAddress="test1.Service::DoSomething()" Options="0" CompilationType="2" />
            <Procedure MapAddress="test1.Service::.ctor()" Options="0" CompilationType="2" />
            <Procedure MapAddress="test1.Program/Proxy::.ctor(class test1.IInterface)" Options="0" CompilationType="2" />
            <Procedure MapAddress="test1.Program/Proxy::Invoke(class System.Runtime.Remoting.Messaging.IMessage)" Options="0" CompilationType="2" />
        </Procedures>
        <Objects />
    </Protection>
    <Script />
</Document>
Original result:

Code: Select all

Proxy is intercepting the method call.
test1.IInterface, test1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Service is doing something.
Proxy has called the service class method.
Protected Result:

Code: Select all

Proxy is intercepting the method call.
System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Proxy has called the service class method.
Proxy is intercepting the method call.
test1.IInterface, test1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Service is doing something.
Proxy has called the service class method.
weloveayaka
Posts: 58
Joined: Wed Jul 05, 2023 6:21 am

Re: Protected file produced different result.

Post by weloveayaka »

In reality, we dynamically create objects in the Invoke method based on the content of the IMethodCallMessage, rather than having a _target variable as shown in the example. Therefore, when the file is protected, an additional Invoke is triggered to call System.Object's GetType. This causes our program to run incorrectly, failing to create the correct object. We end up not knowing who to proxy for, as we can't find the relevant IInterface at that time
Admin
Site Admin
Posts: 2586
Joined: Mon Aug 21, 2006 8:19 pm
Location: Russia, E-burg
Contact:

Re: Protected file produced different result.

Post by Admin »

The virtual machine calls Object.GetType while emulation of castclass instruction in "void test1.Program::Main(string[])":

Code: Select all

.maxstack 8
newobj 06000004 → instance void test1.Service::.ctor()
newobj 06000006 → instance void test1.Program/Proxy::.ctor(class test1.IInterface)
callvirt 0A000011 → instance object [mscorlib]System.Runtime.Remoting.Proxies.RealProxy::GetTransparentProxy()
castclass 02000004 → test1.IInterface
callvirt 06000005 → instance void test1.IInterface::DoSomething()
call 0A000012 → int32 [mscorlib]System.Console::Read()
pop
ret
Anyway, I don't see any bugs here.
weloveayaka
Posts: 58
Joined: Wed Jul 05, 2023 6:21 am

Re: Protected file produced different result.

Post by weloveayaka »

Hello Admin, this is a preliminary example. In terms of the direct outcome of the program, it results in inconsistent execution.
In practical processes, we utilize such a method for IoC + Service Locator.
As a result, since GetType runs before the specific class, we are unable to locate the concrete service.

Code: Select all


 

public override System.Runtime.Remoting.Messaging.IMessage Invoke(System.Runtime.Remoting.Messaging.IMessage message)
        {
            System.Runtime.Remoting.Messaging.IMethodCallMessage methodMessage = new System.Runtime.Remoting.Messaging.MethodCallMessageWrapper((System.Runtime.Remoting.Messaging.IMethodCallMessage)message);
            System.Reflection.MethodBase method = methodMessage.MethodBase;
            if (interfaceType == null) interfaceType = method.DeclaringType;


            // becuase addtional System.Object.GetType() called here. it can't find service and unable to initialize service class
            // if it's not System.Object.GetType() calling, it should be an Iinterface type and then we can get the actual service class initialized then invoke something..
           
           
            if (target == null) target = getObject(interfaceType);                          
            

             // some code to invoke method...
        }   
Can we avoid the initial System.Object.GetType()?
If it's unavoidable, as a workaround, could you tell us what to return?
Whether we return typeof(object), typeof(this), or null, it leads to a NullReferenceException.

I understand that we should return an IInterface Type at this point, but since System.Object.GetType is called first, we are unable to determine what the IInterface is.


give me a liitle time, I'll give you another example later
Admin
Site Admin
Posts: 2586
Joined: Mon Aug 21, 2006 8:19 pm
Location: Russia, E-burg
Contact:

Re: Protected file produced different result.

Post by Admin »

You can use castclass without virtualization with the following code:

Code: Select all

        static T CastTo<T>(object obj) where T : class
        {
            return obj as T;
        }

        static void Main(string[] args)
        {
            var service = new Service();
            var proxy = new Proxy(service);
            var interfaceProxy = CastTo<IInterface>(proxy.GetTransparentProxy());
            interfaceProxy.DoSomething();
            Console.Read();
        }
weloveayaka
Posts: 58
Joined: Wed Jul 05, 2023 6:21 am

Re: Protected file produced different result.

Post by weloveayaka »

Please try this one:

Code: Select all

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;
using System.Reflection;
using System.Runtime.Remoting;

namespace test1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            ServiceFactory.RegisterService(typeof(IInterface), typeof(Service));


            IInterface service = ServiceFactory.GetService() as IInterface;

            service.DoSomething();

            Console.Read();
        }

        public class UniversalServiceProxy : RealProxy, IRemotingTypeInfo
        {
            public string TypeName { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }

            public UniversalServiceProxy() : base(typeof(MarshalByRefObject))
            {
            }

            public override IMessage Invoke(IMessage msg)
            {
                var methodCall = msg as IMethodCallMessage;
                var methodInfo = methodCall.MethodBase as MethodInfo;

                Type serviceInterfaceType = methodInfo.DeclaringType;
                if (!ServiceFactory.IsServiceRegistered(serviceInterfaceType))
                {
                    throw new InvalidOperationException($"No service registered for type {serviceInterfaceType.Name}");
                }

                Type implementationType = ServiceFactory.GetImplementationType(serviceInterfaceType);
                object serviceInstance = Activator.CreateInstance(implementationType);

                try
                {
                    Console.WriteLine($"Invoking method {methodInfo.Name} on {implementationType.Name}");

                    var result = methodInfo.Invoke(serviceInstance, methodCall.Args);

                    Console.WriteLine($"Method {methodInfo.Name} on {implementationType.Name} invoked successfully");

                    return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
                }
                catch (Exception e)
                {
                    Console.WriteLine($"Exception occurred during invocation: {e.Message}");
                    return new ReturnMessage(e, methodCall);
                }
            }

            public bool CanCastTo(Type fromType, object o)
            {
                return ServiceFactory.IsServiceRegistered(fromType);
            }
        }



        public static class ServiceFactory
        {
            private static readonly Dictionary<Type, Type> _services = new Dictionary<Type, Type>();

            public static void RegisterService(Type interfaceType, Type implementationType)
            {
                _services[interfaceType] = implementationType;
            }

            public static object GetService()
            {
                var proxy = new UniversalServiceProxy();
                return proxy.GetTransparentProxy();
            }

            public static bool IsServiceRegistered(Type interfaceType)
            {
                return _services.ContainsKey(interfaceType);
            }
            public static Type GetImplementationType(Type interfaceType)
            {
                return _services[interfaceType];
            }
        }

    
    }



    public class Service : MarshalByRefObject, IInterface
    {
        public void DoSomething()
        {
            Console.WriteLine("Service is doing something.");
        }
    }

    public interface IInterface
    {
        void DoSomething();
    }
}
Before protect:

Code: Select all

Invoking method DoSomething on Service
Service is doing something.
Method DoSomething on Service invoked successfully
After protect:

Code: Select all

System.InvalidOperationException: No service registered for type Object
This example is more closer to our actual situation.
To prevent piracy, we have hundreds of functions that need protection, and if possible, we would prefer to avoid modifying existing code as much as we can. We hope you can provide some suggestions. Thank you!


( Only Main function to be protected, use Ultra )
Last edited by weloveayaka on Mon Nov 27, 2023 10:30 am, edited 1 time in total.
weloveayaka
Posts: 58
Joined: Wed Jul 05, 2023 6:21 am

Re: Protected file produced different result.

Post by weloveayaka »

Admin wrote: Mon Nov 27, 2023 9:10 am You can use castclass without virtualization with the following code:

Code: Select all

        static T CastTo<T>(object obj) where T : class
        {
            return obj as T;
        }

        static void Main(string[] args)
        {
            var service = new Service();
            var proxy = new Proxy(service);
            var interfaceProxy = CastTo<IInterface>(proxy.GetTransparentProxy());
            interfaceProxy.DoSomething();
            Console.Read();
        }
I tried this one, modified to this: (based on above example)

Code: Select all

        {
            IInterface service = CastTo<IInterface>(ServiceFactory.GetService());

            service.DoSomething();

            Console.Read();
        }

        static T CastTo<T>(object obj) where T : class
        {
            return obj as T;
        }
 
It did work, but since there is already a lot of existing code, (it's a very large product.) is there a solution that does not involve modifying the existing code?
Admin
Site Admin
Posts: 2586
Joined: Mon Aug 21, 2006 8:19 pm
Location: Russia, E-burg
Contact:

Re: Protected file produced different result.

Post by Admin »

You still don't understand what your code does (virtualized "isinst" works like "castclass" and it calls Object.GetType too).

Code: Select all

        static T CastTo<T>(object obj) where T : class
        {
            return obj as T;
        }


        static void Main(string[] args)
        {
            ServiceFactory.RegisterService(typeof(IInterface), typeof(Service));

            IInterface service = CastTo<IInterface>(ServiceFactory.GetService());

            service.DoSomething();

            Console.Read();
        }
weloveayaka
Posts: 58
Joined: Wed Jul 05, 2023 6:21 am

Re: Protected file produced different result.

Post by weloveayaka »

It seems we have to modify current code,
Thank you for your help!
weloveayaka
Posts: 58
Joined: Wed Jul 05, 2023 6:21 am

Re: Protected file produced different result.

Post by weloveayaka »

Sorry to bother you again
It's about exception this time:

Code: Select all

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace @catch
{
    internal class Program
    {
        static void Main(string[] args)
        {
            try
            {
                A();
            }
            catch (CustomException ex) {
                Console.WriteLine("Catch Custom Exception");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Catch Exception");
            }
            Console.Read();
        }   
        static void A()
        {
            throw new CustomException();
        }
    }

    class CustomExceptionBase : Exception {
        

    }

    class CustomException : CustomExceptionBase
    {


    }
}
Before virtualization:
it outputs "Catch Custom Exception"

After virtualization
it outputs "Catch Exception"

build 1754 and 1879
Admin
Site Admin
Posts: 2586
Joined: Mon Aug 21, 2006 8:19 pm
Location: Russia, E-burg
Contact:

Re: Protected file produced different result.

Post by Admin »

The virtual machine calls A() via MethodInfo.Invoke that doesn't support BindingFlags.DoNotWrapExceptions for .NET Framework. So the class an exception in the "catch" block is wrong ("TargetInvocationException" instead of "CustomException"). The virtualized "A()" will throw correct exception.
weloveayaka
Posts: 58
Joined: Wed Jul 05, 2023 6:21 am

Re: Protected file produced different result.

Post by weloveayaka »

I think this is very challenging, as it means more functions require virtualization. For instance, if I need to call a service and catch its various exceptions, it's not just about virtualizing this function, but also virtualizing the service and its internal call chain.

It's not only A(), but also A->B->C->D->.....
This also means that if we miss adding any function, it could lead to changes in the behavior of the application.

I earnestly request you to make the necessary modifications.

In our code, a function may call different assembly, and the call chain may be very very long.
We indeed need to virtualize the functions, so we can't remove virtualization.
However, it's nearly impossible for us to fully apply virtualization to the entire call chain.
weloveayaka
Posts: 58
Joined: Wed Jul 05, 2023 6:21 am

Re: Protected file produced different result.

Post by weloveayaka »

Also, some Exceptions are throw by System, which is impossible to virtualization

Code: Select all

 try
            {
                var a = System.IO.File.ReadAllText(@"c:\notexist.txt"); 
            }
            catch (System.IO.FileNotFoundException ex) {
                Console.WriteLine("Catch FileNotFoundException");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Catch Exception");
            }
            Console.Read();
 
this also enter wrong catch block
Could you please find another way to avoid this? Alternative way to replace DoNotWrapExceptions?
Admin
Site Admin
Posts: 2586
Joined: Mon Aug 21, 2006 8:19 pm
Location: Russia, E-burg
Contact:

Re: Protected file produced different result.

Post by Admin »

weloveayaka wrote: Mon Nov 27, 2023 10:54 pm this also enter wrong catch block
Could you please find another way to avoid this? Alternative way to replace DoNotWrapExceptions?
Please try the 1911 build.
weloveayaka
Posts: 58
Joined: Wed Jul 05, 2023 6:21 am

Re: Protected file produced different result.

Post by weloveayaka »

Thank you to fix the problem in 1911build. Could you please also send the fixed build to ...
Which is also the registered user email, it's my employer.
Thank you very very very much!!!!
Post Reply