Process-wide API spying - an ultimate hack 摘要翻译(二)

通过目标进程中挂钩所有对DLL的调用

由于通过修改目标进程的输入表IAT只能对当前进程有效,所以我们即使对当前所有已加载模块都挂钩了对kernel32.dll的调用,但在这之后启动的进程将不会被挂钩。因此除了对已加载进程修改IAT以外,还需要修改kernel32.dll的输出表EAT。
1、修改目标进程的IAT表对所有已加载的模块有效
2、修改kernel32.dll的EAT表,所有将来加载的DLL有效。
因此两种方法必须同时使用。注意,现在说的不是系统钩子,而仅是对目标进程中的操作有效。

EAT表的操作如下:(说明略去)
IMAGE_DOS_HEADER * dosheader=(IMAGE_DOS_HEADER *)hMod;
IMAGE_OPTIONAL_HEADER * opthdr =(IMAGE_OPTIONAL_HEADER *)
((BYTE*)hMod+dosheader->e_lfanew+24);
IMAGE_EXPORT_DIRECTORY *exp=(IMAGE_EXPORT_DIRECTORY *)((BYTE*) hMod
+opthdr->DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT]. VirtualAddress);

ULONG *addressoffunctions=(ULONG*)((BYTE*) hMod +exp->AddressOfFunctions);
ULONG * addressofnames=(ULONG*)((BYTE*) hMod +exp->AddressOfNames);

for(DWORD x=0; x < exp->NumberOfFunctions;x++)
{
char*functionname=(char*)((BYTE*) hMod +addressofnames[x]);

DWORD functionaddress=(DWORD)((BYTE*) hMod +addressoffunctions[x]);
}

这里,不能用代理函数的实际内存地址替换EAT的函数地址,而必须使用RVA来替换。由于RVA地址不能为负数(译注:原因不清楚,但是RVA至少不能小,否则会被认为是输出转向),因为代理函数的地址必须位于Kernel32.dll模块基地址之上(因为我们要对kernel32.dll挂钩)

BYTE* writebuff=(BYTE*)VirtualAllocEx(GetCurrentProcess(),0,5*4096,
  MEM_RESERVE|MEM_TOP_DOWN,PAGE_EXECUTE_READWRITE);
writebuff=(BYTE*)VirtualAllocEx(GetCurrentProcess(),writebuff,5*4096,
  MEM_COMMIT|MEM_TOP_DOWN,PAGE_EXECUTE_READWRITE);

for(int x=1;x<=exp->NumberOfFunctions;x++)
{
//这是因为在windows2000上kernel32.dll的输出函数一共有823个,而对每一个替换需要6字节的间接指令,和16字节的RelocationFunction结构体的大小,也就是需要22字节,按照32位机的4字节对齐方式,补足到24字节。另外对于Intel CPU一页内存是4K字节,可以容纳170个24字节,还有16字节的剩余,但考虑到按页对齐,所以不在它上面另作分配。这样我们就需要用5页内存来存放对kernel32.dll的输出表的替换。
DWORD a=(x-1)/170,pos=a*16+(x-1)*24;
BYTE*currentchunk= &writebuff[pos];
DWORD offset=(DWORD)writebuff-(DWORD)hMod+pos;

//get name and address of the target function
char*functionname=(char*)((BYTE*) hMod +addressofnames[x-1]);
DWORD functionaddress=(DWORD)((BYTE*) hMod +addressoffunctions[x-1]);

// load virtual memory with machine instructions and relocation information
DWORD addr=(DWORD)&writebuff[pos+6];
currentchunk[0]=0xFF;currentchunk[1]=0x15;
memmove(currenttchunk[2],&addr,4);
RelocatedFunction * reloc=(RelocatedFunction*)currenttchunk[6];
reloc->funcname= functionname;
reloc->funcptr=functionaddress;
reloc->proxyptr=(DWORD)&ProxyProlog;

// overwrite export address table
DWORD byteswritten;
WriteProcessMemory(GetCurrentProcess(),&addressoffunctions[x-1],
 &offset,4,&byteswritten);
}

