|
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.