Jump to content
Ron Howard

DLL usage difference between Delphi 11.3 and Delphi 10.4

Recommended Posts

Struggling to identify what the difference is in Delphi 10.4 and Delphi 11.3 in regards to the use of a DLL function.
I have some legacy code that uses a security library.
Works fine when compiled with Delphi 10.4 both for 32-bit and 64-bit.
In Delphi 11.3 it works fine for 32-bit, but not for 64-bit.
No changes to the code, nor project settings.

 

Code:

Interface

Function pp_lfopen (filename: PAnsiChar; lfflags: LongInt; lftype: LongInt; password: PAnsiChar; handle: PLongInt): LongInt; stdcall;

Implementation

const
{$IFDEF WIN32}
  KeyLibModuleName = 'KEYLIB32.dll';
{$ELSEIF Defined(WIN64)}
  KeyLibModuleName = 'KEYLIB64.dll';
{$ENDIF}

Function pp_lfopen; external KeyLibModuleName;

Usage:

var
	LHandle: Longint;
begin
	Lresult := pp_lfopen(PAnsiChar(FlicFilePath), 0, LF_FILE, PAnsiChar(FlicPassword), @Lhandle);
	if Lresult = PP_SUCCESS then
  begin
  	// additional DLL calls, which fail due to the invalid handle
  end;
end;

The path and password are correct and valid text constants.
My analysis so far has shown that the Lhandle returned is not valid, even though the function result indicates success.

Just recently switched over to Delphi 11.3, but I cannot pinpoint the cause so far.
Anyone with knowledge about the differences in 11.3 compared to 10.4 that could explain this? Or ideas to find the cause?

 

Share this post


Link to post
Posted (edited)

I think the problem is the project options: in the new 11.3 (like 12) environment the additional setting about ASLR are ON, try to deselect them.

 

image.thumb.png.9005fd21dc15f203bc4cf78e548843e8.png

Edited by DelphiUdIT

Share this post


Link to post

Looks like this has always been broken but you just got lucky because the 64 bit handles happened to have values that weren't changed when your code truncated them to 32 bits. 

 

With the latest delphi your luck ran out because the values are now being changed by truncation, for whatever reason. But your code was always broken and you just got away with it. Be thankful for the new delphi version highlighting this. 

 

The mistake is in your translation of the header code. There are going to be a bunch of pointer sized types that you have erroneously translated as 32 bit longint or integer. 

Share this post


Link to post
Posted (edited)
6 hours ago, DelphiUdIT said:

I think the problem is the project options: in the new 11.3 (like 12) environment the additional setting about ASLR are ON, try to deselect them.

This isn't the problem. The problem is in the incorrect header file translation. The project settings that you highlight should not be changed. They are just the reason why the code has been exposed as being incorrect. That exposure should be celebrated! It's very wrong headed to try to ignore such a mistake as you propose here. 

Edited by David Heffernan

Share this post


Link to post
2 hours ago, David Heffernan said:

This isn't the problem. The problem is in the incorrect header file translation. The project settings that you highlight should not be changed. They are just the reason why the code has been exposed as being incorrect. That exposure should be celebrated! It's very wrong headed to try to ignore such a mistake as you propose here. 

I agree with you on what you say, the pointer problem is indeed a problem, and obviously needs to be solved.
But the incompatibility with 64-level DLLs and ASLR is well known: at one of the webinars, following a direct question of mine on this very topic (we were talking about DLLs with C++ and Delphi), it was confirmed that full compatibility between 64-level DLLs and ASLR could not be guaranteed when developed with C++ (Embarcadero).
We talked about this a few months ago.
Whether the problem is with the C++ libraries (perhaps some still 32-bit pointers?) or something else has never been clarified.

However, I correctly use 64-bit DLLs developed in C++ Embarcadero with ASLR and ASLR HE without any problems.

Share this post


Link to post
3 minutes ago, DelphiUdIT said:

But the incompatibility with 64-level DLLs and ASLR is well known

