overriding GetSecurityId in IInternetSecurityManager - c++

I have built an executable which launches a dialog box in which is embedded the IE web browser active-x control (C++).
I want this control to allow cross site scripting. One frame on the web page loads local html, the other loads from a server. I then want the server page to call a javascript function that lives in the local html file.
I am trying to achieve this by having the control implement it's own "IInternetSecurityManager" interface in which I am providing my own ProcessUrlAction and GetSecurityId methods.
From what I've read, what I need to do is make GetSecurityId return the same domain for all urls. My custom implementations are getting called, but no matter what I do, I get the "Permission denied" error when the server html tries to access script on the local html file. Below are my implementations. Does anyone see anything wrong?
#define SECURITY_DOMAIN "http:www.mysite.com"
STDMETHOD (GetSecurityId)(
LPCWSTR pwszUrl,
BYTE *pbSecurityId,
DWORD *pcbSecurityId,
DWORD_PTR dwReserved)
{
if (*pcbSecurityId >=512)
{
memset(pbSecurityId,0,*pcbSecurityId);
strcpy((char*)pbSecurityId,SECURITY_DOMAIN);
pbSecurityId[strlen(SECURITY_DOMAIN)] = 3;
pbSecurityId[strlen(SECURITY_DOMAIN)+1] = 0;
pbSecurityId[strlen(SECURITY_DOMAIN)+2] = 0;
pbSecurityId[strlen(SECURITY_DOMAIN)+3] = 0;
*pcbSecurityId = (DWORD)strlen(SECURITY_DOMAIN)+4;
return S_OK;
}
return INET_E_DEFAULT_ACTION;
}
STDMETHOD(ProcessUrlAction)(
/* [in] */ LPCWSTR pwszUrl,
/* [in] */ DWORD dwAction,
/* [size_is][out] */ BYTE __RPC_FAR *pPolicy,
/* [in] */ DWORD cbPolicy,
/* [in] */ BYTE __RPC_FAR *pContext,
/* [in] */ DWORD cbContext,
/* [in] */ DWORD dwFlags,
/* [in] */ DWORD dwReserved)
{
DWORD dwPolicy=URLPOLICY_ALLOW;
if ( cbPolicy >= sizeof (DWORD))
{
*(DWORD*) pPolicy = dwPolicy;
return S_OK;
}
return INET_E_DEFAULT_ACTION;
}

By delegating these functions to the normal security manager and having a look at the structures the normal security manager fills in, I was able to determine that my issue was in GetSecurityId. For my purposes, I wanted to set the security domain to be a local file for all comers.
#define SECURITY_DOMAIN "file:"
if (*pcbSecurityId >=512)
{
memset(pbSecurityId,0,*pcbSecurityId);
strcpy((char*)pbSecurityId,SECURITY_DOMAIN);
pbSecurityId[strlen(SECURITY_DOMAIN)+1] = 0;
pbSecurityId[strlen(SECURITY_DOMAIN)+2] = 0;
pbSecurityId[strlen(SECURITY_DOMAIN)+3] = 0;
pbSecurityId[strlen(SECURITY_DOMAIN)+4] = 0;
*pcbSecurityId = (DWORD)strlen(SECURITY_DOMAIN)+4;
}

Related

Can I create a CommandLink that works the same as the submit button?

