Control flow obfuscation.

Q

q23r

Have you seen real control flow obfuscation(CFO)?
I mean not in advertising material but in work?

I suppose there are three main obfuscators, which supports CFO.
1. Xenocode
2. Dotfuscator
3. Demeanor

1. When you choose max CFO, produces a lot of trash classes, but don't
change flow of your code.
2. Trial version. All settings are default.
I can't see any CFO effect.
3. Trial version doesn't support CFO.
All I have is the code sample from here:
http://groups.google.com/group/micr...trol+flow+obfuscation&rnum=1#d24076125868738a

Please, correct me, if I'm wrong.
And tell me may be there are another ones, which supports CFO?
 
B

Ben Callister

i use Xenocode and it works pretty good. once CFO has been applied, if you
load your assembly into a reflection tool (i use Reflector), and you look at
your various function bodies, you will see that they have indeed been
replaced with alot more simple-instructions and labels that make it very
difficult to reverse engineer. one thing that i have complained to them
already about is that there is currently no GUI for applying CFO to
*particular* function bodies. rather, it just defaults to all function
bodies within your assembly. you can do this, but you have to drop down into
the project file yourself which is a pain.

fyi... i typically use their 'conservative' filter so that i can keep my
public interfaces in tact since i share my components.

one thing to note is that your assembly size will grow substantially, which
is an indicator that CFO has indeed been apllied. i have been seeing about a
5x increase in size after CFO has been applied - which sucks, but i guess
thats what you get for buying into this managed world if you want to protect
yourself somewhat.

unfortunately, i cant speak for the other obfuscator utilities as i have not
used them.

hope this helps somewhat.
ben
 
W

William Stacey [MVP]

I like Xenocode as well. However, in no way is 5x worth it IMO. I would
just obfuscate and call it a day.
 
Q

q23r

Example of xenocode work(trial version 3.1.6 build 2012):
CFO is max.

before obfuscation:

[STAThread]
static void Main(string[] args)
{
System.Console.WriteLine("Please, enter number.");
int number = Convert.ToInt32(System.Console.ReadLine());
System.Console.WriteLine(String.Format("Factorial of {0} is {1}",
number, Calculate(number)));
System.Console.WriteLine(String.Format("Squared {0} is {1}",
number, GetSquared(number)));
double d = 53.0;
System.Console.WriteLine(String.Format("Squared {0} is {1}", d,
GetSquared(d)));
System.Console.WriteLine(String.Format("Square root of {0} is
{1}", d, GetSquareRoot(d)));
}

private static int Calculate(int number)
{
if (number == 1)
return 1;
return number * Calculate(number - 1);
}

private static int GetSquared(int x)
{
return x*x;
}

private static double GetSquared(double x)
{
return x*x;
}

private static double GetSquareRoot(double x)
{
return Math.Sqrt(x);
}
}


after(decompiled by Salamander, RemoteSoft said that main method is
obfuscated):

[STAThreadAttribute()]
private static void xc447809891322395(string[]
xce8d8c7e3c2c2426)
{
int i;

double d;

Console.WriteLine("Please, enter number.");
i = Convert.ToInt32(Console.ReadLine());
ConstantExp: "Factorial of {0} is {1}"
VariableExp: i
ConstantExp: "Square root of {0} is {1}"
VariableExp: d
IL_0054: box [mscorlib]System.Int32
BoxingExp: xb1de1ba20faeeff8(i)
IL_0064: call string
[mscorlib]System.String::Format(string, object, object)
IL_0069: call void
[mscorlib]System.Console::WriteLine(string)
Console.WriteLine(String.Format("Squared {0} is {1}", i,
x68479d4d6a6e9138(i)));
d = 53.0;
Console.WriteLine(String.Format("Squared {0} is {1}", d,
x68479d4d6a6e9138(d)));
IL_008b: box [mscorlib]System.Double
BoxingExp: x51c832559b7f7cb7(d)
IL_009b: call string
[mscorlib]System.String::Format(string, object, object)
IL_00a0: call void
[mscorlib]System.Console::WriteLine(string)
}

private static int xb1de1ba20faeeff8(int x78b0a0bc28459535)
{
if (x78b0a0bc28459535 == 1)
{
return 1;
}
else
{
return x78b0a0bc28459535 *
xb1de1ba20faeeff8(x78b0a0bc28459535 - 1);
}
}

private static int x68479d4d6a6e9138(int x08db3aeabb253cb1)
{
return x08db3aeabb253cb1 * x08db3aeabb253cb1;
}

private static double x68479d4d6a6e9138(double
x08db3aeabb253cb1)
{
return x08db3aeabb253cb1 * x08db3aeabb253cb1;
}

private static double x51c832559b7f7cb7(double
x08db3aeabb253cb1)
{
return Math.Sqrt(x08db3aeabb253cb1);
}
}


