[DLL注入的方法]静态修改PE输入表法

1.三种DLL加载时机:

进程创建加载输入表中的DLL(静态输入)
通过调用LoadLibrary主动加载(动态加载)
系统预设加载

   通过干预输入表处理过程加载目标dll

1.静态修改PE输入表法(测试程序 Notepad.exe)

准备工作:自行编写一个MsgDLL,到处一个函数Msg();

#include "stdafx.h"

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    if (ul_reason_for_call == DLL_PROCESS_ATTACH)                
    {
        CreateThread(NULL, 0, ThreadShow, NULL, 0, NULL);
    }

    return TRUE;
}

DWORD WINAPI ThreadShow(LPVOID lpParameter)
{
    char szPath[MAX_PATH] = { 0 };
    char szBuf[1024] = { 0 };
    //获取宿主进程的路径
    GetModuleFileName(NULL, (LPWSTR)szPath, MAX_PATH);
    sprintf(szBuf, "DLL已经注入到进程:%s	[pid =%d]
", szPath, GetCurrentProcessId());
    //以三种方式显示自己的存在
        //1.
    MessageBox(NULL, (LPWSTR)szBuf, "DLL存在", MB_OK);
    
    //2.
    printf("%s
", szBuf);
    //3.
    OutputDebugString((LPWSTR)szBuf);
    return 0;
}                

参数意义: 

①hModule参数:指向DLL本身的实例句柄;

②ul_reason_for_call参数:指明了DLL被调用的原因,可以有以下4个取值:

1. DLL_PROCESS_ATTACH:
当DLL被进程 <<第一次>> 调用时,导致DllMain函数被调用,

同时ul_reason_for_call的值为DLL_PROCESS_ATTACH,

如果同一个进程后来再次调用此DLL时,操作系统只会增加DLL的使用次数,

不会再用DLL_PROCESS_ATTACH调用DLL的DllMain函数。

2.DLL_PROCESS_DETACH:
当DLL被从进程的地址空间解除映射时,系统调用了它的DllMain,传递的ul_reason_for_call值是DLL_PROCESS_DETACH。
★如果进程的终结是因为调用了TerminateProcess,系统就不会用DLL_PROCESS_DETACH来调用DLL的DllMain函数。这就意味着DLL在进程结束前没有机会执行任何清理工作。

3.DLL_THREAD_ATTACH:
当进程创建一线程时,系统查看当前映射到进程地址空间中的所有DLL文件映像,

并用值DLL_THREAD_ATTACH调用DLL的DllMain函数。 

新创建的线程负责执行这次的DLL的DllMain函数,

只有当所有的DLL都处理完这一通知后,系统才允许线程开始执行它的线程函数。

4.DLL_THREAD_DETACH:
如果线程调用了ExitThread来结束线程(线程函数返回时,系统也会自动调用ExitThread),

系统查看当前映射到进程空间中的所有DLL文件映像,

并用DLL_THREAD_DETACH来调用DllMain函数,

通知所有的DLL去执行线程级的清理工作。
★注意:如果线程的结束是因为系统中的一个线程调用了TerminateThread,

系统就不会用值DLL_THREAD_DETACH来调用所有DLL的DllMain函数。

③lpReserved参数:保留

第二步:判断是否还有足够的空间存储我们的导出函数  —–》(PE格式知识)

 )如果空间不足,那么我们则需要采取扩大节或者新增节来进行位置存储;
Notepad无法存储我们的导出函数,那么我这里采取扩大最后一个节的方法

 根据PE格式可以看到再数据目录项中的导入表RVA为00007604

那么我们这里讲RVA转成FOA文件偏移 :0x7604 -0x1000(Virtual Address) + 0x400 (Raw Address) =0x6a04     —》PE知识,看不懂重学PE

然后用16进制软件打开notepad,我这里使用的是010 editor:

首先我们可以看到一个导入表的结构为20字节也就是16h,输入表中,每20个字节(一个Image_Import_Directory)对应一个动态链接库Dll的调用数据:

 并且导入表是连续的,直到它以一组0x14大小的全0的结束标记来结束

定位到0x6a04,如图所示,后面则是0x14大小的结束标记

 

 接下来,我们将最后一个节区进行扩充:

将原有导入表搬入新地址(直接复制粘贴到新地址):

 

 粘贴好后,在原有导入表区域构建新的OriginalFirstThunk、name和FirstThunk结构(注意我们粘贴后把原有导入表区域清零,腾出空间,做我们自己的结构)

  首先清零

 

然后构建我们的OriginalFirstThunk、name和FirstThunk结构

在PE文件被加载前 ,OriginalFirstThunk和FirstThunk都是指向IMPORT_BY_NAME

根据结构我们可以知道:

  DLLName                     RawOffset =0x6A14          RVA= 0x6A14  -0x400(Raw Address)+0x1000(Virtual Address)  =0x7614

  IMPORT_BY_NAME    RawOffset =0x6A20          RVA= 0x6A20     -0x400(Raw Address)+0x1000(Virtual Address)  =0x7620 

在手动填写数据的时候一定要注意字节顺序问题:

然后根据刚填充的两个结构和Name的偏移,填写新的导入表结构

OriginalFirstThunk :RawOffset =0x6a04            RVA =0x6a04  -0x400(Raw Address)+0x1000(Virtual Address)    =0x7604

FirstThunk:RawOffset =0x6a0c          RVA =   0x6a0c -0x400(Raw Address)+0x1000(Virtual Address)   =0x7620

DLLName                     RawOffset =0x6A14          RVA= 0x6A14  -0x400(Raw Address)+0x1000(Virtual Address)  =0x7614

 修改完成后,我们修正PE文件头信息:

  输入表目录指向位置
  FirstThunk -》可写属性

首先修正输入表目录指向位置:

在010中定位到导入表的RVA :

 将它原本指向的值修改成我们新替换的位置也就是最后一节的位置:

内存偏移 = 0XB000 +0X8000 =0X13000

文件偏移 = 0x8400+0x8000=0x10400

 

 由于使用了原来导入表数组的位置存放FirstThunk,而它原来的位置的RVA是0X7604,根据各节的起始位置和偏移量,可以确定该节属于text节,而该节原来的属性是0X60000020,写属性定义如下

# define IMAGE_SCN_MEM_WRITE      0x800000000  //节是可写的

0x 60000020+0x80000000 =0xE0000020

然后把新节属性就是原属性加上这个值也就是0xE0000020

 至此我们的修改工作全部完成,保存修改结果

接下来运行修改后的NotePad,结果MessageBox没有弹出来!

这里是因为IMAGE_IMPORT_DESCRIPTOR中定义的TimeDateStamp为0xFFFFFFFF也就是-1,表示改输入项是原来预先Bound的,如果系统检测发现预绑定是有效的,那么就不会再去处理输入表加载了,所以我们只需把0x1B0到0x1B8 内容清零再次保存即可

可以看到确实加载了我们的MsgDll.dll

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注