实现DLL注入的另一种方法

DLL注入进程技术在实现Api函数的监视程序中不可缺少的一项工作。其中最常见的就是用SetWindowsHookEx函数实现了。不过,该方法的缺点是被监视的目标进程必须有窗口,这样,SetWindowsHookEx才能将DLL注入目标进程中。而且,目标程序已经运行了,那么,在窗口创建之前的Api函数就不能被Hook了。

另外一种方法用Debug方案,就可以实现在程序创建时监视所有的Api了,缺点是必须是目标进程的Debug源,在监视程序终了时,目标进程会无条件终了。最大的缺点就是无法调试注入的DLL

还有其他多种方案也可以实现DLL的注入,在《Windows核心编程》一书中就介绍了89种,其中有一种采用CreateProcess的方法,实现起来比较复杂,但没有上面几种方法的局限性。且可以用其他工具(VC等)调试注入的DLL。下面进行介绍。

原理如下:

1.  CreateProcessCREATE_SUSPENDED)启动目标进程。

2.  找到目标进程的入口,用ImageHlp中的函数可以实现。

3.  将目标进程入口的代码保存起来。

4.  在目标进程的入口写入LoadLibraryMyDll)实现Dll的注入。

5.  ResumeThread运行目标进程。

6.  目标进程就运行了LoadLibraryMyDll),实现DLL的注入。

7.  目标进程运行完LoadLibrary(MyDll)后,将原来的代码写回目标进程的入口。

8.  目标进程Jmp至原来的入口,继续运行程序。

从原理上可以看出,DLL的注入在目标进程的开始就运行了,而且不是用Debug的方案,这样,就没有上面方案的局限性了。该方案的关键在678三步,实现方法需要监视进程和DLL合作。下面,结合代码进行分析。

在监视进程中,创建FileMapping,用来保存目标进程的入口代码,同时保证DLL中可以访问。在第7步实现将原目标代码写回目标进程的入口。

// 监视程序和DLL共用的结构体

#pragma pack (push ,1)  // 保证下面的结构体采用BYTE对齐(必须)

typedef struct

{

       BYTE      int_PUSHAD;         // pushad        0x60      

       BYTE      int_PUSH;             // push &szDLL     0x68

       DWORD push_Value;           //            &szDLL = "ApiSpy.dll"path

       BYTE      int_MOVEAX;              //  move eax &LoadLibrary  0xB8

       DWORD eax_Value;             //     &LoadLibrary

       WORD    call_eax;         //     call eax    0xD0FF(FF D0) (LoadLibrary("ApiSpy.dll");

       BYTE      jmp_MOVEAX;             //     move eax &ReplaceOldCode  0xB8      

       DWORD jmp_Value;             //     JMP的参数

       WORD    jmp_eax;        //     jmp eax   0xE0FF(FF E0) jmp ReplaceOldCode;

       char szDLL[MAX_PATH]; //  "ApiSpy.dll"FullPath

}INJECT_LOADLIBRARY_CODE, *LPINJECT_CODE;

 

#pragma pack (pop , 1)

上面结构体的代码为汇编代码,对应的汇编为:

pushad

push szDll

mov eax, &LoadLibraryA

call eax  // 实现调用LoadLibrary(szDll)的代码

mov eax, oldentry

jmp eax // 实现在LoadLibrary运行完后, 跳至目标进程的入口继续运行

 

// FileMaping的结构体

typedef struct

{

       LPBYTE  lpEntryPoint;   // 目标进程的入口地址

       BYTE      oldcode[sizeof(INJECT_CODE)];        // 目标进程的代码保存

}SPY_MEM_SHARE, * LPSPY_MEM_SHARE;

 

准备工作:

第一步:用CreateProcessCREATE_SUSPENDED)启动目标进程。

 

CreateProcessA(0, szRunFile, 0, 0, FALSE, CREATE_SUSPENDED

                            0, NULL, &stInfo,

                            &m_proInfo) ;

// CreateProcess启动一个暂停的目标进程

// 找到目标进程的入口点,函数如下

第二步:找到目标进程的入口,用ImageHlp中的函数可以实现。

pEntryPoint = GetExeEntryPoint(szRunFile);

 

LPBYTE  GetExeEntryPoint(char *filename)

{

       PIMAGE_NT_HEADERS      pNTHeader;

       DWORD pEntryPoint;

       PLOADED_IMAGE       pImage;

 

       pImage = ImageLoad(filename, NULL);

 

       if(pImage == NULL)

              return NULL;

       pNTHeader = pImage->FileHeader;

       pEntryPoint = pNTHeader->OptionalHeader.AddressOfEntryPoint + pNTHeader->OptionalHeader.ImageBase;

       ImageUnload(pImage);

       return (LPBYTE)pEntryPoint;

}

 

// 创建FileMapping

hMap = CreateFileMapping((HANDLE)0xFFFFFFFF, NULL,

       PAGE_READWRITE,    0, sizeof(SPY_MEM_SHARE), “MyDllMapView”);

 

