将代码注入到进程的三种方式

[源码下载]

 

介绍

        在Code Project网站有许多关于password spy的文章,但是这些都是基于Windows Hooks的,还有没有其他方式能实现这种效果呢?是的,有,不过,先让我们简要的回顾一下。

要想读控件的内容,一般需要给它发送WM_GETTEXT 消息.edit控件就有这个特点。如果edit控件是在别的进程里,而且被设置成ES_PASSWORD类型,那么我们想要达到的目的就不会成功。这种情况下只有拥有password控件的进程才能实现WM_GETTEXT,所以我们的问题集中在了这一点上:

怎样才能在外部的进程空间内执行::SendMessage( hPwdEdit, WM_GETTEXT, nMaxChars, psBuffer );

通常有三种方式解决这个问题:

1.把代码写到Dll内;然后,通过windows hooks将Dll映射到远程进程。

2.把代码写道Dll内;然后,通过CreateRemoteThread & LoadLibrary 技术将Dll映射到远程进程。

3.不单独写一个DLL;通过WriteProcessMemory将你的代码直接拷贝到远程进程。

 

I.Windows Hooks

Windows hools的主要用途是监控一些线程的消息。

        1.Local hooks,监控属于你的进程的所有线程消息。

        2.Remote hooks:

                a.thread-specific,监控属于其他的进程的线程消息。

                b.system-width,监控当前运行在系统中的所有线程。

如果hook的线程属于别的进程,你的hook程序必须放置在Dll内。然后系统将包含hook程序的dll映射到线程的地址空间,Windows映射全部的Dll,不仅仅是hook程序,这就是为什么Windows hooks可以被用来将代码注入到其他进程的地址空间中去。

这篇文章里,我不去深入讨论hooks(可以在MSDN内详细的看一下SetWindowHookEx API函数)我还会给你两个有用的线索,这在文档里是找不到的。

        1.在调用SetWindowHookEx成功后,系统自动将Dll映射到了线程的地址空间,但不会立即执行。因为Windows hooks全部是基于消息,直到正确的事件返回Dll才被真正映射。例如:

        如果你安装了一个Hook用来监控一些线程的所有消息队列(WH_CALLWNDPROC),在消息被发送到线程之前DLL不会被映射到进程的地址空间。如果在消息发送到线程之前,UnhoolWindowsHoolEx被调用,那么Dll永远都不会被映射到进程(尽管调用SetWondosHookEx成功了)。要想立即执行,在SetWondosHookEx成功之后立即给线程发送一个合适的event。

        在调用UnhoolWindowsHoolEx取消Dll映射的时候,也不会立即取消DLL映射。

   2.当你安装Hooks的时候,他们会影响系统的总性能(尤其是system-wide hooks).如果你将thread-specific hooks单独作为一个Dll映射机制,那么你很容易就避免这个缺点,看下面的代码片断:

 

BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved )
{
    if( ul_reason_for_call == DLL_PROCESS_ATTACH )
    {
        // Increase reference count via LoadLibrary
        char lib_name[MAX_PATH]; 
        ::GetModuleFileName( hModule, lib_name, MAX_PATH );
        ::LoadLibrary( lib_name );

        // Safely remove hook
        ::UnhookWindowsHookEx( g_hHook );
    }    
    return TRUE;
}

 

   那么,发生了什么了?首先我们通过Windows hooks将Dll映射到远程进程。然后,在Dll被真正的映射之后,我们Unhook它,正常情况下,Dll将要被取消映射,同时,第一个到达被hook的线程的消息也被激活, 我们可以通过LoadLibrary增加Dll的引用计数来阻止取消映射。

   随之而来的问题是:现在怎么去卸载Dll,一旦结速了,UnhookWindowsHookEx不会做这些事情的,因为我们已经unhook了线程,可以这样去做:  

   1.在你想要取消Dll映射之前,安装另一个Hook。

   2.发送一个特殊的消息到远程线程

   3.在你的hook程序中,捕获消息,在回复消息中, 调用FreeLibrary & UnhookWindowsHookEx

 

   现在,hooks只在映射/取消映射的时候使用, 在这期间不会对"hooked"线程产生影响。 在第二部分中我们还会讨论比LoadLibrary更好的机制来使Dll映射不影响目标进程。然而,相对于LoadLibrary技术,这种解决方式只能在Winnt和Win9x下运行。

        但是,什么时候才使用这些窍门呢?当Dll不得不被放到远程进程中很长时间时(例如,你将一个控件变成另一个进程的子类),你可能想尽量小的影响目标进程。我不在HookSpy中使用它,因为此时Dll只会注入片刻。

 

