Creating a SecureString type for Delphi, Part 2.

Stefan
3 min readJul 1, 2016

← Continued from Part 1

In Part 1 of this blog post, we looked at how sensitive strings (such as credit card or passwords, for example) are never really zero’ed out from memory, and how this can be a problem (for example: when your process memory is written to a hibernation file or a page file or a memory dump file).

Zero’ing out the bytes that a Delphi string variable is pointing to isn’t all that difficult:

ZeroMemory(Pointer(S), System.Length(S) * SizeOf(Char));

Starting with Delphi 2009, we must multiply the number of characters with the size of Char because…

  1. the Delphi string type is an alias of the UnicodeString type, and
  2. every character in a UnicodeString — and throughout Windows as a platform — is stored in 2 physical bytes.
  3. Char is an alias of WideChar.

But wait. It is important not to zero a string out until the reference count is 0 (zero). Otherwise, we might mistakenly zero out other string variables that are pointing to the same bytes in the memory, leading to confusing problems that are difficult to debug.

How do we extract the reference count? Marco Cantù’s whitepaper on Delphi and Unicode tells us a lot about the internal structure of strings. It turns out that the reference count is actually stored with the string, before the actual text and before the position the string variable points to. The offset is -8 for the reference count:

var
I: Integer;
begin
I := PInteger(PByte(S) — 8)^;
if (I > -1) and (I < 2) then
ZeroMemory(Pointer(S), System.Length(S) * SizeOf(Char));
end;

When and where can we execute this code? The Delphi run-time library does not tell us when a string is about to get released from memory. There is no event. No message. No destructor.

What we can do is “box” our strings inside another reference-counted type.

Interfaces were added to the Delphi language in order to support COM (aka the Microsoft Component Object Model) — the foundation for ActiveX and BHOs and so many other technologies in the Windows ecosystem.

While the essence of an interface is to define a contract for all classes that implement the interface, interfaces as a programming concept provide us with a lot of nice things, among them being referenced-counted.

Introducing ISecureString

Our new, secure replacement for Delphi’s string type is actually quite simple:

type
ISecureString = interface
function Data: string;
function Length: Integer;
end;

When you have a password or a credit card, you should convert such strings to ISecureString at the earliest convenience in your project. Here is the ISecureString factory for that:

function NewSecureString(const S: string): ISecureString;
var
I: Integer;
begin
Result := TSecureString.Create(S);
if System.Length(S) > 0 then
begin
I := PInteger(PByte(S) — 8)^;
if (I > -1) and (I < 2) then
ZeroMemory(Pointer(S), System.Length(S) * SizeOf(Char));
end;
end;

What this function does is…

  1. Create a new TSecureString instance. The TSecureString class implements the ISecureString interface, and
  2. Zero out the Delphi string that was given to this function.

The important thing to take away here is that from here on, all the code in your project should work with ISecureString — not string. You cannot mix them because as soon as you do then your application will be vulnerable to attacks on your process memory.

Why go though all this trouble? Because we can override the TSecureString destructor. This then allows us to zero out our boxed string when TSecureString — the class that implements ISecureString — is destroyed.

Because TSecureString is not visible outside of the SecureString.pas unit, your developers must create a new ISecureString via NewSecureString() — We hereby ensure that the insecure string they pass to it is zero’ed out.

In Part 3 of this blog post, we’ll put everything together, and you will see how we mitigate the problem that started this blog post.

Continue to Part 3 →

--

--

Stefan

Delphi/Rust/Go developer. Ethereum consultant. Embarcadero MVP. Ex-Adobe, Macromedia. Helped build 1Password.