There is no such incompatibility. It's just 64 to 32 bit value truncation caused by defective code. 

  • Like 2

Share this post


Link to post

Thank you all for the replies! Forgot to mention that this is a Windows VCL project (you might have concluded this already of course).

 

Disabling the ASLR options in the project options as DelphiUdIT suggested does make the code work and produce the expected results.

 

But if these options should not be disabled as David is stating, I'd obviously rather correct the code.

The original proection solution only has C support, and the header is as follows:

LONG pp_lfopen(LPSTR filename, LONG flags, LONG lftype, LPSTR password, LPPPLFHANDLE handle)

The LPPPPLFHANDLE is also a pointer to a LONG.

Since there are both 32-bit and 64-bit versions of the DLL, I thougth I could get away with a single function definition, but apparently that is wrong.

I would expect the compiler to get the right size for the Longint and PLongint for the 32/64 setting and it would match the version of the DLL architecture.

Could you help me with the correct definition for the 64-bit version? Or point me to where this is explained?

 

Share this post


Link to post

Thank you!

Already changed it to Int64 from Longint just before your reply... glad I found the same thing.

And that works with the ASLR turned on.

 

Appreciate the help though. This was something I did a while ago, and did not look at until the problem showed with the new Delphi version. Won't make this type of assumption (or really: mistake) again!

 

Share this post


Link to post
30 minutes ago, Ron Howard said:

Since there are both 32-bit and 64-bit versions of the DLL, I thougth I could get away with a single function definition, but apparently that is wrong.

No, you can have a single definition. You just need to get it right.

 

31 minutes ago, Ron Howard said:

The LPPPPLFHANDLE is also a pointer to a LONG.

I doubt that. Where is the header file? Or can you show enough of it so that we can see the declaration of LPPPPLFHANDLE?

Share this post


Link to post
Just now, Ron Howard said:

Already changed it to Int64 from Longint just before your reply

That's also wrong.

Share this post


Link to post

Hmmm. Interesting how many changes one can make, settings or code, and they work, and are still not right.

But I certainly want to get it right, hence my questions earlier.

 

I do have the C++ version of the header definition if that helps.

#ifndef PPPEXPORT
	#define PPPEXPORT
#endif

#ifndef _WINDOWS
    #ifndef LONG
        #define LONG long 
    #endif
    #ifndef LPLONG
        #define LPLONG LONG *
    #endif
    #ifndef CHAR
        #define CHAR char
    #endif
    #ifndef LPSTR
        #define LPSTR CHAR *
    #endif
    #ifndef VOID
        #define VOID void
    #endif
    #ifndef HINSTANCE
        #define HINSTANCE LONG
    #endif
    #ifndef HANDLE
        #define HANDLE VOID *
    #endif
    #ifndef HGLOBAL
        #define HGLOBAL HANDLE
    #endif
#endif

#ifndef PPLFHANDLE
	#ifdef _WIN64
		#define PPLFHANDLE  HGLOBAL
	#else
		#define PPLFHANDLE  LONG
	#endif
#endif

#ifndef LPPPLFHANDLE
	#ifdef _WIN64
		#define LPPPLFHANDLE    HGLOBAL *
	#else
		#define LPPPLFHANDLE    LPLONG
	#endif
#endif


LONG PPPEXPORT WINAPI PP_LFOPEN( LPSTR filename, LONG flags, LONG type, LPSTR password, LPPPLFHANDLE handle );

So, with that, and with your feedback earlier on the 64-bit to 32-bit value truncation, I concluded that for the 64 bit version the Int64 was required to cater to the size.

Think I am just as puzzled as before...

Share this post


Link to post
Posted (edited)

The key here is that PPLFHANDLE is 32 bits in a 32 bit process and 64 bits in a 64 bit process. So you need a type that has the same property. For instance any pointer, or NativeInt or NativeUInt.

 

Now, in this case it seems that the expedient thing to do is to declare PPLFHANDLE as HGLOBAL. And in the Delphi winapi units HGLOBAL will be declared as a type that switches size based on process bitness. Actually it's NativeUInt but that's actually a detail you don't really need to know. Although it's nice to know it and help confirm the understanding!

 

