3
\$\begingroup\$

I'm a first traveler coding in WinApi, Hooks, COM objects. Then, I made a dll to inject (EasyHook) this in the target SIEMENS Step7 process and modify the software code at runtime:

Maybe I want some opinion about this kind of practices, hooking, injection, file system driver, kernel hook, modify .exe vtable before init, interprocess-communication, etc.

This code gets the DataSource from a Grid GUI(COM Object) when the user do a determined "Force" action through menu.

using EasyHook;
using Octtos.S7ussapx.Injected.COM.Interfaces;
using Octtos.S7ussapx.Injected.Step7.COM.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

namespace Octtos.S7ussapx.Injected
{
    public class Main : IEntryPoint
    {
        private ClientCallbackHandler _callbackHandler;

        [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
        unsafe delegate int IDispatchInvokeDelegate(IntPtr pthis, int dispIdMember, ref Guid riid, uint lcid, ushort wFlags,
            ref System.Runtime.InteropServices.ComTypes.DISPPARAMS pDispParams, void* ppvResult,
            ref System.Runtime.InteropServices.ComTypes.EXCEPINFO pExcepInfo, IntPtr[] pArgErr);

        #region CoGetClassObject ole32.dll

        private LocalHook _coGetClassObjectHook;

        private LocalHook _S7StatusOCXIClassFacttoryCreateInstanceHook;
        private LocalHook _S7VarServer50IClassFacttoryCreateInstanceHook2;

        private bool _s7StatusOCXCOMHookCreated = false;
        private bool _s7VarServer50COMHookCreated = false;

        private IntPtr _S7StatusOCXIClassFacttoryCreateInstanceMethodPtr;
        private IntPtr _S7VarServer50IClassFacttoryCreateInstanceMethodPtr;

        private delegate uint CoGetClassObjectDelegate(ref Guid rclsid, int dwClsContext, IntPtr pServerInfo, ref Guid riid, out IntPtr ppv);

        [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
        private delegate int IClassFactoryCreateInstanceDelegate(IntPtr pThis, IntPtr pUnkOuter, ref Guid riid, out IntPtr ppv);

        private static unsafe uint CoGetClassObject_Hook(ref Guid clsid, int dwClsContext, IntPtr pServerInfo, ref Guid iid, out IntPtr ppv)
        {
            var me = (Main)HookRuntimeInfo.Callback;
            var classObjectInstance = CoGetClassObject(ref clsid, dwClsContext, pServerInfo, ref iid);

            ppv = Marshal.GetIUnknownForObject(classObjectInstance);

            var iclassFactory = classObjectInstance as IClassFactory;

            if (!me._s7StatusOCXCOMHookCreated)
            { 
                if (iclassFactory != null && clsid == typeof(IS7StatusOcx).GUID)
                {
                    var method = typeof(IClassFactory).GetMethod("CreateInstance");
                    var methodVTableIdx = Marshal.GetComSlotForMethodInfo(method);

                    int** comVTable = *(int***)ppv.ToPointer();
                    int* hMethod = comVTable[methodVTableIdx];
                    me._S7StatusOCXIClassFacttoryCreateInstanceMethodPtr = new IntPtr(hMethod);

                    me._S7StatusOCXIClassFacttoryCreateInstanceHook = LocalHook.Create(me._S7StatusOCXIClassFacttoryCreateInstanceMethodPtr, new IClassFactoryCreateInstanceDelegate(S7StatusOCXCreateInstance_Hook), me);
                    me._S7StatusOCXIClassFacttoryCreateInstanceHook.ThreadACL.SetExclusiveACL(new int[0]);

                    me._s7StatusOCXCOMHookCreated = true;
                }
            }

            if (!me._s7VarServer50COMHookCreated)
            {
                if (iclassFactory != null && clsid == typeof(IS7VarServer50).GUID)
                {
                    var method = typeof(IClassFactory).GetMethod("CreateInstance");
                    var methodVTableIdx = Marshal.GetComSlotForMethodInfo(method);

                    int** comVTable = *(int***)ppv.ToPointer();
                    int* hMethod = comVTable[methodVTableIdx];
                    me._S7VarServer50IClassFacttoryCreateInstanceMethodPtr = new IntPtr(hMethod);

                    me._S7VarServer50IClassFacttoryCreateInstanceHook2 = LocalHook.Create(me._S7VarServer50IClassFacttoryCreateInstanceMethodPtr, new IClassFactoryCreateInstanceDelegate(S7VarServer50CreateInstance_Hook), me);
                    me._S7VarServer50IClassFacttoryCreateInstanceHook2.ThreadACL.SetExclusiveACL(new int[0]);

                    me._s7VarServer50COMHookCreated = true;
                }
            }

            Marshal.ReleaseComObject(classObjectInstance);

            return 0;
        }

        #endregion

        #region S7StatusOCX IClassFactory.Create ole32.dll

        private IS7StatusOcxDispatch _currentInstance;

        [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
        private delegate int IClassFactoryCreateInstanceDelegate2(IntPtr pThis, IntPtr pUnkOuter, ref Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out object obj);

        private static unsafe int S7StatusOCXCreateInstance_Hook(IntPtr pThis, IntPtr pUnkOuter, ref Guid riid, out IntPtr ppv)
        {
            var me = (Main)HookRuntimeInfo.Callback;

            var baseMethod = (IClassFactoryCreateInstanceDelegate2)Marshal.GetDelegateForFunctionPointer(me._S7StatusOCXIClassFacttoryCreateInstanceMethodPtr, typeof(IClassFactoryCreateInstanceDelegate2));

            object objInstance;
            var hr = baseMethod(pThis, pUnkOuter, ref riid, out objInstance);

            me._currentInstance = (IS7StatusOcxDispatch)objInstance;

            var ppv2 = Marshal.GetIUnknownForObject(objInstance);

            Marshal.QueryInterface(ppv2, ref riid, out ppv);

            return hr;
        }

        #endregion

        #region S7VarServer50 IClassFactory.Create ole32.dll

        private LocalHook _s7VarServer5IDispatchHook;

        private IntPtr _s7VarServer50IDispatchInvokeMethodPtr;

        private bool _S7VarServer50CreateInstanceHookCreated = false;

        private IDictionary<IntPtr, IS7StatusOcxDispatch> _s7StatusOCXDispatchInstances = new Dictionary<IntPtr, IS7StatusOcxDispatch>();

        private static unsafe int S7VarServer50CreateInstance_Hook(IntPtr pThis, IntPtr pUnkOuter, ref Guid riid, out IntPtr ppv)
        {
            var me = (Main)HookRuntimeInfo.Callback;

            var baseMethod = (IClassFactoryCreateInstanceDelegate2)Marshal.GetDelegateForFunctionPointer(me._S7VarServer50IClassFacttoryCreateInstanceMethodPtr, typeof(IClassFactoryCreateInstanceDelegate2));

            object objInstance;
            var hr = baseMethod(pThis, pUnkOuter, ref riid, out objInstance);

            var ppv2 = Marshal.GetIUnknownForObject(objInstance);

            Marshal.QueryInterface(ppv2, ref riid, out ppv);

            if (objInstance is IS7VarServer50Dispatch)
            {
                Guid iidIDispatch = typeof(IDispatch).GUID;
                IntPtr ppv3;
                Marshal.QueryInterface(ppv2, ref iidIDispatch, out ppv3);

                if (me._s7StatusOCXDispatchInstances.ContainsKey(ppv3))
                {
                    me._s7StatusOCXDispatchInstances[ppv3] = me._currentInstance;
                }
                else { 
                    me._s7StatusOCXDispatchInstances.Add(ppv3, me._currentInstance);
                }

                me._currentInstance = null;


                if (!me._S7VarServer50CreateInstanceHookCreated)
                {
                    var methodVTableIdx = Marshal.GetComSlotForMethodInfo(typeof(IDispatch).GetMethod("Invoke"));

                    int** comVTable = *(int***)ppv3.ToPointer();
                    int* hMethod = comVTable[methodVTableIdx];
                    me._s7VarServer50IDispatchInvokeMethodPtr = new IntPtr(hMethod);

                    me._s7VarServer5IDispatchHook = LocalHook.Create(me._s7VarServer50IDispatchInvokeMethodPtr, new IDispatchInvokeDelegate(S7VarServer50IDispatch_Invoke_Hook), me);
                    me._s7VarServer5IDispatchHook.ThreadACL.SetExclusiveACL(new int[0]);

                    me._S7VarServer50CreateInstanceHookCreated = true;
                }
            }

            return hr;
        }

        //Por que esse IDispatch tem IntPtr pThis como primeiro parâmetro? Se na DOC o primeiro parâmetro dessa função é o dispIdMember?!
        //http://msdn.microsoft.com/en-us/library/aa912367.aspx
        unsafe static int S7VarServer50IDispatch_Invoke_Hook(IntPtr pthis, int dispIdMember, ref Guid riid, uint lcid, ushort wFlags, ref System.Runtime.InteropServices.ComTypes.DISPPARAMS pDispParams, void* ppvResult, ref System.Runtime.InteropServices.ComTypes.EXCEPINFO pExcepInfo, IntPtr[] pArgErr)
        {
            var me = (Main)HookRuntimeInfo.Callback;

            switch (dispIdMember)
            {
                case 14:
                    me._callbackHandler.WriteLine("CreateForceJob() called");
                    break;
                case 15:
                    me._callbackHandler.WriteLine("StartForceJob() called");

                    IS7StatusOcxDispatch instance;

                    if (me._s7StatusOCXDispatchInstances.TryGetValue(pthis, out instance))
                    {
                        string varList;
                        instance.GetVariableList(out varList, false);

                        var bytes = Encoding.Unicode.GetBytes(varList);
                        var str = Encoding.Default.GetString(bytes);
                        me._callbackHandler.WriteLine(str);
                    }
                    else
                    {
                        me._callbackHandler.WriteLine("ERROR!!! Tentou pegar uma instancia que não existia. Por que essa instância não existe?");
                    }

                    break;
                default:
                    break;
            }

            var baseMethod = (IDispatchInvokeDelegate)Marshal.GetDelegateForFunctionPointer(me._s7VarServer50IDispatchInvokeMethodPtr, typeof(IDispatchInvokeDelegate));
            return baseMethod(pthis, dispIdMember, ref riid, lcid, wFlags, ref pDispParams, ppvResult, ref pExcepInfo, pArgErr);
        }

        #endregion

        public Main(RemoteHooking.IContext ctx, string channelId)
        {
            _callbackHandler = RemoteHooking.IpcConnectClient<ClientCallbackHandler>(channelId);
        }

        public unsafe void Run(RemoteHooking.IContext ctx, string arg1)
        {
            try
            {
                _coGetClassObjectHook = LocalHook.Create(LocalHook.GetProcAddress("ole32.dll", "CoGetClassObject"),
                    new CoGetClassObjectDelegate(CoGetClassObject_Hook), this);
                _coGetClassObjectHook.ThreadACL.SetExclusiveACL(new int[0]);

                RemoteHooking.WakeUpProcess();
                _callbackHandler.InjectStart();

                while (true)
                {
                    _callbackHandler.SetIsAlive(true);
                    Thread.Sleep(TimeSpan.FromSeconds(2));
                }
            }
            catch (Exception ex)
            {
                throw;
            }
        }

        [DllImport("ole32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
        [return: MarshalAs(UnmanagedType.Interface)]
        private static extern object CoGetClassObject(ref Guid rclsid, int dwClsContext, IntPtr pServerInfo, ref Guid riid);
    }
}
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

A couple if minors I've spotted so far:

  1. You should check the return value of Marshal.QueryInterface. The documentation doesn't state it explicitly but I suspect the out ppv will result in ppv being set to null if the call fails. Any subsequent code accessing that variable could therefore result in a NullReferenceException if the call failed.

  2. The documentation of Marshal.QueryInterface explicitly states that you should call Marshal.Release to release the obtained pointer (decrement the COM reference count). I suspect you might be leaking COM objects by not doing so.

  3. You can assign a value to a dictionary directly without checking if it exists first. So this:

    if (me._s7StatusOCXDispatchInstances.ContainsKey(ppv3))
    {
        me._s7StatusOCXDispatchInstances[ppv3] = me._currentInstance;
    }
    else { 
        me._s7StatusOCXDispatchInstances.Add(ppv3, me._currentInstance);
    }
    

    Can be replaced by this:

    me._s7StatusOCXDispatchInstances[ppv3] = me._currentInstance;
    
  4. In the Run method you have a try { } catch {} block which does nothing but simply re-throw the exception. Did you mean to perform some logging there (maybe in form of a debug or diagnostics trace)?

  5. It's not clear exactly why you do this:

    var bytes = Encoding.Unicode.GetBytes(varList);
    var str = Encoding.Default.GetString(bytes);
    

    varList is already a string - why this conversion through bytes? If there is a particular reason for doing this then a comment should be added explaining the why.

  6. You have three blocks of code which all follow this pattern:

                var methodVTableIdx = Marshal.GetComSlotForMethodInfo(method);
    
                int** comVTable = *(int***)ppv.ToPointer();
                int* hMethod = comVTable[methodVTableIdx];
                me._S7VarServer50IClassFacttoryCreateInstanceMethodPtr = new IntPtr(hMethod);
    
                me._S7VarServer50IClassFacttoryCreateInstanceHook2 = LocalHook.Create(me._S7VarServer50IClassFacttoryCreateInstanceMethodPtr, new IClassFactoryCreateInstanceDelegate(S7VarServer50CreateInstance_Hook), me);
                me._S7VarServer50IClassFacttoryCreateInstanceHook2.ThreadACL.SetExclusiveACL(new int[0]);
    

    I think it's possible to extract this into a common method with some parameters.

\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.