Hi Jeremy,


I'm currently investigating the stuff I told you about (DVCLAL and PACKAGEINFO).

It seems like Borland checks the DVCLAL (again this stands for "Delphi Visual Component Libraray Access License") everytime a module created with Delphi is loaded (which includes obviously BPLs and DLLs). As far as I know now, they have about the following scheme.

Note, the following code is taken from the SYSUTILS.PAS which ships with Delphi. I have commented it a little bit in the common manner with "//"

----------SNIP SYSUTILS.PAS------------
function AL1(const P): LongWord;
asm
//This is obviously the simple trial to implement an encryption/decryption
//routine ... in DOS times I also played with XOR ;-)
        MOV     EDX,DWORD PTR [P]
        XOR     EDX,DWORD PTR [P+4]
        XOR     EDX,DWORD PTR [P+8]
        XOR     EDX,DWORD PTR [P+12]
        MOV     EAX,EDX
end;

function AL2(const P): LongWord;
asm
//Well, you know assembler, so I need not to explain this.
//But frankly speaking, I'm not sure if it is the complement
//to the above routine and if and where it is used.
//There are some calls from the below procedures. But the below
//procedure seem to not be called from anywhere.
        MOV     EDX,DWORD PTR [P]
        ROR     EDX,5
        XOR     EDX,DWORD PTR [P+4]
        ROR     EDX,5
        XOR     EDX,DWORD PTR [P+8]
        ROR     EDX,5
        XOR     EDX,DWORD PTR [P+12]
        MOV     EAX,EDX
end;

const
//this is a constant exclusively for Delphi 4 Professional
// .... at least I guess so ;-)
//since they need to distinguish between the Delphi Versions.
//Again, frankly speaking: I've got no clue about the meaning, yet.
//I'll try to further examine this.
  AL1s: array[0..2] of LongWord = ($FFFFFFF0, $FFFFEBF0, 0);
  AL2s: array[0..2] of LongWord = ($42C3ECEF, $20F7AEB6, $D1C2F74E);

procedure ALV;
begin
//Borland kicks everybody without the fitting license ;-)
//that's terrible, isn't it?
  raise Exception.Create(SNL);
end;

function ALR: Pointer;
//AL=Access License
//so
//ALR=Access License Resource ;-)
var
  LibModule: PLibModule;
begin
//so, if it is the main-module (calling EXE) then get the "DVCLAL"-resource
//from it as a pointer
  if MainInstance <> 0 then
    Result := Pointer(LoadResource(MainInstance, FindResource(MainInstance, 'DVCLAL',
      RT_RCDATA)))
  else
  begin
    Result := nil;
//else step through the module list
    LibModule := LibModuleList;
    while LibModule <> nil do
    begin
      with LibModule^ do
      begin
        Result := Pointer(LoadResource(Instance, FindResource(Instance, 'DVCLAL',
          RT_RCDATA)));
//until we find the first resource with the name ... then return immediately
        if Result <> nil then Break;
      end;
      LibModule := LibModule.Next;
    end;
  end;
//kick everybody without the"DVCLAL" resource record
  if Result = nil then ALV;
end;

function GDAL: LongWord;
type
  TDVCLAL = array[0..3] of LongWord;
  PDVCLAL = ^TDVCLAL;
var
  P: Pointer;
  A1, A2: LongWord;
  PAL1s, PAL2s: PDVCLAL;
  ALOK: Boolean;
begin
//GDAL=Get Delphi Access License?? ;-))
  P := ALR;
//get the resource pointer
  A1 := AL1(P^);
  A2 := AL2(P^);
  Result := A1;
  PAL1s := @AL1s;
  PAL2s := @AL2s;
//check the bitmap (comparing with the above constants)
  ALOK := ((A1 = PAL1s[0]) and (A2 = PAL2s[0])) or
          ((A1 = PAL1s[1]) and (A2 = PAL2s[1])) or
          ((A1 = PAL1s[2]) and (A2 = PAL2s[2]));
  FreeResource(Integer(P));
//if ... you know ... KICK!!!!!
  if not ALOK then ALV;
end;

procedure RCS;
var
  P: Pointer;
  ALOK: Boolean;
begin
  P := ALR;
//wow ... Client/Server edition
  ALOK := not (AL1(P^) <> AL1s[2]) or (AL2(P^) <> AL2s[2]);
  FreeResource(Integer(P));
  if not ALOK then ALV;
end;

procedure RPR;
var
  AL: LongWord;
begin
//and Professional edition ...
  AL := GDAL;
  if (AL <> AL1s[1]) and (AL <> AL1s[2]) then ALV;
end;
----------SNAP SYSUTILS.PAS------------

Re-Hi Jeremy,

Well, I found a call to GDAL() in the DBTABLES.PAS in line 2326

----------SNIP-------------------------
    if DbiGetObjFromName(objCLIENT, nil, ClientHandle) = 0 then
      DbiSetProp(ClientHandle, Integer(clSQLRESTRICT), GDAL);
----------SNAP-------------------------

.... which belongs to the Borland Database Engine (BDE) part of the VCL.
So the method of stripping these resources from the EXE file can be critical with BDE for the first

Well, let's continue ...
Since I've got the Professional Edition, I had to look for the appropriate procedure call RPR() first.
I found it in the SCKTCOMP.PAS which also belongs to the VCL and is the implementation of WinSock Components. (lines 1800 and 1945)

----------SNIP-------------------------
RPR;
----------SNAP-------------------------

