本文在书籍《加密解密 第四版》指导下编写
一、实验准备
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:
二、实践开始
首先我们需要找一块地址可以将新构造的结构写入,这里选择的是直接扩充最后一个节,按照文件对齐粒度为.rsrc节增加0x200字节,注意,增加后还需要修改PE文件头中该节的大小,可通过PE工具快速修改。然后将原IID数组拷贝到这里,原IID数组的文件偏移是0x6a04(RVA - 0x1000 + 0x400),大小为0xc8。修改完后效果如下:
然后我们开始构造新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位置,填写的时候需要注意字节序是小端序,填写结果如下
然后我们开始填写我们新构建的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即可,将以上数据填入得到:
接下来就进入了最后一步,修改PE文件头中指向输入表的位置为我们新构造的表的位置,另外因为PE在加载过程中FIrstThunk会被填充为真正的输入函数地址,所以该区段需要是可写的,原节属性为0x60000020,将其加上可写属性的0x80000000得到0xE0000020 。
修改PE头指向输入表的位置可以通过PE工具快速修改,值应改为我们新添加的节的RVA,该值等于0x10400 - 0x400 + 0x1000 * 3 =0x13000。
节属性也可通过PE工具修改
当我们全部修改完成后,查看导入表,结果如下:
双击运行notepad发现弹窗出现,dll已经成功注入。