Beny
Beny

Reputation: 900

How to crypt or hide a string in Delphi EXE?

I am currently developing an application in Delphi, in which I have to hide (obfuscate) a string in source code like str := 'Example String'.
Why ? Because if I open the EXE in text editor and search for Example String I'll find the string in second...

I tried to use a basic HEX transcription like #$65#$78#$61#$6d#$70#$6c#$65 but it's re-transcribed in clear at compile time.
I looked for packers, but it's not the best solution (PECompact can be detected as a false positive malware, UPX is too easy to de-UPX, ...). I would prefer an idea in my internal code...

Someone would put me on the right way.

Upvotes: 14

Views: 8422

Answers (2)

Warren  P
Warren P

Reputation: 68902

You could use a real encryption library like in this question. A small external utility could take your original string and convert it to a static array of bytes, which would be compiled into your app, and be decrypted and restored in memory as a string. This would have the additional benefit that it would not still look like ascii (range 32..127) and would not be quite so obvious to casual inspectors using a hex editor.

Also, real encryption (even 3DES or Blowfish) would not be so easily removed without even needing to look at your source code or reverse engineer your binaries, as ROT13 or a single level of caesar cypher would be, but then, if your key (used to decrypt/encrypt) is itself not protected, it's not so secure as you might have hoped, either. It would be pretty easy for me to attach a debugger to your code, even the release version, and recover and log all strings in the string heap, at runtime, without even bothering to crack your encryption, even when you use a real library as above. I agree with Andreas that Vigenere seems a reasonable amount of protection and effort for your purposes, and I think ROT13 and a single-level caesar cipher are kind of laughable on their own. Update: The vignere as posted by Andreas is brilliant, though, and I prefer it to a big fat external library, in your case.

For your particular case, which is storing something in a text file, I would have written a small utility that could encrypt and encode your secrets into your source code, and stored the key at least somewhere else. That's like keeping your ammunition in a lockbox, and keeping the key for it somewhere else. Even that is not perfect, but its probably better than ROT13, which is kind of the most "toy" of all the toy encryption styles.

Upvotes: 0

Andreas Rejbrand
Andreas Rejbrand

Reputation: 108963

A very simple method is to store the strings obfuscated by the ROT13 method.

procedure ROT13(var Str: string);
const
  OrdBigA = Ord('A');
  OrdBigZ = Ord('Z');
  OrdSmlA = Ord('a');
  OrdSmlZ = Ord('z');
var
  i, o: integer;
begin
  for i := 1 to length(Str) do
  begin
    o := Ord(Str[i]);
    if InRange(o, OrdBigA, OrdBigZ) then
      Str[i] := Chr(OrdBigA + (o - OrdBigA + 13) mod 26)
    else if InRange(o, OrdSmlA, OrdSmlZ) then
      Str[i] := Chr(OrdSmlA + (o - OrdSmlA + 13) mod 26);
  end;
end;

function ROT13fun(const Str: string): string;
begin
  result := Str;
  ROT13(result);
end;

const
  ObfuscatedString = 'Guvf vf n frperg zrffntr.';

procedure TForm4.FormCreate(Sender: TObject);
begin
  ShowMessage(ROT13fun(ObfuscatedString));
end;

Slightly more sophisticated would be to employ the Caesar chipher or the Vigenère chipher.

To obtain the obfuscated strings to use in the source code, you can use a decent text editor like my own Rejbrand Text Editor or Wolfram|Alpha.

Update

ROT13 is very easy to decipher, but it might be more than enough for your situation, depending on how it looks! At least it will become very hard to identify strings in the binary. It will take some real effort to obtain the strings. (After all, the every-day user don't even look at binaries in a hex editor/text editor!) The Caesar cipher is a very simple generalisation of the ROT13 cipher, and is also easily deciphered. Indeed, there are only 25 different 'passwords'. The Vigenère cipher is far trickier, and takes some really serious effort to crack (especially since you don't know precisely where in the binary the strings are).

As an example, below I give a string obfuscated using the Vigenère cihper:

Xlc tsrgcdk sj ‘vrivem’ mw cei sd kli acirivqhfriw cw qsbsir tfmjmgw, rrh biimrk hyi pygk gilhlvc mf ws, wk leq rpws pvgsqc fj agrvwtvcou mrrsiiwx we izcfp-hew cmji, rpxlmixl ml r piqg xigfbzgep zrrkyyuv. Mlrvih, hyi qmrvvr qctmixw vbtpmwkw ilsikc qclvgiq ks wsqy er soxirr klex hyi ilhzvi cbmmvslavrx mt xli Srvxl wj irboekivcr. Mr hymw qstxmsl, ai uwcp mljvwxmeoki xfs tlcqwtep zojmw mt xli seivkw tsrgcdk.

