Jump to content
sfrazor

Dynamic class member names

Recommended Posts

I've read quite a bit and I can usually either be creative enough to come up with a solution or find a solution on the internet.  In this case I'm not sure I'm even asking the right question so I'm coming up empty.  Hopefully the pro's here can help.

 

My employer says obfuscate.  period.  To make matters worse, they are not interested in purchasing some obfuscation tool.  So, I have to  start off by eliminating obvious names within classes that are subsequently transformed in to JSON.  Then move on to strings etc...   When I ask the question in public I get lectures on Why?  What are  you trying to hide?  Please, I understand but its not an option for me.  The string obfuscation I can handle fine.  But the class members are a bit more challenging and I'm finding that it can't be done because it requires a pre-process pass in compiling that Delphi doesn't have.  I get it, Delphi is strongly typed and variable name substitution is not the best answer.  So, I need to find a work-around.

 

Here is the challenge for me:

 

const

FNAME = 'abcd'

FADDRESS = 'efgh'

FCITY = 'ijkl'

 

type

TPPI = classs

public

FNAME: string;

FADDRESS: string;

FCITY: string;

end;

 

I  utilize the  TJSON.ObjectToJasonString() to flip it to JSON.  Work great! The problem is the JSON class names (FNAME, FADDRSS FCITY and more PI) are easily identifiable in the resulting DLL and EXE's  and are easily identifiable in the transmitted JSON (yes I can encrypt the JSON string being sent) but that still leaves the DLL/EXE.  Instead,  I need the substituted abcd, efgh and ijkl to be both in the binary and in the JSON object.  

 

 I  was hoping that eliminating the fields via compiler directive {RTTI EXPLICIT METHODS([]) PROPERTIES ([]) FIELDS([])} would work.  However FIELDS([]) eliminates ObjectToJasonString from picking up the fields to build the JSON objects.  So eliminating then in the build doesn't seem to be an answer either.

 

I'm pretty sure the obfuscation questions have been asked so pointing me to a topic with some answers is fine.  Or, some creative work-arounds would be really appreciated

 

Edit:  Using RAD Studio 11

 

Scott

 

 

 

 

 

Edited by sfrazor
More info and typo

Share this post


Link to post

I think the easiest is to write a post and pre-processor for JSON. After producing the JSON, you pass it thru the post-processor which will obfuscate it. At the other side when you get the obfuscated JSON, you unobfuscate it with the pre-processor.

Share this post


Link to post

Thanks François , Yes I can pre/post process the JSON text itself, but the class member names still exist in the binary in their original form.  ie 'FNAME' instead of 'abcd'.  That's where I'm having difficulty.  In 'C' this is easy but in Delphi I can't think of a way to ach ive that level of substitution.

Edited by sfrazor

Share this post


Link to post
1 hour ago, sfrazor said:

The problem is the JSON class names (FNAME, FADDRSS FCITY and more PI) are easily identifiable in the resulting DLL and EXE's  and are easily identifiable in the transmitted JSON (yes I can encrypt the JSON string being sent) but that still leaves the DLL/EXE.  Instead,  I need the substituted abcd, efgh and ijkl to be both in the binary and in the JSON object. 

Are you trying to obfuscate the class names or their values?  Or both?

 

You can use attributes to change the field names for the JSON structure. Read this blog by Flix Engineering, especially the section starting at "Custom attributes to the Rescue!" This allows you to keep your class intact but change the field names that show up in the generated JSON data.

 

Share this post


Link to post
2 minutes ago, corneliusdavid said:

Are you trying to obfuscate the class names or their values?  Or both?

 

You can use attributes to change the field names for the JSON structure. Read this blog by Flix Engineering, especially the section starting at "Custom attributes to the Rescue!" This allows you to keep your class intact but change the field names that show up in the generated JSON data.

 

Custom attributes is a good read.  It solves another request they made.  Thanks!

 

To answer your question, ultimately both would be best.  Class and member names within the compiled binary are what caught my employers eye when reviewing the  compiled binaries.  I feel like if I obfuscate the class field names (not their values as I can post process and obfuscate those) , by default the associated JSON names will be obfuscated when they are processed to JSON which I'd then encrypt the JSON string as a whole. and decrypt it on the receiving end when its consumed. So in the above example I don't want FNAME showing in the binary when I run a simple 'strings' on it, I want 'abcd' to show and not 'FNAME'.  When looking at the source, I want the programmer friendly FNAME to be in the source.

 

Not real code here but it would be something like this:

 

cons

FNAME = 'abcd'

 

type

TPPI = classs

public

<$FNAME>: string;

....

end;

 

at compile time <$FNAME> converts to 'abcd' and when a strings is run on the binary, you only see 'abcd'.  Of coarse in Delphi this could create all sorts of problems when referencing that class member later on.  

 

