
Introduction
Most of us know about reflection (Example: Windows COM, .NET) and its flexibilities. Have you ever wondered how to accomplish reflection in Win32 DLL? Here is the funny tool that resulted from that kind of my thought. "Uh!.. Reflection in Win32 DLL", oops!.. don't believe 100%. Don't worry, you can't accomplish perfect reflection in WIn32 DLLs in a simple way. In perfect reflection, you don't need the knowledge of source, function, parameters & types until its usage. When considering Win32 DLL, I feel late binding is some sort of reflection (don't accuse me, just an opinion). Late bound DLLs can do without the path name, function name until its usage, but need the function argument type list to compile. It seems late binding is impossible when you don't know the parameter list & type until execution.
This article is just a crazy demonstration of how you can possibly load and execute any function in a Win32 DLL without compiling the tool for the particular DLL. Here in this section, I'm directly jumping to detail explanation assuming you have sufficient knowledge about DLLs, late binding, etc.
Idea behind this
To accomplish what this tool should do on run time ...
- Load the DLL: Since
LoadLibrary
has LPCTSTR
type parameter for DLL name to load, just get string input from user and pass to it.
- Get the function pointer: Here also,
GetProcAddress
has LPCTSTR
type parameter for function name to pass, get string input from user and pass to it.
- Pass the parameters: Here comes the problem. Compiler, in most cases, passes arguments to function through stack. Try to simulate the compiler, get the parameter types from user in addition to the parameter values. Align & push the parameters in to the stack.
- Call the function: jump to the function using the function pointer that is obtained through
GetProcAddress
.
- Get the return value: Get the return type from user. Function always returns 32 bit value (except in 64 bit applications) in a register. Get this value and type cast into the type specified by the user.
That's it. Last three steps are the exceptional procedures in this tool from normal late binding code. So I'll explain those in detail.
As I stated earlier, every parameter to a function in a DLL is passed through stack. Compiler pushes the parameters in to the stack whenever you compile an application to call a function with _stdcall
, _cdecl
calling conventions. Here, same thing is done on run time. I have used inline assembly to accomplish this (don't be disappointed about inline assembly, if you don't know; in Windows, it is not as much as difficult as Linux GCC inline assembly).
Stack should be 32 bit aligned, i.e., align to DWORD
. So every parameter should be aligned to DWORD
before pushing in to stack. After pushing, call the function using the function pointer (here also, inline assembly is necessary). The return value will be in EAX
register if it is a 32 bit value. If it is 64 bit, then it will be in combination of EAX
and EDX
registers. Using assembly, save this to a variable. Now this variable can be type-cast to any of the user specified return type.
Code detail
The above logic is explained in this below code. This code performs the core functionality of this tool.
void CExecDLLDlg::OnBtnExec()
{
CListCtrl *pcList = (CListCtrl*)GetDlgItem(IDC_LSTPARAM);
char szText1[MAXCHAR_TEXT];
char szText2[MAXCHAR_TEXT];
int nCount = pcList->GetItemCount();
int nArrayCount = nCount;
struct ST_PARAM
{
int nType;
DWORD dwValue;
} *pArray = new ST_PARAM[nArrayCount];
DWORD dwDWord;
DWORD *pdwDWord;
char *lpString;
char **lppString;
for(int i=0,j=0;(i<nCount)&&(j<nArrayCount);i++,j++)
{
pcList->GetItemText(i,0,szText1,MAXCHAR_TEXT);
pcList->GetItemText(i,1,szText2,MAXCHAR_TEXT);
if(!strcmp(szText1,"Int"))
{
pArray[j].nType = 0;
dwDWord = atol(szText2);
pArray[j].dwValue = dwDWord;
}
else if(!strcmp(szText1,"Short"))
{
pArray[j].nType = 1;
dwDWord = atoi(szText2);
pArray[j].dwValue = dwDWord;
}
else if(!strcmp(szText1,"Byte"))
{
pArray[j].nType = 2;
dwDWord = atoi(szText2);
pArray[j].dwValue = dwDWord;
}
else if((!strcmp(szText1,"Int*"))||
(!strcmp(szText1,"Short*"))||
(!strcmp(szText1,"Byte*")))
{
pArray[j].nType = 3;
pdwDWord = new DWORD;
*pdwDWord = atol(szText2);
pArray[j].dwValue = (DWORD)pdwDWord;
}
else if(!strcmp(szText1,"String"))
{
pArray[j].nType = 4;
lpString = new char[strlen(szText2)+1];
strcpy(lpString,szText2);
pArray[j].dwValue = (DWORD)lpString;
}
else if(!strcmp(szText1,"String*"))
{
pArray[j].nType = 5;
lppString = new char*[1];
lpString = new char[strlen(szText2)+1];
strcpy(lpString,szText2);
lppString[1] = lpString;
pArray[j].dwValue = (DWORD)lppString;
}
else if(!strcmp(szText1,"Struct*"))
{
pArray[j].nType = 6;
CMemInfo *pcMemInfo;
pcMemInfo = (CMemInfo*)atol(szText2);
pArray[j].dwValue = pcMemInfo->m_dwMemAddress;
}
else if(!strcmp(szText1,"Array*"))
{
pArray[j].nType = 7;
CMemInfo *pcMemInfo;
pcMemInfo = (CMemInfo *)atol(szText2);
pArray[j].dwValue = pcMemInfo->m_dwMemAddress;
}
else if(!strcmp(szText1,"Struct"))
{
CMemInfo *pcMemInfo;
pcMemInfo = (CMemInfo *)atol(szText2);
DWORD dwLen = pcMemInfo->m_dwMemLen;
DWORD nMemCount = (dwLen/4);
DWORD nCurCnt = i;
nArrayCount += nMemCount;
nArrayCount--;
if(dwLen%4)
{
delete pArray;
MessageBox("Memory is not aligned",
"Execute DLL",
MB_OK|MB_ICONERROR);
return;
}
pArray = (ST_PARAM*)realloc(pArray,
sizeof(ST_PARAM)*nArrayCount);
pdwDWord = (DWORD*)pcMemInfo->m_dwMemAddress;
for(;j<nCurCnt+nMemCount;j++)
{
pArray[j].nType = 8;
pArray[j].dwValue = *pdwDWord;
pdwDWord++;
}
j--;
}
}
typedef int (__stdcall *FUNCTION)(void);
GetDlgItemText(IDC_EDTFILEPATH,szText1 ,MAXCHAR_TEXT);
GetDlgItemText(IDC_CMBFUNCNAME,szText2 ,MAXCHAR_TEXT);
HMODULE hMod = LoadLibrary(szText1);
if(!hMod)
{
MessageBox("DLL not found","Execute DLL",MB_OK|MB_ICONERROR);
return;
}
FUNCTION proc = GetProcAddress(hMod,szText2);
if(!proc)
{
MessageBox("Function not found","Execute DLL",MB_OK|MB_ICONERROR);
return;
}
for(i=nArrayCount-1;i>=0;i--)
{
dwDWord = pArray[i].dwValue;
_asm
{
mov eax, dwDWord
push eax
}
}
_asm
{
call proc
mov dwDWord, eax
}
GetDlgItemText(IDC_CMBRETTYPE,szText1,MAXCHAR_TEXT);
if(!strcmp(szText1,"Void"))
{
strcpy(szText2,"");
}
else if((!strcmp(szText1,"Int"))
||(!strcmp(szText1,"Short"))
||(!strcmp(szText1,"Byte")))
{
sprintf(szText2,"%u",dwDWord);
}
else if(!strcmp(szText1,"Int*"))
{
sprintf(szText2,"%u",*((int*)dwDWord));
}
else if(!strcmp(szText1,"Short*"))
{
sprintf(szText2,"%u",*((SHORT*)dwDWord));
}
else if(!strcmp(szText1,"Byte*"))
{
sprintf(szText2,"%u",*((BYTE*)dwDWord));
}
else if(!strcmp(szText1,"String"))
{
sprintf(szText2,"%s",(char*)dwDWord);
}
else if(!strcmp(szText1,"String*"))
{
sprintf(szText2,"%s",(char*)dwDWord);
}
SetDlgItemText(IDC_EDTRETVALUE,szText2);
FreeLibrary(hMod);
delete pArray;
}
Secondary part of the code includes aligning user defined (structure) type data and array type data.
void CStructDlg::OnOK()
{
..
....
align user input structure
pass the aligned mem address
....
,,
}
void CArrayDlg::OnOK()
{
..
....
align user input array
pass the aligned mem address
....
,,
}
In addition to this, you may find GetDLLFileExports
interesting. This function retrieves all the exported functions in a DLL. This function needs dbghelp.h and dbghelp.lib files from Microsoft Debugging SDK.
bool GetDLLFileExports (char *szFileName,
unsigned int *nNoOfExports, char **&pszFunctions)
{
....
}
Other codes are kind of aesthetic stuff. Like getting the input from user, presenting to user, etc.
Sample tests
These zips contain a tool project and a sample DLL project to test this tool. Use TestDLL if you are squeamish to use some other DLL found in your PC. Else, just ignore this TestDLL and use Windows system DLLs. I'm not encouraging or discouraging you to use these DLLs, it's up to you.
Example 1: Take USER32.DLL found in System32 directory. This DLL has most of the GUI APIs. Select the DLL and then the function MessageBoxA
. Now push (Add) the parameters one by one like in the above picture. For the handle parameter, copy the value from lower left hand edit box. Set the return type to "Int". When you click "Execute", you can see that the message box pops out like in the picture. After you click "Yes", "No" or "Cancel" on that message box, you can see that the appropriate return value comes on the return parameter edit box.
Example 2: Bit more complicated example. On the USER32.DLL, select the function PtInRect
. Set the return type to "Int". Select the "Struct" type in param type combo. When a Struct dialog appears, enter two "Int
" type values 10,10 and click OK. Now push (Add) the integer value that appears on the param value edit box. This is for the second parameter (POINT pt
) of the selected function. Now, select "Struct*" in param type combo. When a Struct dialog appears, enter four "Int
" type values 0, 0, 100 & 100 and click OK. Push (Add) the integer value that appears on the param value edit box. This is for the first parameter (const RECT *lprc
). On execute, you can see the return value 1 on return value edit box.
Specifying any incorrect parameter type for a function may result in tool crash. For example, you can make the tool crash on the second example if you specify "Struct" instead of "Struct*" or vice versa.
Known issues & lacking features
- Passing in/out parameters not tested thoroughly.
- Can not display the returned structures & arrays.
- Supports only Win32 DLL functions, i.e., functions specified with
_stdcall
calling convention.
- Unicode parameter strings are not supported.
- Passing and returning 64 bit values.
Planned features
- Reading the arguments, parameters and types from XML file and execute.
- Executing functions in a sequence.
- Adding logging mechanism.
- Extending this tool to do automated testing.
Conclusion
This tool is not complete to use in real time testing but very useful for learning stuffs behind the curtain. Due to time constraints at this moment, I have put a full stop to this development, and I'm not putting a full stop to those who want to modify, fix bugs or add any additional features. If you do so, please update me with a copy (if you wish). Thanks.