Execute a function in any Win32 DLL - Reflection in Win32 DLL?

来源:互联网 发布:js循环数组 编辑:程序博客网 时间:2024/06/06 05:34
    23 votes for this article. Popularity: 5.54. Rating: 4.07 out of 5.
  • Download source files - 93.6 Kb
  • Download demo project - 13.4 Kb

Sample Image - ExecDLL.jpg

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.

Collapse
void CExecDLLDlg::OnBtnExec() {       //  this list control has the user input of parameter types & values    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;    // if =0 then its DWORD                // if =1 ......        DWORD dwValue;    } *pArray = new ST_PARAM[nArrayCount];    DWORD dwDWord;    DWORD *pdwDWord;    char *lpString;    char **lppString;///////////// go through the list and collect //////////////////////////////////////////////// the arguments in a structure array ////////////////////    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*")) // not supported (not tested :D)        {            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*"))        {// here already aligned memory address (using CStructDlg::OnOK() function )         //will be in current list item. So get it and put it            pArray[j].nType = 6;            CMemInfo *pcMemInfo;            pcMemInfo = (CMemInfo*)atol(szText2);                        pArray[j].dwValue = pcMemInfo->m_dwMemAddress;        }        else if(!strcmp(szText1,"Array*"))        {// here already aligned memory address (using CArrayDlg::OnOK() function )         //will be in this list item. So get it and put it            pArray[j].nType = 7;                        CMemInfo *pcMemInfo;            pcMemInfo = (CMemInfo *)atol(szText2);                        pArray[j].dwValue = pcMemInfo->m_dwMemAddress;        }        else if(!strcmp(szText1,"Struct"))        {// here already aligned memory address (using CStructDlg::OnOK() function )         //will be in this list item. So get it and put it            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--;        }    }/////////////  load the DLL //////////////////////////////////////    typedef int (__stdcall *FUNCTION)(void);//// DLL path    GetDlgItemText(IDC_EDTFILEPATH,szText1 ,MAXCHAR_TEXT);//// Function name (Obtained through enumeration using Dbghelp API)    GetDlgItemText(IDC_CMBFUNCNAME,szText2 ,MAXCHAR_TEXT);    HMODULE hMod = LoadLibrary(szText1);    if(!hMod)    {        MessageBox("DLL not found","Execute DLL",MB_OK|MB_ICONERROR);        return;    }//////////// get the function pointer //////////////////////////////////    FUNCTION proc = GetProcAddress(hMod,szText2);    if(!proc)    {        MessageBox("Function not found","Execute DLL",MB_OK|MB_ICONERROR);        return;    }/////////// one by one push the arguments //////////////////////////////    for(i=nArrayCount-1;i>=0;i--) // args should be pushed in reverse order    {        dwDWord = pArray[i].dwValue;        _asm        {            mov eax, dwDWord            push eax        }    }/////////// call the function and store the return value /////////////////    _asm    {        call proc        mov dwDWord, eax    }//////////////  convert and display the return value ///////////////////////////    GetDlgItemText(IDC_CMBRETTYPE,szText1,MAXCHAR_TEXT);    if(!strcmp(szText1,"Void"))    {        // do nothing        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*"))// at this moment not supported     {        sprintf(szText2,"%s",(char*)dwDWord);    }    SetDlgItemText(IDC_EDTRETVALUE,szText2);    FreeLibrary(hMod);//////////////// clean up the allocated aruments ///////////////////////////// oops !.....    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

原创粉丝点击