Does this help?

 

 

 

Share this post


Link to post

Are you compiling with debug info on?  Try compiling in Release mode and see how many identifiers are recognizable in the DLL.

Share this post


Link to post

You can use the JSONName attribute to decouple the field name from the JSON name when using TJson.ObjectToJasonString. Make sure you have REST.Json.Types in your uses clause before.

type
  TPPI = classs
  public
    [JSONName('item1')]
    FNAME: string;
    [JSONName('item2')]
    FADDRESS: string;
    [JSONName('item3')]
    FCITY: string;
  end;

 

Share this post


Link to post
1 hour ago, corneliusdavid said:

Are you compiling with debug info on?  Try compiling in Release mode and see how many identifiers are recognizable in the DLL.

No, no debug.  The Identifiers are all there in Release.   Gonna throw together a very small sample.  I'll add the decoupling example Uwe mentioned as well.  

Edited by sfrazor

Share this post


Link to post
1 hour ago, sfrazor said:

Class and member names within the compiled binary are what caught my employers eye when reviewing the  compiled binaries.

And?  What exactly about the names being viewable to humans is worrisome to your employer, exactly?  It is not like users will be able to USE the displayed names to do anything malicious to your code, since the actual classes and fields are converted into memory addresses, function calls, etc during compiling.  You are likely just viewing the RTTI or Debug info.

 

What is the actual CONCERN, before you waste your time trying to IMPLEMENT something that, quite frankly, can and will be worked around by anyone who actually intends to be malicious?

  • Like 1

Share this post


Link to post

OK I threw together this example.  The attributes decoupling mentioned above works fine for the production of the JSON.  However the Fieldnames in the binaries is present.

 

unit Unit4;

//{$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, REST.Json, REST.Json.Types;
type
  TPPI = class
  public
    [JSONName('abcd')]
    FName: string;     //client name
    [JSONName('efgh')]
    FAddress: string;  //client address
    [JSONName('ijkl')]
    FCity: string;    //client city
  end;
type
  TForm4 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form4: TForm4;

implementation

{$R *.dfm}

procedure TForm4.Button1Click(Sender: TObject);
var
  MyPPI: TPPI;
  PPIJSON: string;
begin
  MyPPI:= TPPI.Create;
  MyPPI.FName:= 'First Name';
  MyPPI.FAddress:= 'Main Street';
  MyPPI.FCity:= 'New York';
  PPIJSON := TJSON.ObjectToJsonString(MyPPI, [joIndentCaseCamel]);
  // now that we have our JSON lets make if pretty
  //PrettyJson := TJsonObject.ParseJSONValue(TMJSON);
  //TMJSON := TJSON.Format(PrettyJson);
  Memo1.Lines.Add('////////////// JSON DATA //////////////');
  Memo1.Lines.Add(PPIJSON);
  MyPPI.Free;
end;

 

 

That gets my JSON looking like this:
 

////////////// JSON DATA //////////////
{"abcd":"First Name","efgh":"Main Street","ijkl":"New York"}

 

 

However these are still in the binary when I do a strings.  Which is what started all of this when the binaries were reviewed.

 

...
FName
abcd
FAddress
efgh
FCity
ijkl
TPPI,
TPPI
...

 

Scott

 

Share this post


Link to post

You can't have both. For TJson.ObjectToJasonString to do its work it requires RTTI. With RTTI you will have the class and field names inside the binary.

 

Have you tried running UPX on your exe?

Share this post


Link to post
4 minutes ago, Remy Lebeau said:

And?  What exactly about the names being viewable to humans is worrisome to your employer, exactly?  It is not like users will be able to USE the displayed names to do anything malicious to your code, since the actual classes and fields are converted into memory addresses, function calls, etc during compiling.  You are likely just viewing the RTTI or Debug info.

 

What is the actual CONCERN, before you waste your time trying to IMPLEMENT something that, quite frankly, can and will be worked around by anyone who actually intends to be malicious?

I know...  I have asked this question almost exactly.  Obviously I'm using simple field names that have no value.  But in some of the code (In some of the DLL's) there are class names and members that identify proprietary information and they would prefer it to not be there as it paints a target on that binary to be investigated.  Yes the information is sensitive which has created a push for obfuscation.  Unfortunately I don't get a choice.  Obfuscation is something they have adopted and I have to try and figure out.  Like I said, strings and data are easy but if a specific name paints a target on one of the DLL's to be investigated it makes it that much quicker for someone to target.  I know you've seen these question before but if a person snooping around sees 'kowsscud' instead of 'FPriorityAlgorythm' it makes it less obvious to 'start  here'  and not just handing it to some malicious person without trying.  That seems to be all they want.  

 

 In the past I've written python scripts that have a blacklist of strings it will search/replace in the CI/CD pipeline.  Before I go down that road, I want to make sure I can't handle via native coding.