It would certainly be possible to extend the cipher to also take care of digits and special characters, including spaces. It could also be made to mix capitals and small letters. Then it would be terribly hard (although possible) to decipher. It is probably far easier to decipher if the password is a known word, which can be found in the dictionary. If it is not a word, it will be safer.

The text above is obfuscated using a word that you can find in a large-enough dictionary. The text below is obfuscated using a nonsense string as password:

Miwzvjfy m vjsy-tombox zguol ap ahqovz d uwk sbze w conz pe biusvth pagh h njsx. Io puvyeq, fl cjsx xic vmovdq zappzjvz, vnjnatl frcb vy dtmd vhxkt fto babtf davf. Uuxlhqb, khk aa dbn eumsuzq, auk saed vlpnbuuo ywlemz ue pnyl ttmxv. Pa ghof, fl cjsx kmbbzk atmd wv sfjtmxcl rtfysk cb yuta md jsy. Sqf nql njsx ly vs ilusrn o gok uxwupagupaz u.

And, finally, the text below is obfuscated the same way, but - in addition - all spaces and special characters have been removed from the string:

cishkclruervutzgnyarkgzjsaqgsrzvmmrzweolpcnvbkxrvdnqrlurhpmhfaxsuoqncxgzqegnqmngaryfbgozpcgrkgzrrybybmouyzbbkoucbnrnsxkmcbywpllxhkoobmzoydrfvrkhpvsavmzocwjflouboymlotjcnqrirgucdrftllladcwtmnkqehjpmnafoobyvkvdaancbzeokdnsotkkawvanjkryculluyaoklpnojfnqrlatypznpalzocjunuxzdbnzntpqulplekxhrshpttjqyculkkjyxhxgxdozruwlbtkyrsuumkgslbyunabbkryfupvnafobhuoyyvqjlzgzpomc

I challenge you to decipher these three texts. If anyone would succeed in deciphering the last one, I promise to give this person 100 SEK (100 Swedish kronor)!

But, still, Warren P is right: If you really require high security, that even the experts will not be able to decipher, then you should go for some real encryption.

Update

As requested by Warren P, I use the following code to encrypt/decrypt Vigenère:

const
  OrdBigA = Ord('A');
  OrdBigZ = Ord('Z');
  OrdSmlA = Ord('a');
  OrdSmlZ = Ord('z');

function imod(const x: integer; const y: integer): integer;
begin
  if x >= 0 then
    imod := x - floor(x/y) * y
  else
    imod := x + ceil(-x/y) * y;
end;

procedure Vigenère(var Str: string; const Key: string);
var
  n, i, o: integer;
  KeyChrs: TBytes;
begin

  n := length(Key);
  SetLength(KeyChrs, n);
  for i := 1 to n do
    if InRange(ord(Key[i]), OrdBigA, OrdBigZ) then
      KeyChrs[i - 1] := Ord(Key[i]) - OrdBigA
    else
      raise Exception.Create('Invalid character in Vigenère key.');

  for i := 1 to length(Str) do
  begin
    o := Ord(Str[i]);
    if InRange(o, OrdBigA, OrdBigZ) then
      Str[i] := Chr(OrdBigA + imod((o - OrdBigA + KeyChrs[(i-1) mod n]), 26))
    else if InRange(o, OrdSmlA, OrdSmlZ) then
      Str[i] := Chr(OrdSmlA + imod((o - OrdSmlA + KeyChrs[(i-1) mod n]), 26));
  end;

end;

function Vigenèref(const Str: string; const Key: string): string;
begin
  result := Str;
  Vigenère(result, Key);
end;

procedure VigenèreD(var Str: string; const Key: string);
var
  n, i, o: integer;
  KeyChrs: TBytes;
begin

  n := length(Key);
  SetLength(KeyChrs, n);
  for i := 1 to n do
    if InRange(ord(Key[i]), OrdBigA, OrdBigZ) then
      KeyChrs[i - 1] := Ord(Key[i]) - OrdBigA
    else
      raise Exception.Create('Invalid character in Vigenère key.');

  for i := 1 to length(Str) do
  begin
    o := Ord(Str[i]);
    if InRange(o, OrdBigA, OrdBigZ) then
      Str[i] := Chr(OrdBigA + imod((o - OrdBigA - KeyChrs[(i-1) mod n]), 26))
    else if InRange(o, OrdSmlA, OrdSmlZ) then
      Str[i] := Chr(OrdSmlA + imod((o - OrdSmlA - KeyChrs[(i-1) mod n]), 26));
  end;

end;

function VigenèreDf(const Str: string; const Key: string): string;
begin
  result := Str;
  VigenèreD(result, Key);
end;

Upvotes: 23

Related Questions