So I would do this:

type
  PPLFHANDLE = HGLOBAL;
  LPPPLFHANDLE = ^PPLFHANDLE;

function pp_lfopen(filename: LPSTR; lfflags: LONG; lftype: LONG; password: LPSTR; handle: LPPPLFHANDLE): LONG; stdcall;

Here I am using the same types as the header file. I generally think that it's preferable to keep an API translation as close to the original as possible.

 

The header file is a bit odd in the way it defines these types:

#ifndef PPLFHANDLE
	#ifdef _WIN64
		#define PPLFHANDLE  HGLOBAL
	#else
		#define PPLFHANDLE  LONG
	#endif
#endif

#ifndef LPPPLFHANDLE
	#ifdef _WIN64
		#define LPPPLFHANDLE    HGLOBAL *
	#else
		#define LPPPLFHANDLE    LPLONG
	#endif
#endif

It looks like an earlier version wrongly declared PPLFHANDLE as LONG and then they've kept that mistake as they catered for 64 bit. This header code should really look like this:

#ifndef PPLFHANDLE
    #define PPLFHANDLE  HGLOBAL
#endif

#ifndef LPPPLFHANDLE
    #define LPPPLFHANDLE  PPLFHANDLE *
#endif

 

Edited by David Heffernan
  • Like 1

Share this post


Link to post

Again, thanks David. Appreciate the answer with the suggested code changes.

But I indeed want to make sure I understand the behind the scene workings, so I looked at the HGLOBAL definition.

And I got there through: HGLOBAL -> THandle -> System.THandle -> NativeUInt.

Which I understand from the link DelphiUdIT shared earlier, is the only(?) built-in type that actually varies in size for the 32/64 bit compilation (apart from pointers as you already mentioned).

 

So this would give me:

1) a pointer - LPPPLFHANDLE - that changes size with the compiler option (32/64)

2) a memory area where it is pointing to that also varies size with the compiler option (4 bytes or 8 byte)

Is that correct?

 

Will implement these changes and test it on both platforms!

Share this post


Link to post

Things are working. For the third time, but now with the correct implementation.

 

Share this post


Link to post

I think that's about right. The issue you were facing though is 2 rather than 1. The type that matters here is the handle type. LPPPLFHANDLE is a pointer to a handle. You were managing the pointer to correctly, but your handle was wrong.

 

You could actually declare your function like this:

// an out param
function pp_lfopen(filename: LPSTR; lfflags: LONG; lftype: LONG; password: LPSTR; out handle: PPLFHANDLE): LONG; stdcall;

// or a var param
function pp_lfopen(filename: LPSTR; lfflags: LONG; lftype: LONG; password: LPSTR; var handle: PPLFHANDLE): LONG; stdcall;

Either of these would work. I show these to give a view on conceptually what that handle param is doing. 

 

I usually prefer having a literal header translation so the version in my previous message is how I would do it. And for some function you want to be able to pass nil to indicate that you don't need the function to return a handle. In this case I'm pretty sure you'd never want to do that, you always want the handle, and so the out param version would be a reasonable translation.

 

Hope that helps!

Share this post


Link to post

I indeed realized that the second point was the real issue. Better be complete and explicit though, that's why I included it.

 

Personally I also prefer the translation of the header matching the original as close as possible, but I am aware that many Pascal implementations prefer the use of 'var' for the byreference type of parameters.

I have also used the JvSetupApi quite a bit and there it is all 'var'.

 

But thanks for pointing out the options, including the use of 'out' being more applicable in this case than the 'var'.

 

And it all cetainly helps! Thanks again.

Share this post


Link to post
7 hours ago, DelphiUdIT said:

But the incompatibility with 64-level DLLs and ASLR is well known: at one of the webinars, following a direct question of mine on this very topic (we were talking about DLLs with C++ and Delphi), it was confirmed that full compatibility between 64-level DLLs and ASLR could not be guaranteed when developed with C++ (Embarcadero).

