我一直在使用 Delphi 进行一个不允许使用 RTL 的 dll 相关的操作。
在分析 PE(Portable Executable)文件格式后,我发现所有的 PE 文件都有一个“入口点”(Entry Point)。这是 Windows 在加载模块(exe 或者 dll)后第一个调用的函数。
该函数的名称以及其隐含参数取决于所属 PE 的类型:
Dynamic Link Library (*.dll):
DllMain
function DllMain(hinstDLL: HINST; fdwReason: DWORD; lpvReserved: Pointer): BOOL; stdcall;
Executable (*.exe):
WinMain
function WinMain(hInstance: HINST; hPrevInstance: HINST; lpCmdLine: LPSTR; nCmdShow: Integer): Integer; stdcall;
Device Driver (*.sys):
DriverEntry
function DriverEntry(DriverObject: PDriverObject; RegistryPath: PUnicodeString): NTSTATUS; stdcall;
对于DLL来说,可以通过
LoadLibrary
读取传递给我们的“DllMain”的参数。如果在EntryPointAddress处设置断点:
您可以在堆栈上看到三个参数:
你所需要做的就是捕获它们:
library Project1;
function DllMain(hinstDLL: HINST; fdwReason: Cardinal; lpvReserved: Pointer): Integer; stdcall;
begin
Result := 1; //Windows uses FALSE=0, TRUE=1. Delphi uses False=0, True=-1
end;
begin
//Code in here is run during DllMain.
//i.e. DllMain does not return until this code completes.
asm
{ Get the parameters to DllMain that Windows passed to us:
[ESP+4] hinstDLL
[ESP+8] fdwReason
[ESP+12] lpvReserved
}
MOV eax, [ESP+12]; //push lpvReserved onto the stack
PUSH eax;
MOV eax, [ESP+8]; //push fdwReason onto the stack
PUSH eax
MOV eax, [ESP+4]; //push hinstDLL onto the stack
PUSH eax;
CALL DllMain; //Call our DllMain function
//DllMain leaves BOOL result in EAX
end;
end.
但是有一个RTL
问题在于里面不仅仅只有我的代码。编译器会在项目文件中的我的代码块之前和之后插入隐藏的代码:
实际上,真正的 DllMain
代码包含:
function DllMain(hinstDLL: HINST; fdwReason: Cardinal; lpvReserved: Pointer): LongBool; stdcall;
begin
SysInit._InitLib(InitTable, hinstDLL, fdwReason, lpvReserved);
asm
MOV eax, [ESP+12]; //push lpvReserved onto the stack
PUSH eax;
MOV eax, [ESP+8]; //push fdwReason onto the stack
PUSH eax
MOV eax, [ESP+4]; //push hinstDLL onto the stack
PUSH eax;
CALL DllMain; //Call our DllMain function
//DllMain leaves BOOL result in EAX
end;
System._Halt0;
end;
现在,这个对
_InitLib
的前置调用会对尝试提取hinstDLL
和fdwReason
的值造成一些破坏;但这不是一个无法克服的问题(例如,你仍然可以在EBP+8
、+12
和+16
找到它们)。但我的问题是RTL链接到的代码并不总是可用的。查看导入目录表,你可以看到它需要:
-
user32.dll
(例如MessageBoxA
)
- kernel32.dll
(例如VirtualAlloc
、VirtualFree
、CloseHandle
)我能避免使用RTL吗?
有没有编译器选项或定义可以剥离
System._InitLib
和System._Halt0
的所有内容?或者让编译器不将它们放入:function DllMain(hinstDLL: HINST; fdwReason: Cardinal; lpvReserved: Pointer): LongBool; stdcall;
begin
SysInit._InitLib(InitTable, hinstDLL, fdwReason, lpvReserved);
//...
System._Halt0;
end;
很明显,编译器在创建它们时具有特殊的智能。而且,如果是库或应用二进制文件,会隐含地放置在EntryPointProcedure中的内容也会发生变化。
额外闲聊:缺失的WinMain
参数案例
WinMain
入口点文档要求传递四个参数:
function WinMain(hInstance: HINST; hPrevInstance: HINST;
lpCmdLine: LPSTR; nCmdShow: Integer): Integer; stdcall;
hInstance: HINSTANCE
// 应用程序实例句柄hPrevInstance: HINSTANCE
// 前一个应用程序实例句柄lpCmdLine: LPSTR
// 命令行参数字符串nCmdShow: Integer
// 窗口显示状态
这就是为什么我无法弄清楚为什么参数没有传递给EntryPointFunction的原因:
真正的 Windows操作系统以无参数的方式调用该函数
.exe
入口点函数是:DWORD CALLBACK RawEntryPoint(void);
即:
function RawEntryPoint(): DWORD; stdcall;
如果参数没有传递到原始入口点,那WinMain的参数从哪里来呢? 语言启动代码通过向操作系统询问来获取它们: 可执行文件的实例句柄来自GetModuleHandle(NULL) 命令行来自GetCommandLine nCmdShow来自GetStartupInfo hPrevInstance始终为NULL 所以这就解释了。
WinMain(...)
,**.dll** ->DllMain(...)
,**.sys** ->DriverEntry(...)
。我不想提及内核模式设备驱动程序开发,因为它可能会混淆问题。(“它是一种dll。”一个驱动程序就是一个带有导出和入口点的DLL) - Ian BoydSystem
和SysInit
。如果你想要Pascal设备驱动程序,FPC是你最好的选择。 - David Heffernan