Please understand my lack of writing skills.
I am testing to make a custom credential provider.
I want to create a CommandLink that does the same thing with the submit button.
I want to log on through the CommandLink separately from the Submit button.
Currently, only the custom credential provider is exposed through the providerFilter::Filter(CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus, DWORD dwFlags, GUID* rgclsidProviders, BOOL* rgbAllow, DWORD cProviders).
Click [anathor longon button] to log on.
This is my sample code:
HRESULT CSampleCredential::CommandLinkClicked(DWORD dwFieldID)
{
HRESULT hr = S_OK;
DWORD dwResult = 0;
if (dwFieldID < ARRAYSIZE(_rgCredProvFieldDescriptors) &&
(CPFT_COMMAND_LINK == _rgCredProvFieldDescriptors[dwFieldID].cpft))
{
HWND hwndOwner = nullptr;
switch (dwFieldID)
{
case SFI_ANATHOR_SUBMIT_LINK:
dwResult = function_foo();
if(dwResult == 1) {
Call GetSerialization()...?
Run the logon.
}
break;
// ...
}
}
}
Because you are writing credential provider, than you are already implementing ICredentialProvider interface and its Advise method:
virtual HRESULT STDMETHODCALLTYPE Advise(
/* [annotation][in] */
_In_ ICredentialProviderEvents *pcpe,
/* [annotation][in] */
_In_ UINT_PTR upAdviseContext) = 0;
The first argument is pointer to events interface ICredentialProviderEvents which have only one method: CredentialsChanged.
Your task is to grab credentials from user (login/password) store them inside your internals and call this method.
On the next turn your provider will be called this method:
virtual HRESULT STDMETHODCALLTYPE GetCredentialCount(
/* [annotation][out] */
_Out_ DWORD *pdwCount,
/* [annotation][out] */
_Out_ DWORD *pdwDefault,
/* [annotation][out] */
_Out_ BOOL *pbAutoLogonWithDefault) = 0;
Your task is to return the correct values in pdwDefault and pbAutoLogonWithDefault parameters (my suggest is 0 and TRUE).
Than your class that implementing ICredentialProviderCredential interface will be immediately called for GetSerialization method.
Here you can return already stored credentials.
Newbie here,
For the people who don't understand #Alexander 's explanation (like me several days ago), I recommend to read the Sample Harware Event Credential Provider. You need the 3rd class that is not inherited from ICredentialProvider or ICredentialProviderCredential. This is an independent class which is not related to both of them. I use this class to call the CredentialsChanged function.
So, TL;DR this is how I implement it:
Make a condition inside the ICredentialProvider's GetCredentialCount function like this
HRESULT CSampleCredentialProvider::GetCredentialCount(
DWORD* pdwCount,
DWORD* pdwDefault,
BOOL* pbAutoLogonWithDefault
)
{
//SavedCredentialInfo is just a simple Bool variable
if (_pCredential->SavedCredentialInfo == true)
{
*pdwCount = 1;
*pdwDefault = 0;
*pbAutoLogonWithDefault = TRUE;
}else{
*pdwCount = 1;
*pdwDefault = 0;
*pbAutoLogonWithDefault = FALSE;
}
return S_OK;
}
Save the credential (I use username & password) as a variable (string) inside my custom class that inherit ICredentialProviderCredential
HRESULT CGuardianCredential::CommandLinkClicked(DWORD dwFieldID)
{
if (dwFieldID < ARRAYSIZE(_rgCredProvFieldDescriptors) &&
(CPFT_COMMAND_LINK == _rgCredProvFieldDescriptors[dwFieldID].cpft)) {
this._username = "Ramon";
this._domain = "DESKTOP-937SDJ";
this._password = "MyPassword";
}
. . .
}
Call the 3rd class's function that call the ICredentialProvider's CredentialsChanged function
HRESULT CGuardianCredential::CommandLinkClicked(DWORD dwFieldID)
{
. . .
_commandWindow->callOnChange();
}
Then inside the commandWindow's class
BOOL CCommandWindow::callOnChange()
{
_pProvider->OnConnectStatusChanged();
return true;
}
Then you have it

What is the lowest level WinSock API available in the user mode? (For API injection trampoline.)