// 保存目标进程的代码

第三步:将目标进程入口的代码保存起来。

LPSPY_MEM_SHARE   lpMap = pMapViewOfFile(hMap,

                                                                      FILE_MAP_ALL_ACCESS,

                                                                      0, 0, 0);

 

ReadProcessMemory(m_proInfo.hProcess, pEntryPoint,

                    &lpMap->oldcode, sizeof(INJECT_CODE),

                     &cBytesMoved);

       lpMap->lpEntryPoint = pEntryPoint;

 

// 第四步:在目标进程的入口写入LoadLibraryMyDll)实现Dll的注入。

 

// 准备注入DLL的代码

       INJECT_CODE     newCode;

// 写入MyDll―――用全路径

       lstrcpy(newCode.szDLL, szMyDll);

// 准备硬代码(汇编代码)

       newCode.int_PUSHAD = 0x60;   

       newCode.int_PUSH = 0x68;

       newCode.int_MOVEAX = 0xB8;

       newCode.call_eax = 0xD0FF;

       newCode.jmp_MOVEAX = 0xB8;

       newCode.jmp_eax = 0xE0FF;

       newCode.eax_Value = (DWORD)LoadLibrary;

       newCode.push_Value=(pEntryPoint + offsetof(INJECT_CODE,szDLL));

 

// 将硬代码写入目标进程的入口

// 修改内存属性

DWORD dwNewFlg, dwOldFlg;

 

dwNewFlg = PAGE_READWRITE;

VirtualProtectEx(m_proInfo.hProcess, (LPVOID)pEntryPoint, sizeof(DWORD), dwNewFlg, &dwOldFlg);

 

WriteProcessMemory(m_proInfo.hProcess, pEntryPoint,

                                          &newCode, sizeof(newCode), NULL);//&dwWrited);

              VirtualProtectEx(proInfo.hProcess, (LPVOID)pEntryPoint, sizeof(DWORD), dwOldFlg, &dwNewFlg);

// 释放FileMaping  注意,不是Closehandle(hMap)

UnmapViewOfFile(lpMap);

// 继续目标进程的运行

第五步:用ResumeThread运行目标进程。

ResumeThread(m_proInfo.hThread);

 

在监视进程中就结束了自己的任务,剩下的第678步就需要在DllDllMain中进行配合。

DLL中用来保存数据的结构体

typedef struct
{
         DWORD    lpEntryPoint;
         DWORD    OldAddr;
         DWORD    OldCode[4];
}JMP_CODE,* LPJMP_CODE;
 
  
  
static JMP_CODE  _lpCode;

 

// DllMainDLL_PROCESS_ATTACH中调用InitApiSpy函数

// 在该函数中实现第678

 

第六步:目标进程就运行了LoadLibraryMyDll),实现DLL的注入。

 

int    WINAPI DllMain(HINSTANCE hInst, DWORD dwReason, LPVOID lpReserved)

