一、实验准备
winXP的记事本,一个MsgDLL.dll文件,其中导出一个函数Msg()
代码如下:

BOOL APIENTRY DllMain(HANDLE 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;
}

DWOED WINAPI ThreadShow(LPVOID lpParameter)
{
    char szPath[MAX_PATH] = {0};
    char szBUF[1024] = {0};
    GetModuleFileName(NULL,szPath,MAX_PATH);
    sprintf(szBuf,"DLL 已注入到进程 %s [Pid = %d]\n",szPath,GetCurrentProcessId())
    MessageBox(NULL,szBuf,"DLL Inject",MB_OK);
    printf("%s",szBuf);
    OutputDeBugString(szBuf);
    retuern 0;
}

首先要知道,我们要完成的操作是通过修改PE结构来完成注入dll,PE中有关于模块导入的是输入表结构,而输入表是一个IID数组,那么想要将我们的dll导入到程序中就需要将一个我们构造的IID结构写入这个数组中,此时需要考虑我们新写入的IID结构大小,假设原IID大小为old,那么显然,由于只导入了一个函数,我们新的IID结构大小newIIDSize = Old+ sizeof(IMAGE_IMPORT_DESCRIPTOR)。

记事本的程序基本信息如下

ImportAddress RVA Size newIIDSize FileAlignment SectionAlignment
0x7604 0xc8 0xc8 + 0x14 = 0xdc 0x200 0x1000

其中ImportAddress RVA是输入表的RVA(相对虚拟地址),size是输入表的大小。FileAlignment和SectionAlignment分别是文件对齐粒度和内存对齐粒度。这些值可以在PE工具中快速查看,例如StudyPE:

image.png
二、实践开始
首先我们需要找一块地址可以将新构造的结构写入,这里选择的是直接扩充最后一个节,按照文件对齐粒度为.rsrc节增加0x200字节,注意,增加后还需要修改PE文件头中该节的大小,可通过PE工具快速修改。然后将原IID数组拷贝到这里,原IID数组的文件偏移是0x6a04(RVA - 0x1000 + 0x400),大小为0xc8。修改完后效果如下:

image.png

然后我们开始构造新IID的originalFirstThunk、FirstThunk指向的数据和Name结构,由于前两个大小固定,比较容易对齐,所以我们可以先构造这两个Thunk,为了节省空间,我们将这些数据放在原IID的位置,当然放在新构造的200个字节里也是可以的。
两个Thunk结构大小都是4字节+4字节全0结束标记,所以新填入的数据空间安排如下

+00h    OriginalFirstThunk
+04h    全0结束标记
+08h    FirstThunk
+0ch    全0结束标记
+10h    Name --"MsgDll.dll"
+1ah    全0字符串结束标记
+1ch    IMPORT_BY_NAME-> hint
+1eh    IMPORT_BY_NAME-> Name

在PE文件被加载前指向的都是IMPORT_BY_NAME数组,该结构的RawOffset = 0x6a04 + 0x1c = 0x6a20,RVA = 0x6a20 - 0x400 + 0x1000 = 0x7614 。所以我们填入的各个数据如下:

偏移
+00h OriginalFirstThunk 0x7620
+04h 全0结束标记 0x0000
+08h FirstThunk 0x7620
+0ch 全0结束标记 0x0000
+10h Name --"MsgDll.dll" 4d 73 67 44 6c 6c 2e 64 6c 6c
+1ah 全0字符串结束标记 0x0000
+1ch IMPORT_BY_NAME-> hint 0x00
+1eh IMPORT_BY_NAME-> Name 4d 73 67

将以上各值填入原IID位置,填写的时候需要注意字节序是小端序,填写结果如下
image.png

然后我们开始填写我们新构建的IID结构
其中需要填写的有三个字段,分别是OriginalFirstThunk RVA 、Name RVA 以及FirstThunk RVA

OriginalFirstThunk RVA = RawOffset(0x6a04) - 0x400 + 0x1000 = 0x7604
Name RVA = RawOffset(0x6a14) - 0x400 + 0x1000 = 0x7614
FirstThunk RVA = RawOffset(0x6a0c) - 0x400 + 0x1000 = 0x760c

其余TimeDateStamp和FOrwarderChain字段填0即可,将以上数据填入得到:
image.png

接下来就进入了最后一步,修改PE文件头中指向输入表的位置为我们新构造的表的位置,另外因为PE在加载过程中FIrstThunk会被填充为真正的输入函数地址,所以该区段需要是可写的,原节属性为0x60000020,将其加上可写属性的0x80000000得到0xE0000020 。
修改PE头指向输入表的位置可以通过PE工具快速修改,值应改为我们新添加的节的RVA,该值等于0x10400 - 0x400 + 0x1000 * 3 =0x13000。
节属性也可通过PE工具修改
当我们全部修改完成后,查看导入表,结果如下:
image.png
双击运行notepad发现弹窗出现,dll已经成功注入。
image.png