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
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;
begin
SetLength(Buffer, (Length(S) div 2) + 1);
Size := Length(S) div 2;
if (CryptStringToBinary(S, Length(S), 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.