Demeanor for .NET Enterprise
Edition Version 5.0 - Release Notes
These
release notes provide the most current information about the installation,
features, documentation, and known issues for Demeanor for .NET Enterprise
Edition.
1. Introduction
2. Installing
Demeanor for .NET
4. Known Issues
Demeanor
for .NET Enterprise Edition is an obfuscation utility for .NET assemblies. It
is designed to obfuscate as much information in a .NET assembly as possible
while allowing the code to run as the developer intended. Demeanor for .NET is
extremely configurable so that you can disable obfuscations at a type, method,
field, property, or event level should the need arise. Demeanor can obfuscate
symbols using several different techniques allowing you flexibility with regard to the obfuscated names produced.
The
Enterprise Edition renames all possible identifiers in a set of
assemblies to meaningless, trivial names using a specialized algorithm
that produces the maximum possible name overloading. By maximizing the name
overloading, the Enterprise Edition produces assemblies that are more difficult
to reverse engineer and that are significantly smaller than those obfuscated by
the Personal Edition. In addition, the Enterprise Edition contains many
additional features that protect your assemblies against reverse engineering
such as entire application obfuscation, string encryption, metadata obfuscation
and removal, incremental obfuscation, unused code and data detection, automatic
satellite assembly obfuscation, and more.
New and non-breaking changed
functionality in this release is indicated in the document using green text.
Breaking
changes to previously existing functionality is indicated
in the document using red text.
2.1 Software Requirements
Demeanor
for .NET requires the following:
An operating system supporting
development of .NET applications.
Version 4.8 or later of the .NET runtime.
Demeanor
for .NET can obfuscate assemblies built for .NET 1.0 through 4.8 plus .NET
Standard 1.0 and 2.0.
2.2 Recommended Hardware
See the
Visual Studio .NET hardware requirements.
2.3 Installing Demeanor for .NET
Before
installing Demeanor for .NET, you should already have installed the .NET
runtime. As Demeanor for .NET is a tool for .NET developers, we do not include
a redistributable copy of the .NET runtime.
Demeanor
for .NET no longer contains a product activation feature. Your purchase of a
license for Demeanor for .NET Enterprise Edition 5.0 allows the purchaser to
run Demeanor on all their personal development systems.
2.4 Removing Demeanor for .NET
To remove
Demeanor for .NET, open Control Panel, double-click Add/Remove Programs,
and then click the entry for the version of Demeanor that you have installed,
then click Remove.
2.5 Running Demeanor for .NET
Demeanor
for .NET is a command line utility. The only required command line parameter is
the file name of the assembly to obfuscate. The obfuscated version of the
assembly, by default, will be placed in a subdirectory of the current working
directory named 'Demeanor'.
Demeanor
for .NET is a command line utility. Run it as follows:
Demeanor <options> <assemblyName>
where
the options are listed below and assemblyName is
the path to the assembly you wish to obfuscate. The following options are
available:
Command line options |
Description |
/all |
Obfuscate as many
symbols as possible ignoring the visibility of the type. Normally top-level types
with public visibility cannot be obfuscated because a developer defines such
a type with public visibility to allow code in a different assembly to bind
to the type. The runtime performs this binding using the type
name so changing the name would cause this binding to fail. However, some
assemblies, typically .EXE assemblies, expect no external assemblies to bind
to their public types. (In fact, Visual Studio.NET prevents such a use of an
.EXE assembly though it can be done using the compilers at the command line.)
Technically, a developer should define all types contained in such an .EXE
assembly with internal visibility but they often do not. You typically use
the /all option when obfuscating an .EXE assembly to force all types
contained in the assembly to be obfuscated regardless of the type's
visibility. |
/cc |
This option requests Demeanor for
.NET to change the accessibility of obfuscated methods and fields to
compiler-controlled (a.k.a. privatescope)
accessibility. Using compiler-controlled accessibility strengthens the
obfuscation of your assembly in three ways. First,
compiler-controlled accessibility is another lossy transformation. The
original accessibility of the member is discarded. A decompiler will
have to perform a global data flow analysis over the entire assembly in order to determine the proper accessibility of the
obfuscated member. Second, this
accessibility does not exist in high-level languages such as C# and VB.NET.
A decompiler will have to synthesize (i.e. make up) a new accessibility for such fields and
methods. Without a global data flow analysis, a decompiler will
have to assume public or internal accessibility for such members. Third, the runtime never
uses the names of such members. Therefore Demeanor for .NET can assign all compiler-controlled
members exactly the same name. The more members using the same name, the
smaller the metadata tables, and, therefore, the smaller the resulting
assembly. |
/exclude:<name>[,name>] |
This option forces Demeanor for .NET
to not obfuscate the specified type or member. Demeanor expects you to
specify the type or member name using the ILASM and ILDASM syntax for types
and members. For example, when <name> is a top-level type, it should be
in the form Namespace.Type. When name is a nested class, it should be in the form Namespace.ContainingType/NestedType and Namespace.ContainingType/NestedType/DoubleNestedType.
You can also specify that Demeanor for .NET should not obfuscate a particular
member of a type by stating the full name of the member, e.g. Namespace.Type/NestedType::Field and Namespace.Type::Method.
Embedded space characters are preserved and are interpreted as part of the
type or member name. You can specify multiple names using a comma-separated
list. Alternatively, you can specify multiple exclude command line options,
each excluding a single name. (Short form /x:) |
/help |
Display usage information to the
console (short form /?). |
/insitu |
Forces Demeanor to obfuscate the
metadata in situ and leave the rest of the portable-executable (PE) file
untouched. Demeanor normally produces
an obfuscated assembly that it has optimized in a number of ways.
Demeanor rearranges the code and data structures in the PE file more
efficiently than those present in the original assembly. This results,
on average, in an assembly that is 8% - 12% smaller than the original, which
also downloads faster, loads into memory more quickly, and executes using a
more efficient working set. However, when there
is unmanaged code and datain an
assembly as well as managed code and data, the unmanaged code and data may
require that it loads and executes at the original memory location. In this
case, Demeanor must not rearrange the memory layout of the assembly.
Compilers that produce assemblies containing unmanaged and managed code (for
example, Microsoft's Managed C++) normally flag the assembly to
indicate that it contains unmanaged code. Demeanor automatically
enables the 'in situ' option when it determines it is obfuscating an assembly
that contains both managed and unmanaged code. So, typically, you
should never need to specify this option. However, you can use the
ILASM compiler to create an assembly that contains unmanaged code without
setting the appropriate flag. In this rare situation, you may need to specify
the 'in situ' option. |
/keycontainer:<string> |
Specify a strong name key container. You can specify the name
of the container holding the public and private strong name key pair using
this option. Demeanor uses this key container to resign the assembly after
obfuscating it. When you specify both the /keycontainer and
/keyfile options, Demeanor will use the /keyfile option. Obfuscation modifies an assembly.
When the original assembly has a strong name, this modification invalidates
any strong name signature in the assembly causing the runtime to treat the assembly
as altered. You must resign the obfuscated assembly to update it with the
correct strong name signature. |
/keyfile:<file> |
Specify a strong name key file. You can specify the name
of the file holding the public and private strong name key pair using this
option. Demeanor uses this key file to resign the assembly after obfuscating
it. When you specify both the /keycontainer and
/keyfile options, Demeanor will use the /keyfile option. Obfuscation modifies an assembly.
When the original assembly has a strong name, this modification invalidates
any strong name signature in the assembly causing the runtime to treat the
assembly as altered. You must resign the obfuscated assembly to update it
with the correct strong name signature. |
/out:<directory> |
Directory in which to place the
obfuscated assembly. Demeanor for .NET will create the directory when
necessary. The directory cannot be the same as the directory containing the
input assembly. When you omit this option, the directory name defaults to 'Demeanor'
and the utility creates this subdirectory in the current working directory. |
/names:alpha |
This option forces Demeanor for .NET
to use alphabetic Latin characters as the obfuscated
names. The first 26 obfuscated names in a scope will be called 'a' through
'z'. The Second 26 names in the same scope will be called 'A' through 'Z'.
Then it continues on to produce 'aa',
'Aa', 'aA', 'AA', 'ab', etc. This option produces
assemblies with the least number of bytes used for obfuscated names. (This is
the default setting.) |
/names:numeric |
This option forces Demeanor for .NET
to use digits for the obfuscated names. The first obfuscated name in a particular
scope will be '_1', then '_2', and so on. |
/names:Unicode |
This option forces Demeanor for .NET
to use high Unicode code points for obfuscated names. In
particular, Demeanor for .NET creates names using code points
from the Canadian Aboriginal Syllabic character set. Without a font for that
character set installed, all such names display as a box. |
/nologo |
Suppress the logo display. |
/noenumerations /noevents /nofields /nomethods /noparameters /noproperties /notypes /noresources /forceparameters |
These diagnostic options
allow you to disable all obfuscation of the specified category of types or
members. This is useful for diagnosing problems you might encounter when
using an obfuscated assembly. For example, if an assembly doesn't work when
completely obfuscated but works correctly when you specify the nofields option, it's likely that some logic is
late-binding or using Reflection to one or more fields. Method parameters are
normally obfuscated only when the method itself is obfuscated. You can force
all parameter names to be obfuscated, for obfuscated and unobfuscated methods, by specifying the /forceparameters command line option. You can prevent
all parameters from being obfuscated using the normal /noparameters switch.
Note that specifying /noparameters forces the
/forceparameters switch off and vice-verse. In
other words, only one of these two switches is used
and it will be the last one specified on the command line. |
/report[:<filename>] |
The report option
requests Demeanor to create an XML-based obfuscation report file in the
output directory. The report lists all types, fields, methods, properties and events in the obfuscated assembly. For each
entry, the XML document identifies the original type/member
name and metadata values and maps the type/member to its new obfuscated name
and metadata values. The default file name is "<assemblyName>Report.xml"
in the output directory. (Short form /r[:<filename>]). The value for a NestedType Name element has changed in this release.
Previously, it was the full type name, for example,
"a+b+c". Now the value of a NestedType Name element is the simple name of the
nested type, for example, "c". The prior qualification was
redundant because the element defining the nested type "c" resides
in the definition of nested type "b", which itself, resides in the
top-level type definition for "a". |
/verbose |
Display obfuscation statistics to
the console (short form /v). This option displays the number of types, field,
methods, properties and events contained in each
module of the assembly as well as the number obfuscated. |
/xr:<regex> |
This option forces Demeanor for .NET
to not obfuscate all types and members that match the specified regular
expression. The regular expression should match type and member names
specified using ILASM and ILDASM syntax. See the /x command line option for
examples. The regular expression syntax accepted is described in the .NET
Framework SDK documentation at: .NET Framework / Programming with the .NET
Framework / Working with Base Types / Manipulating Strings / .NET Framework
Regular Expressions
(ms-help://MS.VSCC/MS.MSDNVS/cpguide/html/cpconcomregularexpressions.htm). |
/a:<assemblyName> |
This option adds an
assembly to the set of assemblies obfuscated when you specify the
/application command line option. The assemblyName parameter
is the display name of the assembly; it is not the file name. For example,
you would specify /a:"MyAssembly, Version=
1.0.0.0, Culture= neutral, PublcKeyToken=
0123456789abcdef" not /a:MyAssembly.dll. An application that
dynamically loads one or more private assemblies (for example,
"plug-in" assemblies) based on a configuration file, presence in a
well-known directory, or some other dynamic resolution scenario might want to
use this obfuscation option. Such an application typically does not have a
static reference to all possible plug-in assemblies compiled into the
application. Therefore, the /application option wouldn't normally obfuscate
such assemblies. You can specify the /a command line option one or more times
to instruct Demeanor to obfuscate one or more additional assemblies as part
of the whole application obfuscation. |
/application |
This option requests
obfuscation of an entire application. Demeanor begins by loading the assembly
that you specify on the command line; typically this
is an .EXE assembly. Demeanor obfuscates the application plus all dependent
private assemblies that are statically referenced by the specified
application, plus their statically referenced dependent private assemblies,
and so on. Demeanor uses the .NET Framework assembly resolution rules when
locating referenced private assemblies. Demeanor will only obfuscate a
referenced assembly when it can be loaded from the application's private
assembly path. (In other words, Demeanor doesn't process any shared assembly
that the application might reference.) Demeanor recursively
processes private assemblies. For example, when the application assembly
listed on the command line references private assembly A, and assembly A
references private assembly B, Demeanor for .NET will obfuscate all types and
members, public and private, in assembly B. Demeanor then updates the
references in A so they correctly reference the obfuscated types and members
of B. Demeanor then obfuscates the public and private types and members of A,
and similarly updates the references in the application assembly so they
correctly reference the obfuscated types and members of A. Demeanor then
obfuscates the public and private types in the application. Demeanor places all
obfuscated assemblies in the output directory. |
/config:<configFile> |
This option requests
Demeanor for .NET to read one of its previously written report files and use
the information in the file when performing the current obfuscation. Presently, you use this
option to request incremental obfuscation on one or more assemblies.
Incremental obfuscation causes Demeanor to assign the same obfuscated names
to types and members during multiple obfuscation runs. This allows you to
obfuscate a new version of an assembly and having
previously existing types and members to be assigned to the same obfuscated
names as an earlier version. Client applications that reference the
obfuscated types and members will continue to work as the types and members
in the new assembly have the same names as before. When Demeanor assigns an
obfuscated name to a type or member, it first checks to see if an obfuscated
name for the type/member is present in the specified configuration file. When
present, Demeanor assign that obfuscated name, when
possible, to the type or member. When not present, Demeanor assigns a new
obfuscated name to the type or member. When the prior name isn't acceptable
during the current obfuscation run, Demeanor displays a warning message on
the console and assigns a new, unique obfuscated name to the type or member.
Short form (/c:) |
/encryptstrings |
This option requests
Demeanor for .NET to encrypt the literal strings present in the obfuscated
assemblies. The presence of clear-text literals in an assembly can provide
significant information to someone attempting to reverse engineer an
assembly. For example, she could search for a significant literal string, for
example "Invalid password", find the method that references the
literal and have a good idea on which method to concentrate. Demeanor for .NET also
injects the appropriate decryption code into all methods that reference a
string literal. This additional code increases the size of your methods and
causes them to run slightly more slowly. (Short form /es) |
/flow:<level> |
This option requests
Demeanor for .NET to obfuscate the control flow of the methods in the
obfuscated assemblies. The <level> parameter can be 'None', 'Minimum',
'Moderate' and 'Maximum'. The default value is 'None'. Increasing the level
of control flow obfuscation produces more difficult to understand
methods. However Demeanor injects
additional instructions into your methods to obfuscate the code. These
additional instructions increase the size of your methods and causes the methods to run more slowly. (Short form
/f:<level>) |
/noserializable |
When you specify this
option, Demeanor does not obfuscate the name of all types to which you have
application the [Serializable] attribute. Additionally, Demeanor does not
obfuscate the name of any of the fields in such types. However, Demeanor will
continue to obfuscate the name of any methods in such types, as appropriate. When you serialize a
type, the .NET runtime writes the name of the type and the names of each of
the serialized fields to the output stream. When you obfuscate such types,
the output stream contains the obfuscated type and field names. When you want
an unobfuscated build and an
obfuscated build of your assembly, for example, a Debug and a Release build,
to be able to exchange serialized types, it is necessary to
use consistent names in the serialized data stream. While you can
achieve this using incremental obfuscation and configuration files, this
option makes the process much easier. (Short form /noserial) |
/hinderreflection[+|-] |
By default, Demeanor
injects additional constructs into an obfuscated assembly that increase the difficulty of decompiling the assembly. The /hinderreflection- option prevents Demeanor from injecting
these additional constructs. |
/prefix:<value> |
The
/prefix:<value> option allows you to specify a namespace prefix that
Demeanor will prepend to each obfuscated top-level type
name. For example, when you specify the -prefix:WiseOwl command line option, the
top-level obfuscated type names will be, by default, WiseOwl.a, WiseOwl.b, WiseOwl.c and
so on. |
/satelliteassemblies[:<culture>[,<culture>]*] |
The satelliteassemblies option
request Demeanor to update the names of managed resources in culture-specific
satellite resource assemblies. You can specify the exact culture-specific
satellite assemblies to update using this command line option multiple times
or by listing a comma-separated list of cultures on a single command line
option. |
/suppressildasm[+|-] |
By default, Demeanor
injects additional metadata into an obfuscated assembly that suppresses decompilation of the assembly by ILDasm. The /suppressildasm-
option prevents Demeanor from injecting this additional metadata, which
allows decompilation by ILDasm. |
/xa:<assemblyName> |
This option
excludes an assembly from the set of assemblies obfuscated when you
specify the /application command line option. The assemblyName parameter
is the display name of the assembly; it is not the file name. For example,
you would specify /a:"MyAssembly,
Version=1.0.0.0, Culture=neutral, PublcKeyToken=0123456789abcdef"
not /a:MyAssembly.dll. |
4.1 None
There
are no known issues with the latest version of Demeanor for .NET. However, we
continually upgrade our product so check back occasionally to review the latest
release notes.
5.1 The ResourceManager,
Reflection and managed resources
Some
parts of the .NET Framework Class Libraries use Reflection to determine the
name of a type at runtime. They then use the name in various ways to locate
other data. When such code uses a type that has been obfuscated, it may not be
able to find the other data it needs when the code locates the other data by
comparing names.
For
example, the Visual Studio.NET Form Designer generates the following code in
the InitializeComponent method.
System.Resources.ResourceManager resources = new System.Resources.ResourceManager (typeof(MainForm));
This ResourceManager constructor usage tells the resource
manager to infer the assembly, the base name, and a namespace for .resources
files from the constructor's Type argument. The ResourceManager assumes
you will be using satellite assemblies and want to use the default ResourceSet class. Given a full type name such
as MyCompany.MyProduct.MainForm,
the ResourceManager will look for a
.resources file (in the main assembly and satellite assemblies) named "MyCompany.MyProduct.MainForm.[culture name.]resources"
in the assembly that defines MainForm. However,
once Demeanor obfuscates the name of the type, for example, to 'Aa', the
resource manager will look for a .resources file (in the main assembly and
satellite assemblies) named "Aa.[culture name.]resources" in the
assembly that defines MainForm.
Demeanor
looks for managed resources in an obfuscated assembly that have
the same name as an obfuscated class name with the addition of the
".resources" suffix. When it finds such a resource, Demeanor changes
the name of the resource to be the obfuscated class name plus a ".[culture
name.]resources" suffix. This means that the ResourceManager's previously
described behavior continues to work correctly on an obfuscated class and its
resource.
Demeanor,
by default, does not try and update the names of resources in culture-specific
satellite assemblies. Unfortunately for Demeanor (but fortunately for you as a
developer), there is no information in a primary assembly that describes what,
if any, satellite assemblies exist that are associated with the primary
assembly. This is good for you as a developer because it allows you to create
additional satellite resource assemblies, deploy them into the appropriate
culture-specific subdirectory of the primary assembly's directory, and the
Runtime will automatically use the new satellite assembly as required -- all
without any need to recompile or redeploy the primary assembly.
However,
you can request that Demeanor update all culture-specific satellite resource
assemblies by using the -satelliteassemblies command
line option. For example, the following command line requests Demeanor
obfuscate the specified assembly, then find all installed culture-specific
satellite resource assemblies and update the names of their resources.
Demeanor -sa MyAssembly.exe
In the
above example, Demeanor would update the French satellite resource assembly (fr\MyAssembly.resources.dll) and the German satellite
resource assembly (de\MyAssembly.resources.dll) if they are present (specified
relative to the location of the MyAssembly.exe file) and place the obfuscated
satellite resource assemblies in the same subdirectory relative to the Demeanor
output directory. In this example, the output directory defaults to
"Demeanor" so the three obfuscated assemblies would be placed in
"Demeanor\MyAssembly.exe", "Demeanor\fr\MyAssembly.resources.dll"
and "Demeanor\de\MyAssembly.dll".
Because
there is no information in an assembly that describes which cultures the
assembly supports, when you specify the -satelliteassemblies command
line option without any cultures, Demeanor searches for all 202 possible
culture-specific satellite resource assemblies. When you additionally request
entire application obfuscation using the -app command line option, Demeanor
search for each of the 202 possible culture-specific satellite resource
assemblies for each assembly in the entire application. There are, by default,
four locations in which a satellite resource assembly can be placed. This is a
lot of searching for assemblies that frequently are not present. However, it
does make Demeanor easy to use and it automatically finds and obfuscates new
culture-specific resource assemblies as you create them.
However,
if you have a fixed, known set of cultures that you support, you can have
Demeanor more efficiently look for and obfuscate only the satellite resource
assemblies for the specific cultures. For example, the following two command
lines are effectively identical and each requests Demeanor to obfuscate the
specified assembly plus its French and German satellite resource assemblies.
Demeanor -sa:fr -sa:de MyAssembly.exe
Demeanor -sa:fr,de
MyAssembly.exe
You
can theorectically have a resource that has
a name that matches an obfuscated class name without there being any implied
relationship between the resource and the class. In this case, you may want to
disable Demeanor's automatic renaming of the resource. You can globally disable
this automatic resource renaming using the -noresources command
line option. Alternatively, you can specify the name of the resource as an item
to exclude using either the /x or /xr command
line options.
5.2 Examples of regular
expression exclusions
You can
specify the /xr (exclude using regular
expression) switch as often as needed on the command line to specify multiple
exclusion patterns.
The
following regular expression excludes the type "Namespace.Type"
as well as the members of the type:
/xr:Namespace\.Type(::.+)?
The
following regular expression does not exclude the type itself but does exclude
all members of the type "Namespace.Type".
/xr:Namespace\.Type::.+
This
regular expression excludes the type itself but does not exclude all members of
the type "Namespace.Type". Of course, in
this case, it's easier just to use the exclude (-x) command line switch.
/xr:Namespace\.Type$
Demeanor
supports the regular expression syntax used by the classes in the System.Text.RegularExpressions namespace.
5.3 Examples of entire application
obfuscation
You can
specify the /app command line option to request Demeanor to obfuscate the
assembly specified on the command line (called the root assembly) plus all of its dependent assemblies. Demeanor searches the
root assembly's private path and only obfuscates the dependent assemblies that
reside on the private path. Typically, this means Demeanor obfuscates the root
assembly plus any dependent assemblies located in the same directory as the
root assembly.
For
example, consider the case where assembly Client.exe requires private assembly
MyLib.dll and both files reside in the same directory.
The
following command line obfuscates the public and internal symbols in MyLib.dll,
changes Client.exe to reference the public types in MyLib.dll using their new
obfuscated names, then obfuscates the internal types of Client.exe leaving its
public types unchanged.
Demeanor -app Client.exe
Normally,
Demeanor will not obfuscate the public types of an assembly unless it can also
modify all references to those public types to use the new, obfuscated type
names. This means the public types of the root assembly never get obfuscated by
default, as there may exist other assemblies that reference the public types in
the root assembly. Typically, you will not have any references to the public
types in an .exe assembly so you can modify the above example to obfuscate all
types in both assemblies using the following command line. The -all option
requests Demeanor to obfuscate the public types of the specified root assembly.
Demeanor -app -all Client.exe
Some
applications dynamically reference assemblies. For example, an application
could read a configuration file and dynamically load one or more optional
"plug-in" assemblies. During entire application obfuscation, Demeanor
creates a list of all assemblies statically referenced by the root assembly. Demeanor then recursively adds to the list all assemblies
referenced by assemblies in the list that are present in the private path. In
other words, Demeanor creates a list of all statically referenced assemblies
used directly or indirectly by the root assembly and obfuscates the ones
present in the private path. You can add additional dynamically referenced
assemblies to this list to have them obfuscated as part of the entire
application obfuscation processing.
Here is
one example where this is useful. Your application dynamically loads
"plug-in" assemblies. Your application statically references an
interface assembly. The plug-in assembly also statically references the
interface assembly. the application never statically
references any types in the plug-in assembly. Entire application obfuscation of
your client will obfuscate the client and the interface assembly. However the plug-in assembly references types in the
interface assembly that have been obfsucated.
You need the plug-in assembly to be updated to use the new obfuscated type
names and for the plug-in assembly itself to be obfuscated. Use the -a command
line option to add the plug-in assembly to the set of assemblies processed by
entire application obfuscation. For example, to include the assembly
Plugin.dll, use its display name like this:
Demeanor -app -all -a:"Plugin,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=
0123456789abcdef" Client.exe
Demeanor
always needs to load all assemblies referenced directly or indirectly from
every assembly that it obfuscates. However, when Demeanor loads assemblies, it
places the assembly into one of two categories: assemblies to be obfuscated and
assemblies used for reference only. Demeanor always places the assembly
that you specify on the command line into the "to be obfuscated"
category. In addition, Demeanor also places all assemblies that you specify
with the /a command line option into the "to be obfuscated" category.
Additionally, when Demeanor asks the runtime to load an assembly and the
assembly loads from the same directory as the assembly that you specify on the
command line (or one of its subdirectories), Demeanor adds the assembly to
the "to be obfuscated" category.
Demeanor
assumes that assemblies that load from the private path are part of the
application, therefore should be obfuscated during entire application
obfuscation. Conversely, Demeanor places all assemblies that load from outside
of the private path, for example, from the Global Assembly Cache, or elsewhere
due to a codebase assembly redirect, into the "for reference only"
category and doesn't obfuscate them. Usually, this is exactly the behavior you
want. Demeanor obfuscates your application assemblies but doesn't change mscorlib, System.Windows.Forms,
or similar assemblies.
Occasionally,
you may have an assembly in the private path but not want Demeanor to obfuscate
it. A common example is when your application uses a third-party
strong-named assembly that you deploy privately with your application. You
cannot include the assembly in entire application obfuscation because
obfuscating it invalidates its strong name. In this case, you can specify the /xa command line option. Demeanor will then categorize
this assembly as one to be used "for reference only" and therefore
not obfuscate it.
Demeanor -app -all -a:"Plugin,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=0123456789abcdef"
-xa:"ThirdParty,
Version=2.3.0.0, Culture=neutral, PublicKeyToken=0123456789abcdef"
Client.exe
5.4 Examples of string
encryption obfuscation
The
first technique used by someone attempting to reverse engineer your application
is to look at the string literals of the program, find an interesting string,
locate the code that uses the string and concentrate on that code. Demeanor
allows your to
make the search for interesting string literals more difficult by encrypting
the string literals in the obfuscated assemblies. You can specify the /es command
line option to request Demeanor to encrypt the literal strings in the
obfuscated assemblies.
Demeanor -es -app -all Client.exe
5.5 Examples of incremental
obfuscation
Specify a report file from a prior Demeanor
obfuscation run as the input to a subsequent obfuscation run and Demeanor will
assign types and members the same obfuscated name as they had in the prior run.
Demeanor -c:MyAssembly.xml
MyAssembly.dll
You can
also use this feature to control exactly what names are assigned to each type
and member in each assembly in the obfuscation. However, there can be cases
where Demeanor cannot assign the requested name to a type or member. Here's
exactly how this feature works.
Demeanor determines all types and
members that cannot be obfuscated. For example, normally public and protected
types and members are not obfuscated. Excluded types and members are also not
obfuscated.
After Demeanor determines the names
that will not be changed, it begins to rename the remaining types and members.
For each one, it checks the configuration file to see if the type/member has a
preferred name. When a preferred name is present in the configuration file,
Demeanor renames the type/member to that preferred name when possible. When the
preferred name isn't available, for example, when a non-obfuscatable type/member
in the same scope already has the preferred name, Demeanor will issue a warning
and will assign a new obfuscated name to the type/member.
Finally, Demeanor assigns all
remaining obfuscatable types and members a
new, meaningless name.
In addition to incremental obfuscation, you can also use this feature to assign
any desired name to any type or member, within the above constraints. For
example, you can specify the original type/member name
as the obfuscated name. In effect, this says "Do
not obfuscate this name" and is equivalent to excluding the symbol from
obfuscation.
5.7 The -noserializable option
When you
use the .NET Runtime's binary serialization functionality, the runtime writes
the serialized state of an object to a stream. This information includes the type name of the serialized instance plus the name of each
of the serialized fields and their values. .NET determines the type name and the field names at runtime, therefore when
serializing an obfuscated type, the names saved to the stream are the
obfuscated names. Some developers want to serialize a non-obfuscated type instance and deserialize the data into an obfuscated
version of the same type, or vice-versa. Normally this doesn't work because the
serialized type name and serialized field names aren't
identical in the obfuscated and unobfuscated code.
It's easy to work around this issue with the field names. You simply implement
the ISerializable interface on the type and
you have full control over the serialized field names. However, this still
leaves the type name itself as a problem.
You can
exclude a type name from obfuscation but, when you
have numerous serializable types, this can become tedious. The -noserializable option requests Demeanor to never
obfuscate the name of any type with the [Serializable] attribute. In addition,
Demeanor will not obfuscate any serializable field of a serializable type.
Demeanor obfuscates all other members of a serializable type normally. For
example, given the follow class:
[Serializable]
internal class MyClass {
private string m_MyField;
[NonSerialized]
private int m_NotSerialized;
public string MyField
{
get { return m_MyField; }
}
public void Method1 (string s) { ... }
private int Method2 (string s) {
... }
}
When you
specify the -noserializable option and
obfuscate this class, Demeanor translates it to the following:
[Serializable]
internal class MyClass {
private string m_MyField;
[NonSerialized] private
int a;
public string a() {
... }
public void b(string) { ...
}
private int c(string) { ...
}
}
However,
when you do not specify the -noserializable option
and obfuscate this class, Demeanor translates it to this instead:
[Serializable]
internal class a {
private string a;
[NonSerialized] private
int b;
public string a() {
... }
public void b(string )
{ ... }
private int c(string ) {
... }
}
5.8 Obfuscating smart device
applications that use the .NET Compact Framework
Demeanor
no longer supports obfuscating .NET Compact Framework assemblies.
5.9 The System.Reflection.ObfusactionAttribute class
With the
addition of the System.Reflection.ObfuscationAttribute type in .NET 2.0 , there is no
longer any necessity for Demeanor for .NET's equivalent custom attribute
- WiseOwl.ObfuscationAttribute. All features
previously supported by the WiseOwl.ObfuscationAttribute are
now available on the standard system attribute. Click here to view
documentation on the System.Reflection.ObfuscationAttribute.
You have
two alternatives when you need to control exactly which types and members that
Demeanor for .NET obfuscates. You can specify exclusions via the command
line/configuration file. You can also add such specifications directly to your
source files by adding the System.Reflection.ObfuscationAttribute to a type or member. To this
attribute, add the custom attribute as shown in the following examples. Note:
while most examples show the attribute used on a type
definition, you can also apply the attribute to a member of a type (field,
method, property, event, and nested type).
For
example, Demeanor for .NET normally obfuscates the name of an internal
top-level type. You can prevent this default behavior by applying an instance
of the System.Reflection.ObfusactionAttribute to the type. The System.Reflection.ObfusactionAttribute instance contains a property
named Exclude that
has a default value of true. Therefore applying the attribute to a type or member
without setting this property causes Demeanor for .NET to exclude the type or
member from obfuscation.
[System.Reflection.ObfuscationAttribute
()]
internal class MyClass {
.
.
.
}
Exclude property
As one
example, Demeanor will not, by default, obfuscate the name of an public top-level type. You can, however, request
that Demeanor obfuscate such a type name by adding a System.Reflection.ObfuscationAttribute with its Exclude property set to false.
[System.Reflection.ObfuscationAttribute
(Exclude=false)]
public class MyClass {
.
.
.
}
ApplyToMembers property
By
default, the System.Reflection.ObfuscationAttribute attribute applies only to the
type or member that you decorate with the attribute. When you decorate a type
with the attribute, you can set the ApplyToMembers property to true to specify that
the Exclude property
setting should apply not only to the type, but also to all members (fields,
methods, properties and events) of the type. The
following example requests that Demeanor not obfuscate the type and all of its fields, methods, properties and events.
[System.Reflection.ObfuscationAttribute
(Exclude=true, ApplyToMembers=true)]
public class MyClass {
.
.
.
}
StripAfterObfuscation property
Demeanor
for .NET normally strips all System.Reflection.ObfuscationAttribute instances from an obfuscated
assembly as they are not normally needed at runtime. However, you can request
that Demeanor not remove a particular ObfuscationAttribute by setting the StripAfterObfuscation property to false. The default value is true.
[ObfuscationAttribute
(Exclude=false, StripAfterObfuscation=false)]
internal void MyMethod CalculateRate {double interestRate)
{
}
Features
The System.Reflection.ObfuscationAttribute has a string property
called Feature. An
obfuscator can parse the Feature string
to allow you to enable custom features of that obfuscator. Demeanor
for .NET supports the following features: ObfuscatedName, PreserveLiteralFields, PreserveLiteralValues, and PreserveParameters.
ObfuscatedName feature
You can
specify the string to use as the obfuscated name of the type. Note that when
you specify the obfuscated name, it is your responsibility to insure that the specified name will be unique within
the appropriate scope.
[System.Reflection.ObfuscationAttribute
(Exclude=false, Feature="ObfuscatedName=Foo")]
public class MyClass {
.
.
.
}
PreserveLiteralFields feature
When
Demeanor obfuscates an enumerated type, it normally deletes all enumerated
names and values from the enumeration declaration. For example,
internal enum Color {
Red = 1,
Green = 3,
Blue = 5,
}
becomes
internal enum a {
}
This
increases the difficulty of reverse engineering the code. However, when your
code uses the System.Enum static methods to convert an
enumerated value into a string, or parse a string into an enumerated value, or
check if a value is defined, previously you had to exclude the enumerated type
from obfuscation so Demeanor did not remove the fields
of the enumeration. You can use the System.Reflection.ObfuscationAttribute to enable obfuscation for an
enumerated type, but also specify that Demeanor for .NET should preserve the
enumerated value names. The default setting of the PreserveLiteralFields feature is false but specifying the feature
changes the setting to true. For
example,
[System.Reflection.ObfuscationAttribute
(Exclude=false, Feature="PreserveLiteralFields")]
internal enum Color {
Red = 1,
Green = 3,
Blue = 5,
}
after
obfuscation becomes
internal enum a {
Red = 1,
Green = 3,
Blue = 5,
}
You can
explicitly specify the true/false setting of the feature:
[System.Reflection.ObfuscationAttribute
(Exclude=false, Feature="PreserveLiteralFields=true")]
internal enum Color {
Red = 1,
Green = 3,
Blue = 5,
}
PreserveLiteralValues feature
When
Demeanor obfuscates an enumerated type, it normally deletes all enumerated
names and values from the enumeration declaration. For example,
internal enum Color {
Red = 1,
Green = 3,
Blue = 5,
}
becomes
internal enum a {
}
This
increases the difficulty of reverse engineering the code. However, this can
cause your code to fail unexpectedly when your code uses the System.Enum static methods to check if a
value is defined. These static methods inspect the definition of the
enumeration to determine if the enumerated type defines the value. Previously,
to enable such validation code to work, you had to exclude the enumerated type from obfuscation so Demeanor did not remove the fields and
values of the enumeration. However, you can use the System.Reflection.ObfuscationAttribute to enable obfuscation for an
enumerated type, and obfuscate the names of the symbols, but also specify that
Demeanor for .NET should preserve the associated enumerated values. The default
setting of the PreserveLiteralValues feature is false but specifying the feature
changes the setting to true. For
example,
[System.Reflection.ObfuscationAttribute
(Exclude=false, Feature="PreserveLiteralValues")]
internal enum Color {
Red = 1,
Green = 3,
Blue = 5,
}
after
obfuscation becomes
internal enum a {
a = 1,
b = 3,
c = 5,
}
You can
explicitly specify the true/false setting of the feature:
[System.Reflection.ObfuscationAttribute
(Exclude=false, Feature="PreserveLiteralValues=true")]
internal enum Color {
Red = 1,
Green = 3,
Blue = 5,
}
When you
specify both PreserveLiteralFields and PreserveLiteralValues, Demeanor for .NET uses the PreserveLiteralFields setting.
PreserveParameters feature
When
Demeanor for .NET obfuscates a method, by default, Demeanor also obfuscates the
names of the parameters to that method. In very unusual scenarios, you may wish
to obfuscate the name of the method but preserve the names of the parameters to
that method. You can set the PreserveParameters feature to true to request that Demeanor for
.NET not obfuscate the parameter names. The default setting of the PreserveParameters feature is false but specifying the feature
changes the setting to true.
[System.Reflection.ObfuscationAttribute
(Exclude=false, Feature="PreserveParameters")]
internal void MyMethod CalculateRate {double interestRate)
{
}
You can
set multiple features at the same time on an attribute in the expected way:
[ObfuscationAttribute
(Exclude=false, Feature="ObfuscatedName=q, PreserveParameters=true")]
internal void MyMethod CalculateRate {double interestRate)
{
}
5.10 The /prefix:<value> command
line option
Normally
Demeanor obfuscates a top-level type name by removing
any namespace qualification and replacing the original name with a short
meaningless name such as 'a', 'b', 'c' and so on. This technique maximally
reduces the size of the obfuscated assembly and removes semantics that expose
the original namespace organization of the types. Unfortunately, the Microsoft
C# compiler cannot distinguish between two identically named types that reside
in different assemblies. (The IL assembler can distinguish such types for those
extremely adventurous developers.) For example, if you create a type called 'WiseOwl.Foo' in assembly WiseOwl.dll and another vendor
creates a type called 'WiseOwl.Foo' in assembly
FlyByNight.dll, a client application cannot reference both assemblies during a
single compilation and use either of the two 'WiseOwl.Foo'
types.
Normally,
you don't run into the situation because developers often use namespaces and
generally create unique type names - but there are no guarantees. Similarly, if
two vendors each ship an assembly containing a top-level type called 'GreateGizmo' (notice the lack of a namespace
qualification), then a client application cannot reference both vendor's
assemblies even if the application only needs to use only one of the 'GreatGizmo' types.
Obfuscation
exacerbates this problem. By default, an obfuscated assembly contains top-level
types named 'a', 'b', 'c' and so on. Two obfuscated assemblies, even from
different vendors, may easily have identically named types called "a',
'b', 'c' and so on. The Microsoft C# compiler cannot distinguish two types
called 'a' in different assemblies any more than it can type types called GreateGizmo.
5.11 Microsoft Visual Studio
Integration
Microsoft
has removed support for the Visual Studio integration used by Demeanor for
.NET, Enterprise Edition. You will need to setup a
Post Build step in your Visual Studio project to run Demeanor.
5.12 MSBuild Obfuscation
Task
Demeanor
for .NET, Enterprise Edition installs a build task for the .NET Framework MSBuild utility. Here is a sample project file that
invokes the Obfuscation task.
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003 ">
<UsingTask TaskName="Wiseowl.Build.Tasks.Obfuscate" AssemblyFile="$(MSBuildExtensionsPath)\Wise
Owl Consulting LLC\Demeanor for .NET Enterprise
Edition\v5.0\WiseOwl.Build.Tasks.dll"/>
<Target Name="MyTarget">
<Obfuscate RootAssembly="C:\Src\MiniApp\obj\Release\MiniApp.exe"
All="false"
CompilerControlled="false"
EncryptStrings="true"
EntireApplication="false"
ForceParameters="false"
Insitu="false"
NoEnumerations="false"
NoEvents="false"
NoFields="false"
NoLogo="false"
NoMethods="false"
NoParameters="false"
NoProperties="false"
NoResources="false"
NoSerializable="false"
NoTypes="false"
OverloadOnStatic="false"
Verbose="true"
Names="Alpha"
Exclude="SomeClass"
ExcludeRegEx="BadClass*"
Prefix="WiseOwl"
Flow="Minimum"
HinderReflection="true"
SuppressIldasm="true"
KeyFile="C:\SomeDir\PublicPrivate.snk"
KeyContainer="KeyContainerName"
AdditionalAssemblies="MyAssembly,
Version= 1.0.0.0, Culture= neutral, PublcKeyToken=
0123456789abcdef"
ExcludedAssemblies="OtherAssembly,
Version= 1.0.0.0, Culture= neutral, PublcKeyToken=
0123456789abcdef"
Config="MyConfig.xml"
Report="MyReport.xml" />
</Target>
</Project>
|
|
5.0.4054.0 |
Fix 'Unexpected TYPESPEC
coded index' error. The problematic TYPESPEC is now permitted. |
5.0.4055.0 |
Network Edition: Fix
problem with network license server reaper thread failing to reclaim expired
licenses occasionally. |
5.0.4123.0 |
Enhance Demeanor to robustly
operate on assemblies obfuscated by tools that produce invalid assemblies.
You can run PEVerify on such assemblies
and will see errors such as [MD]: Error (Structural): Table=0x00000001,
Col=0x00000000, Row=0x00000159, has coded rid out of range. |
5.0.4364.0 |
Further enhance Demeanor
to robustly operate on assemblies obfuscated by tools that produce invalid
assemblies. Fix a bug where Demeanor
only honored the assembly resigning option when the -verbose option was also
requested. Demeanor now will resign assemblies, if requested, without
-verbose enabled. |
5.0.4366.0 |
Added more
anti-Reflection features to Demeanor obfuscated assemblies. |
5.0.4410.0 |
Added the /hinderreflection and /suppressildasm command
line options. |
5.0.4459.0 |
Update the schema parsed
by the StackDecode utility to support the
/hinderreflection and /suppressildasm command
line options. |
5.0.4573.0 |
Update the licensing
component to support additional licensing features. |
5.0.4774.0 |
Added Visual Studio 2012
integration. |
5.0.5385.0 |
Added Visual Studio 2013
integration. |
5.0.5466.0 |
Corrected an error
during processing of a mixed IL & native code 64-bit assembly. |
5.0.5565.0 |
Significant performance
improvement during obfuscation of very large assemblies. |
5.0.7058.0 |
Significant change so
that Demeanor no longer needs to transitively load all assemblies potentially
used, directly or indirectly, at runtime by the assembly being obfuscated.
Demeanor now only loads assemblies directly and statically referenced by the
assembly being obfuscated. This significantly reduces the time to obfuscated a binary. |
5.0.7077.0 |
Added -unregister
command line option to enable moving a Demeanor installation to a different
computer. |
5.0.8493.0 |
Add support for .NET
Standard assemblies |
Did you find this information useful? Please send your
suggestions and comments about the documentation to support@wiseowl.com.