Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Replacing the default Windows calendar with .NET hooks

0.00/5 (No votes)
2 Dec 2013 1  
Finding another solution to change or alter the native Windows API method calls to replace the default output of the system with our own implementation.

Introduction

It's (the end of) 2013 and we still don't have a true and native Persian calendar on Windows machines. 1382/7/15 is a Persian date and I'm sure, it makes no sense to a lot of you. We (Iranians) have the same feeling about 2013/10/8. It's meaningless to us, because the different parts of a Gregorian date don't match to a Persian date. We don't know '10' here means our tenth month or not. But Linux machines do not have this issue at all. It's open source and easy to change, and it already has true Persian calendar support. How about Windows? No, we can't change its default calendar and we need to find another solution to change or alter the native Windows API method calls to replace the default output of the system, with our own implementation.

Entering EasyHook

It's possible to intercept Windows API method calls using a mechanism called 'Hooking' or 'Windows Hooks'. It's not a new idea and there are a lot of helper libraries to facilitate this operation. Even MS has a helper library for these kinds of actions called Detours. It requires writing Windows hooks using C or similar native languages. In the .NET world, there is a very useful hooking library which allows us to write .NET hooks using .NET languages. It's named EasyHook and you can download it from here: https://easyhook.codeplex.com/.

To write a Windows hook, we need to find out which method is issuing a certain operation. Fortunately there are a lot of Windows API 'Spy' utilities to do this task. I'm using the 'API Monitor' program here: http://www.rohitab.com/apimonitor.

If you want to spy on 32-bit programs, run apimonitor-x86.exe, or to track 64-bit programs, run the apimonitor-x64.exe file. For instance if you can't find firefox.exe in the list of processes in the API Monitor, it means you are not using the x86 version of it. Because the commonly available version of Firefox is still a 32-bit application. The 'API Monitor' comes with thousands of predefined Windows API definitions. We don't know Windows Explorer uses which method(s) to display date and times. So we start by selecting all of the method names containing date or time in the 'API Filter' section of the 'API Monitor'.

Then in the processes list section of the 'API monitor', right click on explorer.exe and select 'Start monitoring'.

Now open a new Explorer window to see the tracked API method calls.

As you can see Windows Explorer uses the GetDateFormatW and GetTimeFormatW methods to show the date and time information. It first sends a lpDate or lpTime parameter to one of the mentioned methods and then gets the returning value from the lpDateStr or lpTimeStr parameters. Now our mission is to intercept these method calls and replace the lpDateStr or lpTimeStr values with our own implementation!

Writing .NET hooks for GetDateFormatW and GetTimeFormatW methods

It's assumed you already downloaded the latest version of EasyHook from https://easyhook.codeplex.com/. Now create a new solution which contains a console application and a new Class Library project. The console application will install the hooks and the Class Library project will contain the hooks. So both of these projects should have a reference to the EasyHook.dll library.

The signature of a hook class should be similar to the following GetDateTimeFormatInjection class.

namespace ExplorerPCal.Hooks
{
    public class GetDateTimeFormatInjection : IEntryPoint
    {

        public GetDateTimeFormatInjection(RemoteHooking.IContext context, string channelName)
        {
            // connect to host...
            _interface = RemoteHooking.IpcConnectClient<MessagesReceiverInterface>(channelName);
            _interface.Ping();
        }

        public void Run(RemoteHooking.IContext context, string channelName)
        {
        }
    }
}

It should implement the IEntryPoint interface. It's an empty interface and will be used just for labeling this class as a hook class. Also a hook class should contain a constructor and a Run method with the mentioned signatures. Because you can specify more than two parameters here, these methods are not included in the IEntryPoint interface.

We will use the Run method to write our own hooks to intercept Window API method calls.

public void Run(RemoteHooking.IContext context, string channelName)
{
    GetDateFormatHook = LocalHook.Create(
     InTargetProc: LocalHook.GetProcAddress("kernel32.dll", "GetDateFormatW"),
     InNewProc: new GetDateFormatDelegate(getDateFormatInterceptor),
     InCallback: this);
}

