Wednesday, January 20, 2010

IFileOperation hook under Vista/Seven

Hey guys. I was able to successfully hook IFileOperation methods under Windows Vista and Windows Seven. This made me able to intercept file operations done by explorer.exe in order to log, change it's parameters, accept or deny file copies, movements, renaming or deletion. All good, but this can be a long subject. Given this, here is the source code. It has being written using Borland Delphi 7 using MadCodeHook, but you can use any library you want for code hooking.


The idea behind this scene is to hook the interface method by it's index. It's likelly if you have a table of procedures in a memory area. This is how the interface is exposed in memory. All we do is to patch this table, using madCodeHook's function hookCode(). It will place a JUMP inside the place where the entries of this table points to.


These are the methods of IFileOperation:



/* IUnknown methods */
STDMETHOD( QueryInterface )( THIS_ REFIID, void ** ) PURE;
STDMETHOD_( ULONG, AddRef )( THIS ) PURE;
STDMETHOD_( ULONG, Release )( THIS ) PURE;


/* IFileOperation methods */
STDMETHOD( Advise )( THIS_ IFileOperationProgressSink *, DWORD * ) PURE;
STDMETHOD( Unadvise )( THIS_ DWORD ) PURE;
STDMETHOD( SetOperationFlags )( THIS_ DWORD ) PURE;
STDMETHOD( SetProgressMessage )( THIS_ LPCWSTR ) PURE;
STDMETHOD( SetProgressDialog )( THIS_ IOperationsProgressDialog * ) PURE;
STDMETHOD( SetProperties )( THIS_ IPropertyChangeArray * ) PURE;
STDMETHOD( SetOwnerWindow )( THIS_ HWND ) PURE;
STDMETHOD( ApplyPropertiesToItem )( THIS_ IShellItem * ) PURE;
STDMETHOD( ApplyPropertiesToItems )( THIS_ IUnknown * ) PURE;
STDMETHOD( RenameItem )( THIS_ IShellItem *, LPCWSTR, IFileOperationProgressSink * ) PURE;
STDMETHOD( RenameItems )( THIS_ IUnknown *, LPCWSTR ) PURE;
STDMETHOD( MoveItem )( THIS_ IShellItem *, IShellItem *, LPCWSTR, IFileOperationProgressSink * ) PURE;
STDMETHOD( MoveItems )( THIS_ IUnknown *, IShellItem * ) PURE;
STDMETHOD( CopyItem )( THIS_ IShellItem *, IShellItem *, LPCWSTR, IFileOperationProgressSink * ) PURE;
STDMETHOD( CopyItems )( THIS_ IUnknown *, IShellItem * ) PURE;
STDMETHOD( DeleteItem )( THIS_ IShellItem *, IFileOperationProgressSink * ) PURE;
STDMETHOD( DeleteItems )( THIS_ IUnknown * ) PURE;
STDMETHOD( NewItem )( THIS_ IShellItem *, DWORD, LPCWSTR, LPCWSTR, IFileOperationProgressSink * ) PURE;
STDMETHOD( PerformOperations )( THIS ) PURE;
STDMETHOD( GetAnyOperationsAborted )( THIS_ BOOL * ) PURE;


Given this, all we need to do is to count how many methods we have till we find the one we want. In case we want this ones: RenameItem, RenameItems, MoveItem, MoveItems, CopyItem, CopyItems, DeleteItem and DeleteItems. As Gabriel Lopes, a friend, said on his tests, DeleteItem will aways call the base kernel32 API called DeleteFileA/W. But we are still hooking inside the interface to keep it standardized. Theier indexes are 12, 13, 14, 15, 16, 17 and 19. The interface GUID we need to look for before hooking is {3AD05575-8857-4850-9277-11B85BDB8E09}Interface hooking is accomplished by hooking the API CoCreateInstance and comparing the GUID with that one.


All functions wich manages with more then one item, according to MSDN will receive something called PunkItems instead of only one ShellItem. This PunkItem can be a ShellItemArray or a DataObject. Those ShellItems have this method called GetDisplayName(), which will tell us the full path of the item being copied, moved or deleted. The ShellItemArray is an interfaced object with the key methods getCount() and getItemAt() that able us to walk trough the ShellItems. The DataObject itself was the one which gave me more work, basically, there is this API from shell32.dll called SHCreateShellItemArrayFromDataObject. Obvious unh? But I would never guess by my self the existence of this API. Anyway, it works and can convert the DataObject passed to DeleteItems, MoveItems and CopyItems to a ShellItem.