I don't understand this but I would like to know what you are talking about. Link?

 

At first glance it makes zero sense that randomizing the address layout would create any kind of problem for properly written code. 

 

Obviously that was not the problem for the OP.

 

In fact, ASLR seems like a great way to make badly written code break during testing rather than only rarely in production...

Share this post


Link to post
7 minutes ago, Brandon Staggs said:

I don't understand this but I would like to know what you are talking about.

There's nothing to understand, the original statement from @DelphiUdIT  is incorrect

Share this post


Link to post
Posted (edited)
38 minutes ago, Brandon Staggs said:

I don't understand this but I would like to know what you are talking about. Link?

 

At first glance it makes zero sense that randomizing the address layout would create any kind of problem for properly written code. 

 

Obviously that was not the problem for the OP.

 

In fact, ASLR seems like a great way to make badly written code break during testing rather than only rarely in production...

I don't have links, the discussion was in one webinar where I post some questions about C++, one of them was about ASLR, since they introduce set on by default (I used them before that with the PEFLAG settings).

 

And the answer was the they are not sure about use the ASLR with Delphi and a C++ DLL. I used them before, I used them now and I had only a little problem with third part library (ASLR HE cause AV).

 

Of course, like @David Heffernan said these is only meaning that the code is not write in the correct way ... but if those is about the C++ base environment, you cannot do nothing about, only try to "stem" the problem.

 

I don't want to create alarms. only report what it's said about that. But for sure if ASLR create problems, some parts of the code are bad written.

 

Another point is that Windows does not impose (force) the use of ASLR by default and that the same ASLR exclusion options are present in the Delphi project. This means that it is absolutely known that incompatibilities with ASLR exist (perhaps with legacy software) and that the exclusion of this option, even if it is a last resort, is possible.

 

Edited by DelphiUdIT

Share this post


Link to post
1 hour ago, Brandon Staggs said:

In fact, ASLR seems like a great way to make badly written code break during testing rather than only rarely in production...

Well, this need some explanation.

 

1) ASLR has nothing to do with code, well written or bad one.

2) ASLR is merely randomization of system allocation memory for this process, so no address can be guessed per execution.

3) Badly written code can violate ASLR, but this is not bad code, it is code that runs out side the control of the compiler, aka override it with fixed address, such code will fail, ASLR in fact being made for this case, now if the code of the executable is abusing the addressing scheme then it will fail, beyond that, only malicious and blindly (or remotely) injected code will fail to run as intended, because there is known addresses before hand.

4) Windows executables should support ASLR by simply have right and well defined relocation table in PE.

5) By default all DLLs should and must be ASLR compatible because by definition and form the beginning of Windows OS and its API, the DLL loading address can't be known before hand, and can't be fixed, yet for many years and many versions, Windows loaded specific DLLs to the same addresses.

6) for EXE to support ASLR it must have Relocation Table https://0xrick.github.io/win-internals/pe7/ without it it will fail if ASLR is enforced by the OS, this is historical problem as for long time the loading address for EXE on windows was $400000 by default so many linker ( and compiler) didn't follow the rules and generated right relocation table, or even worse, there is many tools to minimize your EXE size that does strip that table rendering the EXE not ASLR compatible.

7) to my knowledge Delphi generated binaries relocation table was correct and right and was there, and should be a problem unless there is regression somewhere.

 

Again ASLR compatibility is Linker responsibility, but yet the compiler could generate code that confuses the linker and make it fail to build right relocation table.

 

ps: Relocation table in short words, it is a table that the system will parse and patch (change addresses) after (and while) loading the PE image sections into the memory based on the relative addresses in that table to the addresses that OS picked, and that before giving the new process the execution in case of EXE, and while blocking LoadLibrary and before calling DllMain.

Share this post


Link to post
3 hours ago, Kas Ob. said:

1) ASLR has nothing to do with code, well written or bad one.

2) ASLR is merely randomization of system allocation memory for this process, so no address can be guessed per execution.