使用overwrite函数,通过ToolHelp32把所有已经加载的模块文件的输入表全部进行替换。
void overwrite(HMODULE hMod)
{
IMAGE_DOS_HEADER * dosheader=(IMAGE_DOS_HEADER *)hMod;
IMAGE_OPTIONAL_HEADER * opthdr =(IMAGE_OPTIONAL_HEADER *) ((BYTE*)hMod+dosheader->e_lfanew+24);
IMAGE_IMPORT_DESCRIPTOR *descriptor= (IMAGE_IMPORT_DESCRIPTOR *)((BYTE*)dosheader+opthdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

HANDLE hand=GetCurrentProcess();
HMODULE ker=GetModuleHandle("kernel32.dll");

while(descriptor->FirstThunk)
{
     char*dllname=(char*)((BYTE*)hMod+descriptor->Name);
     if(lstrcmp(dllname,"KERNEL32.dll")){descriptor++;continue;}
     IMAGE_THUNK_DATA* thunk=(IMAGE_THUNK_DATA*)((BYTE*)dosheader+descriptor->OriginalFirstThunk);
     int x=0;

     while(thunk->u1.Function)
     {
       char*functionname=(char*)((BYTE*)dosheader + (unsigned)thunk->u1.AddressOfData+2);
       DWORD*IATentryaddress=(DWORD*)((BYTE*)dosheader+descriptor->FirstThunk)+x;

       DWORD addr=(DWORD)GetProcAddress(ker,functionname);
       DWORD byteswritten;   
       WriteProcessMemory(hand,IATentryaddress, &addr,4,&byteswritten);
       x++;thunk++;
      }

      descriptor++;
}

CloseHandle(hand);
}

HANDLE snap=
CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,GetCurrentProcessId());
MODULEENTRY32 mod;mod.dwSize=sizeof(MODULEENTRY32);
Module32First(snap,&mod);
HMODULE first=mod.hModule;
overwrite(first);
while(Module32Next(snap,&mod))
{
    HMODULE next=mod.hModule;
    if(next==currenthandle)continue;
    overwrite(next);
}

在目标进程注射挂钩DLL

在这里不能使用Jeffrey Richter提出的用CreateRemoteThread()的方法。因为我们把被挂钩函数的地址保存在线程本地存贮器上,如果要目标进程能够正确地进行挂钩,就需要在该进程的每一个线程中动态的分配内存来作为线程本地存贮器,比如挂钩DLL的DllMain()函数被目标进程的每一个线程调用。这就要求目标进程在挂钩DLL被装载以后首先执行其DllMain()函数,然后创建目标进程的其他线程。另一方面,使用CreateRemoteThread()的方法注入DLL文件,目标进程的所有线程都是在挂钩DLL注入前就已经创建了。因此不能使用CreateRemoteThread()方法。

有两种方法能够完成对挂钩DLL的注射,相比较而言第一种方法要相对容易一些。
1、把挂钩DLL文件注射到目标进程的主线程,而且必须在目标进程创建其他线程之前注入,比如在目标进程创建时尽可能早的阶段。
2、让目标进程已经运行的线程调用挂钩DLL文件的入口点函数。


在用户创建的目标进程中注射挂钩进程

void install(char* filename)
{
// 获取创建目标进程的入口点
DWORD bytes;char buff[4096];
HANDLE file=CreateFile(filename ,
GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
ReadFile(file,buff,1024,&bytes,0);
CloseHandle(file);
IMAGE_DOS_HEADER * dosheader=(IMAGE_DOS_HEADER *)buff;
IMAGE_OPTIONAL_HEADER *optionalheader=(IMAGE_OPTIONAL_HEADER
*)((BYTE*)buff+dosheader->e_lfanew+24);
DWORD entryptr=optionalheader->AddressOfEntryPoint+optionalheader->ImageBase;

// 创建目标进程,并把它挂起(Suspend)
STARTUPINFO startup;GetStartupInfo(&startup);PROCESS_INFORMATION procinfo;
CreateProcess(filename,0,0,0,TRUE,CREATE_SUSPENDED,0,0,&startup,&procinfo);

// 在目标进程上分配内存
BYTE* writebuff=(BYTE*)VirtualAllocEx(procinfo.hProcess,0,4096,MEM_RESERVE,PAGE_EXECUTE_READWRITE);
writebuff=(BYTE*)VirtualAllocEx(procinfo.hProcess,writebuff,4096,MEM_COMMIT,PAGE_EXECUTE_READWRITE);

// 获取LoadLibraryA的地址
DWORD function=(DWORD)GetProcAddress(GetModuleHandle("kernel32.dll"),"LoadLibraryA");

// 构造需要运行的机器指令
// push pointer_to_dllname
// push address_of_entrypoint
// jmp dword ptr[_imp_LoadLibraryA]
DWORD stringptr=(DWORD)&writebuff[20];strcpy(&buff[20],"spydll.dll");
DWORD funcptr=(DWORD)&writebuff[16];memmove(&buff[16],&function,4);
buff[0]=0x68;
memmove(&buff[1],&stringptr,4);
buff[5]=0x68;
memmove(&buff[6],&entryptr,4);
buff[10]=0xFF;buff[11]=0x25;
memmove(&buff[12],&funcptr,4);

// 把构造的机器指令拷贝到目标进程上
WriteProcessMemory(procinfo.hProcess,writebuff,buff,4096,&bytes);

// 改变目标进程的执行上下文中的EIP,去执行写入的机器指令
CONTEXT Context;Context.ContextFlags=CONTEXT_CONTROL;
GetThreadContext(procinfo.hThread,&Context);
Context.Eip=(DWORD)writebuff;
SetThreadContext(procinfo.hThread,&Context);
ResumeThread(procinfo.hThread);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值