When I compile a Inno Setup project, the [Code] section is also compiled (as Pascal executable or Pascal DLL)?
In other words, if someone unpacks a Inno Setup project, can he see the [Code] section as original source code (damn! :) ) or as compiled executable/DLL (difficult to disassemble)?
I would like to insert in the [Code] section some strings (password and keys) and I don't know if they would be easily recoverable also with little knowledge of reverse engineering.
The code is compiled into some kind of a binary representation (very roughly like .NET CIL or Java bytecode).
There's Inno Setup Unpacker (and others), which can extract the files from .exe generated by Inno Setup. It can extract the binary representation of the code to CompiledCode.bin (if you use -x -m flags).
Then you can use Inno Setup Decompiler project that is able to decompile/disassemble the CompiledCode.bin file into (pseudo) Pascal Script code. But as with a reverse engineering of .NET or Java, it won't give you exact code. The decompiled code possibly won't even compile (at least it was so the last time I tried), but it is good enough to see, what the code does. They seem to have a paid version now, which may be better than the free one I've tried some time ago. (The latest version of Inno Setup Decompiler can even directly extract code from .exe, but it was not updated to the latest version of Inno Setup [5.6.1] yet, so it did not work for me.)
(Inno Setup Decompiler site is gone, but it does not change anything about the fact that it is technically possible to decompile/disassemble the compiled code)
It is rather easy to see literal strings compiled in the code, even in the CompiledCode.bin.
For example these credentials:
Username := 'secretusername';
Password := 'mysupersecretpassword';
can be seen like this in the CompiledCode.bin file:
Of course, you can obfuscate the strings somehow (at least hex-encode them). But as you are hopefully aware, no matter what you do, once the (even compiled) code is on user's machine, there's really no way you can protect it absolutely.
A simple support code to store the string literal hex-encoded:
function CryptStringToBinary(
sz: string; cch: LongWord; flags: LongWord; binary: AnsiString;
var size: LongWord; skip: LongWord; flagsused: LongWord): Integer;
external 'CryptStringToBinaryW#crypt32.dll stdcall';
const
CRYPT_STRING_HEX = $04;
function UnobfuscateString(S: string): string;
var
Size: LongWord;
Buffer: AnsiString;
Len: Integer;
begin
SetLength(Buffer, (Length(S) div 2) + 1);
Len := Length(S);
Size := Len div 2;
if (CryptStringToBinary(S, Len, CRYPT_STRING_HEX, Buffer, Size, 0, 0) = 0) or
(Size <> Length(S) div 2) then
begin
RaiseException('Error unobfuscating string');
end;
Result := Buffer;
end;
#define ObfuscateString(str S) \
Local[0] = AddBackslash(GetEnv("TEMP")) + "ObfuscatedString.pas", \
Local[1] = \
"-ExecutionPolicy Bypass -Command """ + \
"$bytes = [Text.Encoding]::ASCII.GetBytes('" + S + "'); " + \
"$s = '''' + (($bytes | foreach { $_.ToString('X2') }) -join '') + ''''; " + \
"Set-Content -Path '" + Local[0] + "' -Value $s;" + \
"""", \
Exec("powershell.exe", Local[1], SourcePath, , SW_HIDE), \
Local[2] = FileOpen(Local[0]), \
Local[3] = FileRead(Local[2]), \
FileClose(Local[2]), \
DeleteFileNow(Local[0]), \
"UnobfuscateString(" + Local[3] + ")"
(The code was tested on Unicode version of Inno Setup. Though, it can work with ASCII passwords only.)
With the help of the code above, you can write this (so you have the credentials easily editable in the source code):
Username := {#ObfuscateString("secretusername")};
Password := {#ObfuscateString("mysupersecretpassword")};
But the code will be compiled as:
Username := UnobfuscateString('736563726574757365726E616D65');
Password := UnobfuscateString('6D79737570657273656372657470617373776F7264');
You can verify that by adding this to the end of your .iss script and checking the generated Preprocessed.iss file.
#expr SaveToFile(AddBackslash(SourcePath) + "Preprocessed.iss")
So despite the credentials being readable in the source code, they won't be stored literally to the compiled code:
But again, this is only an obfuscation. Anyone with decent programming skills will be able to unobfuscate (decrypt) the credentials.
Related
I am trying to get Inno Setup define value in Code section but not with {#VersionTool1}. I need to pass defined name dynamically, because there are a lot of them (I want to avoid large switch case). I tried SetupSetting but it's not in Setup section (it's before it). Is there any way to do this?
#define VersionTool1 2019.01.1111
#define VersionTool2 2020.02.2111
...
[Code]
procedure SetSelectedTool(ToolName: String);
var
CurrentTool: string;
begin
...
CurrentTool := 'Version' + ToolName;
CurrentToolVersion := {#CurrentTool};
...
end;
Value of local variable CurrentTool wil for example be 'VersionTool1' and I want to get value of VersionTool1 preprocessor variable which is 2020.02.2111.
It's not possible, see Evaluate preprocessor macro on run time in Inno Setup Pascal Script.
But there are other solutions.
For example:
[Code]
var
ToolNames: TStringList;
ToolVersions: TStringList;
function InitializeSetup(): Boolean;
begin
ToolNames := TStringList.Create;
ToolVersions := TStringList.Create;
#define AddToolVersion(Name, Version) \
"ToolNames.Add('" + Name + "'); ToolVersions.Add('" + Version +"');"
#emit AddToolVersion('Tool1', '2019.01.1111')
#emit AddToolVersion('Tool2', '2020.02.2111')
{ ... }
Result := True;
end;
(of course, the above makes sense only if you actually do not hardcode the version numbers, but use a code that only a preprocessor can do – something like GetStringFileInfo, what I've understood from your comments that you plan to)
And then you can have a function like:
function GetToolVersion(ToolName: string): string;
var
I: Integer;
begin
I := ToolNames.IndexOf(ToolName);
if I >= 0 then Result := ToolVersions[I];
end;
Another similar questions:
Array Variables and dynamic access in [Code] section
Scripting capabilities in the Registry section
I feel like this has got to be something simple I'm missing - in Inno Setup, if I have passed in a directive variable, how do I use it inside the [Code] section?
Say I pass in /DMYVAR=1 to the Inno Setup engine.
In my .iss file I can have something like:
[Setup]
AppName=MyApp v{#MYVAR}
Down in my [Code] section I'd like to be able to use it like this:
function IsVersionOne(param: String): boolean;
begin
Result := {#MYVAR} == "1";
end;
This doesn't work :(
Preprocessor does not care, where its directives are expanded.
So {#name} syntax (inline preprocessor directive call) works everywhere, including Pascal Script.
Though, if you want to compare a define value as a string, you have to, of course, enclose it to quotes to make it a string. Also note that your == "1" syntax is wrong, Pascal uses single = and single quotes.
function IsVersionOne(param: String): boolean;
begin
Result := '{#MYVAR}' = '1';
end;
#expr SaveToFile(AddBackslash(SourcePath) + "Preprocessed.iss")
Run the compiler with /DMYVAR=1, and check the generated Preprocessed.iss. It will show:
function IsVersionOne(param: String): boolean;
begin
Result := '1' = '1';
end;
As the value is a number, you can, of course, use numerical comparison too:
function IsVersionOne(param: String): boolean;
begin
Result := {#MYVAR} = 1;
end;
For a related question with more detailed information, see Evaluate preprocessor macro on run time in Inno Setup Pascal Script.
While the above answered your literal question, your function signature actually suggests that you want to implement a Check function to test a value of a compile-time directive, like:
[Files]
Source: "MYPROG.EXE"; DestDir: "{app}"; Check: IsVersionOne
That's an inefficient overkill.
Use a preprocessor #if directive instead:
[Files]
#if MYVAR == "1"
Source: "MYPROG.EXE"; DestDir: "{app}"
#endif
#expr SaveToFile(AddBackslash(SourcePath) + "Preprocessed.iss")
If you run the compiler with /DMYVAR=1, Preprocessed.iss will show:
[Files]
Source: "MYPROG.EXE"; DestDir: "{app}"
If you run the compiler with a different value of DMYVAR, Preprocessed.iss will show:
[Files]
If you need the script to compile even without /DMYVAR= switch, define a default value at the top of the script, like:
#ifndef MYVAR
#define MYVAR "0"
#endif
Using the following code during uninstall
BitmapImage := TBitmapImage.Create(InstallTopPanel);
BitmapImage.AutoSize := True;
BitmapImage.Bitmap.LoadFromFile(
ExpandConstant( '{tmp}\WizardSmallImageFile.bmp') );
BitmapImage.Parent := InstallTopPanel;
BitmapImage.Top := (InstallTopPanel.ClientHeight - 58) / 2;
BitmapImage.Left := InstallTopPanel.ClientWidth - 55 - 10;
I get an error:
Exception : can not open file.
C:\users\xyz\AppData\Local\Temp\is-U3Q8P.tmp\WizardSmallImageFile.Bmp.
File not found.
I tried also to use ExtractTemporaryFile before I call LoadFromFile which is not supported during uninstall.
ExtractTemporaryFile('WizardSmallImageFile.bmp');
So, the question, how to view an image or specifically WizardSmallImageFile during uninstall?
My code above builds a custom form with a custom panel. Like here: Inno Setup Uninstall some components only.
Correct, the ExtractTemporaryFile extracts files from the installer. Therefore it cannot work in the uninstaller as the installer is not available anymore.
Also note that you cannot extract the file referenced to by the WizardSmallImageFile directive from the installer anyway. You have to add your own copy.
If you need to use some file during uninstallation, you have to install it in the installer and then use the installed copy in the uninstaller.
[Files]
Source: "WizardSmallImageFile.bmp"; DestDir: "{app}";
[Code]
function InitializeUninstall(): Boolean;
begin
...
BitmapImage := TBitmapImage.Create(...);
...
BitmapImage.Bitmap.LoadFromFile(
ExpandConstant('{app}\WizardSmallImageFile.bmp'));
...
end;
If you want to do without installing the file, you can embed the image data into the code.
Unfortunately the Unicode Inno Setup is quite limited when dealing with binary data as it tends to try to convert everything to UTF-8. But after numerous tries I've ended up with some working code.
Note that the code uses a PowerShell code invoked from Inno Setup preprocessor – The PowerShell is needed on compile-time only, not on run/install-time.
Add this code somewhere to the front of your [Code] section:
function CryptStringToBinary(
sz: string; cch: LongWord; flags: LongWord; binary: string; var size: LongWord;
skip: LongWord; flagsused: LongWord): Integer;
external 'CryptStringToBinaryW#crypt32.dll stdcall';
const
CRYPT_STRING_HEX = $04;
procedure WriteBinaryStringToStream(S: string; Stream: TStream);
var
Buffer: string;
Size: LongWord;
Len: Integer;
begin
Len := Length(S);
SetLength(Buffer, (Len div 4) + 1);
Size := Len div 2;
if (CryptStringToBinary(S, Len, CRYPT_STRING_HEX, Buffer, Size, 0, 0) = 0) or
(Size <> Len div 2) then
begin
RaiseException('Error decoding binary string');
end;
Stream.WriteBuffer(Buffer, Size);
end;
function StreamFromBinaryString(S: string): TStream;
begin
Result := TStringStream.Create('');
WriteBinaryStringToStream(S, Result);
Result.Position := 0;
end;
procedure LoadBitmapFromBinaryString(Bitmap: TBitmap; S: string);
var
Stream: TStream;
begin
Stream := StreamFromBinaryString(S);
try
Bitmap.LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
procedure SaveBinaryStringToFile(FileName: string; S: string);
var
Stream: TStream;
begin
Stream := TFileStream.Create(FileName, fmCreate);
try
WriteBinaryStringToStream(S, Stream);
finally
Stream.Free;
end;
end;
#define FileToBinaryString(str FileName) \
Local[4] = ExtractFileName(FileName), \
Local[0] = AddBackslash(GetEnv("TEMP")) + Local[4] + ".pas", \
Local[1] = \
"-ExecutionPolicy Bypass -Command """ + \
"Write-Host 'Generating code for " + Local[4] + "'; " + \
"$bytes = [System.IO.File]::ReadAllBytes('" + FileName + "'); " + \
"$s = '''' + (($bytes | foreach { $_.ToString('X2') }) -join '') + ''''; " + \
"Set-Content -Path '" + Local[0] + "' -Value $s;" + \
"""", \
Exec("powershell.exe", Local[1], SourcePath, , SW_HIDE), \
Local[2] = FileOpen(Local[0]), \
Local[3] = FileRead(Local[2]), \
FileClose(Local[2]), \
DeleteFileNow(Local[0]), \
Local[3]
And then you can use the FileToBinaryString preprocessor macro to convert a file on compile-time (or more precisely, when pre-processing) to a hex string like:
'4D5A50000200000004000F00FFFF0000B800000....'
On runtime, you use the hex string with some of the functions WriteBinaryStringToStream, StreamFromBinaryString, LoadBitmapFromBinaryString or SaveBinaryStringToFile.
In your case you will use:
LoadBitmapFromBinaryString(
BitmapImage.Bitmap, {#FileToBinaryString("C:\path\WizModernSmallImage.bmp")});
On compile-time, this gets converted to a code like:
LoadBitmapFromBinaryString(
BitmapImage.Bitmap, '4D5A50000200000004000F00FFFF0000B800000....');
The pre-processor/Pascal compiler has a limit of about 100M characters for a string. Though you will actually first hit a [compile-time] memory limit of the PowerShell script for files larger than about 20-30 MB. Though even for smaller sizes (larger than few MBs), the compile-time time performance of the PowerShell script is bad. The script can be optimized significantly though.
Due to the hex encoding, the size of the installer increases twice as much. This could be improved by using some more efficient encoding, like Base64 (CRYPT_STRING_BASE64). The code section is not even compressed too, comparing to files included with the [Files] section (not a problem for images as these are compressed already, but makes a difference with DLLs for example).
The code requires the Unicode version of Inno Setup. You should not use the Ansi version anyway, in the 21st century. Though ironically, implementing this in the Ansi version would be way easier. See my answer to Writing binary file in Inno Setup for a use of the CryptStringToBinary that's compatible with both Ansi and Unicode version of Inno Setup. Though in the Ansi version you can actually do with a binary string, instead of a hex string.
Has a any way to make the uninstall files (JPEG, DLL, PNG...) stay inside of the unins000.exe? If so, please show a code.
There is not any way to do this. The Inno uninstall engine cannot contain embedded files.
But on the other hand there's no particular need for it to do so either -- you can just install any files that it requires (into a subfolder of the app folder, if you want to keep things tidy).
The only real reason that the setup files are embedded into the installer is that it makes downloads easier. The uninstaller doesn't have that excuse.
Inno Setup has no native support for embedding files into the uninstaller.
But with some creativity, you can embed the files into the code (in a form of some constant).
Unfortunately the Unicode Inno Setup is quite limited when dealing with binary data as it tends to try to convert everything to UTF-8. But after numerous tries I've ended up with some working code (with use of a PowerShell code invoked from Inno Setup preprocessor).
Add this code somewhere to the front of your [Code] section:
function CryptStringToBinary(
sz: string; cch: LongWord; flags: LongWord; binary: string; var size: LongWord;
skip: LongWord; flagsused: LongWord): Integer;
external 'CryptStringToBinaryW#crypt32.dll stdcall';
const
CRYPT_STRING_HEX = $04;
procedure WriteBinaryStringToStream(S: string; Stream: TStream);
var
Buffer: string;
Size: LongWord;
L: Integer;
begin
L := Length(S);
SetLength(Buffer, (L div 4) + 1);
Size := L div 2;
if (CryptStringToBinary(S, L, CRYPT_STRING_HEX, Buffer, Size, 0, 0) = 0) or
(Size <> L div 2) then
begin
RaiseException('Error decoding binary string');
end;
Stream.WriteBuffer(Buffer, Size);
end;
function StreamFromBinaryString(S: string): TStream;
begin
Result := TStringStream.Create('');
WriteBinaryStringToStream(S, Result);
Result.Position := 0;
end;
procedure LoadBitmapFromBinaryString(Bitmap: TBitmap; S: string);
var
Stream: TStream;
begin
Stream := StreamFromBinaryString(S);
try
Bitmap.LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
procedure SaveBinaryStringToFile(FileName: string; S: string);
var
Stream: TStream;
begin
Stream := TFileStream.Create(FileName, fmCreate);
try
WriteBinaryStringToStream(S, Stream);
finally
Stream.Free;
end;
end;
#define FileToBinaryString(str FileName) \
Local[4] = ExtractFileName(FileName), \
Local[0] = AddBackslash(GetEnv("TEMP")) + Local[4] + ".pas", \
Local[1] = \
"-ExecutionPolicy Bypass -Command """ + \
"Write-Host 'Generating code for " + Local[4] + "'; " + \
"$bytes = [System.IO.File]::ReadAllBytes('" + FileName + "'); " + \
"$s = '''' + (($bytes | foreach { $_.ToString('X2') }) -join '') + ''''; " + \
"Set-Content -Path '" + Local[0] + "' -Value $s;" + \
"""", \
Exec("powershell.exe", Local[1], SourcePath, , SW_HIDE), \
Local[2] = FileOpen(Local[0]), \
Local[3] = FileRead(Local[2]), \
FileClose(Local[2]), \
DeleteFileNow(Local[0]), \
Local[3]
And then you can use the FileToBinaryString preprocessor macro to convert a file on compile-time (or more precisely, when pre-processing) to a hex string like:
'4D5A50000200000004000F00FFFF0000B8000....'
On runtime, you use the hex string with some of the functions WriteBinaryStringToStream, StreamFromBinaryString, LoadBitmapFromBinaryString or SaveBinaryStringToFile.
So for example to load a bitmap to TBitmapImage, use:
LoadBitmapFromBinaryString(
BitmapImage.Bitmap, {#FileToBinaryString("C:\path\WizModernSmallImage.bmp")});
On compile-time, this gets converted to a code like:
LoadBitmapFromBinaryString(
BitmapImage.Bitmap, '4D5A50000200000004000F00FFFF0000B8000....');
To extract a DLL, use:
SaveBinaryStringToFile(
ExpandConstant('{tmp}\InnoCallback.dll'),
{#FileToBinaryString("InnoCallback.dll")});
To use a DLL extracted on runtime, you have to use the delayload flag:
function WrapTimerProc(callback:TTimerProc; paramcount:integer):longword;
external 'wrapcallback#{tmp}\innocallback.dll stdcall delayload';
The pre-processor/Pascal compiler has a limit of about 100M characters for a string. Though you will actually first hit a [compile-time] memory limit of the PowerShell script for files larger than about 20-30 MB. Though even for smaller sizes (larger than few MBs), the compile-time time performance of the PowerShell script is bad. The script can be optimized significantly though.
Due to the hex encoding, the size of the installer increases twice as much. This could be improved by using some more efficient encoding, like Base64 (CRYPT_STRING_BASE64). The code section is not even compressed too, comparing to files included with the [Files] section (not a problem for images as these are compressed already, but makes a difference with DLLs for example).
This solution was initially developed for the question Inno Setup: Reading a file from installer during uninstallation.
The code requires the Unicode version of Inno Setup. With latest Inno Setup 6, it's the only version available anyway. Even if you use Inno Setup 5, you should not use the Ansi version anyway, in the 21st century. Though ironically, implementing this in the Ansi version would be way easier. See my answer to Writing binary file in Inno Setup for a use of the CryptStringToBinary that's compatible with both Ansi and Unicode version of Inno Setup. Though in the Ansi version you can actually do with a binary string, instead of a hex string.
I have GetVersion function in [Code] that returns a string like this "1004", "1003", etc.
I created this function to check the registry value for lowest version numbers and uninstall them.
Here is a snippet of the code it is giving error point to StrtoInt conversion line stating
Comma (,) expected
Here is the snippet:
function DoesOldVersionsExist(): Boolean;
var
AppVersion: integer;
mstr: string;
VersionInstalled: cardinal;
begin
AppVersion := StrToInt(GetVersion({#MyAppVersion}), 0);
...
after that line I'm simple comparing the values and return true or false. Much Appreciated.
This is what error message says:
Line 55
Column 40.
Comma (,) expected
Thanks Deanna but unfortunately that is the error message pointing to this :
AppVersion := StrToInt(GetVersion({#MyAppVersion}), 0);
^
Here is the GetVersion function:
function GetVersion(AppVersion: String): String;
var
Version: String;
CharIndex: integer;
c: char;
begin
for CharIndex := 1 to Length(AppVersion) do begin
c := AppVersion[CharIndex];
if (c <> '.') then
Version := Version + c;
end;
Result := Version;
end;
I think that you can't just have Inno Setup constants in code like this, you have to use ExpandConstant():
AppVersion := StrToInt(GetVersion(ExpandConstant('{#MyAppVersion}')), 0);
You have not given us enough information to give a definite answer, but I think that the situation is as follows.
You have defined some constant called MyAppVersion which you let the ISPP (the Inno Setup pre-processor) substitute. Now, you have not told us what type this variable is, and you have not told us what the signature of GetVersion is (in particular, what type of argument does it expect?). However, if these types are strings, you need to write
StrToInt(GetVersion('{#MyAppVersion}'), 0);
in order to obtain, say,
StrToInt(GetVersion('Some string, this is!'), 0);
instead of
StrToInt(GetVersion(Some string, this is!), 0);
which is malformed (indeed, to such an extent that it hurts my eyes to look at it).