My goal is to intercept outbound TCP packets from a custom-built application that I do not have source code to. I need to adjust several parameters in the outbound data. It is an older application that the original company no longer sells and the developer is no longer available.
So I was planning to install an API injection trampoline into the send() type raw WinSock API from my DLL that I can inject into the target process. But before writing such DLL, I decided to test this concept in my local process. So I did the following:
#ifdef _M_X64
//Simple code to install "API injection trampoline"
//Compiled as 64-bit process
static int WINAPI TestJump1(SOCKET s, const char *buf, int len, int flags);
{
//This part is just for debugging to make sure that my trampoline method is called
//The actual "working" trampoline will involve additional steps to insure that the original method is also called
::MessageBox(NULL, L"Injected method called!", L"Debugger Message", MB_OK);
return SOCKET_ERROR;
}
HMODULE hModWS2 = ::LoadLibrary(L"Ws2_32.dll");
if(hModWS2)
{
int (WINAPI *pfn_send)(SOCKET s, const char *buf, int len, int flags);
int (WINAPI *pfn_sendto)(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen);
if(pfn_send &&
pfn_sendto)
{
//Long absolute JMP
//48 b8 xx xx xx xx xx xx xx xx mov rax, 0xxxx
//ff e0 jmp rax
BYTE subst[] = {
0x48, 0xb8,
0, 0, 0, 0,
0, 0, 0, 0,
0xff, 0xe0
};
HANDLE hProc = ::GetCurrentProcess();
VOID* pPtrAPI = pfn_send;
//Also tried with
//VOID* pPtrAPI = pfn_sendto;
//Make this address writable
DWORD dwOldProtect = 0;
if(::VirtualProtectEx(hProc, pPtrAPI, sizeof(subst), PAGE_EXECUTE_READWRITE, &dwOldProtect))
{
//Install JMP opcodes
VOID* pJumpPtr = TestJump1;
*(VOID**)(subst + 2) = pJumpPtr;
memcpy(pPtrAPI, subst, sizeof(subst));
//Reset it back
DWORD dwDummy;
if(::VirtualProtectEx(hProc, pPtrAPI, sizeof(subst), dwOldProtect, &dwDummy))
{
if(::FlushInstructionCache(hProc, pPtrAPI, sizeof(subst)))
{
//Try to call our method
//int rz = pfn_send(NULL, "", 0, 0); //This works!
//Try real test with the higher level API
//Download a web page into a file:
//This API must be calling raw sockets at some point internally...
//but my TestJump1() is never called from here...
HRESULT hr = URLDownloadToFile(NULL,
L"http://microsoft.com/",
L"C:\\Users\\User\\Desktop\\file.txt", 0, NULL);
}
}
}
}
}
#endif
So the code works fine, the JMP trampoline is installed and called alright if I call send method explicitly (as shown above) but my further assumption that a higher level API (i.e. URLDownloadToFile) would call it as well does not seem to hold true. My trampoline method is never called from it.
So what am I missing here? Is there an even lower WinSock API?
send() is not the only function available for sending TCP data using Winsock in user code. There is also:
WSASend()
WSASendDisconnect()
WSASendMsg()
TransmitFile()
TransmitPackets()
RIOSend/Ex()
At the very least, apps that don't use send() will usually use WSASend() instead, for use with Overlapped I/O or I/O Completion Ports. That is usually good enough for most situations. The other functions are not used very often, but may be used in certain situations where higher performance is really needed.
the lowest level winsock api is implemented by Winsock Service Provider Interface. during interface initialization the WSPStartup function is called from interface provider (this api must be exported by name from provider dll). for MSAFD Tcpip [TCP/IP] "{E70F1AA0-AB8B-11CF-8CA3-00805F48A192}" it implemented in mswsock.dll by default - look more here (strictly said used dll registered in HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\WinSock2\Parameters\Protocol_Catalog9\Catalog_Entries[64] but by default, this is mswsock.dll )
WSPStartup return to ws2_32 WSPPROC_TABLE - here the lowest user mode api which called. say for sendto - this is lpWSPSendTo function. so for hook all sendto you need replace lpWSPSendTo pointer in WSPPROC_TABLE to own. some api pointers returned via WSPIoctl. so you must replace lpWSPIoctl member in table to own and replace in result returned by original WSPIoctl to own api. example for RIO extension:
#include <ws2spi.h>
#include <mswsock.h>
LPWSPIOCTL g_lpWSPIoctl;
LPWSPSENDTO g_lpWSPSendTo;
LPFN_RIOSENDEX g_RIOSendEx;
int WSPAPI WSPIoctl(
__in SOCKET s,
__in DWORD dwIoControlCode,
__in LPVOID lpvInBuffer,
__in DWORD cbInBuffer,
__out LPVOID lpvOutBuffer,
__in DWORD cbOutBuffer,
__out LPDWORD lpcbBytesReturned,
__in LPWSAOVERLAPPED lpOverlapped,
__in LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine,
__in LPWSATHREADID lpThreadId,
__out LPINT lpErrno
)
{
int r = g_lpWSPIoctl(s, dwIoControlCode, lpvInBuffer, cbInBuffer, lpvOutBuffer, cbOutBuffer, lpcbBytesReturned, lpOverlapped, lpCompletionRoutine, lpThreadId, lpErrno);
static GUID functionTableId = WSAID_MULTIPLE_RIO;
if (
!r
&&
dwIoControlCode == SIO_GET_MULTIPLE_EXTENSION_FUNCTION_POINTER &&
cbInBuffer == sizeof(GUID) &&
cbOutBuffer >= sizeof(RIO_EXTENSION_FUNCTION_TABLE) &&
!memcmp(lpvInBuffer, &functionTableId, sizeof(GUID))
)
{
PRIO_EXTENSION_FUNCTION_TABLE priot = (PRIO_EXTENSION_FUNCTION_TABLE)lpvOutBuffer;
if (priot->cbSize >= FIELD_OFFSET(RIO_EXTENSION_FUNCTION_TABLE, RIOSendEx))
{
g_RIOSendEx = priot->RIOSendEx;// save original pointer to use
priot->RIOSendEx = RIOSendEx;// this is your hook function
}
}
return r;
}
so we need hook WSPStartup function before it will be called first time by ws2_32.dll

