Windows-如何给一个PE文件增加一个给定的DLL依赖

Windows-如何给一个PE文件增加一个给定的DLL依赖

瑾兮 发布于 2017-04-24 字数 277 浏览 1335 回复 4

目标是在执行一个EXE时,让系统把一个给定的DLL装载进该EXE的地址空间,以后可在此基础上作进一步的动作,比如进程行为监控。因为需要监控的动作就有远程线程、全局钩子等等,因此不能用通常的办法把给DLL载入目标进程的空间。因对这方面的情况不熟悉,求该方式的实现代码和需要注意的事项。

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

泛泛之交 2017-11-01 4 楼

插入DLL的方法很多,远程线程、全局钩子、注册表APP_DLL、镜像劫持、APC、修改PE导入表、马甲、HOOK LoadLibary、PsSetCreateProcessNotifyRoutineEx等。
但个人觉得这个想法是不可能实现的,矛与盾的故事。有方法把DLL载入目标进程的空间,就需要监控这个方法。在监控的驱动实现中加入逻辑,应用层传入进程ID,到监控的HOOK函数中,是这个ID进则放过。
还是使用PsSetCreateProcessNotifyRoutineEx内核回调函数,修改进程EXE的导入表加载DLL。PsSetCreateProcessNotifyRoutineEx是NtCreateProcess会调用的,所以进程创建时都会调用我们驱动中的回调函数。不过不要注册太多的内核回调函数,系统只支持20多个。

灵芸 2017-09-29 3 楼

//判断文件是否为合法PE文件
BOOL CheckPe(FILE* pFile)
{

fseek(pFile,0,SEEK_SET);
BOOL bFlags=FALSE;
WORD IsMZ;
DWORD IsPE,pNT;
fread(&IsMZ,sizeof(WORD),1,pFile);
if(IsMZ==0x5A4D)
{
fseek(pFile,0x3c,SEEK_SET);
fread(&pNT,sizeof(DWORD),1,pFile);
fseek(pFile,pNT,SEEK_SET);
fread(&IsPE,sizeof(DWORD),1,pFile);
if(IsPE==0X00004550)
bFlags=TRUE;
else
bFlags=FALSE;
}
else
bFlags=FALSE;
fseek(pFile,0,SEEK_SET);
return bFlags;
}

//用来计算对齐数据后的大小
int alig(int size,unsigned int align)
{
if(size%align!=0)
return (size/align+1)*align;
else
return size;
}

