Oxfuscator

You are right. The decompiler itself tries to make some readable names for the local variables, based on the variable type.
So local vars are OK too.

Then the only thing missing is the string encryption - nu push, but it would make me very happy.

Thanks for the fast implementation!

And one more thing: can you generate a mappings file, so I can map the original names to the obfuscated names? This is needed to investigate a crash of an obfuscated assembly.

b.e.:
class a = orgClassName
method a.a = OrgClassName.OrgMethodName
property a.a = OrgClassName.OrgPropertyName

Do you have any ideas for how to best do string encryption?

Thanks, logged as bugs://80509 (For map files)

1 Like

Store a key somewhere in the assembly info.
Encrypt all strings to bytearrays (AES with that key).
Generate a method somewhere in the assembly that uses AES decryption to convert the bytearrays to the original strings.

A string then becomes a call: q() that returns a string.

Absolutely not fail-safe against professionals, but good enough against hobbyist, as the strings are not visible anymore in the decompiled code.

True yeah. I was hoping there was a better way, this of course has quite a bit of overhead.

You can make it a assembly property:

[Assembly:EncryptStrings]

So if the overhead is a problem, you can choose to use it or not.

Or introduce the @:

var a := “unencrypted string”;
var b := @“encrypted string”

This give the possibility to encrypt only sensitive strings.

Thanks, logged as bugs://80510 (string obfuscation)

1 Like

I would suggest both of the above:
[Assembly:EncryptStrings] to encrypt all strings (marked for encryption or not)
[EncryptStrings] to do this on class or method level
@ to encrypt one specific string

and one more, only method level: [DoNotEncrypt]
for methods that have heavy fixed string manipulation.

EncryptStrings would probably best be a property of the Obfuscate attribute.

I’m not sure I like @. For one, it already has a meaning in Pascal (address of), for another, we cant use it in C# because there @ already is a valid string prefix to disable escape chars (plus, it would be confusing for the same prefix to have different meanings in Pascal vs C#, for those of us who use both ;). But if we can diff a different prefix char, I’m all for the general syntax.