VirtualBox IGuestSession::ProcessCreate returns 0x8000FFFF (E_UNEXPECTED)

So I'm trying to execute a process in the guest session from the host, but I keep getting a 0x8000FFFF (E_UNEXPECTED) HRESULT from it. Since I'm getting a COM error rather than VBOX_E_IPRT_ERROR it makes me think my SAFEARRAYs are the issue and not the actual parameters if that makes sense. I'm not too familiar with COM, so it might just be a case of me using SAFEARRAY wrong. Either way, here's the code I'm trying right now:
SAFEARRAY *args_and_env, *creation_flags;
SAFEARRAYBOUND arrayDim[1];
arrayDim[0].lLbound= 0;
arrayDim[0].cElements= 1;
args_and_env = SafeArrayCreate(VT_LPWSTR,1,arrayDim);
SafeArrayPutElement(args_and_env, 0, L"");
creation_flags = SafeArrayCreate(VT_INT, 1, arrayDim);
int flag = ProcessCreateFlag_None;
SafeArrayPutElement(creation_flags, 0, &flag);
IGuestProcess *proca;
rc = guestSession->ProcessCreate(proc, args_and_env, args_and_env, creation_flags, 0, &proca);
Documentation for IGuestSession::ProcessCreate is as following:
IGuestProcess IGuestSession::processCreate(
[in] wstring executable,
[in] wstring arguments[],
[in] wstring environmentChanges[],
[in] ProcessCreateFlag flags[],
[in] unsigned long timeoutMS)
And the function declaration is as following:
HRESULT STDMETHODCALLTYPE ProcessCreate(
/* [in] */ BSTR aExecutable,
/* [in] */ SAFEARRAY * aArguments,
/* [in] */ SAFEARRAY * aEnvironmentChanges,
/* [in] */ SAFEARRAY * aFlags,
/* [in] */ ULONG aTimeoutMS,
/* [retval][out] */ IGuestProcess **aGuestProcess)
I've also tried passing NULL to both arguments and environmentChanges as I'm not looking to use any of them, but with the same result.
When i tested my task i realized that this problem can be if you use session OS without password. You have to set password and create session with password:
BSTR login = ...;
BSTR passsword = ...;
BSTR empty = SysAllocString(L"");
HRESULT rc = guest->CreateSession(login, password, empty, sessionName, &guestSession);
And then create a guest process

Windows Netapi32

