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