For instance, here by using the LocalHook.Create method we will create a callback delegate for intercepting GetDateFormatW method calls of kernel32.dll.

The signature of this delegate is important. If the signature of GetDateFormatW of kernel32.dll is:

[DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetDateFormatW(
                                    uint locale,
                                    uint dwFlags, // NLS_DATE_FLAGS
                                    SystemTime lpDate,
                                    [MarshalAs(UnmanagedType.LPWStr)] string lpFormat,
                                    StringBuilder lpDateStr,
                                    int sbSize);

This delegate should have the same parameters and also should be decorated with the UnmanagedFunctionPointer attribute.

[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
private delegate int GetDateFormatDelegate(
                                    uint locale,
                                    uint dwFlags,
                                    SystemTime lpDate,
                                    [MarshalAs(UnmanagedType.LPWStr)] string lpFormat,
                                    StringBuilder lpDateStr,
                                    int sbSize);

Now our callback which will receive the real GetDateFormatW calls first, before the explorer, also should have the same parameters.

private int getDateFormatInterceptor(
                                    uint locale,
                                    uint dwFlags,
                                    SystemTime lpDate,
                                    string lpFormat,
                                    StringBuilder lpDateStr,
                                    int sbSize)
{
}

That's it! Now we have time to process the received lpDate parameter and set our own lpDateStr value. Windows will use the value later to show the date information.

Installing an EasyHook

Before installing our hooks, we should add a digital signature (an snk file) to both the console application and the class library project using the Signing tab of each project in Visual Studio. Then add a new class to the installer project (the console application) with the following signature:

public class MessagesReceiverInterface : MarshalByRefObject
{
    public void Ping()
    {
    }
}

EasyHook will use this class to receive the messages from the installed hook by using .NET remoting capabilities.

var channel = RemoteHooking.IpcCreateServer<MessagesReceiverInterface>(ref _channelName, WellKnownObjectMode.SingleCall); 

This channel will be created by using the RemoteHooking.IpcCreateServer call which accepts the mentioned MarshalByRefObject class as its generic argument. And then by calling the RemoteHooking.Inject method, it will inject our hooks (the ExplorerPCal.Hooks.dll here) to the target process.

RemoteHooking.Inject(
        explorer.Id,
        InjectionOptions.Default | InjectionOptions.DoNotRequireStrongName,
        "ExplorerPCal.Hooks.dll", // 32-bit version (the same, because of using AnyCPU)
        "ExplorerPCal.Hooks.dll", // 64-bit version (the same, because of using AnyCPU)
        _channelName
        );   

Its first argument is the PID of the target process which can be found by using the Process.GetProcesses() method. Because our hooking class library uses the 'Any CPU' setting in its properties, it can be used for both 32 and 64-bit versions of Windows. The last parameter can be a list of parameters and that's why the ctor and Run method of GetDateTimeFormatInjection can accept multiple parameters.

.ctor(IContext, %ArgumentList%)
void Run(IContext, %ArgumentList%) 

Does it work?!

Yes! Here you can see the effect of these hooks on the Windows calendar and date/time formats:

You can download its full source code from the beginning of the current article.

Important tips

  • Right now EasyHook is not compatible with Windows 8 (it works fine with Windows XP and 7).
  • You should deploy all of the related .dll and .exe files of EasyHook, and the EasyHook.dll file alone is not enough.
  • If your new hook crashes the target process immediately, it means its Win32 API signature is wrong. For example, the GetDateFormatW signature in a lot of sites is defined using the C# structures. But sometimes Window sends a null value to receive the current time, and C# structures are value types and do not accept null values.
  • When you install your EasyHook, it can't be unloaded and the target process should be terminated. It's a pain during the development.
  • You need to keep your EasyHook library in memory this way:
try
{
   while (true)
   {
     Thread.Sleep(500);
     _interface.Ping();
   }
}
catch
{
  _interface = null;
  // .NET Remoting will raise an exception if host is unreachable
}

Otherwise it will be unloaded by the CLR immediately. This method pings the MessagesReceiverInterface class and as long as it's available, it will continue to work.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here