Goal:
While running inside another process (... Say a plugin inside an Office
application like Word
), intercept the calls to OleSetClipboard/OleGetClipboard
and proxy the iDataObject
interface by replacing the object originally set/get by the application with one we control, passing on calls to the original object as needed.
Essentially, I would like to control access to the clipboard for a given application.
Progress:
I have successfully hooked the OleSet/GetClipboard
functions (as well as others) and can replace the objects with my "proxy" objects. My proxy objects use a .Net definition of IDataObject
(System.Runtime.InteropServices.ComTypes.IDataObject
).
Note, in this case, I'm using C#
, but a similar breakdown happened when I reimplemented in c++
.
Problem:
Surprise ... Surprise, the application doesn't function completely normally when I proxy the OleSetClipboard
function. Its does just fine when I hook only the OleGetClipboard
function side.
I can see my proxied objects on the getter and setter sides calling each other. The breakdown seems to be that when the proxy object on the olegetclipboard side needs to pass thru a getdata call, the olesetclipboard side proxy will throw an exception while trying to execute getdata on the original object that is being proxied on the setter side.
Sudo Example:
Note, this example is not specific to this format. Most/All non text formats fails this way.
I see that the set side calls GetData on the object it is proxying from the original OleSetClipboard call from Word:
Proxy Object (OLESETCLIPBOARD): GetData Called for 15
The structure seems correct:
FORMATETC Structure Info: cfFormat (15) dwAspect (DVASPECT_CONTENT) lindex (-1) ptd (0) tymed (TYMED_HGLOBAL)
But I get an exception:
System.Runtime.InteropServices.COMException:GetData: Message:Invalid FORMATETC structure (Exception from HRESULT: 0x80040064 (DV_E_FORMATETC))
What may be wrong? What have I not considered? Other thoughts?
Thanks!
Update:
Example class used to proxy ...
public enum DataObjectSource { OLESETCLIPBOARD = 0, OLEGETCLIPBOARD = 1};
public class DataObjectProxy System.Runtime.InteropServices.ComTypes.IDataObject
{
private System.Runtime.InteropServices.ComTypes.IDataObject OriginalClipboardObject;
private DataObjectSource eSource;
public DataObjectProxy(object OriginalObjectToWrap, DataObjectSource eType)
{
this.OriginalClipboardObject = (System.Runtime.InteropServices.ComTypes.IDataObject)OriginalObjectToWrap;
this.eSource = eType;
}
#region Utility Methods
public string GetSingleLineLoggableOutput(FORMATETC FormatToPrint)
{
return $"FORMATETC Structure Info: cfFormat ({(ushort)FormatToPrint.cfFormat} - {GetClipboardFormatName((uint)FormatToPrint.cfFormat)}) dwAspect ({FormatToPrint.dwAspect}) lindex ({FormatToPrint.lindex}) ptd ({FormatToPrint.ptd}) tymed ({FormatToPrint.tymed})";
}
private String GetClipboardFormatName(uint ClipboardFormat)
{
StringBuilder sb = new StringBuilder(1000);
GetClipboardFormatName(ClipboardFormat, sb, sb.Capacity);
return sb.ToString();
}
#endregion Utility Methods
#region pInvokes
[DllImport("user32.dll")]
static extern int GetClipboardFormatName(uint format, [Out] StringBuilder lpszFormatName, int cchMaxCount);
#endregion pInvokes
#region System.Runtime.InteropServices.ComTypes.IDataObject Interface Implemenation
public int DAdvise(ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection)
{
HookLogger.LogDebug(HookLogger.HOOK_CHANNEL, $"Proxy Object ({eSource}): DAdvise Called");
return OriginalClipboardObject.DAdvise(ref pFormatetc, advf, adviseSink, out connection);
}
public void DUnadvise(int connection)
{
HookLogger.LogDebug(HookLogger.HOOK_CHANNEL, $"Proxy Object ({eSource}): DUnadvise Called");
OriginalClipboardObject.DUnadvise(connection);
}
public int EnumDAdvise(out IEnumSTATDATA enumAdvise)
{
HookLogger.LogDebug(HookLogger.HOOK_CHANNEL, $"Proxy Object ({eSource}): EnumDAdvise Called");
return OriginalClipboardObject.EnumDAdvise(out enumAdvise);
}
public IEnumFORMATETC EnumFormatEtc(DATADIR direction)
{
HookLogger.LogDebug(HookLogger.HOOK_CHANNEL, $"Proxy Object ({eSource}): EnumFormatEtc Called");
return OriginalClipboardObject.EnumFormatEtc(direction);
}
public int GetCanonicalFormatEtc(ref FORMATETC formatIn, out FORMATETC formatOut)
{
HookLogger.LogDebug(HookLogger.HOOK_CHANNEL, $"Proxy Object ({eSource}): GetCanonicalFormatEtc Called");
return OriginalClipboardObject.GetCanonicalFormatEtc(ref formatIn, out formatOut);
}
public void GetData(ref FORMATETC format, out STGMEDIUM medium)
{
try
{
HookLogger.LogDebug(HookLogger.HOOK_CHANNEL, $"Proxy Object ({eSource}): GetData Called for {(ushort)format.cfFormat}");
HookLogger.LogDebug(HookLogger.HOOK_CHANNEL, GetSingleLineLoggableOutput(format));
STGMEDIUM medTemp;
FORMATETC formatTemp = format;
OriginalClipboardObject.GetData(ref formatTemp, out medTemp);
medium = medTemp;
}
catch(Exception getException)
{
HookLogger.LogException(HookLogger.HOOK_CHANNEL, "GetData", getException);
throw;
}
}
public void GetDataHere(ref FORMATETC format, ref STGMEDIUM medium)
{
HookLogger.LogDebug(HookLogger.HOOK_CHANNEL, $"Proxy Object ({eSource}): GetDataHere Called");
OriginalClipboardObject.GetDataHere(ref format, ref medium);
}
public int QueryGetData(ref FORMATETC format)
{
HookLogger.LogDebug(HookLogger.HOOK_CHANNEL, $"Proxy Object ({eSource}): QueryGetData Called");
return OriginalClipboardObject.QueryGetData(ref format);
}
public void SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release)
{
HookLogger.LogDebug(HookLogger.HOOK_CHANNEL, $"Proxy Object ({eSource}): SetData Called");
OriginalClipboardObject.SetData(ref formatIn, ref medium, release);
}
#endregion System.Runtime.InteropServices.ComTypes.IDataObject Interface Implemenation
}
User contributions licensed under CC BY-SA 3.0