Deviare v.2.0‎ > ‎

Deviare Quickstart_v.2.0


Introduction

This quick start will show the basic capabilities for intercepting system calls with Deviare from a C# program. Requirements are: a proper C# compiler/editor (VisualStudio or SharpDevelop), and Deviare 2.0

Projects should build without problems in Visual Studio 2015 with Update 1 or later.

Project Setup (Free COM)

We'll use Deviare with C#. Open a Visual Studio session and create a new C# Windows Forms project. Name it as 'DeviareTest'.

With the project successfully setup, select solution platform (x64 or x86), disable "Visual Studio hosting process" and "Create application without a manifest". Finally build the project and copy the next files to (Release\Debug) directory:

x86

 Nektra.Deviare2.dll  DeviareCOM.dll  DeviareCOM.X.manifest
 DvAgent.dll  Deviare32.db  

rename DeviareTest32.exe.manifest to DeviareTest.exe.manifest

x64

 Nektra.Deviare2.dll  DeviareCOM64.dll  DeviareCOM64.X.manifest
 DvAgent.dll  DvAgent64.dll  Deviare32.db
 Deviare64.db    

Rename DeviareTest64.exe.manifest to DeviareTest.exe.manifest
Then select "Add Reference" => "Browse" and include "Nektra.Deviare2.dll"

Hooking Basics

Our task will be to hook CreateFileW system calls. In this case, we'll use a known application such as NOTEPAD.EXE as a target for our example.

Modify thread model

[STAThread] -> [MTAThread]

Include namespace

using Nektra.Deviare2;

Prior to calling any Deviare library methods, we need to initialize the NktSpyMgr class. This can be done in the Form constructor:

_spyMgr = new NktSpyMgr();
_spyMgr.Initialize();

Get our target process

private NktProcess GetProcess(string proccessName)
{
    NktProcessesEnum enumProcess = _spyMgr.Processes();
    NktProcess tempProcess = enumProcess.First();
    while (tempProcess != null)
    {
        if (tempProcess.Name.Equals(proccessName, StringComparison.InvariantCultureIgnoreCase))
        {
            return tempProcess;
        }
        tempProcess = enumProcess.Next();
    }
    return null;
}

Then you can find a hardcoded process like notepad:

NktProcess _process = GetProcess("notepad.exe");

In the Form load event handler, we create a new Hook object passing the system call to be intercepted in module!function format to the _spyMgr.CreateHook function

NktHook hook = _spyMgr.CreateHook("kernel32.dll!CreateFileW", (int)(eNktHookFlags.flgOnlyPreCall));

Note that our handler will be called *before* the execution of the hooked function begins. Another option is flgOnlyPostCall, which calls our handler when the hooked function terminates (right after return), making it possible to inspect returned values. Using neither flag will generate events for both precall and postcall interception.


When this system call is executed in the context of the target process, we want to catch it in our event handler. To define this handler we can do the following:

hook.OnFunctionCalled += OnFunctionCalled;

We need to call Hook() to enable the interception mechanism for this particular hook.

hook.Hook(true);
hook.Attach(_process, true);

We specify the processes we want to attach. _process was returned from the GetProcess function above, and in our example represents the notepad process.

Finally, we use the OnFunctionCalled event to process the call. In this case, we will inspect CreateFileW information:

void OnFunctionCalled(INktHook hook, INktProcess proc, INktHookCallInfo callInfo)
{
    INktParamsEnum pms = callInfo.Params();
    INktParam p;
    string filename;
    uint access;

    //how to read a string
    p = pms.GetAt(0); //get the first param (LPCWSTR lpFileName)
    if (p.IsNullPointer == false)
        filename = p.ReadString();
    else
        filename = "";

    //how to read a simple value
    p = pms.GetAt(1); //get the second param (DWORD dwDesiredAccess)
    access = p.ULongVal; //you can freely analyze dwDesiredAccess flags
    
    //how to read a structure
    p = pms.GetAt(3); //get the fourth param (LPSECURITY_ATTRIBUTES lpSecurityAttributes)
    if (p.IsNullPointer == false)
    {
        INktParam pC;
        uint len;

        //if not null, analyze it
        p = p.Evaluate(); //now p represents the struct itself not anymore a pointer to it

        //now we want to read the length parameter
        pC = p.Field(0); //get the first field (DWORD nLength)
        len = pC.ULongVal; //store length

        pC = p.Field(1); //get the second field (LPVOID lpSecurityDescriptor)
        if (len > 0 && pC.IsNullPointer == false)
        {
            //at this point we have that the "lpSecurityDescriptor" points to the data and its length is "len" bytes
            //so we will read it into a byte array. To accomplish this task we will use some c# helpers and the INktProcessMemory interface

            INktProcessMemory procMem = _spyMgr.ProcessMemoryFromPID(proc.Id); //get the memory reader/writer

            var buffer = new byte[len]; //create our local buffer

            //use C# pin mechanism to avoid garbage collection
            System.Runtime.InteropServices.GCHandle pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
            IntPtr pDest = pinnedBuffer.AddrOfPinnedObject(); //get the real address of our byte buffer
            procMem.ReadMem(pDest, pC.PointerVal, (IntPtr)len); //copy the data from the hooked process
            pinnedBuffer.Free(); //unpin the buffer

            //at this point, "buffer" has the copy of the security descriptor data

        }
    }
}