I am currently accessing the Windows Netapi32 using Netapi32.lib, currently I am using c++ to access the api. I am having trouble retrieving the computer name, currently one the NetFileEnum here on FILE_INFO_3 structure here. In the documentation it says,
fi3_username
Pointer to a string that specifies which user (on servers that have user-level security) or which computer (on servers that have
share-level security) opened the resource. Note that Windows does not
support share-level security.This string is Unicode if _WIN32_WINNT or FORCE_UNICODE are defined.
Now, the network that I am running this script in has indeed Share-level security, I am just not sure how to list the computer name.
Relevant code
Library includes:
#pragma comment(lib, "Netapi32.lib")
#include <stdio.h>
#include <assert.h>
#include <windows.h>
#include <lm.h>
Initiate structure:
fstatus is defined in my code as, NET_API_STATUS fStatus, it is the I/O structure. Documentation here
if fstatus is successful the Return value NERR_Success.
If the function fails, the return value can be one of the following error codes.
ERROR_ACCESS_DENIED The user does not have access to the requested
information.
ERROR_INVALID_LEVEL The value specified for the level parameter is
not valid.
ERROR_INVALID_PARAMETER The specified parameter is not valid.
ERROR_MORE_DATA More entries are available. Specify a large enough buffer to receive all entries.
....
more here
To handle that I use if ((fStatus == NERR_Success) || (fStatus == ERROR_MORE_DATA))
The user name could not be found.
fStatus = NetFileEnum(
flServerName, //Pointer to a string that specifies the DNS or NetBIOS name of the remote server on which the function is to execute. If this parameter is NULL, the local computer is used.
flBasePath, //Pointer to a string that specifies a qualifier for the returned information. If this parameter is NULL, all open resources are enumerated.
flUserName, //Pointer to a string that specifies the name of the user or the name of the connection.
dfLevel, //Pointer to a string that specifies the name of the user or the name of the connection. Can be either 2 or 3, I am using 3
(LPBYTE*)&pFile, //Pointer to the address of the buffer that receives the information. The format of this data depends on the value of the level parameter.
fwPrefMaxLen, //pecifies the preferred maximum length of returned data, in bytes.
&fwEntriesRead, //Pointer to a value that receives the count of elements actually enumerated.
&fwTotalEntries, //Pointer to a value that receives the total number of entries that could have been enumerated from the current resume position.
&fwResumeHandle); //Pointer to a value that contains a resume handle which is used to continue an existing file search.
NET_API_STATUS NetFileEnum(
_In_ LMSTR servername,
_In_ LMSTR basepath,
_In_ LMSTR username,
_In_ DWORD level,
_Out_ LPBYTE *bufptr,
_In_ DWORD prefmaxlen,
_Out_ LPDWORD entriesread,
_Out_ LPDWORD totalentries,
_Inout_ PDWORD_PTR resume_handle
);
RAW Values for the above:
NET_API_STATUS fStatus;
LPFILE_INFO_3 pFile = NULL;
LPFILE_INFO_3 pTmpFile;
DWORD dfLevel = 3;
LPTSTR flServerName = NULL;
LPTSTR flUserName = NULL;
LPTSTR flBasePath = NULL;
DWORD fwPrefMaxLen = MAX_PREFERRED_LENGTH;
DWORD fwEntriesRead = 0;
DWORD fwTotalEntries = 0;
DWORD fwResumeHandle = 0;
pTmpfile is the level 3 (documentation here) buffer object,
bufptr [out]
Pointer to the address of the buffer that receives the information. The format of this data depends on the value of the levelparameter.
This buffer returns data in this format,
typedef struct _FILE_INFO_3 {
DWORD fi3_id;
DWORD fi3_permissions;
DWORD fi3_num_locks;
LMSTR fi3_pathname;
LMSTR fi3_username;
} FILE_INFO_3, *PFILE_INFO_3, *LPFILE_INFO_3;
Retrieve data:
printf("\n\tComputer: %S\n", pTmpFile->fi3_username); //how do I retrieve computer name???
printf("\n\tid: %D\n", pTmpFile->fi3_id);
printf("\n\tpath: %S\n", pTmpFile->fi3_pathname);
**It is important to note that I have tried this using vbnet and it works, but somehow cant figure it how to do it on c++.
Complete Test Program:
#ifndef UNICODE
#define UNICODE
#endif
//Initialize the NetAPI Library
#pragma comment(lib, "Netapi32.lib")
#include <stdio.h>
#include <assert.h>
#include <windows.h>
#include <lm.h>
int wmain(int argc, wchar_t *argv[])
{
//NetFile Enum, using 3 Level.
NET_API_STATUS fStatus;
LPFILE_INFO_3 pFile = NULL;
LPFILE_INFO_3 pTmpFile;
DWORD dfLevel = 3;
LPTSTR flServerName = NULL;
LPTSTR flUserName = NULL;
LPTSTR flBasePath = NULL;
DWORD fwPrefMaxLen = MAX_PREFERRED_LENGTH;
DWORD fwEntriesRead = 0;
DWORD fwTotalEntries = 0;
DWORD fwResumeHandle = 0;
DWORD fi;
//
// Check command line arguments.
// Dont need this currently.
//
do
{
fStatus = NetFileEnum(flServerName,
flBasePath,
flUserName,
dfLevel,
(LPBYTE*)&pFile,
fwPrefMaxLen,
&fwEntriesRead,
&fwTotalEntries,
&fwResumeHandle);
if ((fStatus == NERR_Success) || (fStatus == ERROR_MORE_DATA))
{
if ((pTmpFile = pFile) != NULL)
{
for (fi=0; fi < fwEntriesRead; fi++)
{
assert(pTmpFile != NULL);
if (pTmpFile == NULL)
{
fprintf(stderr, "An access violation has occurred\n");
break;
}
printf("\n\tComputer: %S", pTmpFile->fi3_username);
printf("\n\tid: %d", pTmpFile->fi3_id);
printf("\n\tpath: %s", pTmpFile->fi3_pathname);
printf("\n\tLocks: %d\n", pTmpFile->fi3_num_locks);
pTmpFile++;
fwTotalEntries++;
}
}
}
else
fprintf(stderr, "A system error has occurred: %d\n", fStatus);
//
// Free the allocated memory.
//
if (pFile != NULL)
{
NetApiBufferFree(pFile);
pFile = NULL;
}
}
//
// Continue to call NetFilEnum while
// there are more entries.
//
while (fStatus == ERROR_MORE_DATA);
if (pFile != NULL)
NetApiBufferFree(pFile);
return 0;
}
Output:
From Build:
1>------ Build started: Project: Perfmon, Configuration: Release Win32 ------
1>Compiling...
1>file_enumerate.cpp
1>Linking...
1>Generating code
1>Finished generating code
1>Embedding manifest...
1>Build log was saved at "file://...."
1>Perfmon - 0 error(s), 0 warning(s)
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
Run:
Computer: User1 //prints only username, not computername (in our system, each user has the same username)
id: 1005687
path: c:\fips\library
Computer: User2 //prints only username, not computername (in our system, each user has the same username)
id: 1005689
path: c:\fips\library\util
If anyone else is wondering about the solution, I figured it out. To query the number of files assoicated with the Computer and not just the User, the NetFileEnum function has to be used, documentation here. The NetFileEnum syntax is shown below,
NET_API_STATUS NetFileEnum(
_In_ LMSTR servername,
_In_ LMSTR basepath,
_In_ LMSTR username,
_In_ DWORD level,
_Out_ LPBYTE *bufptr,
_In_ DWORD prefmaxlen,
_Out_ LPDWORD entriesread,
_Out_ LPDWORD totalentries,
_Inout_ PDWORD_PTR resume_handle
);
Where you have to pass the Computer Name as LMSTR username (you can retrieve the computer name by quering NetSessionEnum(502) which will return all computer names in the network, documentation here ) and the query returns the file details based on the DWORD level, either FILE_INFO_3 documentation here and FILE_INFO_2 documentation here.