Like I said, I've gotten the same response asking my peers.  They respond with Why?  What are they thinking, makes no sense, waste of time etc.....  The answer may be that Delphi simply can't do it.

Share this post


Link to post

Well, without a Delphi pre-parser, you'll either have to do something like what Uwe suggested (UPX) or concoct your own obfuscated class/field names with comments explaining what they are (comments do not get compiled into the binaries).

 

Unless someone else has a better idea.

Share this post


Link to post

One more thing, I think you should look into the compiler options, make sure all debugging info is off, specifically Local Symbols and Symbol Reference Info. Also turn on Optimization and make sure RTTI is off. Then do a full build and check the binaries. If you've already done that, I'm out of ideas.

Share this post


Link to post
3 minutes ago, corneliusdavid said:

Well, without a Delphi pre-parser, you'll either have to do something like what Uwe suggested (UPX) or concoct your own obfuscated class/field names with comments explaining what they are (comments do not get compiled into the binaries).

 

Unless someone else has a better idea.

This is the path I'm headed down.  Its a tremendous amount of code, but hey, job security.  I thought before I took on that task I'd ask here.  I saw where Jedi has a pre-parser but its a bit vague on how to use it and it doesn't seem to exactly fit what I'm wanting to do.  

 

Uwe, I've looked at UPX.  I don't see where it will handle DLL's unless I missed something.  I konw Delphi doesn't NEED a pre parser but man it sure would be nice.  

Share this post


Link to post
24 minutes ago, Uwe Raabe said:

You can't have both. For TJson.ObjectToJasonString to do its work it requires RTTI. With RTTI you will have the class and field names inside the binary.

Oh yeah, that's right. 

Share this post


Link to post

Thank you everyone for taking the time to work through this with me.  I read here often but haven't been in a position to ask for help until this stumped me.  Until some pre-process or pre-pass substitution is supported by Delphi the answer was to take the the long names and replace them with coded alternatives and comment the heck out of the code for future developers to follow.  Hand-jamming that was a chore even with the help of refactoring.  Not very "Delphi" ie pretty, but it works and provides the obfuscation I needed for this mandate.   I picked up some other tidbits here as well.  If, for example if the decoupling would omit the long names and only embed the alternate names in the RTTI that would have been a perfect solution.   

 

Cheers!

Share this post


Link to post

Do you really need automatic JSON (de)serialization for the objects? Maybe disabling all RTTI for the critical structures and implementing manual read/write routines would be the simplest option.

One more option is to name members in a special style like "__Ident__" that is guaranteed to be unique project-wide and then perform simple regexp  replace on all sources before build.

3rd option that comes to mind (and probably the most correct one) is to try refactoring from command line but I'm not sure if this tool exposes command line interface or only runs from IDE

Share this post


Link to post
6 hours ago, Fr0sT.Brutal said:

Do you really need automatic JSON (de)serialization for the objects? Maybe disabling all RTTI for the critical structures and implementing manual read/write routines would be the simplest option.

One more option is to name members in a special style like "__Ident__" that is guaranteed to be unique project-wide and then perform simple regexp  replace on all sources before build.

3rd option that comes to mind (and probably the most correct one) is to try refactoring from command line but I'm not sure if this tool exposes command line interface or only runs from IDE

I appreciate the feedback.  The automatic serialization/deserialization is what was there.  Based on the git history the code was refactored away from coded JSON objects in favor of taking advantage of newer Delphi JSON automation.  And then comes this requirement for "Basic code obfuscation" that is checked pre-release.  I considered doing a pre process regex via python in the CI/CD pipeline using markers like you suggested above.  I shouldn't have to go through those extremes for any language.   I'm really not happy with the way I worked the solution.  The code is ugly and difficult to read.  I have to reference KEY to work with variables.  The classes look like jibberish.  Very embarrassing.  If there was a 3rd party preprocessor I'd consider that. 

Edited by sfrazor

Share this post


Link to post

I assume you are in control of both ends of the communication.
I'd compress and encrypt instead of bothering with obfuscation.

Share this post


Link to post
1 hour ago, sfrazor said:

I considered doing a pre process regex via python in the CI/CD pipeline using markers like you suggested above.  I shouldn't have to go through those extremes for any language

I still consider it the most easy and at the same time working solution. You still use human readable member names but with small non-disrupting addition. Before building a release, run regexp replace for all source files and that's all.

1 hour ago, Lars Fosdal said:

I assume you are in control of both ends of the communication.

No he doesn't. He wants both readable names and auto-JSON-ing which requires RTTI which leaves readable traces in binary.

 

Hmm. How about using

{$RTTI EXPLICIT PROPERTIES vcPublished}

and then

 

TObfuscatedClass = class
private  
  FSecretField: string;
