stdjkdblom 2019-06-26
API HOOK 有很多方式,本文只介绍最常用的dll注入方式进程API HOOK。
dll注入方式大体分为两步:1、dll注入,2、修改API入口
假设注入进程为A,被注入的进程为B,注入的dll名为virus.dll。则进程A注入dll到进程B的大体步骤如下
if ( OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken) ) { TOKEN_PRIVILEGES tkp; LookupPrivilegeValue( NULL,SE_DEBUG_NAME,&tkp.Privileges[0].Luid );//修改进程权限 tkp.PrivilegeCount=1; tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges( hToken,FALSE,&tkp,sizeof tkp,NULL,NULL );//通知系统修改进程权限 CloseHandle(hToken); }
OpenProcess( PROCESS_CREATE_THREAD | //允许远程创建线程 PROCESS_VM_OPERATION | //允许远程VM操作 PROCESS_VM_WRITE| //允许远程VM写 PROCESS_ALL_ACCESS, FALSE, dwRemoteProcessId ) )
pszLibFileRemote = (char *) VirtualAllocEx( hRemoteProcess, NULL, lstrlen(DllFullPath)+1,MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hRemoteProcess,pszLibFileRemote, (void *) DllFullPath, lstrlen(DllFullPath)+1, NULL)
PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");
hRemoteThread = CreateRemoteThread( hRemoteProcess, NULL, 0, pfnStartAddr, pszLibFileRemote, 0, NULL)
经过了上述步骤,virus.dll就能被成功注入到进程B的地址空间中,上面的步骤就是为了干一件事情,让B进程调用LoadLibraryA("virus.dll"),为了做这件事情需要上述6个步骤。,此处需要有几点说明:
a、任何应用进程将Kerner32.dll加载到内存的虚拟地址位置都是一样 b、dll中的方法在dll中的相对位置是固定的
所以,A进程找到的函数地址位置和在B进程中的位置是一样的。
至此,我们将virus.dll加载到了B进程中,接下来就是B进程中的virus.dll怎么对自己进程空间中的API函数做HOOK的问题了。这里涉及到两个问题:
我们先看怎么修改API函数的入口代码,再讨论执行时机的问题。这件事情是在virus.dll中做的,执行的步骤如下
HMODULE hmod=::LoadLibrary(_TEXT("add.dll"));
add=(AddProc)::GetProcAddress(hmod,"add");
// 将add()的入口代码保存到OldCode里 _asm { lea edi,OldCode mov esi,pfadd cld movsd movsb } NewCode[0]=0xe9;//第一个字节0xe9相当于jmp指令 //获取Myadd()的相对地址 _asm { lea eax,Myadd mov ebx,pfadd sub eax,ebx sub eax,5 mov dword ptr [NewCode+1],eax } //填充完毕,现在NewCode[]里面就相当于指令 jmp Myadd HookOn();
void HookOn() { ASSERT(hProcess!=NULL); DWORD dwTemp=0; DWORD dwOldProtect; //将内存保护模式改为可写,老模式保存入dwOldProtect VirtualProtectEx(hProcess,pfadd,5,PAGE_READWRITE,&dwOldProtect); //将所属进程中add的前5个字节改为Jmp Myadd WriteProcessMemory(hProcess,pfadd,NewCode,5,0); //将内存保护模式改回为dwOldProtect VirtualProtectEx(hProcess,pfadd,5,dwOldProtect,&dwTemp); bHook=true; }
这段代码将add函数(API函数)的前5个字节存储到OldCode里面(为了以后恢复add函数),然后生成一个jmp指令到NewCode中,都存储好之后,NewCode的5个字节的内容,替换到原来add函数入口处的5个字节内容。
注意几个问题:
因为jmp XXX 指令占用5个字节(32位体系结构中)
xxx = Myadd - pfadd - 5
为什么是这个公式呢,先引用《深入理解计算机系统-第三版》中一段话:
当CPU计算跳转指令的目标地址时,程序计数器的值是当前指令的后一条指令的地址,而不是跳转指令的地址举个例子
pfadd: 01FF0000 : ?? ?? ?? ?? 01FF0004 : ?? ?? ?? ?? 01FF0008 : ?? ?? ?? ?? ... Myadd: 01FF00A0 : ?? ?? ?? ?? 01FF00A4 : ?? ?? ?? ?? 01FF00A8 : ?? ?? ?? ?? ... relpalce the first 5 bytes pfadd as jmp(E9 xx xx xx xx),the content of pfadd: pfadd: 01FF0000 : E9 xx xx xx 01FF0004 : xx ?? ?? ?? 01FF0008 : ?? ?? ?? ?? ... satisfy : 01FF00A0 = PC + XXX PC = 01FF0000 + 5 then the addr : XXX = 01FF00A0 - 01FF0000 - 5
以上就是修改api入口代码的方法
执行时机问题就比较简单了,我们还记得进程A(起始就是注入器)创建了一个远程线程,让进程B执行了LoadLibraryA("virus.dll")函数。dll中有个入口函数
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved) { HANDLE g_hModule; switch(dwReason) { case DLL_PROCESS_ATTACH: cout<<"Dll is attached!"<<endl; g_hModule = (HINSTANCE)hModule; break; case DLL_PROCESS_DETACH: cout<<"Dll is detached!"<<endl; g_hModule=NULL; break; } return true; }
当dll被加载之后,就会执行DLL_PROCESS_ATTACH后面的代码,如果我们将修改API入口代码的动作放到这里面做,则创建远程线程加载dll后,HooK的动作就会被执行,API函数的入口代码就会被修改。而当进程B再次调用该API时,该API就会跳转到我们自己写的函数了。
以上就是API HOOK 的dll远程注入技术的详细步骤,如果有不明白的地方,可以参考:
远程注入 : http://blog.csdn.net/ithzhang...
api修改 : http://blog.csdn.net/friendan...
还可以参考我的代码 : https://github.com/Jasey/hook...
里面有大量的例子和解释。