Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to avoid the RTL?

Tags:

delphi

I've been playing with something in Delphi where the RTL is not allowed. It's a kind of dll.

After picking apart the PE (Portable Executable) file format, i realized that all PE files have an "Entry Point". This is the first thing that Windows calls after the module (exe or dll) is loaded.

The name of function that resides at this entry point, and it's implied parameters, depend on the kind of PE it is:

  • 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;
    

In Delphi, this Entry Point is the code that sits in your main project file.

In the case of a DLL, it is possible to read the parameters passed to our "DllMain" by LoadLibrary. If you put a breakpoint at the EntryPointAddress:

enter image description here

you can see the three parameters on the stack:

enter image description here

All you would have to do is capture them:

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.

But there's an RTL

The problem is that there is more than just my code in there. The compiler inserts hidden code just before, and just after, my code block in the project file:

enter image description here

Essentially, the real DllMain code contains:

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;

Now, this preamble call to _InitLib does wreak some havoc with trying to pull the values of hinstDLL and fdwReason; but that's not an insurmountable problem (e.g. you can still find them at EBP+8,+12, and +16).

But my issue is that the RTL links to code that isn't always available. Looking at the Import Directory Table, you can see that it needs:

  • user32.dll (e.g. MessageBoxA)
  • kernel32.dll (e.g. VirtualAlloc, VirtualFree, CloseHandle)

Can i avoid the RTL?

Is there a compiler option, or a define, that can strip out all the guts of System._InitLib and System._Halt0? Or just have the compiler not place them into:

function DllMain(hinstDLL: HINST; fdwReason: Cardinal; lpvReserved: Pointer): LongBool; stdcall;
begin
   SysInit._InitLib(InitTable, hinstDLL, fdwReason, lpvReserved);

   //...
   System._Halt0;
end;

It's obviously a special bit of smarts on the part of the compiler that knows to create them. And what gets implicitly placed in the EntryPointProcedure changes depending if it's a Library or Application binary.

Bonus Chatter: The case of the missing WinMain arguments

The WinMain entry point is documented to pass four parameters:

function WinMain(hInstance: HINST; hPrevInstance: HINST; 
          lpCmdLine: LPSTR; nCmdShow: Integer): Integer; stdcall;
  • hInstance: HINSTANCE
  • hPrevInstance: HINSTANCE
  • lpCmdLine: LPSTR
  • nCmdShow: Integer

Which is why i couldn't figure out why the arguments weren't being passed to the EntryPointFunction:

enter image description here

I was debugging further and further back in the stack. I tried other debuggers. Windows just wasn't passing the appropriate parameters to the Entry Point function. Then i found the answer:

The operating system calls the function with no parameters

The real Windows .exe entry point function is:

DWORD CALLBACK RawEntryPoint(void);

aka:

function RawEntryPoint(): DWORD; stdcall;

Where do the parameters to WinMain come from, if they aren't passed to the raw entry point?

The language startup code gets them by asking the operating system:

  • the instance handle for the executable comes from GetModuleHandle(NULL)
  • the command line comes from GetCommandLine
  • and the nCmdShow comes from GetStartupInfo
  • hPrevInstance is always NULL

So that explains that.

like image 504
Ian Boyd Avatar asked Dec 11 '22 00:12

Ian Boyd


2 Answers

KOL is still alive and kicking and the official website http://kolmck.net (maintained by me) contains examples on how to rewrite the system unit.

like image 75
Thaddy de Koning Avatar answered Dec 24 '22 00:12

Thaddy de Koning


There is no way you can achieve what you desire using the stock compiler/RTL. The compiler expects the presence of System and SysInit units and indeed uses those units to supply runtime support for many language features. As an example, strings are a language feature that is implemented by way of support functions implemented in the System unit.

If you provide replacement System and SysInit units that the compiler will accept then it is possible to remove the dependencies on user mode modules. However, this is not for the feint of heart.

Since you seem to wish to write kernel mode driver code, I would suggest using FPC.

like image 28
David Heffernan Avatar answered Dec 24 '22 00:12

David Heffernan