public
  // for use from code
  property SecretField: string read FSecretField write FSecretField;
published
  // for JSON-ing
  property bwoirhoeri: string read FSecretField write FSecretField;
end;

 

Edited by Fr0sT.Brutal
  • Like 1

Share this post


Link to post

@Fr0sT.Brutal The TJson RTTI to Json conversion doesn't work with the property names, but with the field names. 
See @Uwe Raabe's example that shows where the JSON name mangling is done by attributes.

Share this post


Link to post
1 hour ago, Lars Fosdal said:

@Fr0sT.Brutal The TJson RTTI to Json conversion doesn't work with the property names, but with the field names. 
See @Uwe Raabe's example that shows where the JSON name mangling is done by attributes.

Okay

  {$RTTI EXPLICIT FIELDS([vcPublic]) PROPERTIES([])}
  TTest = class
  public
    obfuscated: byte;
  public
    property secret: byte read obfuscated;
  end;


procedure TForm1.FormCreate(Sender: TObject);
var ob: TTest;
begin
 ob:= TTest.Create;
 ob.secret := 2;
 ShowMessage(TJSON.ObjectToJsonString(ob)); // {"obfuscated":2}
end;

I checked binary output and there's only "obfuscated" field visible.

 

One more option is using structure-identical classes and typecasting but that requires more efforts. The solution above seems more simple

Edited by Fr0sT.Brutal

Share this post


Link to post
7 hours ago, Lars Fosdal said:

@Fr0sT.Brutal The TJson RTTI to Json conversion doesn't work with the property names, but with the field names. 
See @Uwe Raabe's example that shows where the JSON name mangling is done by attributes.

Before you posted this I tried it.   I can confirm this is correct...  But you already knew that.  🙂

Share this post


Link to post
6 hours ago, Fr0sT.Brutal said:

Okay


  {$RTTI EXPLICIT FIELDS([vcPublic]) PROPERTIES([])}
  TTest = class
  public
    obfuscated: byte;
  public
    property secret: byte read obfuscated;
  end;


procedure TForm1.FormCreate(Sender: TObject);
var ob: TTest;
begin
 ob:= TTest.Create;
 ob.secret := 2;
 ShowMessage(TJSON.ObjectToJsonString(ob)); // {"obfuscated":2}
end;

I checked binary output and there's only "obfuscated" field visible.

 

One more option is using structure-identical classes and typecasting but that requires more efforts. The solution above seems more simple

This works fantastic!  I couldn't leave my previous solution for the next programmer to untangle so I reverted in the repo and here is what I ended up with.  Again constant strings are handled via a simple en/decode function (while I'm wishing can I wish for macro support?).  The last of my "mandates" was obfuscate the class field names since they were the most revealing.  But as a bonus This solution also takes care of sensitive procedure names.   Unit tests still run!  So it seems I didn't break anything at a glance.

 

unit Unit4;

{$RTTI EXPLICIT METHODS([]) PROPERTIES([]) Fields([vcPublic])}

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, REST.Json, REST.Json.Types;
type

  TPII = class
  public
  //Obfuscated
  Faaaa: string;
  Fbbbb: string;
  Fcccc: string;
  public
  //Human readable
  property FNAME: string read Faaaa write Faaaa;
  property FAddress: string read Fbbbb write Fbbbb;
  property FCity: string read Fcccc write Fcccc;
  end;
type
  TForm4 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure MyPIIProcedure; //Could be sensative.  Needs to be hidden as well...
  end;

var
  Form4: TForm4;

implementation

{$R *.dfm}

procedure TForm4.Button1Click(Sender: TObject);
begin
   MyPIIProcedure;
end;

procedure TForm4.MyPIIProcedure;
var
  MyPII: TPII;
  PIIJSON: string;
begin
  MyPII:= TPII.Create;
  MyPII.FName:= 'John';
  MyPII.FAddress:= 'Main Street';
  MyPII.FCity:= 'New York';
  PIIJSON := TJSON.ObjectToJsonString(MyPII, [joIndentCaseCamel]);
  Memo1.Lines.Add('////////////// JSON DATA //////////////');
  Memo1.Lines.Add(PIIJSON);
  MyPII.Free;
end;
end.

 

To top it off I wrote a small "scrubber" that does the search and replace as mentioned above in the .pas but did it like this:

Used this to take care of the actual class names...  

created an include file with the known-name and an obfuscated equivalent.  Wrote a small Delphi console app to do this.  Works great in the CI/CD pipeline build.  This took care of the remaining 60 or so edge cases hidden in the code.

Added the scrubber to the pre-build command to parse out the .pas files needed.  Before compile.

 

The resulting binaries are petty darn clean!  

 

So thanks again to everyone that chimed in!  All great ideas and led to the working solution.  No more "plain text" identifiers.  

  • Like 3

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×