Another detail before we go into the code. Note that on the hooked methods there's a first param of type POINTER, which there's is not on the MSDN definitions. That's given because all method inside an interface must have somewhere in the function the "self" pointer.


Here's the code. Maybe you want to copy it to notepad or any editor for a better viewing. Ah, of course, you need to inject it in order to get other processes monitoring.




library ifo;

// Coded by Bruno Martins Stuani
// brunildo@gmail.com
// ---------------------
// You are free to use this code on any situation.

uses
  SysUtils,
  Classes,
  Windows,
  madCodeHook,
  ActiveX;

{$R *.res}

type
  _tagpropertykey = packed record
    fmtid: TGUID;
    pid: DWORD;
  end;
  PROPERTYKEY = _tagpropertykey;

   //----------------------------------------------
   // IShellItem interface declaration
   //----------------------------------------------

  IShellItem = interface(IUnknown)
    ['{43826d1e-e718-42ee-bc55-a1e261c37bfe}']
    function BindToHandler(const pbc: IBindCtx; const bhid: TGUID;
      const riid: TIID; out ppv): HResult; stdcall;
    function GetParent(var ppsi: IShellItem): HResult; stdcall;
    function GetDisplayName(sigdnName: DWORD; var ppszName: LPWSTR): HResult; stdcall;
    function GetAttributes(sfgaoMask: DWORD; var psfgaoAttribs: DWORD): HResult; stdcall;
    function Compare(const psi: IShellItem; hint: DWORD;
      var piOrder: Integer): HResult; stdcall;
  end;

   //----------------------------------------------
   // ISHellEnumItems interface declaration
   //----------------------------------------------

  IEnumShellItems = interface(IUnknown)
    ['{70629033-e363-4a28-a567-0db78006e6d7}']
    function Next(celt: ULONG; out rgelt; pceltFetched: PLongint): HResult; stdcall;
    function Skip(celt: ULONG): HResult; stdcall;
    function Reset: HResult; stdcall;
    function Clone(out ppenum: IEnumShellItems): HResult; stdcall;
  end;

   //----------------------------------------------
   // IShellItemArray interface declaration
   //----------------------------------------------

  IShellItemArray = interface(IUnknown)
    ['{b63ea76d-1f85-456f-a19c-48159efa858b}']
    function BindToHandler(const pbc: IBindCtx; const rbhid: TGUID;
      const riid: TIID; out ppvOut): HResult; stdcall;
    function GetPropertyStore(flags: DWORD; const riid: TIID; out ppv): HResult; stdcall;
    function GetPropertyDescriptionList(const keyType: PropertyKey;
      const riid: TIID; out ppv): HResult; stdcall;
    function GetAttributes(dwAttribFlags: DWORD; sfgaoMask: DWORD;
      var psfgaoAttribs: DWORD): HResult; stdcall;
    function GetCount(var pdwNumItems: DWORD): HResult; stdcall;
    function GetItemAt(dwIndex: DWORD; var ppsi: IShellItem): HResult; stdcall;
    function EnumItems(var ppenumShellItems:    IEnumShellItems): HResult; stdcall;
  end;

   //----------------------------------------------
   // SHCreateShellItemArrayFromDataObject declaration
   //----------------------------------------------

   TSHCreateShellItemArrayFromDataObject = function(pdo: IDataObject;
      const riid: TGUID; ppv: Pointer): HRESULT; StdCall;

   //----------------------------------------------
   // Operations done by IFileOperation
   //----------------------------------------------

   TFileOperation = (opCopy, opMove, opDelete, opRename);

