In part 1, we reverse engineered the registration code licensing mechanism of this particular software. However, that mechanism was not the mechanism actually in use in 2004; rather, a different mechanism was used based on licence files named license.bin. In this part, we investigate that licensing mechanism.

Identifying the licensing code

Using Ghidra, we note that a "license.bin" string is defined within the software binary:

Ghidra disassembly

This string is referenced within a function that Ghidra/IDR has identified as TSecurity.Create. The relevant decompiled code reads:

void TSecurity.Create(int param_1,char param_2) {
  // ...
  TApplication.GetExeName(*(undefined4 *)Application,&local_30);
  // ...
  ExtractFilePath(puVar2,&local_2c);
  // ...
  @LStrCat3(&local_28,local_2c,"license.bin");
  // ...
  cVar3 = FileExists(local_28);
  if (cVar3 != '\0') {
    // ...
    local_10 = (code **)TFileStream.Create(&PTR_TStream.GetSize_0041735c,1,local_28);
    // ...
    local_14 = (code **)TStringStream.Create(&PTR_TStream.GetSize_004174c8,1,0);
    // ...
    FUN_004e7fec(local_20,"innocuous-looking string",0);
    // ...
    TStringStream.ReadString(local_14,uVar6,&local_18,pcVar5,uVar4);
    FUN_004e8028(local_20,local_18,local_1c);
    // ...
    return;
  }
  // ...
  (**(code **)(**(int **)(local_8 + 4) + 0x38))(*(int **)(local_8 + 4),"Functionality=0");
  // ...
  (**(code **)(**(int **)(local_8 + 4) + 0x38))(*(int **)(local_8 + 4),"RegTo=UNREGISTERED");
  // ...
}

It appears that the code gets the path to the software binary, appends "license.bin", and checks if the file exists. If the file does not exist, it calls some function with "Functionality=0" and "RegTo=UNREGISTERED". If the file does exist, it opens the file, and calls TStringStream.ReadString, passing a pointer local_18. local_18 is then passed to a function FUN_004e8028.

Inspecting the raw disassembly, we see that, for the call to TStringStream.ReadString, a pointer to local_18 is stored in ecx:

Ghidra disassembly

We can investigate this further using the debugger. We create a license.bin file, and fill it with 0x50 ASCII a characters (0x61). In the debugger, we set a breakpoint before the TStringStream.ReadString call and note the value of ecx:

$ winedbg --gdb foobar.exe
Wine-gdb> b *0x4e8206
Breakpoint 1 at 0x4e8206
Wine-gdb> c
Continuing.

Breakpoint 1, 0x004e8206 in ?? ()
Wine-gdb> info reg
eax            0x101215c           16851292
ecx            0x32fed0            3342032
edx            0x50                80
ebx            0x70                112
[...]

We then set another breakpoint immediately after the TStringStream.ReadString call and read the contents at the pointed address:

Wine-gdb> b *0x4e820b
Breakpoint 2 at 0x4e820b
Wine-gdb> c
Continuing.

Breakpoint 2, 0x004e820b in ?? ()
Wine-gdb> x/wx 0x32fed0
0x32fed0:       0x010123f4
Wine-gdb> x/32bx 0x010123f4
0x10123f4:      0x61    0x61    0x61    0x61    0x61    0x61    0x61    0x61
0x10123fc:      0x61    0x61    0x61    0x61    0x61    0x61    0x61    0x61
0x1012404:      0x61    0x61    0x61    0x61    0x61    0x61    0x61    0x61
0x101240c:      0x61    0x61    0x61    0x61    0x61    0x61    0x61    0x61

It looks like this buffer does indeed contain the contents of our license.bin file!

Investigating the format of a decrypted licence file

Returning to TSecurity.Create, examining the raw disassembly, we see that the decompiled code generated by Ghidra is not correct. The line identified by Ghidra as return; actually transfers control to another part of the code. This is due to the use of structured exception handling (SEH), which Delphi uses to implement try–except–finally blocks. The relevant disassembly reads:

; ...
004e81ba 33 c0           XOR        EAX,EAX                                          ; Begin try block
004e81bc 55              PUSH       EBP
004e81bd 68 37 82        PUSH       DAT_004e8237
         4e 00
004e81c2 64 ff 30        PUSH       dword ptr FS:[EAX]
004e81c5 64 89 20        MOV        dword ptr FS:[EAX],ESP
; ...
004e8200 8d 4d ec        LEA        ECX=>local_18,[EBP + -0x14]                      ; Try block contents
004e8203 8b 45 f0        MOV        EAX,dword ptr [EBP + local_14]
004e8206 e8 81 43        CALL       TStringStream.ReadString
         f3 ff
004e820b 8d 4d e8        LEA        ECX=>local_1c,[EBP + -0x18]
004e820e 8b 55 ec        MOV        EDX=>DAT_004e8374,dword ptr [EBP + local_18]
004e8211 8b 45 e4        MOV        EAX,dword ptr [EBP + local_20]
004e8214 e8 0f fe        CALL       FUN_004e8028
         ff ff
004e8219 33 c0           XOR        EAX,EAX                                          ; End try block contents
004e821b 5a              POP        EDX
004e821c 59              POP        ECX
004e821d 59              POP        ECX
004e821e 64 89 10        MOV        dword ptr FS:[EAX],EDX
004e8221 68 3e 82        PUSH       LAB_004e823e
         4e 00
; ...                                                                                ; Miscellaneous destructor code
004e8236 c3              RET                                                         ; "return" in decompiled code
                                                                                     ; Jumps to LAB_004e823e
                     DAT_004e8237                                                    ; SEH handler
