继续上回的讨论,在开始之前,首先感谢论坛里热心的兄弟们给出的建议和帮助,在他们的帮助下,我对这个问题有了进一步的理解。
尤其感谢syncpk99兄弟提供了国外的完整解决方案()的和给与详细解释的OwnWaterloo 兄弟,感谢!
你们是未来的希望。鉴于国外的这个解决方案已经大大的超过了我的预期,因此这里不再讨论我的想法,按照我的理解,对这个方案
谈点体会。
上次讨论我们实行了初步的动态调用的可行性,但是,若想达到灵活的调用,我们必须解决参数压栈的问题,问题在于如何将未知个数
的参数们压进栈中呢?以下参考的做法,见代码:
int DyCall(void* pfAddr,void* pData,int nsize)
{
//pfAddr为被调用函数的地址,pData为参数的"串"缓冲区,nsize为参数缓冲区的大小,以上几个参数将从其他方法构造
int nRet;
__asm
{
//注意调用方式,这是stdcall
push esi ; 保存esi和edi,因为我们要用这两个来拷贝串,拷贝串的目的是模拟压栈
push edi
mov esi, dword ptr [pData] ; esi 指向参数串
mov ecx, nsize ; ecx 保存串长度
sub esp, ecx ; 以stdcall的方式在栈上开辟size大小的空间
mov edi, esp ; edi 指向栈顶
rep movsb ; 拷贝参数到edi指向的地址,即栈上,拷贝的循环次数由ecx保存的大小决定
call pfAddr ; 开始调用
pop edi ; 恢复 edi, esi
pop esi
mov nRet,eax ;取得回值
}
return nRet;
}
以上是windows下stdcall方式的实现的调用过程,其他的调用方式对参数压栈的处理是类似的。基本思想是改push为copy,很巧妙不是,问题
又来了,如何构造参数缓冲区呢?以下我做了几个例子。先来两个加载库和卸载库的函数,节省体力
#include
#include
int DyLoad(HMODULE* phMod,FARPROC* pfAddr,char* libName,char* procName)
{
//加载函数库
*phMod = LoadLibraryA(libName);
//取得函数地址
if(*phMod != NULL)
*pfAddr = GetProcAddress(*phMod,procName);
return 0;
}
int DyUnload(HMODULE hMod)
{
//释放函数库
if(hMod != NULL)
FreeLibrary(hMod);
return 0;
}
下面是 Beep的构造参数缓冲区及调用过程,Beep的原型是BOOL Beep(DWORD dwFreq,DWORD dwDuration),有两个DWORD的参数,就是两个int
void __Beep(DWORD dwFreq,DWORD dwDuration)
{
HMODULE hMod ;
FARPROC pfAddr ;
DyLoad(&hMod,&pfAddr,"kernel32.dll","Beep"); //加载库,并取得Beep的地址
int nRet ;//返回值
//构造缓冲区
BYTE buf[255] = {0} ;
ZeroMemory(buf,sizeof(buf)); //Beep的原型是BOOL Beep(DWORD dwFreq,DWORD dwDuration),
memcpy(buf,&dwFreq,sizeof(dwFreq)); //按照stdcall的顺序,从左向右顺序拷贝到缓冲区
memcpy(buf + sizeof(dwFreq),&dwDuration,sizeof(dwDuration));
//构造结束
int len = sizeof(dwFreq) + sizeof(dwDuration) ; //计算缓冲区真实长度
nRet = DyCall(pfAddr,buf,len); // 开始调用
printf("%d",nRet);
DyUnload(hMod);
}
再来个不含参数的GetCurrentProcessId(),原型是DWORD GetCurrentProcessId()
void __GetCurrentProcessId()
{
HMODULE hMod ;
FARPROC pfAddr ;
DyLoad(&hMod,&pfAddr,"kernel32.dll","GetCurrentProcessId");//加载库,并取得GetCurrentProcessId的地址
int nRet ;//返回值
//构造缓冲区
BYTE buf[255] = {0} ;
ZeroMemory(buf,sizeof(buf));
int len = 0; //无参数,置0即可
nRet = DyCall(pfAddr,buf,len); //开始调用
printf("%d",nRet);
DyUnload(hMod);
}
一个char*参数的SetConsoleTitleA,原型是SetConsoleTitle(char* pszTitle)
void __SetConsoleTitle(char* title)
{
HMODULE hMod ;
FARPROC pfAddr ;
DyLoad(&hMod,&pfAddr,"kernel32.dll","SetConsoleTitleA");
int nRet ;
BYTE buf[255] = {0} ;
int len = 0;
ZeroMemory(buf,sizeof(buf));
memcpy(buf,&title,sizeof(title));
len += sizeof(title);//strlen(title);
nRet = DyCall(pfAddr,buf,len);
printf("%d",nRet);
DyUnload(hMod);
}
再来个多种参数的MessageBoxW,原型是int MessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType);
void __MessageBox()
{
HMODULE hMod ;
FARPROC pfAddr ;
DyLoad(&hMod,&pfAddr,"user32.dll","MessageBoxW");
int nRet ;//返回值
//初始化调用所需的参数
DWORD* dHandle = NULL; //没有所属窗口
TCHAR* pszText = L"您好";
TCHAR* pszCaption =L" 谢谢 ";
int ntype = MB_OK|MB_APPLMODAL|MB_ICONINFORMATION;
int len = 0;
//构造缓冲区,并计算大小
BYTE buf[255] = {0} ;
ZeroMemory(buf,sizeof(buf));
memcpy(buf,&dHandle,sizeof(dHandle)); //拷贝所属窗口参数
len += sizeof(dHandle);
memcpy(buf + len,&pszText,sizeof(pszText)); //拷贝显示的消息文本
len += sizeof(pszText) ;
memcpy(buf + len,&pszCaption,sizeof(pszCaption)); //拷贝显示的消息标题
len += sizeof(pszCaption) ;
memcpy(buf + len,&ntype,sizeof(ntype)); // 拷贝消息框风格参数
len += sizeof(ntype);
nRet = DyCall(pfAddr,buf,len); //调用
printf("%d",nRet);
DyUnload(hMod);
}
int main()
{
__SetConsoleTitle("Demo");
__GetCurrentProcessId();
__MessageBox();
__Beep(750,300);
return 0;
}
入口函数:
好,既然这个办法可行了,剩下的问题是如何在脚本里根据解析到的函数原型构造参数缓冲区,毕竟,写包装函数不是我们的最终目的,有兴趣的
可以参照的做法,根据参数类型去构造参数缓冲区,这样,我们把任务一步一步的推向了调用者