var
   //---------------------------------------------------------------------------
   // Hook's nextProcs
   //---------------------------------------------------------------------------

   CoCreateInstance_np: function(const clsid: TCLSID; unkOuter: Pointer;
     dwClsContext: Longint; const iid: TIID; pv: Pointer): HResult; stdcall;

   CopyItems_np: function( p: POinter; punkItems: IUnknown;
      psiDestinationFolder: IShellItem ): HRESULT; stdcall;

   CopyItem_np: function( p: POinter; psiitem: IShellItem;
      psiDestinationFolder: IShellItem; pszCopyName: LPCWSTR;
      pfopsItem: Pointer ): HRESULT; stdcall;

   DeleteItem_np: function( p: POinter; psiItem: IShellItem; pfopsItem:
      Pointer ): HRESULT; stdcall;

   DeleteItems_np: function( p: POinter; punkItems: IUnknown ): HRESULT; stdcall;

   MoveItems_np: function( p: POinter; punkItems: IUnknown;
      psiDestinationFolder: IShellItem ): HRESULT; stdcall;

   MoveItem_np: function( p: POinter; psiitem: IShellItem;
      psiDestinationFolder: IShellItem; pszCopyName: LPCWSTR;
      pfopsItem: Pointer ): HRESULT; stdcall;

   RenameItem_np: function( p: Pointer; psiItem: IShellItem; pszNewName:
      LPCWSTR; pfopsItem: Pointer ): HRESULT; stdcall;

   RenameItems_np: function( p: Pointer; punkItems: IUnknown; pszNewName:
      LPCWSTR ): HRESULT; stdcall;

const
   IID_ShellItemArray : TGUID = '{b63ea76d-1f85-456f-a19c-48159efa858b}';
   IID_DataObject: TGUID = '{0000010E-0000-0000-C000-000000000046}';
   SIGDN_FILESYSPATH     = $80058000;
   SIGDN_NORMALDISPLAY   = $00000000;

//----------------------------------------------
// Little function for string to PWideChar convertion
//----------------------------------------------

function PWideToString( wide: PWideChar ): string;
var
   pAtual: Pointer;
begin
   pAtual := wide;
   Result := '';

   // A PWideChar ends when a #0#0 is found. 2 bytes, then we can
 // typecast for a Word comparasion

   while PWord( pAtual )^ <> 0 do
   begin

      // We use only the first byte. Jump 2.
      Result := Result + Chr( PByte( pAtual )^ );
      pAtual := Pointer( Integer( pAtual ) + 2 );
   end;
end;

//----------------------------------------------
// Convert and returns as true, to be used on one line IF
//----------------------------------------------


function ConvertWtoS( wide: PWideChar; var output: string ): Boolean;
begin
   Result := True;
   output := PWideToString( wide );
end;

//----------------------------------------------
// All one item operation will step here.
//----------------------------------------------

function canPerform_ShellItem( item, dest: IShellItem; secParam: LPCWSTR;
   op: TFileOperation ): Boolean;
var
   itemPath, destPath: PWideChar;
   sItemPath, sDestPath: string;