What is coming in IDataObject?

When you implement IDropTarget you must implement this:
virtual HRESULT STDMETHODCALLTYPE Drop(
/* [unique][in] */ __RPC__in_opt IDataObject *pDataObj,
/* [in] */ DWORD grfKeyState,
/* [in] */ POINTL pt,
/* [out][in] */ __RPC__inout DWORD *pdwEffect)=0;
I want to know what kind of data is coming in the IDataObject.
I did this:
FORMATETC fdrop = {CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
if (SUCCEEDED(pDataObj->QueryGetData(&fdrop)) ){
STGMEDIUM stgMedium = {0};
stgMedium.tymed = TYMED_HGLOBAL;
HRESULT hr = pDataObj->GetData(&fdrop, &stgMedium);
if (SUCCEEDED(hr))
{
But this only works when someone drop files. I saw that there's also a CF_TEXT and CF_BITMAP, but I don't want to query for all types of Clipboard Formats, so I want to know if there's a way of querying IDataObject's type of data.
CF_HDROP works fine for files, but when I drop an image from a browser for example, I don't know what kind of CF_ to use... I tried CF_BITMAP but doesn't work.
See IDataObject::EnumFormatEtc. As you can see from the documentation it may be possible to query for the data in multiple formats and EnumFormatEtc is a means to enumerate the various formats available.