windows核心编程
1DLL的进入点
DllMain
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
DLL_PROCESS_ATTACH:当DLL初次映射到进程的地址空间时,系统将调用DllMain,并为ul_reason_for_call传递值
DLL_PROCESS_ATTACH。此时DLL可以执行任何与进程相关的初始化。
实例,如果我将DLL注入到一个新的进程后,如果要自动执行一些任务,则代码写在case DLL_PROCESS_ATTACH:后。
DLL_PROCESS_DETACH:当DLL从进程的地址空间被卸载后,系统将调用DllMain,并为ul_reason_for_call传递值DLL_PROCESS_DETACH。例如调用FreeLibrary,先调用DllMain完成DLL_PROCESS_DETACH通知后,才从FreeLibrary中返回。
DLL_THREAD_ATTACH:当在一个进程中创建线程时,系统要察看当前映射到该进程的地址空间中的所有DLL文件映像,并调用每个带有DLL_THREAD_ATTACH的DllMain函数,此时可以告诉DLL执行线程的初始化操作。注意,系统不为进程的主线程调用带有
DLL_THREAD_ATTACH值的任何DllMain.进程初次启动进程地址空间的DLL接到的是DLL_PROCESS_ATTACH通知,而不是
DLL_THREAD_ATTACH通知。
DLL_THREAD_DETACH:线程终止时调用ExitThread来撤销线程,此时先调用带DLL_THREAD_DETACH的DLL的DllMain,在撤销线程。
2 DLL注入
2.1通过windows 挂钩消息来注入DLL
先看下面代码:
HHOOK
hHook=SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,hinstDll,0);
WH_GETMESSAGE:指出钩子的类型,是消息钩子。
GetMsgProc:窗口准备处理消息的时候,系统调用的函数地址,,,,,,,,,,
hinstDll:DLL被映射到的进程的地址空间中的RVA。 0:挂钩所有的GUI进程。
下面我们看看发生了什么:
1,进程B的一个线程准备把一条消息发送到一个窗口。 2,系统察看该线程是否安装WH_GETMESSAGE钩子。 3,系统察看包含GetMsgProc的DLL是否被映射到进程B的地址空间。 4,如果没有被映射,则强制映射到B的地址空间,实现了DLL的注入。
5,系统调用进程B空间的GetMsgProc函数(刚被映射)。
2.2使用远程进程来插入DLL
思路:在你希望的进程中建立一个远程线程,通过这个线程调用LoadLibrary加载需要的DLL。我们似乎只需要调用 CreateRemoteThread(hRemoteProcess,NULL,0,LoadLibraryA,"c:\\
mylib.dll",0,NULL)即可。但不可以。
错误一,LoadLibraryA不能直接作为参数传入,因为这导致远程线程执行一些莫名其妙的东西,很可能造成访问违规。
解决办法:
pfnAddr=(PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHand
le(TEXT("Kernel32")),"LoadLibraryA");然后把 pfnAddr传入。
错误二,不可以直接传入"c:\\mylib.dll",因为这段缓冲区在本进程,不在远程线程内。应该在远程线程申请空间(VirtualAllocEx),并 把"c:\\mylib.dll"写入这段远程线程空间
(WriteProcessMemory)。
示例代码:
#include
#include
void main()
{
LPCTSTR dllpath="d:\\print.dll";//将要被注入的DLL,内容是向硬盘上写日志文件
DWORD sizedllpath=0,dwWritten=0; HANDLE pRemoteThread,hRemoteProcess; PTHREAD_START_ROUTINE pfnAddr; DWORD pId;
void *pFileRemote;
BOOL fok;
sizedllpath=lstrlenA( dllpath ) + 1;
printf("size:%d",sizedllpath);
HWND hWinPro=::FindWindow("ProgMan",NULL);//获得explorer窗
口
if(!hWinPro)
printf("Exploere没有启动");
else
{
::GetWindowThreadProcessId(hWinPro,&pId); //获得explorer
句柄
hRemoteProcess=::OpenProcess(PROCESS_ALL_ACCESS|PROCESS_VM_WRITE,false,pId);
pFileRemote=::VirtualAllocEx(hRemoteProcess,NULL,sizedllpath,MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE);
//在explorer进程分配空间
if(pFileRemote)
{
printf("分配成功\n");
}
fok=::WriteProcessMemory(hRemoteProcess,pFileRemote,(LPVOID)dllpath,sizedllpath,&dwWritten);
printf("实际写入的字节数:%d\n",dwWritten);
printf("需要写入的字节数:%d\n",sizedllpath);
if (fok==false)
{
printf("WriteProcessMemory error\n");
return;
}
pfnAddr=(PTHREAD_START_ROUTINE)GetProcAddress(GetModuleH
andle(TEXT("Kernel32")),"LoadLibraryA");
pRemoteThread=::CreateRemoteThread(hRemoteProcess,NULL,0
,pfnAddr,pFileRemote,0,NULL);
if(pRemoteThread==NULL)
return;
else
printf("success!\n");
}
}
结构化异常处理(SEH)
结构化异常处理包括两部分:结束处理和异常处理。
4 结束处理
4.1 形式
__try
{
//被保护部分
}
__finally
{
//结束处理部分
}
无论try部分以如何方式退出(调用return,break,continue,goto
等),在退出之前finally部分总会被执行。如果try部分没有return,break,continue等,则程序的是执行完try部分自动流
入finally部分(注意,这与异常处理是不同的)。 4.2 一个程序
为了加深对结束处理的理解,看如下程序。 DWORD FuncaDoodleDoo() {
DWORD dwTemp=0;
while(dwTemp<10)
{
__try{
if(dwTemp==2)
continue;
if(dwTemp==3)
break;
}
__finally{
dwTemp++;
}
dwTemp++;
}
dwTemp+=10;
return(dwTemp) ;
}
当dwTemp进入while后,顺序进入finally中,dwTemp变为1,接着执行dwTemp++,变为2,再次进入循环开始部分,遇到了continue指令,这将改变程序流程进入finally块,dwTemp++变为3,这时返回循环开头,注意不执行finally块外的dwTemp++。再次进入try块,遇到break指令,再次进入finally块,dwTemp变为4,然后退出while,执行dwTemp+=10,所以最终返回14。
4.3 结束处理程序的应用
可以用结束处理程序来简化编程,但是有一点必须注意:尽量避免在try块中使用return,break,continue,goto等,因为这会使编译程序产生很多代码来进行程序流程改变的处理。
先看一个程序
BOOL Fun1()
{
HANDLE hFile=INVALID_HANDLE_VALUE;
PVOID pvBuf=NULL;
DWORD dwNumBytesRead;
BOOL fok;
hFile=CreateFile("SOMEDATA.BAT",GENERIC_READ,FILE_SHARE_REA
D,NULL,OPEN_EXISTING,0,NULL);
if(hFile==INVALID_HANDLE_VALUE)
return FALSE;
pvBuf=VirtualAlloc(NULL,1024,MEM_COOMIT,PAGE_READWRITE); if( pvBuf==NULL)
{
CloseHandle(hFile);
return FALSE;
}
fok=ReadFile(hFile,pvBuf,1024,&dwNumBytesRead,NULL); if(!fok||dwNumBytesRead==0)
{
VirtualFree(pvBuf,MEM_RELEASE|MEM_DECOMMIT);
CloseHandle(hFile);
return FALSE;
}
//do some calculation on the data .
.
.
.
VirtualFree(pvBuf,MEM_RELEASE|MEM_DECOMMIT);
CloseHandle(hFile);
return TRUE;
}
这个程序的一个缺点是清理代码VirtualFree,CloseHandle重复出现,罗嗦的很,使程序可读性降低。fun2对其改进。 BOOL Fun2()
{
HANDLE hFile=INVALID_HANDLE_VALUE; PVOID pvBuf=NULL;
DWORD dwNumBytesRead;
BOOL fok;
__try
{
hFile=CreateFile("SOMEDATA.BAT",GENERIC_READ,FILE_SHARE_
READ,NULL,OPEN_EXISTING,0,NULL);
if(hFile==INVALID_HANDLE_VALUE)
return FALSE;
pvBuf=VirtualAlloc(NULL,1024,MEM_COOMIT,PAGE_READWRITE);
if( pvBuf==NULL)
{
return FALSE;
}
fok=ReadFile(hFile,pvBuf,1024,&dwNumBytesRead,NULL);
if(!fok||dwNumBytesRead==0)
{
return FALSE;
}
}
//do some calculation on the data .
.
.
.
__finally
{
if( pvBuf!=NULL)
VirtualFree(pvBuf,MEM_RELEASE|MEM_DECOMMIT);
if(hFile!=INVALID_HANDLE_VALUE)
CloseHandle(hFile);
}
return TRUE;
}
fun2程序代码可读性有了很大提高,但是try块中出现了return这会降低程序的效率。fun3使用__leave很好地解决了这个问题。 BOOL Fun3()
{
HANDLE hFile=INVALID_HANDLE_VALUE; PVOID pvBuf=NULL;
DWORD dwNumBytesRead;
BOOL fok;
BOOL functionok=FALSE;
__try
{
hFile=CreateFile("SOMEDATA.BAT",GENERIC_READ,FILE_SHARE_
READ,NULL,OPEN_EXISTING,0,NULL);
if(hFile==INVALID_HANDLE_VALUE)
__leave;
pvBuf=VirtualAlloc(NULL,1024,MEM_COOMIT,PAGE_READWRITE);
if( pvBuf==NULL)
{
__leave;
}
fok=ReadFile(hFile,pvBuf,1024,&dwNumBytesRead,NULL);
if(!fok||dwNumBytesRead==0)
{
__leave
}
}
functionok=TRUE;
//do some calculation on the data .
.
.
.
__finally
{
if( pvBuf!=NULL)
VirtualFree(pvBuf,MEM_RELEASE|MEM_DECOMMIT);
if(hFile!=INVALID_HANDLE_VALUE)
CloseHandle(hFile); }
return functionok;
}
fun2达到了理想的效果,只需要增加变量functionok进行控制。 4 总结
结束处理的好处如下:
(1)简化错误处理,因所有的清理工作都在一个位置并且保证被执行。
(2)可读性提高
(3)使用得当,可以写出健壮却有很小系统开销的代码。 5 异常处理
cpu引发的异常是硬件异常(除0异常,非法内存访问等),操作系统和应用程序也可以自己引发异常,称为软件异常。 5.1 形式
__try
{
//被保护部分
}
__except(exception filter) {
//异常处理部分
}
注意,如果程序运行正常,则不进入except块,这与finally是不同的。
5.2 异常过滤器
__except(exception filter)中的filter参数就是异常过滤器,他可以取三个值:
EXCEPTION_EXECUTE_HANDLER EXCEPTION_CONTINUE_SEARCH EXCEPTION_CONTINUE_EXECUTION (1)EXCEPTION_EXECUTE_HANDLER
这个值的意思是告诉系统:“我认出了一个异常。即我感觉这个异常可能在某个时刻发生,我已编写了代码来处理,现在我想执行这个 代码”。
一个例子。
char * strcpy(char *strDestionation,char *strSource)
当传递了无效地址时,这会导致进程结束。为了使程序更加健壮,可以:
char * RobustStrCpy
{
__try
{
strcpy(strDestionation,strSource) }
__except(EXCEPTION_EXECUTE_HANDLER) {
//nothing to do here
}
return strDestionation;
}
再看一个例子。
PBYTE RobustMemDup(PBYTE pbSrc,size_t cb)
{
PBYTE pbDup=NULL;
__try
{
pbDup=(PBYTE)mallo(cb);
memcpy(pbDup,pbSrc,cb); }
__except
{
free(pbDup);
pbDup=NULL;
}
return pbDup;
}
但函数失败的时候,会释放分配的空间,并以返回值通知调用函数者知道。
(2)EXCEPTION_CONTINUE_EXECUTION
当代码产生异常,而过滤器值为EXCEPTION_CONTINUE_EXECUTION
时,系统跳回到产生异常的指令,然后再重新执行一次。所以,如果我们确实知道哪条指令产生了什么什么异常时,我们可以在except块进行修正,然后系统自动回去执行发生异常的指令,而此时已经修正了,则程序可以正常执行。这个功能如果好好利用的话会产生很奇妙的效果。后面电子的例子会看到它的巧妙的应用。 (3) EXCEPTION_CONTINUE_SEARCH 这个值告诉系统查找前面一个与except块相匹配的try块,并调用这个try块的异常处理代码。
5.3 一个应用实例--电子表格
电子表格程序(如excel),如果在程序开始执行时就分配空间,则会浪费很多的内存(因为通常电子表格很大,每个格一个值的话,1024*1024就会浪费1024*1024*sizeof(int)的内存)。解决的策略是:先使用VirtualAlloc(.. ,MEM_RESERVE,..)在进程虚拟空间保
留(reserve)一个足够大的空间,而不提交,所以不消耗物理内存。当访问某个格的时候就 VirtualAlloc(.. ,MEM_COMMIT,..)提交哪个格的内存。具体实现的时候,当访问某个表格的时候,而没有提交内存,将会引起EXCEPTION_ACCESS_VIOLATION异常,而在except的过滤器的值设为
EXCEPTION_CONTINUE_EXECUTION,在except块中为要访问的内存提交物理内存,即VirtualAlloc(.. ,MEM_COMMIT,..),然后重新执行访问代码而此时已经分配了物理内存则程序正常执行。下面程序采用了windows核心编程中的思想,具体实现的时候参考了网上的很多例子,并进行了改进,最终形成耗内存少,效率高的代码。 //VirtualMatrix.h头文件
#include
class VirtualMatrix
{
public:
VirtualMatrix(int nRows, int nCols);
~VirtualMatrix();
void setElement(int i, int j, int value);
int getElement(int i, int j);
LONG ExpFilter(DWORD dwExceptionCode,int i,int j);
protected:
int nCols;
int nRows;
LPVOID m_pdata;
};
//cpp文件
#include
#include
#include "VirtualMatrix.h" VirtualMatrix::VirtualMatrix(int nRows, int nCols):
m_pdata (NULL),
nCols(0),
nRows(0)
{
this->nCols = nCols;
this->nRows = nRows;
m_pdata = VirtualAlloc (NULL, nRows*nCols*sizeof(int),
MEM_RESERVE, PAGE_READWRITE ); if (m_pdata == NULL)
{
MessageBox(NULL, TEXT("reserve failed"), TEXT("virtual
matrix"), MB_OK);
return;
}
}
VirtualMatrix::~VirtualMatrix(void) {
if (m_pdata != NULL)
VirtualFree (m_pdata,0,MEM_RELEASE); }
void VirtualMatrix::setElement(int i, int j, int value)
{
if (i < 0 || i >= nRows)
return;
if (j < 0 || j >= nRows)
return;
int * p = (int*)(m_pdata);
__try
{
*(p + i*nCols+j) = value;
}
__except (ExpFilter(GetExceptionCode(),i,j)) {
}
}
LONG VirtualMatrix::ExpFilter(DWORD dwExceptionCode,int
i,int j)
{
if(dwExceptionCode==EXCEPTION_ACCESS_VIOLATION) {
VirtualAlloc
(LPVOID((long)m_pdata+sizeof(int)*(i*nCols+j)), 10, MEM_COMMIT, PAGE_READWRITE );
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_EXECUTE_HANDLER;
}
int VirtualMatrix::getElement(int i, int j)
{
if (i < 0 || i >= nRows) return -1;
if (j < 0 || j >= nRows) return -1;
__try
{
int * p = (int*)(m_pdata); int val = *(p + i*nCols+j);
return val;
}
__except (EXCEPTION_EXECUTE_HANDLER) {
return -1;
}
}
void main()
{
VirtualMatrix a(100,100); a.setElement(10,10,3); printf("%d",a.getElement(10,10));
}