我一直在玩Delphi中不允许使用RTL的东西。这是一种dll。
在拆分PE(可移植可执行文件)文件格式之后,我意识到所有PE文件都有"入口点" 。这是Windows在加载模块(exe或dll)后调用的第一件事。
驻留在此入口点的函数的名称及其隐含参数取决于它的PE类型:
动态链接库(* .dll): DllMain
function DllMain(hinstDLL: HINST; fdwReason: DWORD;
lpvReserved: Pointer): BOOL; stdcall;
可执行文件(* .exe): WinMain
function WinMain(hInstance: HINST; hPrevInstance: HINST;
lpCmdLine: LPSTR; nCmdShow: Integer): Integer; stdcall;
设备驱动程序(* .sys): DriverEntry
function DriverEntry(DriverObject: PDriverObject;
RegistryPath: PUnicodeString): NTSTATUS; stdcall;
在Delphi中,此入口点是位于主项目文件中的代码。
对于DLL,可以读取传递给我们的" DllMain"按LoadLibrary
。如果在 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.
问题是,那里不仅有我的代码。编译器只在之前插入隐藏的代码,而在项目文件中的代码块之后只需:
基本上,真正的 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
)是否有编译器选项或定义可以消除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只是没有将适当的参数传递给入口点功能。然后我找到the answer:
操作系统调用没有参数的函数
真正的 Windows .exe
入口点功能是:
DWORD CALLBACK RawEntryPoint(void);
又名:
function RawEntryPoint(): DWORD; stdcall;
WinMain的参数来自哪里,如果它们没有传递到原始入口点?
语言启动代码通过询问操作系统来获取它们:
- 可执行文件的实例句柄来自
GetModuleHandle(NULL)
- 命令行来自
GetCommandLine
- 而
nCmdShow
来自GetStartupInfo
hPrevInstance
始终为NULL
所以这解释了。
答案 0 :(得分:8)
KOL仍然活着,并且官方网站http://kolmck.net(由我维护)包含有关如何重写系统单元的示例。
答案 1 :(得分:4)
使用stock编译器/ RTL无法实现您的目标。编译器期望存在System
和SysInit
单元,并且确实使用这些单元为许多语言功能提供运行时支持。例如,字符串是一种语言功能,它是通过System
单元中实现的支持功能实现的。
如果您提供编译器将接受的替换System
和SysInit
单元,则可以删除用户模式模块上的依赖项。然而,这不是为了佯装。
由于您似乎希望编写内核模式驱动程序代码,我建议使用FPC。
答案 2 :(得分:1)
当加载DLL并且其主DPR代码开始运行时,您可以从RTL的全局hinstDLL
变量中获取SysInit.HInstance
值,并且您隐含地知道{{1值必须为fdwReason
。在DLL_PROCESS_ATTACH
期间无法检索的唯一值是DLL_PROCESS_ATTACH
值,因为RTL忽略它而不是将其保存在某处。但是,如果您希望代码对其他lpvReserved
值做出反应,并在其他原因状态中接收fdwReason
值,那么您所要做的就是分配RTL调用的lpvReserved
回调从其内部DllProcEx
入口点开始,例如:
DllMain