Is there any CFO? I think no. The trash in the main method appears
because of xenocode damaged some metadata.

May be it is also restriction of trial version? If so why settings of
CFO is available?
 
H

Huihong

This kind of control flow obfuscation is not difficult to defeat. I
just didn't bother to add the support into our salamander decompiler.
Internally, I have a version that can easily remove those extra jump
instructions injected by obfuscators, and yield high quality source
code.

What CFO does is to insert extra br instructions to mess up stack
evaluation. Usually, decompiler assumes that a stack starts with empty,
and becomes empty during a straight excution path, e.g., path
immediately follow an if...else.. node. CFO uses jump instruction to
make this simple stack evaluation a bit more difficult, but still easy
to remove. I may release a version that defeats this kind of
obfuscation.

Be aware, extra jumps introduce more branches, and thus will slow down
the performance.

I still think if you use obfuscators, renaming is the key feature.
Otherwise, try our native compiler, it provides THE best protection
against reverse engineering by compiling everying into x86 code, and it
runs without .NET Framework, no IL code, no JIT compiling on execution
time.

Huihong
Remotesoft, Inc.
http://www.remotesoft.com

Example of xenocode work(trial version 3.1.6 build 2012):
CFO is max.

before obfuscation:

[STAThread]
static void Main(string[] args)
{
System.Console.WriteLine("Please, enter number.");
int number = Convert.ToInt32(System.Console.ReadLine());
System.Console.WriteLine(String.Format("Factorial of {0} is {1}",
number, Calculate(number)));
System.Console.WriteLine(String.Format("Squared {0} is {1}",
number, GetSquared(number)));
double d = 53.0;
System.Console.WriteLine(String.Format("Squared {0} is {1}", d,
GetSquared(d)));
System.Console.WriteLine(String.Format("Square root of {0} is
{1}", d, GetSquareRoot(d)));
}

private static int Calculate(int number)
{
if (number == 1)
return 1;
return number * Calculate(number - 1);
}

private static int GetSquared(int x)
{
return x*x;
}

private static double GetSquared(double x)
{
return x*x;
}

private static double GetSquareRoot(double x)
{
return Math.Sqrt(x);
}
}


after(decompiled by Salamander, RemoteSoft said that main method is
obfuscated):

[STAThreadAttribute()]
private static void xc447809891322395(string[]
xce8d8c7e3c2c2426)
{
int i;

double d;

Console.WriteLine("Please, enter number.");
i = Convert.ToInt32(Console.ReadLine());
ConstantExp: "Factorial of {0} is {1}"
VariableExp: i
ConstantExp: "Square root of {0} is {1}"
VariableExp: d
IL_0054: box [mscorlib]System.Int32
BoxingExp: xb1de1ba20faeeff8(i)
IL_0064: call string
[mscorlib]System.String::Format(string, object, object)
IL_0069: call void
[mscorlib]System.Console::WriteLine(string)
Console.WriteLine(String.Format("Squared {0} is {1}", i,
x68479d4d6a6e9138(i)));
d = 53.0;
Console.WriteLine(String.Format("Squared {0} is {1}", d,
x68479d4d6a6e9138(d)));
IL_008b: box [mscorlib]System.Double
BoxingExp: x51c832559b7f7cb7(d)
IL_009b: call string
[mscorlib]System.String::Format(string, object, object)
IL_00a0: call void
[mscorlib]System.Console::WriteLine(string)
}

private static int xb1de1ba20faeeff8(int x78b0a0bc28459535)
{
if (x78b0a0bc28459535 == 1)
{
return 1;
}
else
{
return x78b0a0bc28459535 *
xb1de1ba20faeeff8(x78b0a0bc28459535 - 1);
}
}

private static int x68479d4d6a6e9138(int x08db3aeabb253cb1)
{
return x08db3aeabb253cb1 * x08db3aeabb253cb1;
}

private static double x68479d4d6a6e9138(double
x08db3aeabb253cb1)
{
return x08db3aeabb253cb1 * x08db3aeabb253cb1;
}

private static double x51c832559b7f7cb7(double
x08db3aeabb253cb1)
{
return Math.Sqrt(x08db3aeabb253cb1);
}
}


