Method to determine if an exe file has been compressed with UPX - delphi

Is there a method to determine if an exe file has been compressed with UPX?
The function to determine if an exe file has been compressed is excellent except I found a problem with the code. If the function IsUPXCompressed is called then you try to run upx, upx can not save the file it modifies. There is something not sharing rights correctly in the function. I have tested this for several hours. If I do not call the method then UPX can write the files with no problem. You you call it then try to run UPX it will not save the file. UPX reports an IOException Permission denied error when trying to write the file.
Can anyone spot something wrong in the code that would cause this problem?
Thank-you
The function to determine if an exe file has been compressed is excellent except I found a problem with the code. If the function IsUPXCompressed is called then you try to run upx, upx can not save the file it modifies. There is something not sharing rights correctly in the function. I have tested this for several hours. If I do not call the method then UPX can write the files with no problem. You you call it then try to run UPX it will not save the file. UPX reports an IOException Permission denied error when trying to write the file.
Can anyone spot something wrong in the code that would cause this problem?
Thank-you

Another Method, when a exe is packed with the UPX tool, the section of the PE header contains sections called UPX0,UPX1, etc. so if read these sections and compare the name with the string UPX you can determine if the exe was compressed using the UPX packer.
check this function
uses
Windows;
function IsUPXCompressed(const Filename:TFileName): Boolean;
var
i : integer;
pBaseAddress : PByte;
pDosHeader : PImageDosHeader;
pNtHeaders : PImageNtHeaders;
hFile : Cardinal;
hFileMap : Cardinal;
pSectionHeader: PImageSectionHeader;
dwOffset : Cardinal;
SectName : AnsiString;
begin
Result:=False;
hFile := CreateFile(PChar(Filename), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (hFile = INVALID_HANDLE_VALUE) then Exit;
hFileMap := CreateFileMapping(hFile, nil, PAGE_READONLY or SEC_IMAGE, 0, 0, nil);
if (hFileMap = 0) then
begin
CloseHandle(hFile);
Exit;
end;
pBaseAddress := MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
if (pBaseAddress = nil) then
begin
CloseHandle(hFileMap);
CloseHandle(hFile);
Exit;
end;
try
dwOffset := Cardinal(pBaseAddress);
pDosHeader := PImageDosHeader(pBaseAddress);
pNtHeaders := PImageNtHeaders(dwOffset + Cardinal(pDosHeader._lfanew));
pSectionHeader := pImageSectionHeader(Cardinal(pNtHeaders) + SizeOf(TImageNtHeaders));
for i := 0 to pNtHeaders.FileHeader.NumberOfSections-1 do
begin
SetString(SectName, PAnsiChar(#pSectionHeader.Name), SizeOf(pSectionHeader.Name));
Result:=Pos('UPX',SectName)>0;
If Result then break;
Inc(pSectionHeader);
end;
finally
UnmapViewOfFile(pBaseAddress);
CloseHandle(hFileMap);
CloseHandle(hFile);
end;
end;

UPX itself does it like this:
if (memcmp(isection[0].name,"UPX",3) == 0)
throwAlreadyPackedByUPX();
This is the implementation for 32-bit PEs; 64-bit PEs need different offsets,
and other executable formats have to be handled separately.
#include <stdio.h>
typedef unsigned int uint;
uint peek_d( FILE* f, uint offs ) {
fseek( f, offs, SEEK_SET );
uint a = 0;
fread( &a, 1,sizeof(a), f );
return a;
}
int main( int argc, char** argv ) {
FILE* f = fopen( argv[1], "rb" ); if( f==0 ) return 1;
uint p,n,x,y;
p = peek_d( f, 0x3C ); // PE header offset
n = peek_d( f, p+0x74 ); // pointer table size
x = p + 0x78 + n*8;
y = peek_d( f, x+0*0x28+0 ); // 1st section name
if( (y&0xFFFFFF) == ('U'+('P'<<8)+('X'<<16)) ) {
printf( "UPX detected!\n" );
} else {
printf( "No UPX!\n" );
}
return 0;
}

try to uncompress it with upx?

// Returns IsUPXCompressed - Modified for Delphi 2010
function IsUPXCompressed( const Filename: TFileName ): Boolean;
var
i: integer;
pBaseAddress: PByte;
pDosHeader: PImageDosHeader;
pNtHeaders: PImageNtHeaders;
hFile: Cardinal;
hFileMap: Cardinal;
pSectionHeader: PImageSectionHeader;
dwOffset: Cardinal;
SectName: AnsiString;
begin
Result := False;
hFile := CreateFile( PChar( Filename ), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 );
if ( hFile = INVALID_HANDLE_VALUE ) then
Exit;
hFileMap := CreateFileMapping( hFile, nil, PAGE_READONLY or SEC_IMAGE, 0, 0, nil );
if ( hFileMap = 0 ) then
begin
CloseHandle( hFile );
Exit;
end;
pBaseAddress := MapViewOfFile( hFileMap, FILE_MAP_READ, 0, 0, 0 );
if ( pBaseAddress = nil ) then
begin
CloseHandle( hFileMap );
CloseHandle( hFile );
Exit;
end;
dwOffset := Cardinal( pBaseAddress );
pDosHeader := PImageDosHeader( pBaseAddress );
pNtHeaders := PImageNtHeaders( dwOffset + Cardinal( pDosHeader._lfanew ) );
pSectionHeader := pImageSectionHeader( Cardinal( pNtHeaders ) + SizeOf( TImageNtHeaders ) );
for i := 0 to pNtHeaders.FileHeader.NumberOfSections - 1 do
begin
SetString( SectName, PAnsiChar( #pSectionHeader.name ), SizeOf( pSectionHeader.name ) );
if Pos( 'UPX', SectName ) > 0 then
begin
Result := True;
exit;
end;
Inc( pSectionHeader );
end;
end;
Thanks Rob for the pointers.

The section names are not included the UPX word always. It mabebe contain another name changed by user. For certain. Ypu must search for UPX copmpressor signature in the whole file.

Related

IStorage does not unlock after commit

When I run the prog below, the result value of the stgOpenStorage is STG_E_SHAREVIOLATION. How should I close the IStorage to get it unlocked?
procedure TForm1.btnSaveClick(Sender: TObject);
var
fileName : string;
streamName : string;
procedure storeTextIntoStorageStream( text_ : string );
var
documentStorage : IStorage;
levelIStream : IStream;
i, j : integer;
begin
if ( fileExists( fileName ) ) then
deleteFile( fileName );
stgCreateDocfile( #fileName[1], STGM_WRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT or STGM_CREATE, 0, documentStorage );
try
documentStorage.CreateStream( #streamName[1], STGM_WRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, 0, 0, levelIStream );
try
i := length( text_ );
levelIStream.write( #i, sizeOf( integer ), #j );
levelIStream.write( #text_[1], i*sizeOf( char ), #j );
finally
levelIStream.Commit( 0 );
levelIStream := NIL;
end;
finally
documentStorage.Commit( 0 );
documentStorage := NIL;
end;
end;
function readTextFromStorageStream : string;
var
documentStorage : IStorage;
levelIStream : IStream;
i, j : integer;
begin
i := stgOpenStorage( #fileName[1], NIL, STGM_READ or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, NIL, 0, documentStorage );
try
documentStorage.OpenStream( #streamName[1], NIL, STGM_READ or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, 0, levelIStream );
try
levelIStream.read( #i, sizeOf( integer ), #j );
setLength( result, i );
levelIStream.read( #result[1], i*sizeOf( char ), #j );
finally
levelIStream := NIL;
end;
finally
documentStorage := NIL;
end;
end;
begin
fileName := 'c:\temp\test.stg';
streamName := 'Stream-0';
storeTextIntoStorageStream( memo1.Lines.DelimitedText );
memo1.Lines.DelimitedText := readTextFromStorageStream;
end;
And how could I set the IStorage/IStream default size / size step? Because my test 1.6K byte content stored in 16K.
There are two IStorage implementations in the Delphi source libraries.
WinApi.OLE2 and WinApi.ActiveX. Which one do you use? In the WinApi.OLE2 unit, the IStorage and IStream are CLASSES, they are not INTERFACES. If you use this unit, the interface garbage collection and so the automatic closing does not work on the variables. If you use the WinApi.ActiveX unit, the example will work just fine.
The code looks fine 1, so I have a feeling that the problem is due to your use of STGM_SHARE_EXCLUSIVE. The file is in use at the time you are trying to open it, so I'm betting your OS/AV is the one keeping the file open (ie, to scan its content), not the interfaces in storeTextIntoStorageStream(), which are long gone by the time readTextFromStorageStream() is entered.
1: well, aside from the lack of adequate error handling. And the redundant nil'ing of interface variables. And, consider replacing your string indexes with PChar() casts instead.
In readTextFromStorageStream(), try replacing STGM_SHARE_EXCLUSIVE (which makes sense for a writer, but not a reader) with STGM_SHARE_DENY_WRITE instead and see if the error goes away:
procedure TForm1.btnSaveClick(Sender: TObject);
var
fileName : string;
streamName : string;
procedure storeTextIntoStorageStream( const text_ : string );
var
documentStorage : IStorage;
levelIStream : IStream;
i, j : integer;
begin
if ( FileExists( fileName ) ) then
DeleteFile( fileName );
OleCheck( StgCreateDocFile( PChar(fileName), STGM_WRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT or STGM_CREATE, 0, documentStorage ));
try
OleCheck( documentStorage.CreateStream( PChar(streamName), STGM_WRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, 0, 0, levelIStream ) );
try
i := Length( text_ );
OleCheck( levelIStream.Write( #i, SizeOf( i ), #j ) );
if ( i > 0 ) then
OleCheck( levelIStream.Write( PChar(text_), i * SizeOf( Char ), #j ) );
finally
levelIStream.Commit( 0 );
end;
finally
documentStorage.Commit( 0 );
end;
end;
function readTextFromStorageStream : string;
var
documentStorage : IStorage;
levelIStream : IStream;
i, j : integer;
begin
Result := '';
OleCheck( StgOpenStorage( PChar(fileName), nil, STGM_READ or STGM_SHARE_DENY_WRITE or STGM_DIRECT, NIL, 0, documentStorage ) );
OleCheck( documentStorage.OpenStream( PChar(streamName), nil, STGM_READ or STGM_SHARE_DENY_WRITE or STGM_DIRECT, 0, levelIStream ) );
OleCheck( levelIStream.Read( #i, SizeOf( i ), #j ) );
if ( i > 0 ) then
begin
SetLength( Result, i );
OleCheck( levelIStream.Read( PChar(Result), i * SizeOf( Char ), #j ) );
end;
end;
begin
fileName := 'c:\temp\test.stg';
streamName := 'Stream-0';
storeTextIntoStorageStream( Memo1.Lines.DelimitedText );
Memo1.Lines.DelimitedText := readTextFromStorageStream;
end;

Save text from the clipboard to a file

I was trying out below code that should save clipboard text to a text file in Delphi XE6. The code runs fine but generates only junk values in the output file, even when the clipboard contains a copied text fragment. How can the code be changed to work properly?
function SaveClipboardTextDataToFile(
sFileTo : string ) : boolean;
var
ps1,
ps2 : PChar;
dwLen : DWord;
tf : TextFile;
hData : THandle;
begin
Result := False;
with Clipboard do
begin
try
Open;
if( HasFormat( CF_TEXT ) ) then
begin
hData :=
GetClipboardData( CF_TEXT );
ps1 := GlobalLock( hData );
dwLen := GlobalSize( hData );
ps2 := StrAlloc( 1 + dwLen );
StrLCopy( ps2, ps1, dwLen );
GlobalUnlock( hData );
AssignFile( tf, sFileTo );
ReWrite( tf );
Write( tf, ps2 );
CloseFile( tf );
StrDispose( ps2 );
Result := True;
end;
finally
Close;
end;
end;
end;
You see junk because CF_TEXT is ANSI. You request ANSI text, the OS converts the clipboard contents to ANSI, and you put it in unicode string. Use CF_UNICODETEXT for unicode applications.
Also consider the points raised in the comments to the question.
If you have Delphi XE6 then you can use some of the already implemented features
uses
System.SysUtils,
System.IOUtils,
Vcl.Clipbrd;
function SaveClipboardTextDataToFile( const sFileTo : string ) : boolean;
var
LClipboard : TClipboard;
LContent : string;
begin
// get the clipboard content as text
LClipboard := TClipboard.Create;
try
LContent := LClipboard.AsText;
finally
LClipboard.Free;
end;
// save the text - if any - into a file
if not LContent.IsEmpty
then
begin
TFile.WriteAllText( sFileTo, LContent );
Exit( True );
end;
Result := False;
end;

CreateFileMapping fails with hFile other than INVALID_HANDLE_VALUE

update - solved and answered, offending lines have been commented out
Brief description
I am having a problem linking CreateFile with CreateFileMapping even though I use (GENERIC_WRITE or GENERIC_WRITE) for CreateFile, with PAGE_READWRITE for CreateFileMapping
Detailed description
I am using CreateFileMapping to share memory between proceses.
the actual mechanics of that is working fine, assuming I don't map to a physical file and instead use INVALID_HANDLE_VALUE for the first parameter to CreateFileMapping. This is fine, however what I'd like to achieve is for the main process that creates this map to use a disk based file, and flush it to the drive, periodically, and when it shuts down, so the data is automatically saved.
Code follows...
(when i run this, i get "Error 5 : Access is Denied" as the ShowMessage)
const MaximumMapSize = 256 * 1024;
var virtualMemoryPath : string = '';
function getFileHandle(mapname : string; maxSize : dword) :THandle;
var diskfilename : string;
lpFileName: PChar;
dwDesiredAccess: DWORD;
dwShareMode: DWORD;
lpSecurityAttributes: PSecurityAttributes;
dwCreationDisposition : dword;
dwFlagsAndAttributes: DWORD;
hTemplateFile : THandle ;
temp : pointer;
begin
Result := INVALID_HANDLE_VALUE;
if (maxSize <= MaximumMapSize) and (Length(virtualMemoryPath) > 0) then
begin
diskfilename := virtualMemoryPath+mapname+'.bin';
if FileExists(diskfilename) then
Sysutils.DeleteFile(diskfilename);
lpFileName := PChar(diskfilename);
//dwDesiredAccess := GENERIC_WRITE or GENERIC_WRITE;//<<<wrong
dwDesiredAccess := GENERIC_READ or GENERIC_WRITE;
dwShareMode := 0;//FILE_SHARE_READ or FILE_SHARE_WRITE;//<<wrong
lpSecurityAttributes := nil;
dwCreationDisposition := CREATE_ALWAYS;
dwFlagsAndAttributes := 0;
hTemplateFile := 0 ;
Result := CreateFile(
lpFileName,
dwDesiredAccess,
dwShareMode,
lpSecurityAttributes,
dwCreationDisposition,
dwFlagsAndAttributes,
hTemplateFile);
if (Result <> INVALID_HANDLE_VALUE) then
begin
GetMem(temp,maxsize);
ZeroMemory(temp,maxsize);
FileWrite(result,temp^,maxSize);
FreeMem (temp,maxsize);
FlushFileBuffers(result);
SetFilePointer(result,0,0,FILE_BEGIN);
end;
end;
end;
function createMap (mapname : string ; maxSize: dword; var hFile: THandle) : THandle;
var
lpFileMappingAttributes: PSecurityAttributes;
flProtect : DWORD;
dwMaximumSizeHigh : DWORD;
dwMaximumSizeLow : DWORD;
lpName : PChar;
LastError : dword;
begin
Result := INVALID_HANDLE_VALUE;
if (maxSize > MaximumMapSize) then
exit;
// create a disk the file or return INVALID_HANDLE_VALUE if settings have not defined a path to folder
hFile := getFileHandle(mapname,maxSize);
lpFileMappingAttributes := nil;
flProtect := PAGE_READWRITE;
dwMaximumSizeHigh := 0;
dwMaximumSizeLow := maxSize;
lpName := PChar(mapname);
Result := CreateFileMapping(
hfile,
lpFileMappingAttributes,
flProtect,
dwMaximumSizeHigh,
dwMaximumSizeLow,
lpName);
if (Result = 0) then
begin
if (not (hFile = INVALID_HANDLE_VALUE) ) then
CloseHandle(hFile);
hFile := 0;
LastError := GetLastError();
ShowMessage( Format('Error %d : %s',[LastError,SysErrorMessage(LastError)]) );
end
else
begin
ShowMessage(Format('Returing handle %d',[result]));
if (hFile = INVALID_HANDLE_VALUE) then
hFile := 0;
end;
end;
dwDesiredAccess := GENERIC_WRITE or GENERIC_WRITE;
dwShareMode := FILE_SHARE_READ or FILE_SHARE_WRITE;
This cannot work. You'll need read and write access. Also, creating a view requires the operating system to be sure that it is the only one that can write to the file. There's no mechanism to detect another process writing to the file, other than through a view, and ensure that such a write is visible in memory in a timely and synchronous manner. Particularly the synchronous update is impossible to implement with multiple threads accessing the view.
Read sharing is similarly unwise, the operating system provides no guarantee at which time it updates the file from the view. The only guarantee is that it will be updated when all views are closed. Which also means that an orderly shutdown of Windows is required, you cannot make this reliable against mishaps like a power loss. Again, reads need to be done through the view, not the file. The only appropriate choice is no sharing.
The mistake is here:
GENERIC_WRITE or GENERIC_WRITE
You meant:
GENERIC_READ or GENERIC_WRITE
Fix that and your file mapping can be created.
I agree with Hans that you should not be sharing the file handle.

Check if everyone may read/write to a directory

We are facing the problem, that different user groups shall be able to read and write files from a common data directory (e.g. c:\ProgramData\xyz).
The data is written from different sources e.g. a service writes files into it and a user shall be able to change it's content later on.
The problem now is that this only works if "everybody" is allowed to read/write/change
files in that directory (und subdirs).
What I want to check in the installer is if all users are allowed to do so aka. check if the "every users" group (or "Jeder" group in german) is in the access list.
I have only basic knowledge about ACL and can change that in the explorer but I would need a few lines of code which push me into the right direction (in Delphi).
many thanks
Mike
I think it's not a Delphi but WinAPI question. Delphi doesn't have any special facilities to make this easier AFAIK.
Getting information from an ACL says you need to do GetSecurityInfo on an open handle, then GetEffectiveRightsFromACL on an ACL you get.
You specify a trustee, which can be by name, but better use SID. Name for "Everyone" can change, but there's a special SID for it which is valid on any PC, google it. Okay, here it is: "(S-1–1–0)". Or you can use CreateWellKnownSid and give it WinWorldSid to get the same SID (more proper but longer way).
All of that from five minutes of googling so watch out for mistakes.
Alright, here's some code.
function ConvertStringSidToSid(StringSid: PWideChar; var Sid: PSID): boolean; stdcall; external advapi32 name 'ConvertStringSidToSidW';
function AclGetEffectiveRights(const path, sid: string): cardinal;
var h: THandle; //handle to our directory
err: integer;
dacl: PACL; //access control list for the object
secdesc: pointer;
tr: TRUSTEE;
bsid: PSid;
begin
Result := 0;
//Open directory
h := CreateFile(PChar(path), GENERIC_READ, FILE_SHARE_READ, nil,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL or FILE_FLAG_BACKUP_SEMANTICS, 0);
//we need FILE_FLAG_BACKUP_SEMANTICS to open a directory
if h=INVALID_HANDLE_VALUE then RaiseLastOsError();
try
bsid := nil;
//Query access control list for a directory -- the list you see in the properties box
err := GetSecurityInfo(h, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
nil, nil, #dacl, nil, secdesc);
//GetSecurityInfo can return many things but we only need DACL,
//and we are required to also get a security descriptor
if err<>ERROR_SUCCESS then
raise Exception.CreateFmt('Cannot retrieve DACL: error %d',[err]);
try
//Convert string sid to binary sid
if not ConvertStringSidToSid(PChar(sid), bsid) then
RaiseLastOsError();
//Query effective rights for a trustee
BuildTrusteeWithSid(#tr, bsid);
err := GetEffectiveRightsFromAcl(dacl^, tr, Result);
if err<>ERROR_SUCCESS then
raise Exception.CreateFmt('Cannot calculate effective rights: error %d',[err]);
finally
//Documentation says to free some resources this way when we're done with it.
LocalFree(NativeUint(bsid));
LocalFree(NativeUint(secdesc));
end;
finally
CloseHandle(h);
end;
end;
It's used like this:
var rights,test: cardinal;
rights := AclGetEffectiveRights('C:\My\Folder','S-1-1-0');
//List rights you want tested
test := FILE_LIST_DIRECTORY + FILE_ADD_FILE + FILE_ADD_SUBDIRECTORY
+ FILE_READ_EA + FILE_WRITE_EA + FILE_TRAVERSE + FILE_DELETE_CHILD;
Result := (rights and test) = test;
Might not work; gives ACCESS_DENIED on my PC but that's probably because of my complicated domain situation. Anyway that's something for a start.
So.. The above version worked... until Windows Update 1903 hit me and GetEffectiveRightsFromAcl always resulted in an error ERROR_NO_SUCH_DOMAIN (which is obscure since there is no Domain whatsoever involved here).
So I needed to switch to the following procedure:
// ###########################################
// ### translated and extended from https://learn.microsoft.com/de-de/windows/win32/api/aclapi/nf-aclapi-geteffectiverightsfromacla
procedure DisplayAccessMask(Mask : ACCESS_MASK );
begin
{
// This evaluation of the ACCESS_MASK is an example.
// Applications should evaluate the ACCESS_MASK as necessary.
}
if (((Mask and GENERIC_ALL) = GENERIC_ALL)
or ((Mask and FILE_ALL_ACCESS) = FILE_ALL_ACCESS))
then
begin
OutputDebugString( 'Full control');
exit;
end;
if (((Mask and GENERIC_READ) = GENERIC_READ)
or ((Mask and FILE_GENERIC_READ) = FILE_GENERIC_READ))
then
OutputDebugString( 'Read');
if (((Mask and GENERIC_WRITE) = GENERIC_WRITE)
or ((Mask and FILE_GENERIC_WRITE) = FILE_GENERIC_WRITE))
then
OutputDebugString('Write');
if (((Mask and GENERIC_EXECUTE) = GENERIC_EXECUTE)
or ((Mask and FILE_GENERIC_EXECUTE) = FILE_GENERIC_EXECUTE))
then
OutputDebugString('Execute');
end;
function CheckMask( MASK : ACCESS_MASK; refMask : ACCESS_MASK ) : boolean;
var msk : ACCESS_MASK;
begin
msk := 0;
if (((Mask and GENERIC_READ) = GENERIC_READ)
or ((Mask and FILE_GENERIC_READ) = FILE_GENERIC_READ))
then
msk := msk or FILE_GENERIC_READ;
if (((Mask and GENERIC_WRITE) = GENERIC_WRITE)
or ((Mask and FILE_GENERIC_WRITE) = FILE_GENERIC_WRITE))
then
msk := msk or FILE_GENERIC_WRITE;
if (((Mask and GENERIC_EXECUTE) = GENERIC_EXECUTE)
or ((Mask and FILE_GENERIC_EXECUTE) = FILE_GENERIC_EXECUTE))
then
msk := msk or FILE_GENERIC_EXECUTE;
Result := (msk and refMask) = refMask;
end;
function GetAccess(hAuthzClient :AUTHZ_CLIENT_CONTEXT_HANDLE; psd : PSECURITY_DESCRIPTOR) : BOOL;
var AccessRequest : AUTHZ_ACCESS_REQUEST;
AccessReply : AUTHZ_ACCESS_REPLY;
buffer : Array[0..1023] of Byte;
begin
FillChar(AccessRequest, sizeof(AccessRequest), 0);
FillChar(AccessReply, sizeof(AccessReply), 0);
FillChar(buffer, sizeof(buffer), 0);
AccessRequest.DesiredAccess := MAXIMUM_ALLOWED;
AccessRequest.PrincipalSelfSid := nil;
AccessRequest.ObjectTypeList := nil;
AccessRequest.ObjectTypeListLength := 0;
AccessRequest.OptionalArguments := nil;
AccessReply.ResultListLength := 1;
AccessReply.GrantedAccessMask := PACCESS_MASK( LongWord(#Buffer[0]));
AccessReply.Error := PDWORD( LongWord( AccessReply.GrantedAccessMask ) + sizeof(Access_Mask));
Result := AuthzAccessCheck( 0,
hAuthzClient,
#AccessRequest,
0,
psd,
nil,
0,
#AccessReply,
nil);
if Result then
begin
DisplayAccessMask( AccessReply.GrantedAccessMask^ );
Result := CheckMask( AccessReply.GrantedAccessMask^, FILE_GENERIC_WRITE or FILE_GENERIC_READ );
end
else
RaiseLastOSError;
end;
function ConvertStringSidToSid(StringSid: PWideChar; var Sid: PSID): boolean; stdcall; external advapi32 name 'ConvertStringSidToSidW';
function ConvertNameToBinarySid(pAccountName : PCHAR): PSID ;
var pDomainName : PChar;
dwDomainNameSize : DWord;
aSID : PSID;
dwSIDSIZE : DWORD;
sidType : SID_NAME_USE;
begin
pDomainName := nil;
dwDomainNameSize := 0;
aSID := nil;
LookupAccountName( nil, pAccountName, aSID, dwSIDSIZE, pDomainName, dwDomainNameSize, sidType);
aSid := Pointer( LocalAlloc( LPTR, dwSIDSIZE*sizeof(char)) );
pDomainName := Pointer( LocalAlloc(LPTR, dwDomainNameSize*sizeof(char)) );
if not LookupAccountName( nil, pAccountName, aSID, dwSIDSIZE, pDomainName, dwDomainNameSize, sidType) then
begin
LocalFree( Cardinal(aSID) );
Result := nil;
end
else
begin
Result := aSid;
end;
LocalFree( Cardinal(pDomainName) );
end;
function GetEffectiveRightsForSID(hManager :AUTHZ_RESOURCE_MANAGER_HANDLE;
psd : PSECURITY_DESCRIPTOR;
sid : PChar) : BOOL;
var asid : PSID;
bResult : BOOL;
unusedID : LUID;
hAuthzClientContext : AUTHZ_CLIENT_CONTEXT_HANDLE;
begin
Result := False;
asid := nil;
hAuthzClientContext := 0;
FillChar(unusedID, sizeof(unusedID), 0);
if not ConvertStringSidToSid(sid, asid) then
RaiseLastOsError();
// asid := ConvertNameToBinarySid('rabatscher');
if asid = nil then
RaiseLastOSError;
try
if asid <> nil then
begin
bResult := AuthzInitializeContextFromSid( 0, aSid, hManager, nil, unusedId, nil, #hAuthzClientContext );
try
if bResult then
Result := GetAccess(hAuthzClientContext, psd);
finally
if hAuthzClientContext <> 0 then
AuthzFreeContext(hAuthzClientContext);
end;
end;
finally
if asid <> nil then
LocalFree(LongWord(asid));
end;
end;
function UseAuthzSolution( psd : PSECURITY_DESCRIPTOR; const sid : string = 'S-1-1-0') : boolean;
var hManager : AUTHZ_RESOURCE_MANAGER_HANDLE;
bResult : BOOL;
pSid : PChar;
begin
bResult := AuthzInitializeResourceManager(AUTHZ_RM_FLAG_NO_AUDIT,
nil, nil, nil, nil, #hManager);
if bResult then
begin
pSid := PChar(sid);
bResult := GetEffectiveRightsForSID(hManager, psd, psid);
AuthzFreeResourceManager(hManager);
end;
Result := bResult;
end;
function GetSecurityInfo(handle: THandle; ObjectType: SE_OBJECT_TYPE;
SecurityInfo: SECURITY_INFORMATION; ppsidOwner, ppsidGroup: PPSID; ppDacl, ppSacl: PACL;
var pSecurityDescriptor: PSECURITY_DESCRIPTOR): DWORD; stdcall; external 'ADVAPI32.DLL' name 'GetSecurityInfo'; {use localfree to release ppSecurityDescriptor}
function CheckDirectoryAccess( path : string ) : boolean;
var dw : DWORD;
apacl : PACL;
psd : PSECURITY_DESCRIPTOR;
apSID : PSID;
h : THandle;
begin
try
apSID := nil;
//Open directory
h := CreateFile(PChar(path), GENERIC_READ, FILE_SHARE_READ, nil,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL or FILE_FLAG_BACKUP_SEMANTICS, 0);
//we need FILE_FLAG_BACKUP_SEMANTICS to open a directory
if h = INVALID_HANDLE_VALUE then
RaiseLastOsError();
try
//Query access control list for a directory -- the list you see in the properties box
dw := GetSecurityInfo(h, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION or OWNER_SECURITY_INFORMATION or GROUP_SECURITY_INFORMATION,
nil, nil, #apacl, nil, psd);
if dw <> ERROR_SUCCESS then
RaiseLastOSError;
try
Result := UseAuthzSolution(psd);
finally
if apSID <> nil then
LocalFree(NativeUint(apSID));
LocalFree(NativeUint(psd));
end;
finally
CloseHandle(h);
end;
except
on E : Exception do
begin
Result := False;
end;
end;
end;
Note that there are a few changes so the procedure works:
GetSecurityInfo (from the above procedure) needs the parameter DACL_SECURITY_INFORMATION or OWNER_SECURITY_INFORMATION or GROUP_SECURITY_INFORMATION (not only DACL_SECURITY_INFORMATION) otherwise you get an Error 87 in AuthzAccessCheck!
In addition you need to check out the JWA headers from the jedi library.
Hope that helps other people too.

Freeware ZIP component for Delphi 2010/Delphi XE?

Do you know any free component, compatible with Delphi 2010 or XE to manage ZIP archives (actually, only reading archive contents and extracting files required)?
Please no betas.
I thought about ZipForge from ComponentAce, but it's free only for personal use. No software distribution allowed.
You can get the TurboPower Abbrevia for 2010 from:
http://tpabbrevia.sourceforge.net/
you can take a look at this if you like 7zip
If you only need decoding (developed for Delphi 2007, not yet tested under Delphi 2010/XE):
unit UnitZip;
interface
uses
SysUtils, Classes;
type
EZipException = class( Exception );
TZipFileInfo = record
LastModified: TDateTime;
Crc32: Longword;
CompressedSize: Longword;
UncompressedSize: Longword;
end;
TZipFileReader = class
private
// Information about the memory mapped file
FFileHandle: THandle;
FFileMapping: THandle;
FMappedAddress: Pointer;
// Location of the ZIPfile in memory. Currently we only support memory mapped ZIPfiles without disk spanning.
FStart: Pointer;
FSize: Longword;
// ZIP file contents
FFilenames: TStrings;
function GetZipFileInfo ( const FileName: AnsiString ): TZipFileInfo;
public
constructor Create ( const FileName: string; ZipStartOffset: Int64 = 0; Size: Longword = 0 ); overload;
constructor Create ( const ResourceName, ResourceType: string; Instance: HMODULE = 0 ); overload;
constructor Create ( Buffer: Pointer; Size: Longword ); overload;
destructor Destroy; override;
function GetFile ( const FileName: string ): TBytes; overload;
function GetFile ( FileID: Integer ): TBytes; overload;
property FileNames: TStrings read FFileNames;
property FileInfo [ const FileName: AnsiString ]: TZipFileInfo read GetZipFileInfo;
end;
implementation
uses
ZLib, Windows;
const
cResourceNotFound = 'Resource not found: %s.%s.';
cResourceNotLoaded = 'Resource not loaded: %s.%s.';
cCannotOpenFile = 'Cannot open file %s: OS error: %d.';
cCannotGetFileSize = 'Cannot get file size of file %s: OS error: %d.';
cCannotMapFile = 'Cannot create file mapping of file %s: OS error: %d.';
cZipFileTooSmall = 'ZIP file is too small.';
cZipFileFormatError = 'ZIP file is invalid.';
cZipBufferInvalid = 'ZIP memory buffer is invalid.';
cUnsupportedMethod = 'ZIP unsupported compression method: %d.';
cFileNotFoundInZip = 'File not found in ZIP content: %s';
// ZIP file format records.
// The generic zip file format is ( TLocalFileHeader; Name; Extra; compressed data )* ( TFileHeader; Name; Extra; Remark )* TLastHeader
type
TFileInfo = packed record
NeededVersion: Word; // 20
Flags: Word; // 1=Text,4=extra present
ZipMethod: Word; // 0=stored 8=deflate
LastModified: Longword; // time in dos format or Unix Timestamp
Crc32: Longword;
CompressedSize: Longword;
UncompressedSize: Longword;
NameSize: Word;
ExtraSize: Word;
end;
TFileHeader = packed record
Signature: Longword; // $02014b50 PK#1#2
MadeBy: Word; // Version number, 20
FileInfo: TFileInfo;
CommentSize: Word; // 0
FirstDiskNumber: Word; // 0
IntFileAttr: Word; // 0 = binary; 1 = text
ExtFileAttr: Longword; // DOS file attributes (Archived=32)
LocalFileHeaderHeadOff: Longword; // #TLocalFileHeader
end;
PFileHeader = ^TFileHeader;
TLocalFileHeader = packed record
Signature: Longword; // $02014b50 PK#3#4
FileInfo: TFileInfo;
end;
PLocalFileHeader = ^TLocalFileHeader;
TLastHeader = packed record
Signature: Longword; // $02014b50 PK#5#6
ThisDiskNumber: Word;
CentralDirDisk: Word;
ThisDiskFileCount: Word;
TotalFileCount: Word;
FileHeaderSize: Longword;
FileHeaderOffset: Longword;
CommentSize: Word;
end;
PLastHeader = ^TLastHeader;
const
MagicLastHeader = $06054b50;
MagicLocalHeader = $04034b50;
MagicFileHeader = $02014b50;
type
IntPtr = Longword; // NativeInt on Delphi2007 is an Int64 ??
{$if CompilerVersion < 19}
procedure SetAnsiString( var S: AnsiString; P: PAnsiChar; L: Integer ); inline;
begin
SetString( S, P, L );
end;
{$ifend}
{ TZipFileReader }
constructor TZipFileReader.Create( const FileName: string; ZipStartOffset: Int64; Size: Longword );
begin
// Open the file in question.
FFileHandle := CreateFile( PChar( FileName ), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0 );
if FFileHandle = INVALID_HANDLE_VALUE then raise EZipException.CreateFmt( cCannotOpenFile, [ Filename, GetLastError() ] );
if Size = 0 then Size := GetFileSize( FFileHandle, nil );
if Size = INVALID_FILE_SIZE then raise EZipException.CreateFmt( cCannotGetFileSize, [ Filename, GetLastError() ] );
try
// Create a file mapping of the file in question
FFileMapping := CreateFileMapping( FFileHandle, nil, PAGE_READONLY, 0, 0, nil);
if FFileMapping = 0 then raise EZipException.CreateFmt( cCannotMapFile, [ Filename, GetLastError() ] );
try
// Get the file mapped in memory (NOTE: The offset needs to be on the memory allocation granularity of the system)
// Hence we assign it it's own pointer -> todo rounding etc.
FMappedAddress := MapViewOfFile( FFileMapping, FILE_MAP_READ, Int64Rec( ZipStartOffset ).Hi, Int64Rec( ZipStartOffset ).Lo, Size );
if not Assigned( FMappedAddress ) then EZipException.CreateFmt( cCannotMapFile, [ Filename, GetLastError() ] );
Create( FMappedAddress, Size );
except
CloseHandle( FFileMapping );
FFileMapping := 0;
raise;
end;
except
CloseHandle( FFileHandle );
FFileHandle := 0;
raise;
end;
end;
constructor TZipFileReader.Create( const ResourceName, ResourceType: string; Instance: HMODULE );
var
Resource: HRSRC;
Global: HGLOBAL;
begin
Resource := FindResource( Instance, PChar( ResourceName ), PChar( ResourceType ) );
if Resource = 0 then raise EZipException.CreateFmt( cResourceNotFound, [ ResourceName, ResourceType ] );
Global := LoadResource( Instance, Resource );
if Global = 0 then raise EZipException.CreateFmt( cResourceNotLoaded, [ ResourceName, ResourceType ] );
Create( LockResource( Global ), SizeofResource( HInstance, Resource ) );
// Note: kb57808: SizeofResource() returns the resource size rounded up to the alignment size.
end;
constructor TZipFileReader.Create( Buffer: Pointer; Size: Longword );
var
LastHeader: PLastHeader;
FileHeader: PFileHeader;
i, Off: Longword;
Name: AnsiString;
begin
// Note the location.
FStart := Buffer;
FSize := Size;
// Some sanity checks.
if FSize < sizeof( TLocalFileHeader ) + sizeof( TFileHeader ) + sizeof( TLastHeader ) then raise EZipException.Create( cZipFileTooSmall );
if IsBadReadPtr( Buffer, Size ) then raise EZipException.Create( cZipBufferInvalid );
if PLongword( Buffer )^ <> MagicLocalHeader then raise EZipException.Create( cZipFileFormatError );
// Find the last header. Due to the alignment of SizeofResource, we need o search for it.
LastHeader := Pointer( IntPtr( Buffer ) + Size - sizeof( TLastHeader ) );
for i := 0 to 31 do begin
if LastHeader^.Signature = MagicLastHeader then Break;
Dec( IntPtr( LastHeader ) );
end;
if LastHeader^.Signature <> MagicLastHeader then raise EZipException.Create( cZipFileFormatError );
FFilenames := TStringList.Create();
Off := LastHeader^.FileHeaderOffset;
for i := 0 to LastHeader^.TotalFileCount - 1 do begin
// Get header
if Off + sizeof( TFileHeader ) >= Size then raise EZipException.Create( cZipFileFormatError );
FileHeader := Pointer( IntPtr( Buffer ) + Off );
Inc( Off, sizeof( TFileHeader ) );
if FileHeader^.Signature <> MagicFileHeader then raise EZipException.Create( cZipFileFormatError );
// Get filename
if Off + FileHeader^.FileInfo.NameSize + FileHeader^.FileInfo.ExtraSize >= Size then raise EZipException.Create( cZipFileFormatError );
SetAnsiString( Name, Pointer( IntPtr( Buffer ) + Off ), FileHeader^.FileInfo.NameSize );
Inc( Off, FileHeader^.FileInfo.NameSize + FileHeader^.FileInfo.ExtraSize );
// Save filename and offset into ZIPfile where it can be found.
FFileNames.AddObject( Name, Pointer( FileHeader^.LocalFileHeaderHeadOff ) );
end;
// For quick access.
TStringList( FFilenames ).Sorted := True;
end;
destructor TZipFileReader.Destroy;
begin
if Assigned( FMappedAddress ) then UnmapViewOfFile( FMappedAddress );
if FFileMapping <> 0 then CloseHandle( FFileMapping );
if FFileHandle <> 0 then CloseHandle( FFileHandle );
inherited Destroy;
end;
function TZipFileReader.GetFile( const FileName: string ): TBytes;
var
ID: Integer;
begin
// Convert filename in FileID and access by ID.
ID := FFilenames.IndexOf( FileName );
if ID < 0 then raise EZipException.CreateFmt( cFileNotFoundInZip, [ FileName ] );
Result := GetFile( ID );
end;
function TZipFileReader.GetFile( FileID: Integer ): TBytes;
var
Off: Longword;
Local: PLocalFileHeader;
ZRec: TZStreamRec;
const
ZLibHeader: array [ 0..1 ] of Byte = ( $78, $01 ); // Deflate 32KB window size no preset dictionary.
begin
// Sanity check
if ( FileID < 0 ) or ( FileID >= FFilenames.Count ) then raise EZipException.CreateFmt( 'Invalid File ID: %d', [ FileID ] );
// Get the file header and perform sanity check
Off := Longword( FFilenames.Objects[ FileID ] );
if Off + sizeof( TLocalFileHeader ) >= FSize then raise EZipException.Create( cZipFileFormatError );
Local := Pointer( IntPtr( FStart ) + Off );
if Local^.Signature <> MagicLocalHeader then raise EZipException.Create( cZipFileFormatError );
Inc( Off, sizeof( TLocalFileHeader ) + Local^.FileInfo.NameSize + Local^.FileInfo.ExtraSize );
if Off + Local^.FileInfo.CompressedSize >= FSize then raise EZipException.Create( cZipFileFormatError );
// note: should we check the name again?
SetLength( Result, Local^.FileInfo.UncompressedSize );
if Length( Result ) > 0 then case Local^.FileInfo.ZipMethod of
0: begin // STORED
if Local^.FileInfo.CompressedSize <> Local^.FileInfo.UncompressedSize then raise EZipException.Create( cZipFileFormatError );
Move( Pointer( IntPtr( FStart ) + Off )^, Result[ 0 ], Local^.FileInfo.UncompressedSize );
end;
8: begin // DEFLATE
ZeroMemory( #ZRec, sizeof( ZRec ) );
ZRec.next_in := #ZLibHeader;
ZRec.avail_in := sizeof( ZLibHeader );
ZRec.total_in := sizeof( ZLibHeader ) + Local^.FileInfo.CompressedSize;
ZRec.next_out := #Result[ 0 ];
ZRec.avail_out := Local^.FileInfo.UncompressedSize;
ZRec.total_out := Local^.FileInfo.UncompressedSize;
ZRec.zalloc := zlibAllocMem;
ZRec.zfree := zlibFreeMem;
if inflateInit_( ZRec, zlib_Version, sizeof( ZRec ) ) <> 0 then raise EZipException.Create( cZipFileFormatError );
try
if not( inflate( ZRec, Z_FULL_FLUSH ) in [ Z_OK, Z_STREAM_END ] ) then raise EZipException.Create( cZipFileFormatError );
ZRec.next_in := Pointer( IntPtr( FStart ) + Off );
ZRec.avail_in := Local^.FileInfo.CompressedSize;
if not( inflate( ZRec, Z_FINISH ) in [ Z_OK, Z_STREAM_END ] ) then raise EZipException.Create( cZipFileFormatError );
finally
inflateEnd( ZRec );
end;
end;
else raise EZipException.CreateFmt( cUnsupportedMethod, [ Local^.FileInfo.ZipMethod ] );
end;
// todo: CRC32 sanity check if requested.
end;
function TZipFileReader.GetZipFileInfo( const FileName: AnsiString ): TZipFileInfo;
var
FileID: Integer;
Off: Longword;
Local: PLocalFileHeader;
begin
// Get the correct file ID
FileID := FFilenames.IndexOf( FileName );
if FileID < 0 then raise EZipException.CreateFmt( cFileNotFoundInZip, [ FileName ] );
// Get the file header and perform sanity check
Off := Longword( FFilenames.Objects[ FileID ] );
if Off + sizeof( TLocalFileHeader ) >= FSize then raise EZipException.Create( cZipFileFormatError );
Local := Pointer( IntPtr( FStart ) + Off );
if Local^.Signature <> MagicLocalHeader then raise EZipException.Create( cZipFileFormatError );
Inc( Off, sizeof( TLocalFileHeader ) + Local^.FileInfo.NameSize + Local^.FileInfo.ExtraSize );
if Off + Local^.FileInfo.CompressedSize >= FSize then raise EZipException.Create( cZipFileFormatError );
// Return requested data.
Result.LastModified := Local^.FileInfo.LastModified;
Result.Crc32 := Local^.FileInfo.Crc32;
Result.CompressedSize := Local^.FileInfo.CompressedSize;
Result.UncompressedSize := Local^.FileInfo.UncompressedSize;
end;
end.
Take a look at this OpenSource SynZip unit. It's even faster for decompression than the default unit shipped with Delphi, and it will generate a smaller exe (crc tables are created at startup).
No external dll is needed.
I just made some changes to handle Unicode file names inside Zip content, not only Win-Ansi charset but any Unicode chars. Feedback is welcome.
I like the WinZip compatible TZipMaster for Delphi, available here: http://www.delphizip.org/
TZipMaster is a non-visual VCL wrapper
created by ChrisVleghert and
EricW.Engler for their freeware Zip
and Unzip DLLs.
Those DLLs are based on the InfoZip
Official Freeware Zip/Unzip source
code, but are NOT equivalent to
InfoZip's DLLs. The InfoZip source
code has been modified to enhance
their ease-of-use, power, and
flexibility for use with Delphi and
C++ Builder.
Also, this question has been covered before on Stack Overflow, which may yield some other solutions for you.
If distributing an ActiveX DLL with your project is not a problem for you, then Chilkat Zip (http://www.chilkatsoft.com/zip-activex.asp) seems to do the trick. Delphi examples are here: http://www.example-code.com/delphi/zip.asp
DotNetZip is a managed code (.NET) library, that exposes a COM interface.
Free.
Open source
MS-PL licensed.

Resources