Interception Handling

Now let's see how we can implement a simple reporting routine for every CreateFileW call.

private void OnFunctionCalled(NktHook hook, NktProcess process, NktHookCallInfo hookCallInfo)
{
...
...
}

As an example, we want to report the CreateFileW function parameters for each interception. Its prototype is:

HANDLE WINAPI CreateFile(
  __in      LPCTSTR lpFileName,
  __in      DWORD dwDesiredAccess,
  __in      DWORD dwShareMode,
  __in_opt  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  __in      DWORD dwCreationDisposition,
  __in      DWORD dwFlagsAndAttributes,
  __in_opt  HANDLE hTemplateFile
);

We can easily traverse those parameters using the hookCallInfo.Params() enumerator:

string strCreateFile = "CreateFile(\"";

INktParamsEnum paramsEnum = hookCallInfo.Params();

//lpFileName
INktParam param = paramsEnum.First();
strCreateFile += param.ReadString() + "\", ";

//dwDesiredAccess
param = paramsEnum.Next();
if ((param.LongVal & 0x80000000) == 0x80000000)
    strCreateFile += "GENERIC_READ ";
else if ((param.LongVal & 0x40000000) == 0x40000000)
    strCreateFile += "GENERIC_WRITE ";
else if ((param.LongVal & 0x20000000) == 0x20000000)
    strCreateFile += "GENERIC_EXECUTE ";
else if ((param.LongVal & 0x10000000) == 0x10000000)
    strCreateFile += "GENERIC_ALL ";
else
    strCreateFile += "0";
strCreateFile += ", ";

//dwShareMode
param = paramsEnum.Next();
if ((param.LongVal & 0x00000001) == 0x00000001)
    strCreateFile += "FILE_SHARE_READ ";
else if ((param.LongVal & 0x00000002) == 0x00000002)
    strCreateFile += "FILE_SHARE_WRITE ";
else if ((param.LongVal & 0x00000004) == 0x00000004)
    strCreateFile += "FILE_SHARE_DELETE ";
else
    strCreateFile += "0";
strCreateFile += ", ";

//lpSecurityAttributes
param = paramsEnum.Next();
if (param.PointerVal != IntPtr.Zero)
{
    strCreateFile += "SECURITY_ATTRIBUTES(";

    INktParamsEnum paramsEnumStruct = param.Evaluate().Fields();
    INktParam paramStruct = paramsEnumStruct.First();

    strCreateFile += paramStruct.LongVal.ToString();
    strCreateFile += ", ";

    paramStruct = paramsEnumStruct.Next();
    strCreateFile += paramStruct.PointerVal.ToString();
    strCreateFile += ", ";

    paramStruct = paramsEnumStruct.Next();
    strCreateFile += paramStruct.LongVal.ToString();
    strCreateFile += ")";
}
else
    strCreateFile += "0";
strCreateFile += ", ";

//dwCreationDisposition
param = paramsEnum.Next();
if (param.LongVal == 1)
    strCreateFile += "CREATE_NEW ";
else if (param.LongVal == 2)
    strCreateFile += "CREATE_ALWAYS ";
else if (param.LongVal == 3)
    strCreateFile += "OPEN_EXISTING ";
else if (param.LongVal == 4)
    strCreateFile += "OPEN_ALWAYS ";
else if (param.LongVal == 5)
    strCreateFile += "TRUNCATE_EXISTING ";
else
    strCreateFile += "0";
strCreateFile += ", ";

//dwFlagsAndAttributes
strCreateFile += param.LongVal;
strCreateFile += ", ";

//hTemplateFile
strCreateFile += param.LongLongVal;
strCreateFile += ");\r\n";

Output(strCreateFile);


With this code, we get an output like:

CreateFile("C:\file.dat", GENERIC_READ , FILE_SHARE_READ , SECURITY_ATTRIBUTES(24, 1700896, 0), OPEN_EXISTING , 3, 0);
CreateFile("D:\Music\desktop.ini", GENERIC_READ , FILE_SHARE_READ , 0, OPEN_EXISTING , 3, 0);
CreateFile("D:\Links\Downloads.lnk", GENERIC_READ , FILE_SHARE_READ , 0, OPEN_EXISTING , 3, 0);

Further research

Feel free to investigate and modify the C++/C# Deviare samples available, along with examining the API documentation provided.