{

       switch(dwReason)

       {

       case DLL_PROCESS_ATTACH:

              return InitApiSpy();

……

 

// InitApiSpy函数的实现

BOOL WINAPI InitApiSpy()

{

       HANDLE hMap;

       LPSPY_MEM_SHARE   lpMem;

       DWORD dwSize;

       BOOL     rc;

       BYTE*    lpByte;

 

       // 取得FileMapping的句柄

       hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, 0, “MyDllMapView”);

       if(hMap)

       {

              lpMem = (LPSPY_MEM_SHARE)MapViewOfFile(hMap,

                                                                      FILE_MAP_ALL_ACCESS,

                                                                      0, 0, 0);

              if(lpMem)

              {

第七步:目标进程运行完LoadLibrary(MyDll)后,将原来的代码写回目标进程的入口。

                     // 恢复目标进程的入口代码

// 得到mov eax, value代码的地址

                     _lpCode.OldAddr = (DWORD)((BYTE*)lpMem->lpEntryPoint + offsetof(INJECT_CODE, jmp_MOVEAX));

                     _lpCode.lpEntryPoint = (DWORD)lpMem->lpEntryPoint;

                     // 保存LoadLibrary()后面的代码

                     memcpy(&_lpCode.OldCode, (BYTE*)lpMem->oldcode + offsetof(INJECT_CODE, jmp_MOVEAX), 2*sizeof(DWORD));

                     // 恢复目标进程的入口代码

                     rc = WriteProcessMemory(GetCurrentProcess(), lpMem->lpEntryPoint, lpMem->oldcode, sizeof(INJECT_CODE), &dwSize);

                     lpByte = (BYTE*)lpMem->lpEntryPoint + offsetof(INJECT_CODE, jmp_MOVEAX);

                     UnmapViewOfFile(lpMem);

              }

              CloseHandle(hMap);

       }

 

// 实现自己Dll的其他功能,如导入表的替换

//     ……

// LoadLibrary后面的代码写为转入处理程序中

// 指令为:mov eax, objAddress

//         jmp eax

       {

              BYTE*    lpMovEax;

              DWORD*      lpMovEaxValu;

              WORD*  lpJmp;

              DWORD fNew, fOld;

 

              fNew = PAGE_READWRITE;

              lpMovEax = lpByte;

              VirtualProtect(lpMovEax, 2*sizeof(DWORD), fNew, &fOld);

              *lpMovEax = 0xB8;

              lpMovEaxValu = (DWORD*)(lpMovEax + 1);

              *lpMovEaxValu = (DWORD)&DoJmpEntryPoint;

              lpJmp = (WORD*)(lpMovEax + 5);

              *lpJmp = 0xE0FF;  // (FF E0)

              VirtualProtect(lpMovEax, 2*sizeof(DWORD), fOld, &fNew);

       }

       return TRUE;

}

 

// 转入处理程序

DWORD*      lpMovEax;

DWORD fNew, fOld;

 

void __declspec(naked) DoJmpEntryPoint ()

{

  // 恢复LoadLibrary后面的代码

       _gfNew = PAGE_READWRITE;

       _glpMovEax = (DWORD*)_lpCode.OldAddr;

       VirtualProtect(_glpMovEax, 2*sizeof(DWORD), _gfNew, &_gfOld);

       *_glpMovEax = _lpCode.OldCode[0];

       *(_glpMovEax + 1) = _lpCode.OldCode[1];

       VirtualProtect(_glpMovEax, 2*sizeof(DWORD), _gfOld, &_gfNew);

第八步:目标进程Jmp至原来的入口,继续运行程序。

// 跳至目标代码的入口

       _asm       popad

       _asm       jmp _lpCode.lpEntryPoint

}

 

这样就实现了原来的目标,将DLL的注入放在目标进程的入口运行,实现了目标进程运行之前运行我们的注入Dll的功能。

 

下面用图形表示在目标进程中代码发生变化的情况。(示意图)

 

监视程序

FileMapping

目标进程

第一步

启动目标进程

第二步

找到入口

启动(暂停中)

第三步

保存代码

目标进程的代码

无变化

第四步

写入注入代码

同上

入口代码为:

LoadLibrary(MyDll);

jmp xxxxxxxx

其他原来的代码

第五步

运行目标进程

同上

运行LoadLibrary

第六步

无变化

无变化

LoadLibrary中处理

第七步

无变化

无变化

入口代码被写为原来的代码

7.5

无变化

无变化

上面jmp xxxxxxxx的代码写为jmp DoJmpEntryPoint

7.9

无变化

无变化

运行完LoadLibrary后,转入DoJmpEntryPoint函数中处理

在该函数中,将jmp DoJmpEntryPoint的指令恢复为原指令。

第八步

无变化

无变化

转入目标进程的入口执行,完成注入目标。

 
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
首先关于 [评价可免费] 的严重声明: 一、评价=评论加评价(评星星); 二、评价必须是下载完了该资源后的评价,没下载就评论无效; 三、如果正确评价了,返还积分可能需要等等,系统需要反应下。呵呵 评论时记得要评分。然后会返回给你花费的分再加1分.理论上有十分就可以下载完所有的资源了。一般人我不告诉他。 本教程的目的是描述我们关于装载器的研究、介绍问题所在以及两个不同的编写装载器的方法。这里提供的一个框架,我们曾经在一些成功的补丁制作使用过,希望你能够灵活应用它。 阅读本文至少需要懂得一点 C/C++ 程序语言知识,我们在以下章节使用的所有代码是用C语言编写的 (而且在 Visual C++ 6.0 控制台应用程序工程类型下经过测试)。 在本教程我们还发布一个用C++语言编写的框架,帮助快速编写通用的、复杂的应用程序装载器。我们并不打算发布一个框架库,因为若要编写装载器你首先得读懂附件C或C++源码,所以就把它的编译任务做为你的家庭作业了。 我们还结合一个实例,讲解如何通过装载器嗅探VB应用程系列号算法,并指出关于VB魔术破解 (不使用参考资料 [2], [3] , [4] 和 [5] 使用的远程线程技术或DLL注入方式) 的一些注意事项。 如果你已经了解如何自己编码一个装载器,你可以跳越到第4节。如果你还不知道如何编写调试器装载器,可以跳到第 3.3 节开始,否则请从零开始按部就班地阅读全文。 装载器是做什么的? 如果你还完全不了解装载器以及程序如何装入内存的话,我们建议你阅读参考资料[1] 和 [8] 以便理解本教程其余部分;鉴于读者应该已经具备了基础知识,所以本文只会复述少数的基本概念。 1.1 装载器分类和行为 装载器一个程序,它能够把另一个程序载入内存并运行。每次你启动一个程序,标准的window装载器在幕后替你做这项工作。装载器有许多种,但是所有装载器基本上可以分为两大类型: l 标准的装载器 l 调试器装载器 1.1.1 标准的装载器

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值