从资源文件中的DLL调到内存,然后加载DLL(Delphi)
前提要求:
有个需求是把一个DLL作为数据打包到EXE中,运行的时候动态加载.但要求不是释放出来生成DLL文件加载.
不过由于是直接分配内存加载DLL的.有一些小缺陷.例如遍历进程中加载的模块的时候是找不到这个DLL的.GetModuleXXXX之类的API也就不能用了.当然也可以Hook这些函数做处理.不过便利不到这个模块也未必不是一个优点.例如写木马黑客之类的代码的时候,可以作为隐藏模块的手段.
先分析一下Windows系统加载PE文件时候的步骤吧.可以简单的理解为如下步骤:
1.读入文件(利用文件镜像)
2.如果是加载的位置和PE头规定的镜像基址不一致(通常是DLL),并且有重定位节就进行重定位.
3.RVA地址填写.如果有导入函数就加载DLL,把函数地址付给导入表项
4.执行入口代码.DLL的话就是DLLMain.
好了.条理清晰了,代码如下
{
内存加载DLL
wr960204
}
unitPELoader;
interface
uses
Windows;
functionLoadPE(Buf:Pointer;Len:Integer):Cardinal;
procedureFreePE(Handle:Cardinal);
functionGetProcAddress(Module:Cardinal;ProcessName:PChar):Pointer;
implementation
const
IMAGE_ORDINAL_FLAG=DWORD($80000000);
functionGetProcAddress(Module:Cardinal;ProcessName:PChar):Pointer;
functionstrcmp(p1,p2:PChar):boolean;
begin
Result:=False;
while(p1^=p2^)do
begin
if(P1^=#0)or(P2^=#0)then
begin
Result:=True;
Exit;
end;
Inc(P1);
Inc(P2);
end;
end;
var
ExportName:pChar;
Address:Cardinal;
J:Cardinal;
ImageDosHeader:PImageDosHeader;
ImageNTHeaders:PImageNTHeaders;
ImageExportDirectory:PImageExportDirectory;
begin
ImageDosHeader:=Pointer(Module);
ImageNTHeaders:=Pointer(Module+ImageDosHeader._lfanew);
ImageExportDirectory:=Pointer(ImageNtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress+Module);
J:=0;
Address:=0;
repeat
ExportName:=Pointer(Cardinal(Pointer(Cardinal(ImageExportDirectory.AddressOfNames)+Module+J4)^)+Module);
ifstrcmp(ExportName,ProcessName)then
Address:=Cardinal(Pointer(Word(Pointer(Jshl1+Cardinal(
ImageExportDirectory.AddressOfNameOrdinals)+Module)^)and
$0000FFFFshl2+Cardinal(ImageExportDirectory.AddressOfFunctions)
+Module)^)+Module;
Inc(J);
until(Address<>0)or(J=ImageExportDirectory.NumberOfNames);
Result:=Pointer(Address);
end;
type
TImageSectionHeaders=array[0..0]ofTImageSectionHeader;
PImageSectionHeaders=^TImageSectionHeaders;
TIIDUnion=record
caseIntegerof
0:(Characteristics:DWORD);
1:(OriginalFirstThunk:DWORD);
end;
PIMAGE_IMPORT_DESCRIPTOR=^IMAGE_IMPORT_DESCRIPTOR;
_IMAGE_IMPORT_DESCRIPTOR=record
Union:TIIDUnion;
TimeDateStamp:DWORD;
ForwarderChain:DWORD;
Name:DWORD;
FirstThunk:DWORD;
end;
IMAGE_IMPORT_DESCRIPTOR=_IMAGE_IMPORT_DESCRIPTOR;
TImageImportDecriptor=IMAGE_IMPORT_DESCRIPTOR;
PImageImportDecriptor=PIMAGE_IMPORT_DESCRIPTOR;
PIMAGE_THUNK_DATA32=^IMAGE_THUNK_DATA32;
_IMAGE_THUNK_DATA32=record
caseIntegerof
0:(ForwarderString:DWORD);
1:(Function_:DWORD);
2:(Ordinal:DWORD);
3:(AddressOfData:DWORD);
end;
IMAGE_THUNK_DATA32=_IMAGE_THUNK_DATA32;
TImageThunkData32=IMAGE_THUNK_DATA32;
PImageThunkData32=PIMAGE_THUNK_DATA32;
IMAGE_THUNK_DATA=IMAGE_THUNK_DATA32;
PIMAGE_THUNK_DATA=PIMAGE_THUNK_DATA32;
PIMAGE_IMPORT_BY_NAME=^IMAGE_IMPORT_BY_NAME;
_IMAGE_IMPORT_BY_NAME=record
Hint:Word;
Name:array[0..0]ofByte;
end;
IMAGE_IMPORT_BY_NAME=_IMAGE_IMPORT_BY_NAME;
TImageImportByName=IMAGE_IMPORT_BY_NAME;
PImageImportByName=PIMAGE_IMPORT_BY_NAME;
{计算对齐后的大小}
functionGetAlignedSize(Origin,Alignment:Cardinal):Cardinal;
begin
result:=(Origin+Alignment-1)divAlignmentAlignment;
end;
{计算加载pe并对齐需要占用多少内存,未直接使用OptionalHeader.SizeOfImage作为结果是因为据说有的编译器生成的exe这个值会填0}
functionCalcTotalImageSize(MzH:PImageDosHeader;FileLen:Cardinal;peH:PImageNtHeaders;
peSecH:PImageSectionHeaders):Cardinal;
var
i:Integer;
begin
{计算pe头的大小}
result:=GetAlignedSize(PeH.OptionalHeader.SizeOfHeaders,PeH.OptionalHeader.SectionAlignment);
{计算所有节的大小}
fori:=0topeH.FileHeader.NumberOfSections-1do
ifpeSecH[i].PointerToRawData+peSecH[i].SizeOfRawData>FileLenthen//超出文件范围
begin
result:=0;
exit;
end
elseifpeSecH[i].VirtualAddress<>0then//计算对齐后某节的大小
ifpeSecH[i].Misc.VirtualSize<>0then
result:=GetAlignedSize(peSecH[i].VirtualAddress+peSecH[i].Misc.VirtualSize,PeH.OptionalHeader.SectionAlignment)
else
result:=GetAlignedSize(peSecH[i].VirtualAddress+peSecH[i].SizeOfRawData,PeH.OptionalHeader.SectionAlignment)
elseifpeSecH[i].Misc.VirtualSize result:=result+GetAlignedSize(peSecH[i].SizeOfRawData,peH.OptionalHeader.SectionAlignment)
else
result:=result+GetAlignedSize(peSecH[i].Misc.VirtualSize,PeH.OptionalHeader.SectionAlignment);
end;
{加载pe到内存并对齐所有节}
functionAlignPEToMem(constBuf:Pointer;Len:Integer;varPeH:PImageNtHeaders;
varPeSecH:PImageSectionHeaders;varMem:Pointer;varImageSize:Cardinal):Boolean;
var
SrcMz:PImageDosHeader;//DOS头
SrcPeH:PImageNtHeaders;//PE头
SrcPeSecH:PImageSectionHeaders;//节表
i:Integer;
l:Cardinal;
Pt:Pointer;
begin
result:=false;
SrcMz:=Buf;
ifLen ifSrcMz.e_magic<>IMAGE_DOS_SIGNATUREthenexit;
ifLen SrcPeH:=pointer(Integer(SrcMz)+SrcMz._lfanew);
if(SrcPeH.Signature<>IMAGE_NT_SIGNATURE)thenexit;
if(SrcPeH.FileHeader.CharacteristicsandIMAGE_FILE_DLL=0)//不是DLL,
or(SrcPeH.FileHeader.CharacteristicsandIMAGE_FILE_EXECUTABLE_IMAGE=0)//不可执行
or(SrcPeH.FileHeader.SizeOfOptionalHeader<>SizeOf(TImageOptionalHeader))thenexit;
SrcPeSecH:=Pointer(Integer(SrcPeH)+SizeOf(TImageNtHeaders));
ImageSize:=CalcTotalImageSize(SrcMz,Len,SrcPeH,SrcPeSecH);
ifImageSize=0then
exit;
Mem:=VirtualAlloc(nil,ImageSize,MEM_COMMIT,PAGE_EXECUTE_READWRITE);//分配一块可以执行,可以读写的内存
ifMem<>nilthen
begin
//计算需要复制的PE头
l:=SrcPeH.OptionalHeader.SizeOfHeaders;
fori:=0toSrcPeH.FileHeader.NumberOfSections-1do
if(SrcPeSecH[i].PointerToRawData<>0)and(SrcPeSecH[i].PointerToRawData l:=SrcPeSecH[i].PointerToRawData;
Move(SrcMz^,Mem^,l);
PeH:=Pointer(Integer(Mem)+PImageDosHeader(Mem)._lfanew);
PeSecH:=Pointer(Integer(PeH)+sizeof(TImageNtHeaders));
Pt:=Pointer(Cardinal(Mem)+GetAlignedSize(PeH.OptionalHeader.SizeOfHeaders,PeH.OptionalHeader.SectionAlignment));
fori:=0toPeH.FileHeader.NumberOfSections-1do
begin
//定位该节在内存中的位置
ifPeSecH[i].VirtualAddress<>0then
Pt:=Pointer(Cardinal(Mem)+PeSecH[i].VirtualAddress);
ifPeSecH[i].SizeOfRawData<>0then
begin
//复制数据到内存
Move(Pointer(Cardinal(SrcMz)+PeSecH[i].PointerToRawData)^,pt^,PeSecH[i].SizeOfRawData);
ifpeSecH[i].Misc.VirtualSize pt:=pointer(Cardinal(pt)+GetAlignedSize(PeSecH[i].SizeOfRawData,PeH.OptionalHeader.SectionAlignment))
else
pt:=pointer(Cardinal(pt)+GetAlignedSize(peSecH[i].Misc.VirtualSize,peH.OptionalHeader.SectionAlignment));
//pt定位到下一节开始位置
end
else
pt:=pointer(Cardinal(pt)+GetAlignedSize(PeSecH[i].Misc.VirtualSize,PeH.OptionalHeader.SectionAlignment));
end;
result:=True;
end;
end;
{是否包含可重定向列表}
functionHasRelocationTable(peH:PImageNtHeaders):Boolean;
begin
result:=(peH.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress<>0)
and(peH.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size<>0);
end;
type
PImageBaseRelocation=^TImageBaseRelocation;
TImageBaseRelocation=packedrecord
VirtualAddress:cardinal;
SizeOfBlock:cardinal;
end;
{重定向PE用到的地址}
procedureDoRelocation(peH:PImageNtHeaders;NewBase:Pointer);
var
Delta:Cardinal;
p:PImageBaseRelocation;
pw:PWord;
i:Integer;
begin
Delta:=Cardinal(NewBase)-peH.OptionalHeader.ImageBase;
p:=pointer(cardinal(NewBase)+peH.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
while(p.VirtualAddress+p.SizeOfBlock<>0)do
begin
pw:=pointer(Integer(p)+Sizeof(TImageBaseRelocation));
fori:=1to(p.SizeOfBlock-Sizeof(TImageBaseRelocation))divsizeof(WORD)do
begin
ifpw^and$F000=$3000then
Inc(PCardinal(Cardinal(NewBase)+p.VirtualAddress+(pw^and$0FFF))^,Delta);
inc(pw);
end;
p:=PImageBaseRelocation(pw);
end;
end;
{填充引入地址表}
functionFillImports(peH:PImageNtHeaders;pImageBase:Pointer):BOOL;
type
TIMAGE_THUNK_DATAs=array[0..0]ofIMAGE_THUNK_DATA;
PIMAGE_THUNK_DATAs=^TIMAGE_THUNK_DATAs;
var
Offset:Cardinal;
pID:PIMAGE_IMPORT_DESCRIPTOR;
pRealIAT,pOriginalIAT:PIMAGE_THUNK_DATAs;
buf:array[0..$FF]ofchar;
pName:PChar;
I:Integer;
hDll:HMODULE;
lpFunction:Pointer;
pByName:PIMAGE_IMPORT_BY_NAME;
begin
Result:=True;
Offset:=peH^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
ifOffset=0then//无导入表
Exit;
pID:=PIMAGE_IMPORT_DESCRIPTOR(Cardinal(pImageBase)+Offset);
while(pID^.Union.Characteristics<>0)do
begin
pRealIAT:=PIMAGE_THUNK_DATAs(Cardinal(pImageBase)+pID^.FirstThunk);
pOriginalIAT:=PIMAGE_THUNK_DATAs(Cardinal(pImageBase)+pID^.Union.OriginalFirstThunk);
//获取DLL名字
PName:=PChar(Cardinal(pImageBase)+pID^.Name);
FillMemory(@Buf,$FF,0);
fori:=0to$FFdo
begin
if(pName[i]=#0)then
break;
buf[i]:=pName[i];
end;
//判断是否加载过,没加载过就加载
hDLL:=GetModuleHandle(Buf);
ifhDLL=0then
hDLL:=LoadLibrary(Buf);
I:=0;
whileTruedo
begin
if(pOriginalIAT[i].Function_=0)thenbreak;
lpFunction:=nil;
if(pOriginalIAT[i].OrdinalandIMAGE_ORDINAL_FLAG<>0)then//按序号
begin
lpFunction:=Windows.GetProcAddress(hDll,PChar(pOriginalIAT[i].Ordinaland$0000FFFF));
end
else//按名字
begin
//获取此IAT项所描述的函数名称
pByName:=PIMAGE_IMPORT_BY_NAME
(DWORD(pImageBase)+DWORD(pOriginalIAT[i].AddressOfData));
lpFunction:=Windows.GetProcAddress(hDll,PChar(@pByName^.Name));
if(lpFunction<>nil)then//找到了!
pRealIAT[i].Function_:=DWORD(lpFunction)
else
begin
Result:=False;
Exit;
end;
end;
Inc(I);
end;
pID:=PIMAGE_IMPORT_DESCRIPTOR(DWORD(pID)+sizeof(IMAGE_IMPORT_DESCRIPTOR));
end;
end;
functionLoadPE(Buf:Pointer;Len:Integer):Cardinal;
var
peH:PImageNtHeaders;//PE头
peSecH:PImageSectionHeaders;
peSz:Cardinal;
P:Pointer;
DLLMain:function(hinstDLL:Cardinal;fdwReason,lpvReserved:DWORD):BOOL;stdcall;
begin
//分配可执行的内存块
ifalignPEToMem(Buf,Len,peH,peSecH,P,peSz)then
begin
ifHasRelocationTable(peH)then//如果有重定位表就进行重定位
DoRelocation(peH,P);
FillImports(peH,P);//填写导入表
//获取并执行动态链接库的入口函数
DLLMain:=Pointer(peH^.OptionalHeader.AddressOfEntryPoint+DWORD(P));
DLLMain(DWORD(P),DLL_PROCESS_ATTACH,0);
Result:=Cardinal(P);
end
else
Result:=0;
end;
procedureFreePE(Handle:Cardinal);
var
dosH:PImageDosHeader;
peH:PImageNtHeaders;//PE头
DLLMain:function(hinstDLL:Cardinal;fdwReason,lpvReserved:DWORD):BOOL;stdcall;
P:Pointer;
begin
P:=Pointer(Handle);
dosH:=PImageDosHeader(P);
peH:=PImageNtHeaders(DWORD(P)+dosH^._lfanew);
DLLMain:=Pointer(peH^.OptionalHeader.AddressOfEntryPoint+DWORD(P));
DLLMain(DWORD(P),DLL_PROCESS_DETACH,0);//反初始化DLL
VirtualFreeEx(GetCurrentProcess(),
P,
0,
MEM_RELEASE);
end;
end.
使用的方式如这个例子.
{
测试DLL
}
libraryDLL;
uses
Windows;
{$R.res}
var
vMsg:String=''abc'';//用一个全局变量可以检查重定位是否正确.重定位不正确一定访问不到这个变量.
procedureA(Msg:PChar);
begin
MessageBox(0,PChar(vMsg+Msg),'''',MB_OK);
end;
exports
A;
begin
end.
在EXE中就可以直接在内存中加载这个DLL了.
var
hDLL:Cardinal;
begin
hDLL:=LoadPE(DLL的数据,DLL数据的大小);
A:=PELoader.GetProcAddress(DWORD(hDLL),''A'');
A(''aa'');
FreePE(hDLL); |
|