004e8237 e9              ??         E9h
004e8238 64              ??         64h    d
004e8239 c0              ??         C0h
004e823a f1              ??         F1h
004e823b ff              ??         FFh
004e823c eb              ??         EBh
004e823d e8              ??         E8h
                     LAB_004e823e                                                    ; End try block
; ... (execution continues)
004e825b 8b 45 fc        MOV        EAX,dword ptr [EBP + -0x4]
004e825e 8b 40 04        MOV        EAX,dword ptr [EAX + 0x4]
004e8261 8b 55 e8        MOV        EDX,dword ptr [EBP + -0x18]
004e8264 8b 08           MOV        ECX,dword ptr [EAX]
004e8266 ff 51 2c        CALL       dword ptr [ECX + 0x2c]
004e8269 8d 4d d0        LEA        ECX,[EBP + -0x30]
004e826c 8b 45 fc        MOV        EAX,dword ptr [EBP + -0x4]
004e826f 8b 40 04        MOV        EAX,dword ptr [EAX + 0x4]
004e8272 ba e4 83        MOV        EDX=>s_RegBy_004e83e4,s_RegBy_004e83e4           ; "RegBy"
         4e 00
004e8277 e8 d4 28        CALL       TStrings.GetValue
         f3 ff
; ...

Recall that local_18, containing the contents of license.bin, was passed to FUN_004e8028. local_1c (at EBP + -0x18) is also passed to FUN_004e8028, and is then later passed to the dynamic call at 0x4e8266 (as edx).

Using the debugger, we can inspect the contents of edx at this point, and identify where the dynamic call leads:

$ winedbg --gdb foobar.exe
Wine-gdb> b *0x4e8266
Breakpoint 1 at 0x4e8266
Wine-gdb> c
Continuing.

Breakpoint 1, 0x004e8266 in ?? ()
Wine-gdb> info reg
eax            0x1012ea0           16854688
ecx            0x41715c            4288860
edx            0x1013ff4           16859124
ebx            0x70                112
[...]
Wine-gdb> x/64bx $edx
0x1013ff4:      0x86    0x0d    0x96    0x9f    0xb8    0xa3    0xec    0x46
0x1013ffc:      0x13    0x1f    0x7b    0x8a    0x4a    0x96    0x31    0xbd
0x1014004:      0x24    0x43    0x30    0x2c    0x72    0xc2    0x6c    0x0e
0x101400c:      0xd3    0x58    0xc3    0xca    0xed    0xf6    0xb9    0x13
0x1014014:      0x46    0x4a    0x2e    0xdf    0x1f    0xc3    0x64    0xe8
0x101401c:      0x71    0x16    0x65    0x79    0x27    0x97    0x39    0x5b
0x1014024:      0x86    0x0d    0x96    0x9f    0xb8    0xa3    0xec    0x46
0x101402c:      0x02    0x0f    0xb8    0x74    0x00    0x61    0x61    0x61
Wine-gdb> si
0x0041b198 in ?? ()

Ghidra identifies for us that the function at 0x41b198 is TStrings.SetTextStr. According to the Delphi documentation, TStrings represents a list of strings, and TStrings.SetTextStr initialises the list of strings according to the parameter passed, with each value in the list separated by newlines.

It is worth at this point briefly mentioning the default Delphi calling convention, known as Borland fastcall. In this convention, the first 3 arguments to a function are passed as eax, edx and ecx (in that order), with further arguments pushed to the stack. In TStrings.SetTextStr, the first argument (eax) would be a reference to the TStrings instance, and so the second argument (edx) should be the string to initialise the list from.

We surmise, then, that edx contains the decrypted version of the license.bin data, ready to be later parsed. But what format should it take?

We see that the next call is a call to TStrings.GetValue with the parameter "RegBy". Of that function, the Delphi documentation says:

When the list of strings for the TStrings object includes strings that are name-value pairs, use Values to get or set the value part of a string associated with a specific name part.

For more information on name-value pairs, refer to the NameValueSeparator property.

And of NameValueSeparator, the documentation says:

Strings that contain the NameValueSeparator character are considered name-value pairs. NameValueSeparator defaults to the equal sign (=). …

Strings that are name-value pairs consist of a name part, the separator character, and a value part. … For example:

DisplayGrid=1
SnapToGrid=1
GridSizeX=8
GridSizeY=8

The code, then, seems to be getting the value associated with the RegBy key. We know now what format the decrypted data in edx should take – a sequence of Key=Value pairs separated by newlines.

Injecting licence file data

Recall from earlier that, when the license.bin file did not exist, there was a reference to the string "Functionality=0". Perhaps this key relates to the value at DAT_007e8d44 from part 1, which determined whether the software was registered.

Using winedbg/GDB, we insert a breakpoint just before the call to TStrings.SetTextStr:

$ winedbg --gdb foobar.exe
Wine-gdb> b *0x4e8266
Breakpoint 1 at 0x4e8266
Wine-gdb> c
Continuing.

Breakpoint 1, 0x004e8266 in ?? ()

We then replace the buffer at edx with the string "Functionality=1", and continue execution:

Wine-gdb> set {char[16]} $edx = "Functionality=1"
Wine-gdb> x/s $edx
0x1013ff4:      "Functionality=1"
Wine-gdb> c
Continuing.

And with that, the software again reports that it is a registered copy:

Successful registration

If we add further entries, such as "Functionality=1\nRegBy=foobar", the registration message adjusts accordingly:

Successful registration

Next steps

In this part, we reverse engineered the format of the decrypted licence file data, and demonstrated an approach injecting this data to enable the full functionality of the software. In the next and final part, we investigate the encryption mechanism, in order to produce valid licence files from scratch.