You are correct.
@ was just an idea - any prefix (or whatever to encrypt one string) will do.
We could also use the ` character (instead of ") for an encrypted string.

Or even an attribute ON the string const

writeLn([Obfuscate]"secret string");

Very nice!

1 Like

Carlo, I was thinking that encryption is indeed too much overhead.
But the goal is to make it unreadable - we could use a byte array for this.

  1. Generate a method that converts a bytearray to a string using UTF32 encoding
  2. Convert all strings to a byte array using UTF32 encoding

So in the code a string will get the form of:
var myString := obfuscatedfunction([77, 0, 0, 0, 121, 0, 0, 0, 83, 0, 0, 0, 116, 0, 0, 0, 114, 0, 0, 0, 105, 0, 0, 0, 110, 0, 0, 0, 103, 0, 0, 0]);
instead of
var myString := “MyString”;

This will make it 100% unreadable.

Edit: Timing on this call: only 2 ms (on an 8 year old 2 Ghz AMD Opteron machine with 1333 Mhz DDR3 memory)

Yeah. something like that.

Just tested: it works.

2 Likes

bugs://80509 got closed with status fixed.

For string obfuscation:

namespace RemObjects.Elements.Obfuscation;

uses
  RemObjects.Elements.Cirrus.*;

type 
  [MethodAspectAttribute(typeOf(RemObjects.Elements.Obfuscation.__Global), true, 'ObfuscateString', 0, [typeOf(String)])]
  StringObfuscationAspect = public class(IMethodCallDecorator, IAspectFinishCallback)
  private 
    class var fRandom: Random := new Random();
    class var fObfuscatedString: IFieldDefinition;
    class var fDemangleString: IMethodDefinition;
    class var fCtor: IMethodDefinition;
    class var fValue: String;

    class method MangleString(s: String; aKey: Integer): String;
    begin 
      var c := new Char[s.Length + 2];
      var lLen := s.Length xor aKey;
      c[0] := Char(lLen);
      c[1] := Char(lLen shr 16);
      aKey := ((aKey shl 3) or (aKey shr 29)) + s.Length;
      for i: Integer := 0 to s.Length -1 do begin 
        c[i + 2] := Char(Word(s[i]) xor aKey);
        aKey := ((aKey shl 3) or (aKey shr 29)) + ord(s[i]);
      end;
      exit new String(c);
    end;
  public 
    method Finish(services: IServices);
    begin 
      if fObfuscatedString <> nil then 
        fCtor.ReplaceMethodBody(new BeginStatement(
          new PlaceHolderStatement,
          new AssignmentStatement(new FieldValue(new TypeValue(fObfuscatedString.Owner), fObfuscatedString), fValue)));
      fObfuscatedString := nil;
    end;

    method ProcessMethodCall(aContext: IContext; aMethod: IMethod; aValue: ParameterizedValue): Value;
    begin
      if aValue.Parameters.Count <> 1 then begin 
        aContext.Services.EmitError('Single string parameter expected!');
        exit 
     end;
      var lVal := aValue.Parameters[0];
      if lVal.Kind <> ValueKind.Data then begin 
        aContext.Services.EmitError('Single string parameter expected!');
        exit 
     end;
      var lStringValue := String(lVal);

      if fObfuscatedString = nil then begin 
        var lTD := coalesce(
          ITypeDefinition(aContext.Services.FindType('<PrivateImplementationDetails>')),
          aContext.Services.CreateTypeDefinition('', '<PrivateImplementationDetails>', TypeDefKind.Class));
        fCtor := coalesce(lTD.GetClassConstructor as IMethodDefinition, lTD.AddConstructor(true));
        fObfuscatedString := lTD.AddField('data', aContext.Services.GetBaseType(13), true);
        // TODO: Java, Toffee implementation.
        fDemangleString := lTD.AddMethod('a', aContext.Services.GetBaseType(13), true);

        //fDemangleString.AddParameter('a', ParameterModifier.In, aContext.Services.GetBaseType(13));
        fDemangleString.AddParameter('b', ParameterModifier.In, aContext.Services.GetBaseType(7));
        fDemangleString.AddParameter('d', ParameterModifier.In, aContext.Services.GetBaseType(7));
        var parameter := new FieldValue(new TypeValue(fObfuscatedString.Owner), fObfuscatedString);
        var parameter2 := fDemangleString.GetParameter('b');
        var parameter3 := fDemangleString.GetParameter('d');
        /*

    class method DemangleString(aIn: String; aOffset, aKey: Integer): String;
    begin
      var lLen := Word(aIn[aOffset + 0]) or (Integer(aIn[aOffset + 1]) shl 16);
      lLen := lLen xor aKey;
      aKey := ((aKey shl 3) or (aKey shr 29)) + lLen;
      var c := new Char[lLen];
      for i: Integer := 0 to lLen -1 do begin 
        c[i] := Char(Word(aIn[i + aOffset + 2]) xor aKey);
        aKey := ((aKey shl 3) or (aKey shr 29)) + ord(c[i]);
      end;
      exit new String(c);
    end;

*/


        fDemangleString.ReplaceMethodBody(new BeginStatement([
            new LocalVariable("lLen", fDemangleString.GetContextType("System.Int32")),
            new LocalVariable("c", fDemangleString.GetContextType("array of System.Char"))
          ], 
          new AssignmentStatement(new NamedLocalValue("lLen"), new BinaryValue(new UnaryValue(new ProcValue(parameter, "get_Chars", new IType[0], false, new BinaryValue(parameter2, new DataValue(fDemangleString.GetContextType("System.Int32"), 0), BinaryOperator.Add, fDemangleString.GetContextType("System.Int32"))), UnaryOperator.ImpCast, fDemangleString.GetContextType("System.UInt16")), new BinaryValue(new UnaryValue(new ProcValue(parameter, "get_Chars", new IType[0], false, new BinaryValue(parameter2, new DataValue(fDemangleString.GetContextType("System.Int32"), 1), BinaryOperator.Add, fDemangleString.GetContextType("System.Int32"))), UnaryOperator.ImpCast, fDemangleString.GetContextType("System.Int32")), new DataValue(fDemangleString.GetContextType("System.Int32"), 16), BinaryOperator.Shl, fDemangleString.GetContextType("System.Int32")), BinaryOperator.Or, fDemangleString.GetContextType("System.Int32"))), 
          new AssignmentStatement(new IdentifierValue("lLen"), new BinaryValue(new IdentifierValue("lLen"), parameter3, BinaryOperator.Xor, fDemangleString.GetContextType("System.Int32"))), 
          new AssignmentStatement(parameter3, new BinaryValue(new BinaryValue(new BinaryValue(parameter3, new DataValue(fDemangleString.GetContextType("System.Int32"), 3), BinaryOperator.Shl, fDemangleString.GetContextType("System.Int32")), new BinaryValue(parameter3, new DataValue(fDemangleString.GetContextType("System.Int32"), 29), BinaryOperator.Shr, fDemangleString.GetContextType("System.Int32")), BinaryOperator.Or, fDemangleString.GetContextType("System.Int32")), new IdentifierValue("lLen"), BinaryOperator.Add, fDemangleString.GetContextType("System.Int32"))), 
          new AssignmentStatement(new NamedLocalValue("c"), new ArrayCtorCallValue(IArrayType(fDemangleString.GetContextType("array of System.Char")), new IdentifierValue("lLen"))), 
          new ForStatement("i", fDemangleString.GetContextType("System.Int32"), new DataValue(fDemangleString.GetContextType("System.Int32"), 0), new BinaryValue(new IdentifierValue("lLen"), new DataValue(fDemangleString.GetContextType("System.Int32"), 1), BinaryOperator.Sub, fDemangleString.GetContextType("System.Int32")), nil, new BeginStatement(new AssignmentStatement(new SubArrayValue(new IdentifierValue("c"), new IdentifierValue("i")), new UnaryValue(new BinaryValue(new UnaryValue(new ProcValue(parameter, "get_Chars", new IType[0], false, new BinaryValue(new BinaryValue(new IdentifierValue("i"), parameter2, BinaryOperator.Add, fDemangleString.GetContextType("System.Int32")), new DataValue(fDemangleString.GetContextType("System.Int32"), 2), BinaryOperator.Add, fDemangleString.GetContextType("System.Int32"))), UnaryOperator.ImpCast, fDemangleString.GetContextType("System.UInt16")), parameter3, BinaryOperator.Xor, fDemangleString.GetContextType("System.Int32")), UnaryOperator.ImpCast, fDemangleString.GetContextType("System.Char"))), new AssignmentStatement(parameter3, new BinaryValue(new BinaryValue(new BinaryValue(parameter3, new DataValue(fDemangleString.GetContextType("System.Int32"), 3), BinaryOperator.Shl, fDemangleString.GetContextType("System.Int32")), new BinaryValue(parameter3, new DataValue(fDemangleString.GetContextType("System.Int32"), 29), BinaryOperator.Shr, fDemangleString.GetContextType("System.Int32")), BinaryOperator.Or, fDemangleString.GetContextType("System.Int32")), new UnaryValue(new SubArrayValue(new IdentifierValue("c"), new IdentifierValue("i")), UnaryOperator.ImpCast, fDemangleString.GetContextType("System.UInt16")), BinaryOperator.Add, fDemangleString.GetContextType("System.Int32")))), false, false), 
          new ExitStatement(new NewValue(fDemangleString.GetContextType("System.String"), [new IdentifierValue("c")], new System.Collections.Generic.KeyValuePair<String, Value>[0]))));

      end;
      var lOffset: Integer;
      var lKey := fRandom.Next;
      if fValue = nil then begin
        fValue := MangleString(lStringValue, lKey);
        lOffset := 0;
      end else begin 
        var lOld := fValue;
        lOffset := lOld.Length;
        fValue := lOld + MangleString(lStringValue, lKey);
      end;

      exit new ProcValue(new TypeValue(fDemangleString.Owner), fDemangleString, [lOffset, lKey]);
    end;
  end;

method ObfuscateString(s: String): String;
begin 
  exit s;
end;

end.

(.NET only at the moment, but that’s just a question of porting the code)

Usage: put it in a dll, use it like:


type
  Program = class
  public

    class method Main(args: array of String): Int32;
    begin
      writeLn(ObfuscateString('HAHA'));
      writeLn(ObfuscateString('HIHI'));
      // add your own code here
      writeLn('The magic happens here.');
    end;

  end;

end result looks like:

        public static int Main(string[] args)
	{
		Console.Out.WriteLine(<PrivateImplementationDetails>.a(0, 1325031605));
		Console.Out.WriteLine(<PrivateImplementationDetails>.a(6, 1888006488));
		Console.Out.WriteLine("The magic happens here.");
		int result = default(int);
		return result;
	}

a single string is stored as "悱仺צⷺvɹ녜炈誏\rȡᏙ"

this needs todays build to compile

2 Likes

This works on all 4 platforms now, and is integrated in cirrus.

2 Likes