Is there any CFO? I think no. The trash in the main method appears
because of xenocode damaged some metadata.

May be it is also restriction of trial version? If so why settings of
CFO is available?
 
Q

q23r

Huihong said:
What CFO does is to insert extra br instructions to mess up stack
evaluation.
How do you know kind of cfo in xenocode?
I still think if you use obfuscators, renaming is the key feature.
I think know.
But it's easy to implement.

I want obfuscator which results can't be compiled code after cycle of
obfuscation, decompilation.
Is it possible to change some methadata(or smth else) so, that binaries
are correct to execute, but when decompiled, can't be compiled again
without human interference?
Otherwise, try our native compiler, it provides THE best protection
against reverse engineering by compiling everying into x86 code, and it
runs without .NET Framework, no IL code, no JIT compiling on execution
time.
If I want native code which runs without .net, I should have been using
c++ )
 
H

Huihong

No, we don't check whether the code is CFO-ed, rather than, perform
some optimization before stack evaluation for the decompiler. In your
case, the Main() method is failed to decompile, because some nodes with
branches are inserted, which can be completely removed. In other words,
CFO-enabled obfuscators make a straight execution path to become a
conditional path, I'll give you an example,

Before:

int sum = 0;
sum = x + y;

After CFO: (for illustration only, not exact, should use IL
instructions to accurate description)

int sum = 0 ;
push sum
if (sum ! = 0) goto L1; <=== extra node with branched
pop x; <== this exectution path's stack does not start
with empty
L1:
sum = x + y;

Pretty much, this is the techniques CFO uses. It relies on branches to
mess up the decompilers, but as you can see, a bit more advanced stack
evaluation will undo the work.

As for native compilation vs programming with native C++, I think it's
totally different concept. Native compilation means you still use .NET
programming languages, such as C# and VB.NET, so you still can take
advantage of the productivity of these wonderful languages, and only if
you are really serious about reverse engineering, you have an option to
compile into native code. Garbage collection is still in place.
 
H

Huihong

No, we don't check whether the code is CFO-ed, rather than, perform
some optimization before stack evaluation for the decompiler. In your
case, the Main() method is failed to decompile, because some nodes with
branches are inserted, which can be completely removed. In other words,
CFO-enabled obfuscators make a straight execution path to become a
conditional path, I'll give you an example,

Before:

int sum = 0;
sum = x + y;

After CFO: (for illustration only, not exact, should use IL
instructions to accurate description)

int sum = 0 ;
push sum
if (sum ! = 0) goto L1; <=== extra node with branched
pop x; <== this exectution path's stack does not start
with empty
L1:
sum = x + y;

Pretty much, this is the techniques CFO uses. It relies on branches to
mess up the decompilers, but as you can see, a bit more advanced stack
evaluation will undo the work.

As for native compilation vs programming with native C++, I think it's
totally different concept. Native compilation means you still use .NET
programming languages, such as C# and VB.NET, so you still can take
advantage of the productivity of these wonderful languages, and only if
you are really serious about reverse engineering, you have an option to
compile into native code. Garbage collection is still in place.
 
Q

q23r

I assume it is just very simple example of CFO or most obfuscators
really use the same technics?

Do you know obfuscators which use more complex CFO strategy? You know
there are plenty of theory about it, but i didn't see it in practice...
 
H

Huihong

What I can say is that decompiler always wins in these kinds of
battles. There are some variations of CFO, but the principle is same,
trying to mess up decompiler's control analysis by inserting extra
nodes with in/out edges.

What protection measure to take really depends on your comfort level.
If you think "well, I better do something", then obfuscators are fine.
If you think "God, I really need to protect my code", then try native
compilation. I wrote my salamander decompiler in java languages, before
release, I tried obfuscation, but didn't meet my goals, so I converted
java source code to C++ (for this I developed our java to C++ Octopus
translator), and recompiled it to native executables. Today, I still
develop in Java for the decompiler, but generate native code for
release.

This will be true for C#, with our native compiler:

http://ww.remotesoft.com/linker

Huihong
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top