begin
   Result := True;

   if Item.GetDisplayName( SIGDN_FILESYSPATH, itemPath ) = S_OK then
   begin
      // Extract the origin file path
      sItemPath := PWideToString( itemPath );

      // For deletion, there's no need for destiny
      if op <> opDelete then
      begin
         // There's destiny
         if ( dest <> nil ) and (dest.GetDisplayName(
            SIGDN_FILESYSPATH, destPath ) = S_OK) then

              // Transforms the string including a path delimiter
              sDestPath := IncludeTrailingPathDelimiter( PWideToString( destPath ) )
         else sDestPath := IncludeTrailingPathDelimiter( ExtractFilePath( sItemPath ) );


         // If there's no destiny, we'll use the origin
         if secParam = nil then
              sDestPath := sDestPath + ExtractFileName( sItemPath )
         else sDestPath := sDestPath + PWideToString( secParam );
      end;

      case op of
         opCopy:
            // Shows a messageBox
            Result := MessageBoxA( 0, PChar( 'Copying from: ' + sItemPath + #13#10 + 'to: ' + sDestPath ),'Allow?', MB_YESNO ) = ID_YES;

         opMove  :
            // Shows a messageBox
            Result := MessageBoxA( 0, PChar( 'Moving from: ' + sItemPath + #13#10 + 'to: ' + sDestPath ),'Allow?', MB_YESNO ) = ID_YES;

         opDelete:
            // Shows a messageBox
            Result := MessageBoxA( 0, PChar( 'Deleteing file: ' + sItemPath ),'Allow?', MB_YESNO ) = ID_YES;

         opRename:
            // Shows a messageBox
            Result := MessageBoxA( 0, PChar( 'Renaming from: ' + sItemPath + #13#10 + 'to: ' + sDestPath ),'Allow?', MB_YESNO ) = ID_YES;

      end;
   end;
end;

//-------------------------------------
// Many items performing method
//-------------------------------------

function canPerform_ShellItemArray( itemArr: IShellItemArray; dest: IShellItem;
   op: TFileOperation ): Boolean;
var
   nTotal: Cardinal;
   nAux: Integer;
   shellItem: IShellItem;
begin
   Result := True;

   // Is a valid array?
   if itemArr.GetCount( nTotal ) = S_OK then
   begin
      for nAux := 0 to nTotal -1 do
      begin
   
         // Extract the current item
         if itemArr.GetItemAt( nAux, shellItem ) = S_OK then
         begin
            // check if the operation can be performed
            Result := Result and canPerform_ShellItem( shellItem, dest, nil, op );

            // In abortion case, break
            if not Result then
               Break;
         end;
      end;
   end;
end;

//-------------------------------------
// Many items performing method, trough IDataObject
//-------------------------------------

function canPerform_DataObject( dataObject: IDataObject; dest: IShellItem;
   op: TFileOperation ): Boolean;
var
   SHConverteFromData: TSHCreateShellItemArrayFromDataObject;
   shellItemArr: IShellItemArray;
begin
   Result := True;

   // Windows Vista has implemented a function to convert an
 // IDataObject to ISHellItemArray. We'll use it.

   @SHConverteFromData := GetProcAddress( GetModuleHandle('shell32.dll'),
      'SHCreateShellItemArrayFromDataObject' );

   // Functoun found, use it.
   if (@SHConverteFromData <> nil) and ( SHConverteFromData(
      dataObject, IID_ShellItemArray, @shellItemArr ) = S_OK ) then
   begin

      // Perform the item now.
      Result := canPerform_ShellItemArray( shellItemArr, dest, op );
   end;
end;

//------------------------------------
// Many items performing method, trough punkData
//------------------------------------

function canPerform_PunkItem( punkItems: IUnknown; dest: IShellItem;
   op: TFileOperation ): Boolean;
var
   shellItemArr: IShellItemArray;
   dataObject: IDataObject;
begin
   Result := True;

   if punkItems.QueryInterface( IID_ShellItemArray, shellItemArr ) = S_OK then

      // If we have a ShellItemArr, check directly
      result := canPerform_ShellItemArray( shellItemArr, dest, op )


   // In case of IDataObject, convert to IShellItemArray
   else if punkItems.QueryInterface( IID_DataObject, dataObject ) = S_OK then

      result := canPerform_DataObject( dataObject, dest, op );
end;

//------------------------------------------
// Given an interface pointer, find out the position by it's index
//------------------------------------------

function GetInterfaceMethod(const intf; methodIndex: dword) : pointer;
begin
   result := pointer(pointer(dword(pointer(intf)^) + methodIndex * sizeOf(cardinal))^);
end;

//-----------------------------
// DeleteItems callBack
//-----------------------------

function DeleteItems_cb( p: POinter; punkItems: IUnknown ): HRESULT; stdcall;
begin
   if canPerform_PunkItem( punkItems, nil, opDelete ) then
      Result := deleteItems_np( p, punkItems )

   else Result := E_ABORT;
end;




//-----------------------------
// DeleteItem callBack
//-----------------------------


function DeleteItem_cb( p: POinter; psiItem: IShellItem;
   pfopsItem: Pointer ): HRESULT; stdcall;
begin
   if canPerform_ShellItem( psiItem, nil, nil, opDelete ) then
      Result := deleteItem_np( p, psiItem, pfopsItem )

   else Result := E_ABORT;
end;




//-----------------------------
// CopyItem callBack
//-----------------------------


function CopyItem_cb( p: POinter; psiItem: IShellItem; psiDestinationFolder:
   IShellItem; pszCopyName: LPCWSTR; pfopsItem: Pointer ): HRESULT; stdcall;
begin
   if canPerform_ShellItem( psiItem, psiDestinationFolder, pszCopyName, opCopy ) then
      Result := CopyItem_np( p, psiItem, psiDestinationFolder, pszCopyName,
         pfopsItem )

   else Result := E_ABORT;
end;




//-----------------------------
// CopyItems callBack
//-----------------------------


function CopyItems_cb( p: POinter; punkItems: IUnknown; psiDestinationFolder:
   IShellItem ): HRESULT; stdcall;
begin
   if canPerform_PunkItem( punkItems, psiDestinationFolder, opCopy ) then
      Result := CopyItems_np( p, punkItems, psiDestinationFolder )

   else Result := E_ABORT;
end;




//-----------------------------
// MoveItem callBack
//-----------------------------


function MoveItem_cb( p: POinter; psiItem: IShellItem; psiDestinationFolder:
   IShellItem; pszCopyName: LPCWSTR; pfopsItem: Pointer ): HRESULT; stdcall;
begin
   if canPerform_ShellItem( psiItem, psiDestinationFolder, pszCopyName, opMove ) then
      Result := MoveItem_np( p, psiItem, psiDestinationFolder, pszCopyName,
         pfopsItem )

   else Result := E_ABORT;
end;




//-----------------------------
// MoveItems callBack
//-----------------------------


function MoveItems_cb( p: POinter; punkItems: IUnknown; psiDestinationFolder:
   IShellItem ): HRESULT; stdcall;
begin
   if canPerform_PunkItem( punkItems, psiDestinationFolder, opMove ) then
      Result := MoveItems_np( p, punkItems, psiDestinationFolder )

   else Result := E_ABORT;
end;




//-----------------------------
// RenameItem callBack
//-----------------------------


function RenameItem_cb( p: Pointer; psiItem: IShellItem; pszNewName:
   LPCWSTR; pfopsItem: Pointer ): HRESULT; stdcall;
begin
   if canPerform_ShellItem( psiItem, nil, pszNewName, opRename ) then
      Result := RenameItem_np( p, psiItem, pszNewName, pfopsItem )

   else Result := E_ABORT;
end;




//-----------------------------
// RenameItems callBack
//-----------------------------


function RenameItems_cb( p: Pointer; punkItems: IUnknown; pszNewName:
   LPCWSTR ): HRESULT; stdcall;
begin
   if canPerform_PunkItem( punkItems, nil, opRename ) then
      Result := RenameItems_np( p, punkItems, pszNewName )

   else Result := E_ABORT;
end;




//-----------------------------
// coCreateInstance callBack
//-----------------------------


function CoCreateInstance_cb(const clsid: TCLSID; unkOuter: Pointer;
  dwClsContext: Longint; const iid: TIID; pv: Pointer): HResult; stdcall;
const
   IFileOperation_GUID = '3AD05575-8857-4850-9277-11B85BDB8E09';

   procedure HookFunctionIndex( index: Integer; CallBack: Pointer; var NextProc: Pointer );
   begin
      // Hook if it is not yet hooked
      if NextProc = nil then
         HookCode( GetInterfaceMethod( pv^, index ), CallBack, NextProc );
   end;

begin
   // Call the original API to get it's instance pointer
   Result := CoCreateInstance_np( clsid, unkOuter, dwClsContext, iid, pv );

   // Check IFileOperation GUID
   if pos( IFIleOperation_GUID, GUIDToString(clsid) ) <> 0 then
   begin

      //------------------------------------
      // Hook each function of our interface
      //------------------------------------

      HookFunctionIndex( 12, @RenameItem_Cb , @RenameItem_np  );
      HookFunctionIndex( 13, @RenameItems_Cb, @RenameItems_np );
      HookFunctionIndex( 14, @MoveItem_Cb   , @MoveItem_np    );
      HookFunctionIndex( 15, @MoveItems_cb  , @MoveItems_np   );
      HookFunctionIndex( 16, @CopyItem_cb   , @CopyItem_np    );
      HookFunctionIndex( 17, @CopyItems_cb  , @CopyItems_np   );
      HookFunctionIndex( 18, @DeleteItem_cb , @DeleteItem_np  );
      HookFunctionIndex( 19, @DeleteItems_cb, @DeleteItems_np );
   end;
end;

begin
   // coCreateInstance hook
   HookAPI( 'ole32.dll', 'CoCreateInstance', @CoCreateInstance_cb, @CoCreateInstance_np );
end.

34 comments:

Unknown said...

yes, it's useful.
Do you sink its event?

Bruno Stuani said...

Perfect. I'll start this writting by this week ;-)

Anonymous said...

Hi.
Wow Wonderful....
I want to get your the source code.
Please.....

send to juneblue99@naver.com

thanks

Anonymous said...

Thanks your goodwilllllllll

I'm to got it, obvious your code.

TT

But I don't understand to "HookFunctionIndex".

How to hook Interface......??

I'm wait your answer.


Best Regard....

Bruno Stuani said...

Hi Anonymous. I've updated the text to describe the step of hooking the interface. Take a look.

Bests

Anonymous said...

I only get the following two GUIDS from explorer in my CoCreateInstance hook:
{11DBB47C-A525-400B-9E80-A54615A090C0}
{682159D9-C321-47CA-B3F1-30E36B2EC8B9}

Do you hit those too then get the IFileOperation one after you do a copy paste operation? (Running Win7 Ultimate 64bit with MadHook 3)

Bruno Stuani said...

Maybe you are hooking the wrong explorer.exe instance?

Anonymous said...

My hook is system wide and I get all 32bit/64bit processes. Which GUIDs do you get on your end? Do you get the two I mentioned before when explorer.exe is launched? then the FileOperation one when you do Copy/Paste?

Bruno Stuani said...

Well, I don't remember exactly which ones I got. But I can tell you it took me almost two weeks to find the exactly GUID. I started logging all GUIDs in an interval of copy & paste, and, after that started looking at Google when I finally found this one: '3AD05575-8857-4850-9277-11B85BDB8E09'.

If you are not getting it, make sure to test on a 32b Vista/Seven. Do you have an specific 64b hook DLL for the 64b processes with the signed driver and everything else OK for the 3.0 madchook?

If you are having specific problems you can contact me via brunildo [at] gmail {dot} com

Anonymous said...

Just tried on Vista 64bit and I do get the IFileOperation GUID and a bunch of others, but not on Win7 64bit...Maybe a good old reboot will clear up my Win7 64bit machine!!!

Bruno Stuani said...

Keep us up to date =)

Unknown said...

hi, please put the MadHookcode unit link

Bruno Stuani said...

Well, MadCodeHook is a package you can find in http://www.madshi.net/

Anonymous said...

Could you post the complete source? :O

John said...

Great Work!
I think if the source code is also available in C++ form, you contribution to the world would be more.

Bruno Stuani said...

John, I agree. If I had more time I would write it in C++ form, but when I wrote my needs were to write it in Delphi :-(

John said...

I tried to implement your method with C++, so far it's good for CoCreateInstance, and the GUID is correct for my win 7 PC.
But after GetInterfaceMethod the code crashed at HookCode function.
I tried both API Hook and memcpy way to mimic MadCodeHook's HookCode function, with no luck.
Would you please help me with HookCode in C++?

Bruno Stuani said...

John, try with this:

PVOID GetInterfaceMethod(PVOID intf, DWORD methodIndex)
{
return *(PVOID*)(*(DWORD*)intf + methodIndex * 4);
}

and then use to call as the hookCode parameter:

GetInterfaceMethod(*pv, index)

Please, also, do not forget the WINAPI calling convention - in Delphi we use "stdcall". Still the same results?

John said...

it worked!
I think my old code's crash is due to the GetInterfaceMethod(*pv, index) was GetInterfaceMethod(pv, index)
Now I can continue to implement Win 7's File Copy/Move Monitoring.
Thank you, Bruno.

Bruno Stuani said...

Awesome! Great work

John said...

Dear Bruno,
Now my implementation won't crash but after CoCreateInstance procedure it does'nt catch CopyItem event as expected.
My C++ code in CoCreateInstance is like this:
HRESULT WINAPI HookedCoCreateInstance (
__in REFCLSID rclsid,
__in LPUNKNOWN pUnkOuter,
__in DWORD dwClsContext,
__in REFIID riid,
__out LPVOID *ppv )
{
const char *IFileOperation_GUID="{3AD05575-8857-4850-9277-11B85BDB8E09}";
char GUIDString[64];

HRESULT HR=OriginalCoCreateInstance(rclsid,pUnkOuter,dwClsContext,riid,ppv);

sprintf_s(GUIDString,64,"{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\0",
rclsid.Data1, rclsid.Data2, rclsid.Data3,
rclsid.Data4[0], rclsid.Data4[1],
rclsid.Data4[2], rclsid.Data4[3],
rclsid.Data4[4], rclsid.Data4[5],
rclsid.Data4[6], rclsid.Data4[7]);

if(strcmp(GUIDString,IFileOperation_GUID)==0)
{
MessageBoxA(NULL,"IFileOperation_GUID Found",GUIDString,MB_OK);
if(OriginalCopyItem==NULL)
{
OriginalCopyItem=(PCopyItem)GetInterfaceMethod(*ppv,16);
MessageBoxA(NULL,"AFTER GetInterfaceMethod","TEST",MB_OK);
if(Mhook_SetHook((PVOID*)&OriginalCopyItem,HookedCopyItem))
MessageBoxA(NULL,"AFTER Mhook_SetHook","OK",MB_OK);
else
MessageBoxA(NULL,"AFTER Mhook_SetHook","Failed",MB_OK);
}
}

return HR;
}

And the CopyItem part is like this:
HRESULT HookedCopyItem( __in IShellItem *psiItem,
__in IShellItem *psiDestinationFolder,
__in_opt LPCWSTR pszCopyName,
__in_opt IUnknown *pfopsItem )
{
WCHAR srcFN[MAX_PATH];
WCHAR dstFN[MAX_PATH];

MessageBox(NULL,L"Before GetDisplayName",L"HookedCopyItem",MB_OK);

psiItem->GetDisplayName(SIGDN_FILESYSPATH,(LPOLESTR*)&srcFN[0]);
psiDestinationFolder->GetDisplayName(SIGDN_FILESYSPATH, (LPOLESTR*)&dstFN[0]);

MessageBox(NULL,dstFN,srcFN,MB_OK);
return OriginalCopyItem(psiItem,psiDestinationFolder,pszCopyName,pfopsItem );
}

I thought I have followed your method but it must have something wrong. I really appreciate your help.

John said...

Well, I found the problem was due to explorer's behavior, copying a single file triggers CopyItems instead of CopyItem. The IFileOperation hook method is still OK.

Bruno Stuani said...

Sorry for the late repply. Awesome!

Sandip Karia said...

Excellent code. But there is a dependency of madCodeHook (http://www.madshi.net/) and it is not free and tiral version also also not available. So how to test?

Bruno Stuani said...

Hi Sandip Karia. There are some free but not so good alternatives out there. You could try uallCollection, magicApiHook, BmpApiHook, there's one inside Denomo you can use, here: http://www.kbasm.com/download.html

You can also try googling for Delphi API Hooking. The concept is the same on all libraries, the only difference is how to call their APIs.

Bests

M. Murat Dicle said...

First of all, thank you.
This code works in Windows 7. Super:) and thanks again. However, Vista also does not work.

XP (32bit) works fine for the SHFileOperation.

Win7 (32bit) works fine for the IFileOperation.

If possible, I am requesting your help.

----------------
I'm using 32bit Vista.
Delphi 32bit, 2007 version.
p.s.: I'm madCodeHook licensed user. v2.x version

Bruno Stuani said...

Hi Murat Dicle!

Vista has some dificulties regarding the UAC, comparing to Seven. Given this, are you sure your DLL is being injected? Are you sure Windows is really calling IFileOperations? I heard sometimes IFileOperation is not used. You should create a testing app to make sure IFileOperation can be hooked using this code. Please update us with these answers!

Bests,
Bruno

M. Murat Dicle said...

Your codes works fine in Vista/Win7

Thanks.

M. Murat Dicle said...

BTW,

How can I hook filetype for ".URL"

Bruno Stuani said...

Hello Murat,

You should be able to hook only .URL files by placing some IFs inside your callback code. Actually you are just filtering, and not hooking only .URL extensions.

e2 said...

Dear John,
Can you share your C++ at equityloverhater@gmail.com!

e2 said...

I mean the code!

Unknown said...

Hi guys....its an awesome discussion on IFileOperation. Can you send the vc++ code to ashraf798@gmail.com plz? I need it for my college project. I tried the snippet using minhook, but the real function of copyitems is not getting called.

Used PC Distributor said...

Nice Blog Post !