If your code assumes that a pointer is an Integer (likely due to legacy but still invalid assumptions from a decade or more in the past), high-entropy ASLR will more quickly expose those errors  when compiling for 64-bit. That's what I was referring to.

Share this post


Link to post
4 hours ago, Kas Ob. said:

By default all DLLs should and must be ASLR compatible because by definition and form the beginning of Windows OS and its API, the DLL loading address can't be known before hand, and can't be fixed, yet for many years and many versions, Windows loaded specific DLLs to the same addresses.

Like exe, the DLL have a field in the IMAGE_OPTIONAL_HEADER64 - DllCharacteristics that enable or not the ASLR. In effect lot of DLLs are not ALSR compatible (especially the old ones). And they should works whenever ASLR is ON or OFF.

https://learn.microsoft.com/it-it/windows/win32/api/winnt/ns-winnt-image_optional_header64

Security things a part of course.

 

Some my customer more then 10 years ago explicity want the ASLR flags On and DEP too in my applications. And that works because some libraries that the customer forced me to use were not ALSR compatible (and neither was the application obviously :classic_laugh:). Some years ago also ASLR HE was imposed, and all worked well. For this I asked the full compatibility of ASLR (HE) of C++ DLL.

 

Share this post


Link to post
17 hours ago, Brandon Staggs said:

high-entropy ASLR will more quickly expose those errors

No, will not.

 

Because :

1) for 32 bit it will stay on 32 bit and will not hit the signed not signed range anyway, hence it will still working.

2) for 64 bit: well same as above !

 

 

Now back to this in whole

17 hours ago, Brandon Staggs said:

If your code assumes that a pointer is an Integer (likely due to legacy but still invalid assumptions from a decade or more in the past), high-entropy ASLR will more quickly expose those errors  when compiling for 64-bit. That's what I was referring to.

You are hitting different village here, what are you talking about or referring to, are stuff belongs to runtime pointer operation, at run time calculated :

1) These bug are exposed by FastMM with high memory allocation, there is a setting in FastMM4Options.inc called AlwaysAllocateTopDown, this will catch or at least help in catching such bugs.

2) No matter what your code is doing with pointers the code will stay at its place and it is where the image had being loaded, (not talking about runtime generated code like JIT or any), we are talking about default a process started by executing an exe, and we assuming the compiler is doing right job, the EIP will not leave the designated as execution pages when the image had being loaded by the OS, now if we moved these pages up by address, meaning we pushed the loading address 100mb or $F000000, will it fail ?

if there is relocation table then it will not fail, if there is no relocation table, then the OS will revert to the default which most likely $400000 and call it a day.

 

16 hours ago, DelphiUdIT said:

Like exe, the DLL have a field in the IMAGE_OPTIONAL_HEADER64 - DllCharacteristics that enable or not the ASLR. In effect lot of DLLs are not ALSR compatible (especially the old ones). And they should works whenever ASLR is ON or OFF.

ASLR merely load the image at different places as i said, and here i want to list what it does exactly for last time

1) Load the executable binary/image at random address, in the old days it was always the same and it comes form the PE.

2) Make the stack allocation at random, without it and like the old age, the stack like for the main thread was always at the same address, this is very observable even now with a debugged process, the stack position between two runs, and that because (3) :

3) Make memory allocation at random, this is second most important change as a process calls VirtualAlloc at beginning of its start without ASLR enabled most likely will have the same address, ASLR will prevent that.

Now, in both case id a DLL does support ASLR or not it will comply with relocation table to be loadable, duh, and this is most of the ASLR critical point, the rest is : it does expect a memory allocation or a position at the stack to be the same every time, i never saw a Delphi generated code have that, hence all Delphi DLLs are ASLR ready ! , and they will work fine.

For EXE it might be different but only for the image loading addresses, but it will also be ready for the randomization of the stack and allocations.

 

This is a nice article about ASLR when it failed to be ASLR https://insights.sei.cmu.edu/blog/when-aslr-is-not-really-aslr-the-case-of-incorrect-assumptions-and-bad-defaults/

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×