... as you can see it is a single call, which either kicks you or not. It is not a nested function call as the above example which will be more difficult to patch.
I also found a call to RPR in the DBCGRIDS.PAS (line 204) which was also a single call (not nested).
Note, that I only searched for the string "RPR" in case-sensitive mode, assuming the correctness of Borland programmers ;-) ... I decided to write a little prog which outputs ANY occurance of the string (including the original and the filename). This will make it easier to find further occurances. (14:30 Ukrainian time)

15:18 ... my assumption was right ... after the first trials I decided to write a better readable form to disk ... I choose HTML ... I attach them to the mail. There are no more, than the yet found files.

Now I'm going to play a little bit with the constants.

GDAL() gives 0xFFFFEBF0 with my version of the constants.
Neither RPR() nor RCS() raise an exception (I substituted the exception by a simple message box) so, ... NO RESULTS. I expected an exception with RCS() which is presumably the Client/Server-edition-call.

I advise everybody without client server version to create a variable in the mentioned files which substitutes the original or to patch the SYSUTILS.PAS directly.

Okay, now I'm going to write a little (resource-) patcher ;-)

Okay, got it ... ALMOST.

Well you can get the resources as follows:

----------SNIP-------------------------
     AL:=false; // Access License
     PI:=false; // PACKAGEINFO
     EXE:=LoadLibrary(pchar(edit4.text));
     if EXE<>0 then
        try
           bla:=nil;
           bla:=LockResource(LoadResource(EXE, FindResource(EXE, 'DVCLAL', RT_RCDATA)));
           if bla<>nil then AL:=TRUE
              else application.MessageBox('No AL found','',MB_OK);
           bla:=nil;
           bla:=LockResource(LoadResource(EXE, FindResource(EXE, 'PACKAGEINFO', RT_RCDATA)));
           if bla<>nil then PI:=TRUE
              else application.MessageBox('No PI found','',MB_OK)
        finally
               freelibrary(EXE);
        end else application.MessageBox('Nope, no module loaded','',MB_OK)
----------SNAP-------------------------

now you know, if our EXE has the appropriate resources.

By the way:, the following function is somewhat a byproduct of my examination ... it deals also with DLL not as the SHGetFileInfo() function which seems to not recognize DLLs at all.

----------SNIP-------------------------
const
  IMAGE_DOS_SIGNATURE=$5A4D;
  IMAGE_NT_SIGNATURE =$00004550;

type
  PIMAGE_DOS_HEADER = ^IMAGE_DOS_HEADER;
  IMAGE_DOS_HEADER = packed record
    e_magic,
    e_cblp,
    e_cp,
    e_crlc,
    e_cparhdr,
    e_minalloc,
    e_maxalloc,
    e_ss,
    e_sp,
    e_csum,
    e_ip,
    e_cs,
    e_lfarlc,
    e_ovno          : WORD;
    e_res           : packed array [0..3] of WORD;
    e_oemid,
    e_oeminfo       : WORD;
    e_res2          : packed array [0..9] of WORD;
    e_lfanew        : Longint;
  end;

function isexe(s:string):boolean;
var hfile,hmap,test:DWORD;
    pEXE:pchar;
begin
     result:=false;
     hfile:=createfile(pchar(s),GENERIC_READ, FILE_SHARE_READ, NIL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
     if hfile<>INVALID_HANDLE_VALUE then
        try
           hmap:=CreateFileMapping(hFile, NIL, PAGE_READONLY,0,0,NIL);
           if hmap<>0 then
           try
              pEXE:=MapViewOfFile(hMap,FILE_MAP_READ,0,0,0);
              result:=PWORD(pEXE)^=IMAGE_DOS_SIGNATURE;
              if result then begin
                 messagebox(0,pchar(format('DOS-Header found'+#13#10+'PE offset: 0x%8.8x',[PIMAGE_DOS_HEADER(pEXE)^.e_lfanew])),'',MB_OK);
                 result:=false;
                 pEXE:=pEXE+PIMAGE_DOS_HEADER(pEXE)^.e_lfanew;
                 result:=PDWORD(pEXE)^=IMAGE_NT_SIGNATURE;
                 if result then begin
                    messagebox(0,'Yepp, it''s a PE','',MB_OK);
                    test:=PIMAGE_FILE_HEADER(pEXE)^.TimeDateStamp;
                    messagebox(0,pchar(format('%8.8x (%d) - %d',[test, test, test])),'',MB_OK);
                 end;
              UnmapViewOfFile(pEXE);
              end;
           finally
              closehandle(hmap);
           end;
        finally
           closehandle(hfile);
        end;
end;
----------SNAP-------------------------

(maybe it's somehow useful to you ... even if I thing you know about this ... ;-)

You may now strip the resources as follows:

----------SNIP-------------------------
var hEXE:DWORD;
begin
     hEXE:=BeginUpdateResource(pchar(edit4.text),FALSE);
     if hEXE<>0 then begin
        UpdateResource(hEXE,RT_RCDATA,'DVCLAL',0,nil,0);
        EndUpdateResource(hEXE,FALSE);
     end else messagebox(0,pchar(format('nope %d',[GetLasterror])),'',MB_OK);
end;
----------SNAP-------------------------

DON'T TRY to implement this before you know the following! These functions (resource functions) used herein are only available with the WINNT platform ... if you use the WIN9X/ME platforms you have to deal with the filemapping stuff and so on ... (the isexe() function I wrote is a beginning). You need to find the ".rsrc" section inside the EXE image and to do the rest manually which might be somewhat dangerous. ... If I find time I'll make further investigations on that.
Last tip: A friend wrote a little program to get the AL and the type (Std, Pro, C/S, Ent) ... If he agrees and you want, I may send you this program


Copyright (c) 2000 by -=Assarbad=- ... MAIL