int main(int argc,char* argv[])
{
if(argc!=2)
{
printf("ttusage:add_section filenamen");
exit(-1);
}
FILE* rwFile;
if((rwFile=fopen(argv[1],"rb"))==NULL)//打开文件失败则退出
{
printf("ttOpen file faildn");
exit(-1);
}

if(!CheckPe(rwFile))
{
printf("ttinvalid pe......!n");
exit(-1);
}
//备份原文件
char szNewFile[10]="_New.exe";
if(!CopyFile(argv[1],szNewFile,0)) //若备份文件出错则退出
{
printf("ttbak faildn");
exit(-1);
}

IMAGE_NT_HEADERS NThea; //PE头的数据结构
DWORD pNT; //pNT中存放IMAGE_NT_HEADERS结构的地址

fseek(rwFile,0x3c,0);// 跳到到PE头偏移的保存位置
fread(&pNT,sizeof(DWORD),1,rwFile);// 读取存PE头的偏移位置
fseek(rwFile,pNT,0);//跳到PE头所在的位置
fread(&NThea,sizeof(IMAGE_NT_HEADERS),1,rwFile); //读取原文件的IMAGE_NT_HEADERS结构

//保存原文件区块数量与OEP
int nOldSectionNo=NThea.FileHeader.NumberOfSections;//获取NT头结构中的区块数量
int OEP=NThea.OptionalHeader.AddressOfEntryPoint; // 获取NT头结构中的OEP(程序启动位置)

//保存文件对齐值与区块对齐值
int SECTION_ALIG=NThea.OptionalHeader.SectionAlignment; // 获取区段的对齐值
int FILE_ALIG=NThea.OptionalHeader.FileAlignment; // 获取文件中的对齐值

IMAGE_SECTION_HEADER NewSection;//定义要添加的区块
memset(&NewSection, 0, sizeof(IMAGE_SECTION_HEADER));//将该结构全部清零

IMAGE_SECTION_HEADER SEChea; //再定义一个区块,来存放原文件最后一个区块的信息
//读原文件最后一个区块的信息
fseek(rwFile,pNT+248,0);
for(int i=0;i<nOldSectionNo;i++)
fread(&SEChea,sizeof(IMAGE_SECTION_HEADER),1,rwFile);

// 打开要写的新的文件
FILE *newfile=fopen(szNewFile,"rb+");
if(newfile==NULL)
{
printf("ttOpen bak file faildn");
exit(-1);
}

// 跳转到最后一个区段的末尾的下一个位置
fseek(newfile,SEChea.PointerToRawData+SEChea.SizeOfRawData,SEEK_SET);
goto shellend;

// 让编译器帮我们生成shell code
__asm
{
shell:

PUSHAD
MOV EAX,DWORD PTR FS:[30H] ;FS:[30H]指向PEB
MOV EAX,DWORD PTR [EAX+0CH] ;获取PEB_LDR_DATA结构的指针
MOV EAX,DWORD PTR [EAX+1CH] ;获取LDR_MODULE链表表首结点的inInitializeOrderModuleList成员的指针
MOV EAX,DWORD PTR [EAX] ;LDR_MODULE链表第二个结点的inInitializeOrderModuleList成员的指针
MOV EAX,DWORD PTR [EAX+08H] ;inInitializeOrderModuleList偏移8h便得到Kernel32.dll的模块基址
MOV EBP,EAX ; 将Kernel32.dll模块基址地址放至kernel中
MOV EAX,DWORD PTR [EAX+3CH] ;指向IMAGE_NT_HEADERS
MOV EAX,DWORD PTR [EBP+EAX+120] ;指向导出表
MOV ECX,[EBP+EAX+24] ;取导出表中导出函数名字的数目
MOV EBX,[EBP+EAX+32] ;取导出表中名字表的地址
ADD EBX,EBP
PUSH WORD PTR 0X00 ;构造GetProcAddress字符串
PUSH DWORD PTR 0X73736572
PUSH DWORD PTR 0X64644163
PUSH DWORD PTR 0X6F725074
PUSH WORD PTR 0X6547
MOV EDX,ESP
PUSH ECX

F1:
MOV EDI,EDX
POP ECX
DEC ECX
TEST ECX,ECX
JZ EXIT
MOV ESI,[EBX+ECX4]

ADD ESI,EBP
PUSH ECX
MOV ECX,15
REPZ CMPSB
TEST ECX,ECX
JNZ F1

POP ECX
MOV ESI,[EBP+EAX+36] ;取得导出表中序号表的地址
ADD ESI,EBP
MOVZX ESI,WORD PTR[ESI+ECX
2] ;取得进入函数地址表的序号
MOV EDI,[EBP+EAX+28] ;取得函数地址表的地址
ADD EDI,EBP
MOV EDI,[EDI+ESI*4] ;取得GetProcAddress函数的地址
ADD EDI,EBP

PUSH WORD PTR 0X00 ;构造LoadLibraryA字符串
PUSH DWORD PTR 0X41797261
PUSH DWORD PTR 0X7262694C
PUSH DWORD PTR 0X64616F4C
PUSH ESP
PUSH EBP
CALL EDI ;调用GetProcAddress取得LoadLibraryA函数的地址
PUSH WORD PTR 0X00 ;构造test符串,测试新增节后的EXE是否能正常加载test.dll
PUSH DWORD PTR 0X74736574
PUSH ESP
CALL EAX
EXIT: ADD ESP,36 ;平衡堆栈
POPAD
}

shellend:
// 计算要写入到新的区块中的SHELLCODE的位置和长度
char *pShell;
int nShellLen;
__asm
{
LEA EAX,shell
MOV pShell,EAX;
LEA EBX,shellend
SUB EBX,EAX
MOV nShellLen,EBX
}
//把SHELLCODE写入新的区块
for(int i=0;i<nShellLen;i++)
fputc(pShell[i],newfile);

//写完SHELLCODE之后是写跳转到原OEP的指令
NewSection.VirtualAddress=SEChea.VirtualAddress+alig(SEChea.Misc.VirtualSize,SECTION_ALIG);
BYTE jmp = 0xE9;
OEP=OEP-(NewSection.VirtualAddress+nShellLen)-5;
fwrite(&jmp, sizeof(jmp), 1, newfile);
fwrite(&OEP, sizeof(OEP), 1, newfile);
//将最后增加的数据用0填充至按文件中对齐的大小
for(int i=0;i<alig(nShellLen,FILE_ALIG)-nShellLen-5;i++)
fputc('',newfile);

//建立新区块头结构中的数据
strcpy((char)NewSection.Name,".odkav");
NewSection.PointerToRawData=SEChea.PointerToRawData+SEChea.SizeOfRawData;
NewSection.Misc.VirtualSize=nShellLen;
NewSection.SizeOfRawData=alig(nShellLen,FILE_ALIG);
NewSection.Characteristics=0xE0000020;//新区块可读可写可执行
fseek(newfile,pNT+248+sizeof(IMAGE_SECTION_HEADER)
nOldSectionNo,0);

//写入新的区块头的数据
fwrite(&NewSection,sizeof(IMAGE_SECTION_HEADER),1,newfile);

NThea.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].VirtualAddress=0;
NThea.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].Size=0;

// 建立新的PE头数据
int nNewImageSize=NThea.OptionalHeader.SizeOfImage+alig(nShellLen,SECTION_ALIG);
int nNewSizeofCode=NThea.OptionalHeader.SizeOfCode+alig(nShellLen,FILE_ALIG);
NThea.OptionalHeader.SizeOfCode=nNewSizeofCode;
NThea.OptionalHeader.SizeOfImage=nNewImageSize;
NThea.FileHeader.NumberOfSections=nOldSectionNo+1;//新的区块数
NThea.OptionalHeader.AddressOfEntryPoint=NewSection.VirtualAddress;//新的OEP

//写入更新后的PE头数据
fseek(newfile,pNT,0);

fwrite(&NThea,sizeof(IMAGE_NT_HEADERS),1,newfile);

printf("ttok.........!n");

fclose(newfile);
fclose(rwFile);

return 1;

}

想挽留 2017-08-17 2 楼

可以使用createRemoteThread API注入远程线程吧,远程线程加载你所需要的dll是不是就可以满足你得需求了

泛泛之交 2017-05-01 1 楼

补充一下楼上,
普通exe的话,用LordPE直接把dll添加到目标exe的导入表 就好了
不打补丁的话,做个usp10.dll 估计也成
当然还可以用写调试程序的方法插入dll
:)