II.CreateRemoteThread & LoadLibrary技术

通常,所有的进程都能通过LoadLibrary动态的导入Dll。但是,怎么才能让外部进程调用这个函数?回答是用CreateRemoteThread.

首先,让我们看一下LoadLibrary and FreeLibrary APIs 的声明:

HINSTANCE LoadLibrary(
  LPCTSTR lpLibFileName   // address of filename of library module
);

BOOL FreeLibrary(
  HMODULE hLibModule      // handle to loaded library module
);

现在,跟ThreadProc(线程程序,用来传给CreateRemoteThread)比较一下:

DWORD WINAPI ThreadProc(
  LPVOID lpParameter   // thread data
);

正如你所看到的,所有的函数都使用同样的调用转换,都接受一个32位的参数。返回值的大小也是一样的。换句话说,我们可以将LoadLibrary/FreeLibrary 作为线程函数传递给CreateRemoteThread.

 

然而,还有两个问题:

    1.CreateRemoteThread 里的lpStartAddress参数必须是远程进程的线程程序的首地址。

         2.如果lpParameter(传递给ThreadFunc)被认为是一个普通的32位值,(FreeLibrary 认为它是一个HMODULE),所有事情就好说了,然而,如果lpParameter 被描述成一个指针,(LoadLibraryA 将它描述成一个指向字符串的指针),那么它就必须指向远程进程的一些数据。

第二个问题好解决,用WriteProcessMemory拷贝Dll的模块名(需要LoadLibrary)。

 

下面是使用CreateRemoteThread & LoadLibrary 技术的步骤:

1.得到远程进程的一个句柄(OpenPreocess).

2.在远程进程里给Dll名称分配内存(VirtualAllocEx).

3.将Dll名称,包括全路径,写到分配的内存里(WritePreocessMemory).

4.通过CreateRemoteThread & LoadLibrary将你的Dll映射到远程进程.

5.等待远程进程结束(WaitForSingleObject);也就是等到LoadLibrary返回.当我们的DllMain返回的时候线程就结束了(调用原因DLL_PROCESS_ATTACH).

6.返回远程线程的结束码,(GetExitCodeThread).注意这个值是LoadLibrary的返回值,

7.释放在步骤#2分配的内存(VirtualFreeEx).

8.从远程进程卸载DLL,将#6得到的返回句柄HMODULE传递给FreeLibrary(lpParameter in CreateRemoteThread).

  注意:如果你将Dll注入任何新的线程,确保在卸载的时候全部结束。

9.等待线程结束(WaitForSingleObject).

 

而且,一旦完成,别忘了关闭所有的句柄,对于在#4和#8创建的线程和在步骤#1返回的远程进程的句柄。

HANDLE hThread;
char    szLibPath[_MAX_PATH];  // The name of our "LibSpy.dll" module
                               // (including full path!);
void*   pLibRemote;   // The address (in the remote process) where 
                      // szLibPath will be copied to;
DWORD   hLibModule;   // Base address of loaded module (==HMODULE);
HMODULE hKernel32 = ::GetModuleHandle("Kernel32");

// initialize szLibPath
//...

// 1. Allocate memory in the remote process for szLibPath
// 2. Write szLibPath to the allocated memory
pLibRemote = ::VirtualAllocEx( hProcess, NULL, sizeof(szLibPath),
                               MEM_COMMIT, PAGE_READWRITE );
::WriteProcessMemory( hProcess, pLibRemote, (void*)szLibPath,
                      sizeof(szLibPath), NULL );


// Load "LibSpy.dll" into the remote process
// (via CreateRemoteThread & LoadLibrary)
hThread = ::CreateRemoteThread( hProcess, NULL, 0,
            (LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32,
                                       "LoadLibraryA" ),
             pLibRemote, 0, NULL );
::WaitForSingleObject( hThread, INFINITE );

// Get handle of the loaded module
::GetExitCodeThread( hThread, &hLibModule );

// Clean up
::CloseHandle( hThread );
::VirtualFreeEx( hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE );

III.The CreateRemoteThread & WriteProcessMemory 技术

   这种方法是将代码拷贝到要注入进程的地址空间,然后在进程的上下文中执行这些代码,包括远程线程的使用。这就不必再使用分离的DLL了。可以用API函数WriteProcessMemory将代码直接拷贝到远程地址空间,用CreateRemoteThread启动代码执行。   

   先看一个CreateRemoteThread的声明       

HANDLE CreateRemoteThread(
  HANDLE hProcess,        // handle to process to create thread in
  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // pointer to security
                                             // attributes
  DWORD dwStackSize,      // initial thread stack size, in bytes
  LPTHREAD_START_ROUTINE lpStartAddress,     // pointer to thread
                                             // function
  LPVOID lpParameter,     // argument for new thread
  DWORD dwCreationFlags,  // creation flags
  LPDWORD lpThreadId      // pointer to returned thread identifier
);

 

   用它和CreateThread的声明相比,有下面几点不同:

  • hProcess参数在CreateRemoteThread内是多加的。它是创建线程的进程句柄。
  • lpStartAddress参数是CreateRemoteThread 内描述远程地址空间内的线程的起始地址。函数必须存在于远程空间内。我们首先必须将代码拷贝到远程空间。
  • 同样的, lpParameter指针必须存在于远程空间, 所以我们也要将它拷贝到那儿。  

    现在我们将这种技术总结为如下几步:

    1.取得远程空间的句柄(OpenProcess).

    2.在远程地址空间为注入的数据分配内存(VirtualAllocEx)

    3.将自定义结构INJDATA拷贝到分配的内存里(WriteProcessMemory)

    4.在远程地址空间为注入的代码分配内存。

    5.将ThreadFunc拷贝到分配的内存里。

    6.通过CreateRemoteThread启动远程的ThreadFunc。

    7.等待远程线程的结束(WaitForSingleObject). 

    8.从远程进程返回结果(ReadProcessMemory or GetExitCodeThread). 

    9.释放在步骤2#和步骤#4分配的内存(VirtualFreeEx);

    10.关闭在步骤#1和步骤#6返回的句柄(CloseHandle).

 

   ThreadFunc必须服从下面的要求:

    1.ThreadFunc 只能调用kernel32.dll 和 user32.dll内部的函数,只有kernel32.dll 和 user32.dll(注意user32没有被映射的每一个win32进程)能保证本地和目标进程有同样的加载地址。如果你需要其他库中的函数,将LoadLibrary and GetProcAddress的地址传入注入代码,如果有其他有争议的DLL已经被映射到了进程空间你也可以用GetModuleHandle 代替of LoadLibrary,

    同样的,如果你想从ThreadFunc内调用你自己的子程序。可以将地址先通过INJDATA传入到ThreadFunc,再经过ThreadFunc加载到远程空间。

    2.不要使用任何静态字符串,最好将所有的字符串通过INJDATA传递给ThreadFunc.

      为什么呢?编译器将所有的静态字符串放入到了可执行文件的".data"段,并且只是在代码内引用。这样的话,远程空间中的ThreadFunc的拷贝将指向一些不存在的地址,(最少不在它的地址空间内).

    3.将/GZ编译开关关掉,在debug里它是默认的。

    4.要不将ThreadFunc and AfterThreadFunc设为静态的,要不取消增量链接。

    5.在ThreadFunc内本地变量必须小于page-worth(4kb)。注意在debug中有效的4kb中的大约10字节是内部变量使用的。

    6.如果switch块大于3个case,就要把它象下面这样分开。

      或者改用if-else if

switch( expression ) {
    case constant1: statement1; goto END;
    case constant2: statement2; goto END;
    case constant3: statement2; goto END;
}
switch( expression ) {
    case constant4: statement4; goto END;
    case constant5: statement5; goto END;
    case constant6: statement6; goto END;
}
END:

如果不使用这些规则,很可能使目标进程崩溃,记住,不要将目标进程的空间当成你自己进程的空间。

 

 

[源址]http://www.codeproject.com/KB/threads/winspy.aspx

// Unload "LibSpy.dll" from the target process // (via CreateRemoteThread & FreeLibrary) hThread = ::CreateRemoteThread( hProcess, NULL, 0,
            (LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32,
                                       "FreeLibrary" ),
            (void*)hLibModule, 0, NULL );
::WaitForSingleObject( hThread, INFINITE );

// Clean up ::CloseHandle( hThread );
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值