JMP HOOK IAT detours
Win9x 系统中,系统DLL被装入实际的物理存储器,然后映射到每个进程的0x80000000~0xBFFFFFFF共享内存区,如果修改这段区域的DLL代码,则对于所有进程都有效(实际Win98对主要的系统DLL作了保护,除非进入ring0才能修改)。
Win2000/NT 的进程空间不存在共享内存区,尽管DLL被装入之初在实际的物理存储器只有一份拷贝,但是Win2000/NT对DLL映射的内存页面采用copy-on-write保护机制,如果任何进程试图写入DLL所在的页面,系统将在实际的物理存储器创建一份该页面的拷贝,然后修改该进程的地址映射,使之指向拷贝的页面,这样进程对DLL页面的修改将仅仅在此进程有效。
本文一介绍第二种方法在Win2k下的使用。第二种方法,Win98/me 下因为进入Ring0级的方法很多,有LDT,IDT,Vxd等方法,很容易在内存中动态修改代码,但在Win2k下,这些方法都不能用,写WDM太过复杂,表面上看来很难实现,
在Win2k下因为Dll和所属进程在同一地址空间,这点又和Win9x/me存在所有进程存在共享的地址空间不同,
因此,必须通过钩子函数和远程注入进程的方法,现以一个简单采用钩子函数对MessageBoxA进行拦截例子来说明:
其中Dll文件为:
HHOOK g_hHook;
HINSTANCE g_hinstDll;
FARPROC pfMessageBoxA;
int WINAPI MyMessageBoxA(HWND hWnd, LPCTSTR lpText,LPCTSTR lpCaption,UINT uType);
BYTE OldMessageBoxACode[5],NewMessageBoxACode[5];
HMODULE hModule ;
DWORD dwIdOld,dwIdNew;
BOOL bHook=false;
void HookOn();
void HookOff();
BOOL init();
LRESULT WINAPI MousHook(int nCode,WPARAM wParam,LPARAM lParam);
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
if(!init())
{
MessageBoxA(NULL,"Init","ERROR",MB_OK);
return(false);
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
if(bHook) UnintallHook();
break;
}
return TRUE;
}
LRESULT WINAPI Hook(int nCode,WPARAM wParam,LPARAM lParam)//空的钩子函数
{
return(CallNextHookEx(g_hHook,nCode,wParam,lParam));
}
HOOKAPI2_API BOOL InstallHook()//输出安装空的钩子函数
{
g_hinstDll=LoadLibrary("HookApi2.dll");
g_hHook=SetWindowsHookEx(WH_GETMESSAGE,(HOOKPROC)Hook,g_hinstDll,0);
if (!g_hHook)
{
MessageBoxA(NULL,"SET ERROR","ERROR",MB_OK);
return(false);
}
return(true);
}
HOOKAPI2_API BOOL UninstallHook()//输出御在钩子函数
{
return(UnhookWindowsHookEx(g_hHook));
}
BOOL init()//初始化得到MessageBoxA的地址,并生成Jmp XXX(MyMessageBoxA)的跳转指令
{
hModule=LoadLibrary("user32.dll");
pfMessageBoxA=GetProcAddress(hModule,"MessageBoxA");
if(pfMessageBoxA==NULL)
return false;
_asm
{
lea edi,OldMessageBoxACode
mov esi,pfMessageBoxA
cld
movsd
movsb
}
NewMessageBoxACode[0]=0xe9;//jmp MyMessageBoxA的相对地址的指令
_asm
{
lea eax,MyMessageBoxA
mov ebx,pfMessageBoxA
sub eax,ebx
sub eax,5
mov dword ptr [NewMessageBoxACode+1],eax
}
dwIdNew=GetCurrentProcessId(); //得到所属进程的ID
dwIdOld=dwIdNew;
HookOn();//开始拦截
return(true);
}
int WINAPI MyMessageBoxA(HWND hWnd, LPCTSTR lpText,LPCTSTR lpCaption, UINT uType )//首先关闭拦截,然后才能调用被拦截的Api 函数
{
int nReturn=0;
HookOff();
nReturn=MessageBoxA(hWnd,"Hook",lpCaption,uType);
HookOn();
return(nReturn);
}
void HookOn()
{
HANDLE hProc;
dwIdOld=dwIdNew;
hProc=OpenProcess(PROCESS_ALL_ACCESS,0,dwIdOld);//得到所属进程的句柄
VirtualProtectEx(hProc,pfMessageBoxA,5,PAGE_READWRITE,&dwIdOld);//修改所属进程中MessageBoxA的前5个字节的属性为可写
WriteProcessMemory(hProc,pfMessageBoxA,NewMessageBoxACode,5,0);//将所属进程中MessageBoxA的前5个字节改为JMP 到MyMessageBoxA
VirtualProtectEx(hProc,pfMessageBoxA,5,dwIdOld,&dwIdOld);//修改所属进程中MessageBoxA的前5个字节的属性为原来的属性
bHook=true;
}
void HookOff()//将所属进程中JMP MyMessageBoxA的代码改为Jmp MessageBoxA
{
HANDLE hProc;
dwIdOld=dwIdNew;
hProc=OpenProcess(PROCESS_ALL_ACCESS,0,dwIdOld);
VirtualProtectEx(hProc,pfMessageBoxA,5,PAGE_READWRITE,&dwIdOld);
WriteProcessMemory(hProc,pfMessageBoxA,OldMessageBoxACode,5,0);
VirtualProtectEx(hProc,pfMessageBoxA,5,dwIdOld,&dwIdOld);
bHook=false;
}
//测试文件:
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
if(!InstallHook())
{
MessageBoxA(NULL,"Hook Error!","Hook",MB_OK);
return 1;
}
MessageBoxA(NULL,"TEST","TEST",MB_OK);//可以看见Test变成了Hook,也可以在其他进程中看见
if(!UninstallHook())
{
MessageBoxA(NULL,"Uninstall Error!","Hook",MB_OK);
return 1;
}
return 0;
}
void CHookClass::Init( LPCTSTR lpOldFun, FARPROC NewFun)
{
FARPROC OldFun; //被截函数的指针
OldFun = GetFunctionAddress(lpOldFun); //获取被截函数的地址
if(OldFun==NULL) //被截函数地址获取失败返回false
{
return;
}
DWORD * pfNewFun; //自定义函数得临时变量
pfOldFunction=OldFun;
memcpy(m_OldFunctionCode,(char *)OldFun,5); //保存被截函数得前五个字节到数组m_OldFunctionCode[]中;
m_NewFunctionCode[0]=0xe9;
pfNewFun=(DWORD*)&(m_NewFunctionCode[1]); //指针指向数组第二个字节;
*pfNewFun=(DWORD)NewFun-(DWORD)OldFun-5;
m_dwIdNew=GetCurrentProcessId(); //得到当前进程的ID
HookChange(); //开始拦截
return;
}
//----------------------------------------------------------------------------------------------
//HookChange(),拦截开始
void CHookClass::HookChange(void)
{
m_dwIdOld=m_dwIdNew;
//由所属进程的ID得到所属进程的句柄;
m_hProc = OpenProcess(PROCESS_ALL_ACCESS, 0, m_dwIdOld);
if (m_hProc == NULL)
{
OutputDebugString("拦截开始时未找到所属进程的句柄!");
exit(-1);
}
//修改所属进程中被截函数的前五个字节的属性为可写
VirtualProtectEx(m_hProc, pfOldFunction , 5, PAGE_READWRITE, &m_dwIdOld);
//将所属进程中被截函数的前5个字节改为JMP (自定义函数地址)
if( !bIsOSWin9x )
WriteProcessMemory(m_hProc, pfOldFunction , m_NewFunctionCode, 5, 0);
else
WriteMemoryWithRing0((DWORD)pfOldFunction, m_NewFunctionCode, 5);
//修改所属进程中被截函数的前5个字节的属性为原来的属性
VirtualProtectEx(m_hProc, pfOldFunction , 5, m_dwIdOld, &m_dwIdOld);
}
//-----------------------------------------------------------------------------------------------
//HookRestore(),恢复被截函数的地址
void CHookClass::HookRestore(void)
{
m_dwIdOld=m_dwIdNew;
//得到所属进程的句柄
m_hProc=OpenProcess(PROCESS_ALL_ACCESS,0,m_dwIdOld);
if (m_hProc == NULL)
{
OutputDebugString("恢复被截函数时未找到进程句柄!");
exit(-1);
}
//修改所属进程中被截函数的的前5个字节的属性为可写
VirtualProtectEx(m_hProc, pfOldFunction , 5, PAGE_READWRITE, &m_dwIdOld);
//将所属进程中被截函数的前5个字节改为JMP (被截函数的地址)
if( !bIsOSWin9x )
WriteProcessMemory(m_hProc, pfOldFunction ,m_OldFunctionCode, 5, 0);
else
WriteMemoryWithRing0((DWORD)pfOldFunction, m_OldFunctionCode, 5);
//修改所属进程中的前5个字节的属性为原来的属性
VirtualProtectEx(m_hProc, pfOldFunction , 5, m_dwIdOld, &m_dwIdOld);
}
//下面我将利用Ring3 to Ring0技术实现写共享内存区的代码贴出来共大家参考:
//功能:转到用Ring0级并写指定内存单元
//参数:
// dwAddress - 写要入数据的目的地址
// lpBuf - 源数据的缓冲区
// nSize - 源数据的大小
//说明:
// 本函数将运行级别调整至Ring0级,然后写数据到dwAddress的内存中,并返回
//Ring3级.
// 本函数只能在9x下使用
#define HookIntNo 5 //转到Ring0时要调用的中断号
void WriteMemoryWithRing0(DWORD dwAddress,LPVOID lpBuf,UINT nSize)
{
DWORD OldIntHook; //保存原来的中断入口
BYTE IDTR_1[6]; //保存中断描述符表寄存器
_asm
{
jmp StartModifyInt
//新的中断处理程序
NewIntHook:
push eax;
push ecx;
push esi;
push edi;
mov esi,edx;
mov edi,ebx;
NewIntHookLoop:
mov al,[esi];
mov [edi],al;
inc esi;
inc edi;
loop NewIntHookLoop;
pop edi;
pop esi;
pop ecx;
pop eax;
iretd
StartModifyInt:
push eax
//获取修改的中断的中断描述符(中断门)地址
sidt IDTR_1
mov eax,dword ptr IDTR_1+02h
add eax,HookIntNo*08h+04h
cli
//保存原先的中断入口地址
push ecx
mov ecx,dword ptr [eax]
mov cx,word ptr [eax-04h]
mov dword ptr OldIntHook,ecx
pop ecx
//设置修改的中断入口地址为新的中断处理程序入口地址
push ebx
lea ebx,NewIntHook
mov word ptr [eax-04h],bx
shr ebx,10h
mov word ptr [eax+02h],bx
pop ebx
//执行中断,转到Ring 0(与CIH 病毒原理相似!)
push ebx
push ecx
push edx
mov ebx,dword ptr dwAddress //目的地址
mov edx,dword ptr lpBuf //源地址
mov ecx,dword ptr nSize
int HookIntNo //执行中断,转到Ring0
pop edx
pop ecx
pop ebx
//操作结束
sti
pop eax
}
}
在Ring0状态下修改系统共享的代码
#define HookExceptionNo 5
void Ring0WriteMemory(void * dst,void *src,int copySize)
{
BYTE IDTR_1[6];
DWORD OldExceptionHook;
__asm
{
JMP __Continue
Ring0Proc:
PUSHAD
MOV AX,30h // 定义一个系统级别的数据段选择子
MOV BX,DS // 保存原DS与ES
MOV DX,ES
MOV DS,AX // 修改DS与ES
MOV ES,AX
REP MOVSB // 插入指令
MOV DS,BX // 复原DS与ES
MOV ES,DX
POPAD
IRETD //返回
__Continue:
SIDT FWORD PTR IDTR_1 // 修改中断门
MOV EAX,DWORD PTR IDTR_1+02h
ADD EAX,HookExceptionNo*08h+04h
CLI
MOV ECX,DWORD PTR [EAX] // 保存原异常处理例程入口
MOV CX,WORD PTR [EAX-04h]
MOV OldExceptionHook,ECX
LEA EBX,Ring0Proc // 指定新入口
MOV WORD PTR [EAX-04h],BX
SHR EBX,10h
MOV WORD PTR[EAX+02h],BX
PUSHAD // 配置参数
MOV EDI,dst
MOV ESI,src
MOV ECX,copySize
INT HookExceptionNo // 激活Ring0代码
POPAD
MOV ECX,OldExceptionHook // 复原入口
MOV WORD PTR[EAX-04h],CX
SHR ECX,10h
MOV WORD PTR[EAX+02h],CX
STI
}
}