This is a manual for version 3.3 of the 'bobs' Python source code obfuscator by Chris Niswander.
Other Python obfuscation tools by Chris Niswander are not covered by this manual.
The obfuscator can provide two main benefits:
The obfuscator outputs Python source code:
The obfuscator can systematically change to gibberish most of the identifiers defined by your code: variable names, function names, function argument names, class names, class member names (data, methods, and method arguments), object names, and module names.
A body of code consisting of multiple directories, constituting many packages and subpackages, can be obfuscated as a single unit. These (sub)directory/(sub)package names can themselves be obfuscated in the output code.
The obfuscator recognizes through a general automated process which identifiers in your code can safely be changed without harming code functionality.
You have options to override the obfuscator's automatic judgement in the cases you specify, to selectively control which identifiers are changed and which are preserved by the obfuscation process.
Comments and docstrings are removed and/or replaced. Module docstrings can optionally be replaced by your up-to-date copyright notices.
In case your code might include bugs, the obfuscator includes a utility that Python code's author/publisher can use for de-obfuscating tracebacks.
Additional features, options, and use scenarios are discussed in more detailed parts of this manual.
The obfuscator runs on Windows, Linux/BSD, and Mac OS X (Apple Macintosh.) Some other platforms may also be supported.
The obfuscator runs under Python 2.5, 2.6, and 2.7.
All features of Python 2.5 are supported, and almost all features of Python 2.6 and/or 2.7 are also supported.
For example, from __future__ import absolute_import
is fully supported. Absolute imports and explicit relative imports are supported. Older-style imports are also supported. from __future__ import print_function
is best supported when running in Python 2.7 or later.
Some features of Python 3 (devised for and made standard features of the Python language in Python 3) are best supported by using from __future__ import ...
statements in your code to make your code compatible with a broader range of Python versions. See the appropriate subsection of this manual for details.
For more information, see Supported and Unsupported Python Language Features, and Obfuscating code that uses some Python 3 features while obfuscating with a Python 2 obfuscator.
In Python, a "module" normally is a single file of source code (or is a special form corresponding to that single file of source code, e.g. a .pyc file.)
A file named foo.py
corresponds to a module named foo
.
In Python, a "package" is an entire directory of files of source code,
which can be imported as a unit.
To permit importation as a package, one of the files
in the directory is an __
init__
.py file.
For more information about the obfuscator's handling of packages and subpackages, see Obfuscating Packages and Subpackages as a Single Unit, in Which Even Inter-Package Interfaces Are Themselves Obfuscated.
In our jargon, a body of code that you want to obfuscate.
A corpus might be a program or a package. While the manual often refers to "your program", the same obfuscation method normally will work on a corpus that is a single-directory package. (See above for definition of "package".)
If the corpus' directory includes subdirectories that are actually packages in their own right, those subdirectories are part of the corpus (as of version 3.0 of the obfuscator.)
Just as the pre-obfuscated corpus did, a properly obfuscated corpus will continue to work correctly when used with the nonobfuscated packages.
also known as a .poi file:
a file which, through settings/parameters it specifies, instructs the obfuscator to obfuscate a corpus according to your wishes.
An identifier might be a variable name, a module name, a class name, an object name, a function name, a method name, a package name, etc.
An identifier is a name used in/by software code.
In our obfuscation jargon, a 'sacred' identifier is one that the obfuscator shall not change/replace.
The obfuscator attempts to automatically treat identifiers as sacred if they appear to be needed to interface to code outside your corpus.
Some examples of identifiers likely to be sacred by default:
__
init__
(The Python interpreter needs to recognize this function/method by name,
to know when to call it as a constructor.)
(Many other identifiers might be sacred by default, depending on which non-obfuscated modules and packages your corpus imports directly or indirectly. You can also manually specify whether an indentifier is to be sacred or profane.)
In our obfuscation jargon, a 'profane' identifier is one that the obfuscator shall almost certainly change/replace.
Usually profane identifiers are identifiers defined by your own code, that are not obviously used/needed by sacred code.
The obfuscator is provided as a collection of .pyc files that a Python interpreter can run. You might copy these files to a directory on the machine where you wish to run the obfuscator. If the files are supplied to you on a CD, you can even run them directly on (from) the CD.
Use the .pyc files that are appropriate for your version of Python. Supported versions of Python are 2.5, 2.6, and 2.7. The .pyc files for each version of Python are delivered to you in their own separate directory or in their own separate zipfile (or other type of archive file as appropriate to your case). (In some cases, the three individual zipfiles might be delivered within a combined zipfile with a name like 'all.zip'.)
You may add the obfuscator files' directory to your PYTHONPATH shell variable, or when you run the obfuscator you may simply specify it by pathname so it will be found.
If you are using advanced features of the obfuscator that require ob_directives.py
be imported by your code, you can copy that file directly into your project's source code directory (or directories).
Alternatively, you could copy ob_directives.py
into any directory
that you have in your PYTHONPATH
, so it will be available to your corpus.
A copy of the ob_directives.py file is provided within this manual.
Before you run the obfuscator you will have to create a .poi file specifying what the obfuscator does for your project. (Creating .poi files is explained in the Beginning Use Cases: Let's Obfuscate and Project Settings File (.poi file) sections of this manual.)
Windows example of command line to run the obfuscator, if d:\ob-4py27 is where you happen to have placed the obfuscator .pyc files for Python 2.7:
python d:\ob-4py27\bobs.py someprojectfile.poi
a similar UNIX example:
python ~/ob-4py27/bobs.py someprojectfile.poi
If you don't specify the the .poi file on the command line, the obfuscator will prompt you for it.
Suppose you have a program called simple.py, that you want to obfuscate into a program called simple_public.py before you release it to the public.
With a text editor you can make a .poi file that contains the following instructions to the obfuscator. Note that the .poi file is simply Python source code, using Python data types.
The name of a .poi file may include letters, numerals, and underscores. Examples:
simple_public_1.poi
vidplayer_customized_4_ABC.poi
Any relative pathnames (of directories or files) specified in the .poi file are assumed to be specified relative to the location of the .poi file itself.
On Windows, any absolute pathnames should not be in UNC form: for example, 'X:\\foo\\'
is ok, but '\\\\SRVR19\\DRIVE5\\foo\\'
is not ok.
For example, here's a .poi file that obfuscates a program 'simple.py' to an output program called 'simple_public.py' in subdirectory 'out'. Any other modules in the same directory that are imported by simple.py (imported directly or indirectly) are automatically included in the obfuscated output.
# main file of the program to be obfuscated. # (You can leave off the .py extension.) input_root_module = 'simple' # name for program's main file once obfuscated. # (You can leave off the .py extension.) output_root_module = 'simple_public' # where to look for corpus code to obfuscate, # and where to put the obfuscated code. # Takes code from directory '.' # and writes obfuscated code to directory 'out' project_dirs_in2out = ['.', './out'] # easiest way to handle keyword arguments and get fully working code. keyword_arguments_shortcut = True
To produce an obfuscated program simple_public.py in subdirectory out, run the obfuscator, and when it prompts you, enter the pathname of the .poi file.
Alternatively, you can specify the .poi file on the command line, for example:
python bobs.pyc simple_public_1.poi
The single easiest way to handle your code's use of keyword arguments in function and method calls is to use this setting in your .poi file:
keyword_arguments_shortcut = True
The only reason this isn't the default setting is to provide reverse compatibility to people using project files that they developed for older versions of the obfuscator.
This setting prevents any keyword argument's name from being obfuscated, wherever it might appear in your code, if the keyword argument name isn't always made profane by associating it with OB_KEYARG() whenever it occurs in in function/method calls.
To get more obfuscation of some argument names in your code than this setting alone provides, see the other techniques discussed in Keyword Arguments: Intermediate And Advanced Techniques.
For most programs you'll want a few more settings in your .poi file.
To emplace a copyright notice into the module docstring (aka "Documentation String") of each file of your obfuscated code, where reverse engineers could easily see it, you'll want to add the setting message_for_module_docstring.
Example:
message_for_module_docstring = \ """ ( C ) Copyright 2003-2012 by Bob Smith. All Rights Reserved. Please see http://www.bitboost.com for more information. """
If your obfuscated program crashes, the user will probably get a "traceback" message showing the state of your program's call stack when it crashed. If the user sends you the traceback message (possibly by email?), the message will look relatively cryptic, because identifiers will be obfuscated.
When you obfuscate your program, you can also produce a utility script that deobfuscates tracebacks, by using the trfix setting in your .poi file.
Example:
# produces my_trfix.py, # in subdirectory my_secret_troubleshooting_tools trfix = 'my_secret_troubleshooting_tools/my_trfix.py'
Feed the traceback text into the utility's stdin, followed by a newline/return, and the deobfuscated traceback will be written to stdout.
For more information, see the trfix entry in the Project Settings File -- Introductory Optional Settings section of this manual .
Related settings include makefix and revdict.
It is possible that the obfuscator might incorrectly obfuscate some identifier name in your corpus that it should not obfuscate, or might fail to obfuscate an identifier that it should obfuscate. In this case, you might want to use the .poi file settings profane_names and/or sacred_names
See the Project Settings File -- Introductory Optional Settings section of this manual for instructions.
First, obfuscate your corpus to .py files in the normal way (see other use cases).
Then, you can use the compileall.py file, which is included in the Python standard library, to compile your .py files to .pyc files.
Example command line for Python 2.4 and up:
python -m compileall .
where
.
is the directory containing the .py files you want to compile.
Example command line for Python 2.3 and up:
python c:\python23\lib\compileall.py .
where
.
is the directory containing the .py files you want to compile.
c:\python23
is where you have your Python interpreter installed.
Finally, use a shell script, bat file, makefile, or any other appropriate method to copy and/or package up your .pyc files.
If you are using the .poi file setting message_for_module_docstring, when you byte-compile your Python code you won't want to specify special optimizations that might remove the docstring (aka "Documentation String") from each module.
Also see: makefix and revdict, in the Project Settings File -- Intermediate and Advanced Optional Settings section of this manual.
Obfuscated code bundles up into an executable using py2exe, freeze, and other similar tools just like non-obfuscated code does.
Depending on your build process, you might need to use makefix to adapt a makefile, shell script, or batchfile for obfuscated module names. If so, please see the makefix setting in the Project Settings File -- Intermediate And Advanced Optional Settings section of this manual.
For more use cases, please see the Intermediate and Advanced Use Cases section of this manual.
The project settings file, or .poi file, is simple Python source code, supplying settings to the obfuscator so it can do the right thing for your obfuscation project.
Settings are supplied by setting variables, e.g.
settingname = settingvalue
Each of your obfuscation projects has its own .poi file, which is used as described in Running the Obfuscator.
They are:
For explanations of these, see the Beginning Use Cases -- Simplest Possible Case section of this manual.
If set to True, causes obfuscator to tell you where function calls use keyword arguments.
See this section.
Originally designed to help you make sure your copyright notice is present in each and every module of your obfuscated code.
Specifies a message to be emplaced into the obfuscated code, in each module's docstring (aka "Documentation String").
Example:
message_for_module_docstring = \ """ ( C ) Copyright 1900 by Y2K Scare Consulting Co. All Rights Reserved. Please see http://www.y2krazy.com for more information. """
Specifies a list of strings, which will begin the replacement identifier names generated by the obfuscator.
Example:
If your project settings file (.poi file) says:
namebases = ['truth__', 'beauty__', 'what__']
You might get obfuscated code resembling this:
def truth__2e3(what__2f8, beauty__2f7): what__2f8.beauty__2f7 = beauty__2f7
What if the obfuscator refuses to obfuscate one or more identifiers in your code that you would prefer that it obfuscate?
You can use profane_names to override the obfuscator's default behavior.
Specifies a single string, containing one or more identifier names separated by whitespace. These identifier names should be obfuscated!
Example:
# Standard library includes built-in functions, modules, etc. # named upper, zip, and path, # but I use my own hand-rolled functions instead, # and I want their names obfuscated! profane_names = ''' upper zip path '''
Logically, these names should not include names that you make 'sacred' (protect from obfuscation) by sacred_names nor by NOOB_IDS... in-code directives.
For use if the obfuscator, contrary to your wishes, obfuscates one or more identifiers in your code that you would prefer that it not obfuscate.
Specifies a single string, containing one or more identifier names separated by whitespace. These identifier names shall not be obfuscated!
Example:
# I want a user of my library to be able to see # these variables, functions, and classes # under their proper names! sacred_names = ''' star planet dwarf_planet asteroid planetoid comet spacecraft orbital_elements avg_albedo time_step '''
These names should not include names that you make 'profane' (mark for obfuscation) by profane_names nor by OB_IDS... in-code directives.
Write to this pathname a Python utility script that deobfuscates 'traceback' / "stack crawl" from the obfuscated Python code. Also deobfuscates the code itself.
To deobfuscate:
Example:
trfix = 'my_secret_troubleshooting_tools/trfix.py'
assert_enhancement = True
Slightly enhances assert statements so that:
blockout_sacred_names = True
The rationale behind this feature is discussed in Intermediate And Advanced Use Cases -- Copyright Registration.
If you wish to produce a version of your source code with the names of most standard library functions, modules, etc. provided by the Python interpreter 'blocked out', this setting can provide such a version of your source code. The resulting source code cannot be run, as the blocked out names have lost information!
One or more blocked-out spaces might be inserted before or after some names, before both the names and the inserted spaces are 'blocked out.' This increases the difficulty of guessing the blocked out strings.
(In previous versions of the obfuscator, this feature wrote output source code files with utf-16 encoding to accommodate Unicode characters that had the appearance of solid black blocks. However, in the present version, to block out the deleted information we use X or space characters that can be represented in either ASCII or Unicode, following a suggestion from the United States Copyright Office: see e.g. http://www.copyright.gov/eco/help-deposit.html)
Also see:
The numbers included in replacement identifier names shall include none less than this number.
Example:
countstart_renaming = 0x10000 # I like xx__10001, not xx_1.
When renaming things, instead of using commonplace counting systems such as octal or hexadecimal, use the specified strings as digits.
Example:
# I like code with names like: # ____wowsoverydoge = ___muchohsoshiba(___muchdogemuchso, 2) namebases = ['____', '___'] digits_renaming = ['shiba', 'wow', 'so', 'very', 'much', 'oh', 'doge']
If True
, name generation shall try to use both lowercase and uppercase versions of the same number, if that number contains any letters. Fully compatible with e.g. similar_chars_renaming.
In theory, this setting could present problems if renamed source code files will be outputted to and/or used on an operating system that does a poor job of distinguishing between uppercase and lowercase letters in filenames.
Write to this pathname a Python utility script that obfuscates standard input by applying the name replacements used in obfuscating the names of modules (source code files) in the project source code.
Example:
makefix = 'release_builder/makefix.py'
Caveats:
Can be used for converting a makefile that acts upon your non-obfuscated code to a corresponding makefile to act upon your obfuscated code.
To obfuscate, run the utility script with the non-obfuscated input fed into stdin; obfuscated form is written to stdout.
A dictionary that maps profane names to specific replacement names specified by the obfuscator's user.
Example:
manual_renames = { 'my_module_1': 'this_is_not_a_module1', 'my_function_1': 'list_', 'MyClass1': 'getatr' }
Now MyClass1 will be renamed getatr.
Tells the obfuscator that any specific identifier to be replaced, when replaced, should always be replaced with the same replacement.
Example:
If you aren't using this setting, and you input the code
class class_2: def __init__(self, a): self.a = a def get_a(self): return self.a
You might get the output:
class xx__20001S: def __init__(xx__200015, xx__20000O): xx__200015.xx__20000O = xx__20000O def xx__20001O(xx__20001Z): return xx__20001Z.xx__20000O
Notice that 'self' has been obfuscated to two different identifiers: xx__200015
and xx__20001Z
.
If for some reason this behavior is unsatisfactory, you can set non1to1renaming=False, in which case you might get the output:
class xx__200012: def __init__(xx__200005, xx__20000Z): xx__200005.xx__20000Z = xx__20000Z def xx__20001l(xx__200005): return xx__200005.xx__20000Z
Notice that 'self' has been obfuscated to only one identifier: xx__200005
.
Shall replacement identifier names include octal numbers (rather than the default, hexadecimal)?
Example:
octal_renaming = True # I like xx_174, not xx_7c.
Each line of obfuscated code is given an end-of-line comment showing the corresponding line number in the original, non-obfuscated code. (Exceptions: the comment may be omitted if no corresponding line actually exists in the original code, and is certainly omitted whenever necessary to avoid syntax errors.)
Some people find this very useful with trfix.
Permits (sub)packages/(sub)directories in your profane code directory to be renamed by the obfuscation process.
To rename specific (sub)packages/(sub)directories, their original names must also be mentioned in the profane_names and/or manual_renames .poi file settings. (Otherwise, they will not be renamed by the obfuscator.)
For more information, also see Obfuscating Packages and Subpackages as a Single Unit, in Which Even Inter-Package Interfaces Are Themselves Obfuscated.
In your project file (.poi file), you can use the
redact_calls_to_callables_named
setting
to instruct the obfuscator to remove calls to functions or methods
that are called by using specific names.
Example:
redact_calls_to_callables_named = [ 'debug_log', 'internal_debug', ]
If a call's return value is actually used by the calling code, the call cannot be removed as cleanly, so the call is not removed in this case.
In your project file (.poi file), you can use the
redact_directives_added
setting to define additional equivalents
to the OB_REDACT
directive.
redact_directives_added = ['OB_REDACT_FOR_OSX']
These added equivalents to OB_REDACT
work
as OB_REDACT
does in both
if: 'OB_REDACT'
directives
and OB_REDACT
docstrings.
You can use this feature to redact different code from different builds (from different obfuscated versions) of your software.
Similar to sacred_names, except that it is case-insensitive.
Accepts a list of one or more 'wildcard'-style name patterns, to make matching names sacred.
For example,
sacred_names_wildcards = [ 'test_*', 'another_example*', '*_is_a_great_programmer' ]
will cause test_abc
, another_exampleQwerty15_stuff
, and Bala_is_a_great_programmer
to be sacred names. The asterisk acts as a wildcard. One or more asterisks may be placed at any point within a pattern.
This is a relatively advanced option, but potentially useful for some specialized and/or custom software builds.
In your project file (.poi file), you can use the
setting_replacements
setting to specify that you want
assignments to variables or attributes having certain names
to have their rvalues replaced with certain other rvalues.
The replacement values may be specified in the .poi file as constant values or as expressions. If an expression is used to specify the replacement rvalue, the ultimate rvalue used will be the expression's actual value in the .poi file.
setting_replacements = [ ('licensee_name', _licensee_name), ('default_user_language', 'Inuktitut'), ]
At a minimum, the types supported are integer, float, string, unicode, list, and tuple. Type bytestring is also supported, in Python 2.6 and up.
The numbers included in replacement identifier names shall use a set of digits in which some digits are letters chosen for their visual similarity to other digits, e.g. lowercase L looks like the numeral 1.
Example:
similar_chars_renaming = True # I'd rather have xx_1l than xx_13.
Experimental feature.
Tells the obfuscator that variables (and attributes) having certain names should not have their attributes' names obfuscated.
Uses a single-string format similar to sacred_names.
value_replacements = ( # These are xmlrpclib ServerProxy objects; # calls to them are usually Remote Procedure Calls # to remote code wherein we cannot obfuscate names. 'server_yoyodyne ' 'server_weyland_yutani' )
Now we can make RPC calls to remote procedures, such as
print server_yoyodyne.dimensionTracker.getForecast() print remotes.server_weyland_yutani.employeeDB.findReplicantsByFirstName('John')
and obfuscate those calls without worrying so about the fact that dimensionTracker
and getForecast
must match the names used in non-obfuscated remote code.
This is a relatively advanced option, but potentially useful for some specialized and/or custom software builds.
In your project file (.poi file), you can use the
value_replacements
setting to specify that you want certain
simple constant values occurring in your code
to be replaced by certain others.
value_replacements = [ ('LICENSEE_NAME', _licensee_name), ('DEFAULT_USER_LANGUAGE', 'Inuktitut'), ]
The types supported are integer, float, string, and unicode. Type bytestring is also supported, in Python 2.6 and up.
Directives are categorized below according to the form in which you can emplace in your code.
The standard Python tutorial at http://docs.python.org/tut/tut.html or http://www.python.org explains what a docstring (aka "Documentation String") is.
If the docstring for a function, class, module, or method begins with the all-caps word EXPORT
If the docstring for a function definition begins with the all-caps word OB_REDACT, the entire function definition will be redacted (removed) from the code during obfuscation.
Example code:
# The obfuscator will remove # the useful version of my_debugger_help_fn1(), # leaving only a useless version. my_debugger_help_fn1(a): pass my_debugger_help_fn1(a): """OB_REDACT This function is useful for debugging and testing, but in a release it would provide help to reverse engineers. """ print "The true significance of a is:", repr(a.significance) # At this point in the original code, # the useful version of the function has overriden the useless version.
An 'orphan string' is a string that sits in your source code but is not used in any way, not even e.g. placed in a data structure or assigned to a variable.
Example code:
'FOO' # <-- This is an 'orphan string'. def foo(a): """This is a docstring about function foo.""" '''BAR''' # <-- This is an 'orphan string'. do_something(a)
A docstring (aka "Documentation String") is usually incorporated into your compiled code, so it is used in a way, and it is not an 'orphan string'.
The obfuscator's default behavior is to at least change the contents of 'orphan strings' during obfuscation, in case the orphan string provides useful clues to your code's inner workings.
Orphan strings can also be used for certain directives to the obfuscator.
If you use an orphan string directive, please place it into your code before (above) the first mention in your code of any identifier(s) that the orphan string is intended to modify the obfuscation of!
Instructs the obfuscator to obfuscate the specified identifiers. Identifiers are within the orphan string and are separated by whitespace.
Example code:
# obfuscate identifiers identifier1 and identifier2. '''OB_IDS identifier1 identifier2'''
As an alternative, you might use .poi file setting profane_names instead of this directive.
The opposite of OB_IDS: instructs the obfuscator to NOT obfuscate certain identifiers.
Example code:
# DO NOT obfuscate identifiers identifier3 and identifier4. '''NOOB_IDS identifier3 identifier4'''
As an alternative, you might use .poi file setting sacred_names instead of this directive.
Instructs the obfuscator that identifier renaming within the affected function/method should be identical to renaming outside the afffected function/method.
For example, the input code
happy = 1 def foo(dopey, happy): """just prints its arguments.""" "OB_NO_LDN" print dopey, happy
will definitely, because of "OB_NO_LDN"
, be obfuscated to have happy
outside any functions/methods/classes and happy
inside foo
renamed to the same name.
If you want to apply this directive to all of your code, see non1to1renaming in Project Settings File -- Intermediate And Advanced Optional Settings.
These functions are defined in ob_directives.py.
This function takes a single argument: a string constant. This function returns its argument unchanged. This function's purpose is to cause the obfuscator to obfuscate the argument string constant as if the string constant were an identifier.
When the obfuscator obfuscates the code, it can replace the OB_ASID call with the (properly obfuscated) argument.
For more explanation and a code example, read all of Code that needs to know names of its own identifiers.
A directive which can cause some keyword arguments in function calls to be obfuscated.
Please see
if 'OB_REDACT': ... do something ...
Suppose this code occurs in your corpus.
In your unobfuscated (original) code, 'OB_REDACT'
evaluates to True.
When code is obfuscated, this entire block of code is removed (replaced by pass
.)
So this directive is useful for test code that you want to run during development, but don't want to release.
This directive should not be used in connection with else, nor elif!
Different renaming options are best for different situations.
For example...
If you are determined to crunch down your code size, you might want to reduce the size of your symbol names for variables, functions, etc. You might recall that each variable name consists of a 'namebase' string with a number appended. So you want:
So appropriate settings might be:
namebases = ['q', 'qa', 'qb', 'qc', 'qd', 'qe', 'qf', 'qg'] octal_renaming = False # use the default hexadecimal numbering
If you aren't concerned that your target environment's filesystem might not adequately distinguish between capital letters and lowercase letters in filenames, you might also want to set
doublecase_renaming = True
Of course, you can also take advantage of the obfuscator's normal removal of all or some docstrings or comments.
If some functionality is not required on some target systems or for some customers, you might also want to take advantage of the OB_REDACT and the redact_directives_added .poi file options. For example, you might remove some debugging code when building a release. Or you might use different redact_directives_added values when building releases for different target systems or customers.
Trying to impress people who don't like an appearance of simple, clean code? To make your variable names reflect an appearance of complexity, you might not want them to look too short. You might prefer settings such as
# These two namebase strings are spelled slightly differently. namebases = ['some_long_compilcated_name', 'some_long_complicated_name' ] octal_renaming = True # numbers look 'longer' :-) countstart_renaming = 017720 # octal number # ^ chosen so high digit of number will flip # within the range of numbers used by names.
To require that notetakers read carefully and use good handwriting, (or even tempt them to use abbreviation systems that might lead to errors) you might want settings such as
octal_renaming = True countstart_renaming = 0377777750 # ^ write many digits please similar_chars_renaming = True # ^ use ambiguously similar-looking characters # for some numeric digits. # one z: two z's: namebases = ['yxxxzxxxy', 'yxxxzxxzxy', 'yxxxxzxxxy', 'yxxxzxzxxy', 'yxxxxxzxxy', 'yxxxxzxzxy', 'yxxxzxxxxy', 'yxxxxzxxzy', ]
You might prefer obfuscated names that look like non-obfuscated names, to make the real non-obfuscated names less obvious to a casual reader of your code.
Until automation specially for this option is added to the obfuscator, you might want to try using a rather long namebases list, including many words related to your software's purpose / problem domain.
If enough interest is shown by obfuscator users, automation to support this kind of renaming might be added to the obfuscator.
In the United States, when the author(s) of a computer program register(s) the copyright on that program, one or more copies of (some or all of) the program code are typically submitted to the Library of Congress. Lawyers and the general public might have the opportunity to inspect the copies.
Fortunately, U.S. law does provide options to help protect trade secrets within a copyright registration deposit of computer code, as mentioned in http://www.copyright.gov/eco/help-deposit.html, especially the section headed "Trade Secret/Confidential Material". As one option, some portions of the code (up to 49% of the deposited code in some cases) that contain trade secrets may be blacked out (or replaced by sequences of capital X's) by the submitter in the copies of the program that are submitted to the Library of Congress.
IF YOUR LAWYER APPROVES, you might choose, as part of your blocking out, to block out the names of some identifiers such as variables, function names, and class names.
You might decide to block out some of your code's own identifiers on paper, with a thick felt-tip pen or special black tape, before making a mediocre-quality photocopy.
To provide the least information about which of these blocked out identifiers are which, you might want all of your own identifiers to be the same length.
You might choose to obfuscate program code with settings such as
# make identifiers 6 characters long. # namebase always 2 chars long. e.g. namebases = ['xx'] # first 0xefff names always 6 chars long (if namebase(s) are length 2). countstart_renaming = 0x1000
Alternatively, or additionally, you might choose to block out the names of most standard library functions and other names defined by the Python interpreter. For this, please see
Project Settings File -- Intermediate And Advanced Optional Settings -- blockout_sacred_names
WARNING! IN THIS SECTION WE HAVE *NOT* EXPLAINED THE *DETAILED LEGAL* RULES GOVERNING *UNDER WHAT CIRCUMSTANCES* AND IN WHICH SPECIFIC WAYS OR STYLES YOU MAY OR MAY NOT BLACK OUT PORTIONS OF YOUR PROGRAM CODE WHEN REGISTERING COPYRIGHT. WE ARE NOT LAWYERS AND WE CANNOT ADVISE YOU AS TO WHETHER THIS METHOD IS APPROPRIATE FOR YOUR LEGAL NEEDS. PLEASE CONSULT A LAWYER KNOWLEDGABLE IN THIS AREA OF LAW BEFORE USING THIS METHOD.
Metaprogramming is creating program code that itself creates or manipulates program code, quite possibly even manipulating itself. When obfuscating metaprogramming-related code, special issues can arise.
To manually correct any misjudgements by the obfuscator on this issue, please see the following sections of this manual:
A trivial example (that's barely even metaprogramming):
# Not properly adapted for obfuscator. def do_something(): if (my_debug.verbose): sys.stderr.write('do_something() was called.\n')
But suppose you run your code through an obfuscator that renames do_something? You could easily get
def xxx_1001(): if xxx_5.xxx_17: sys.stderr.write('do_something() was called.\n')
The name of do_something
is unobfuscated in the string.
Fortunately, the obfuscator provides an option to obfuscate string constants as identifiers. Here's how to do it.
OB_ASID()
is correctly defined.
# (you must copy ob_directives.py into your project to import it) from ob_directives import * # defines fn OB_ASID()Note: a copy of ob_directives.py is included in this manual.
def OB_ASID(a): return a
name_of_var__species = OB_ASID('species')
# In this function, do not use replacement names # that differ from the default replacement names. 'OB_NO_LDN'
So your code might finally become
from ob_directives import * def do_something(): """Does something.""" 'OB_NO_LDN' # don't use different renames within this function. if (my_debug.verbose): sys.stderr.write(OB_ASID('do_something') + '() was called.\n')
which might be obfuscated to something like
from xxx_999 import * def xxx_1001(): """pass""" 'except' if xxx_5.xxx_17: sys.stderr.write(('xxx_1001' + '() was called.\n'))
If you make a function (or method) call using keyword argument syntax, for example
print foo( a = 1, b = 3 )
the obfuscator's default behavior is to leave any keyword argument names unobfuscated in the function call.
This is often correct.*
However, this is the wrong behavior if all three (not one or two) of the following are true:
There are three fixes for this. Any of these three will work.
def foo(a, b, c): return a + b + c print foo(1, b=3, c=7) # b and c are keyword arguments in function call!after applying this fix becomes:
def foo(a, b, c): return a + b + c print foo(1, 3, 7) # Fix 1: no longer using keyword argument syntax in call.
def bar(a, b): return a - b print bar(a=4, b=2) # won't obfuscate a and b correctly in this call.with this fix becomes:
def OB_KEYARG(x): return x # or import ob_directives.py def bar(a, b): return a - b print bar(a=OB_KEYARG(4), b=OB_KEYARG(2)) # will obfuscate correctly.This fix can safely be combined with Fix 1 above (setting the .poi file parameter keyword_arguments_shortcut = True). The obfuscator will then obfuscate the names of keyword arguments in function/method definitions and calls, in cases wherein the obfuscator figures out that such obfuscation is completely safe.
To quickly find any function/method calls in your corpus that use keyword arguments, you can use the .poi file setting
keyword_arguments_report = True
which causes the obfuscator to output a report listing the locations of all function/method calls using keyword argument syntax.
Also see Why Keyword Arguments Are not Obfuscated By Default.
The various function directives are defined in the file ob_directives.py. A copy of ob_directives.py is in an appendix of this manual.
I (the obfuscator's author) really do want to know if you need or strongly desire more/better automation for obfuscated keyword arguments in function calls.
When the obfuscator analyzes your source code to be obfuscated, part of the obfuscator's analysis is to look at the (sacred) modules and packages that your corpus of code to be obfuscated imports (or might try to import) from outside your corpus.
Looking at these sacred modules and packages helps the obfuscator to infer which identifiers occurring in your code are sacred and which are profane (which are OK to change).
However, it's possible to legitimately write code that incorporates a set of import (or from) statements which can't all succeed on any single environment.
For example, your code might incorporate one import to help it do something on Windows, and a different import to help it do something on a different operating system.
# I think this is terrible code, but it illustrates a point. # Try to make a beep on Windows or IRIX. try: # Try to make a Windows operating system beep. from winsound import * # works only on Windows (maybe not on 95/98) Beep(1000, 500) # Make old-style PC speaker beep without sound card except: # I guess we're not on Windows. try: # Try to make an IRIX operating system beep. from al import * # works only on SGI's IRIX from AL import * # ; print 'sorry, the IRIX beep isn't implemented yet.' except: print 'You should run this software on Windows or on SGI IRIX.'
Either winsound
is missing (if you're not on Microsoft Windows) or al
and AL
are missing (if you're not on SGI IRIX).
Either way, the missing module(s) cause:
If you hate to see the obfuscator output a warning message complaining about a missing module, you can put the import (or from) statement inside an exec statement so that the obfuscator will ignore it.
However, if the obfuscator ignores the import, you will have
to tell it manually about sacred identifers that you use in your code. To force the obfuscator to treat
identifiers as sacred, you can use the sacred_names setting in your .poi file, or the 'NOOB_IDS' directive within your source code.
Example:
# Ugly code now incorporates ugly workaround! # Try to make a beep on Windows or IRIX. try: # Try to make a Windows operating system beep. 'NOOB_IDS winsound Beep' # Preserve identifiers winsound or Beep. from winsound import * # works only on Windows (maybe not on 95/98) Beep(1000, 500) # Make old-style PC speaker beep without sound card except: # I guess we're not on Windows. try: # Try to make an IRIX operating system beep. '''NOOB_IDS al AL getqueuesize setqueuesize getwidth setwidth getchannels setchannels getsampfmt setsampfmt getfloatmax setfloatmax closeport getfd getfilled readsamps writesamps getfillpoint setfillpoint getconfig setconfig getstatus MAYBE_EVEN_MORE_IDENTIFIERS___ ''' from al import * # works only on SGI's IRIX from AL import * # ; print 'sorry, the IRIX beep isn't implemented yet.' except: print 'You should run this software on Windows or on SGI IRIX.'
As we saw in an above section, the project_dirs_in2out setting of the .poi file specifies an input directory of profane code to be obfuscated, and an output directory into which the obfuscated code is written.
If the input directory contains subdirectories that are packages imported by the profane code to be obfuscated, those subdirectory packages are also profane code that is outputted in obfuscated form into subdirectories of the output directory. Such package directories can be nested to form multiple levels of packages and subpackages. That is how multiple packages and subpackages can be obfuscated as a single unit, in which even inter-package interfaces are obfuscated.
In version 3.0 of the obfuscator, the default behavior is for these (sub)directories to retain their non-obfuscated names. Renaming these (sub)packages in the output code will require the .poi file setting permit_pkg_renaming.
One way to embed a copyright notice into every module of source code outputted by the obfuscator is to use the message_for_module_docstring setting of your project's .poi file.
If you want more thorough and widespread embedding of copyright information, and don't mind somewhat enlarging your code's size, some additional methods include:
namebases = (['Copyright_2012_Bitboost_Systems__', 'copyright_2012_bitboost_systems_', 'Copyright_2012_by_Bitboost_Systems_', '_short__', '__short_', '_x_x_', '_x__x_', '_xx_', '_xz_', '_zx_', '_xxx', '_xxz', '_xzx', '_xzz', '_zxx', '_zxz', '_zzx', '_zzz', # comma after final list member is ok. ] )You might get obfuscated code resembling this:
def Copyright_2012_by_Bitboost_Systems_2e3(_xx_2f8, copyright_2012_bitboost_systems_2f7): _xx_2f8.copyright_2012_bitboost_systems_2f7 = copyright_2012_bitboost_systems_2f7
If you are obfuscating a Django project, please contact our technical support for additional tips and the latest version of our Django-related tools.
Here is a list of problems, with workarounds for the problems.
For example, suppose you obfuscate
# xml.minidom, xml.pulldom are modules in the Python standard library. from xml import minidom, pulldom minidom._nodeTypes_with_children.append(mySpeciallyHackedUpNodeClass)
and it becomes:
from xml import xxx_51, xxx_52 xxx_51.xxx_53.append(mySpeciallyHackedUpNodeClass)
but you've decided you don't want minidom
, pulldom
, and _nodeTypes_with_children
to be changed to gibberishy names!
One solution is to use a NOOB_IDS
'orphan string directive'
in your source code.
For example:
'NOOB_IDS minidom pulldom _nodeTypes_with_children' from xml import minidom, pulldom # minidom, pulldom are modules in the Python standard library. minidom._nodeTypes_with_children.append(mySpeciallyHackedUpNodeClass)
Your code will then be obfuscated more correctly to:
from xml import minidom, pulldom minidom._nodeTypes_with_children.append(xxx_51)
For more information see:
Alternative solutions
For example, suppose you obfuscate
def myfunc(planets=None, moons=None, stars=None, comets=None): do_something(planets, moons, stars, comets, False) my_func(moons=Luna)
and it obfuscates to:
def xxY_25(xxx_22=None, xxx_23=None, xxx_24=None, xxx_25=None): xxx_26(xxx_22, xxx_23, xxx_24, xxx_25, False) xxY_25(moons=Luna)
This is wrong! In the last line above, moons
should be xxx_23
(to make the function call match the function definition.)
For solutions, see:
def myMethod(): '''NOOB_IDS somesetting1''' import somepkg somepkg.somesetting1 = TrueThe 'orphan string' was mistaken for a documentation string. (See: Using 'Orphan Strings'.) Workaround: place something immediately preceding the 'orphan string', such as
pass
or an actual documentation string.
Example:
def myMethod(): """Changes somepkg to make it skip characters.""" '''NOOB_IDS somesetting1''' import somepkg somepkg.somesetting1 = True
'NOOB_IDS foo bar baz' def bar(x): do_something(x, 2) # ... 'OB_IDS bar fret' def bar(x): do_something_else(x)The obfuscator can treat
bar
as a sacred name,
or as a profane name,
but it probably can't treat bar
as both, or even as sometimes sacred and sometimes profane. (Sorry, it's just not quite clever enough to figure out what you were really thinking in that case.)
In fact, even directly contradictory 'orphan string' directives in separate files
within the same project (same corpus) will lead to confusion.
Sorry about that.
See Why Keyword Arguments Are Not Obfuscated By Default.
In this specific example code, the error shown is not likely to happen in recent versions of the obfuscator.
In this specific example code showing a workaround for (an) error(s), the errors shown are difficult or impossible to replicate in recent versions of the obfuscator (because we fixed them. Boo hoo.)
This is a working version of ob_directives.py, suitable for most normal purposes.
Also see Function Directives.
''' ob_directives.py YOU *MAY* COPY ob_directives.py INTO THE SOURCE CODE OF YOUR PROJECT AS LONG AS - you use this file and its contents as source code rather than as text nor as documentation for end users of your project. - you have a valid license to use Chris Niswander's / BitBoost's Python obfuscator. Defines do-nothing functions which a Python obfuscator may interpret as directives. - Instruction to Python obfuscator to obfuscate a string as if it were an identifier (variable name / fn name / etc.) in the Python code, IF THIS FEATURE IS ENABLED. OB_ASID() e.g. OB_ASID('somevariable1') - Indications of whether a value is being handed to a keyword argument that should or should not be obfuscated. OB_KEYARG() e.g. do_something(some_keywordarg1 = OB_KEYARG('some value')) def do_secret(secretname1, secretname2): print "no one knows what " + secretname1 + " and " \ + secretname2 + " are used in reference to." do_secret(secretname1=OB_KEYARG('edelweiss'), secretname2=OB_KEYARG('abba')) In the above example, because OB_KEYARG has been applied to the values of the function call's keyword arguments, secretname1 and secretname2 in the function call will be obfuscated to match the definition of function do_secret. Use in function calls e.g. def somefn(someargname1): 'does something, which I'll fill in here later.' do_something(someargname1) # someargname1 gets obfuscated to match the fn def. somefn(someargname1=OB_KEYARG(someval)) Copyright 2007 by Chris Niswander and BitBoost (of Arizona, USA) All Rights Reserved. ''' def OB_ASID(a): return a def OB_KEYARG(a): return a
This is a working version of a .poi / project settings file, suitable for demonstrating blockout_sacred_names.
Also see blockout_sacred_names.
# main_blockout.poi # # Demonstration of blockout_sacred_names (.poi file setting). # # Suitable for # - test case # - obfuscator manual example input_root_module = 'main' output_root_module = 'main' project_dirs_in2out = ['.', './out-blockout'] if 1: message_for_module_docstring = \ ''' file produced by: blockout-example-1/main.poi ( C ) Copyright 2012 by Chris Niswander. All Rights Reserved. Please see http://www.bitboost.com for more information. ''' # make 'profane' identifiers 18 characters long. # namebase always 12 chars long. e.g. namebases = ['identifier__'] # first 0xefff name numbers always 6 chars long (if namebase(s) are length 2). countstart_renaming = 0x1000 # For secrecy's sake don't ship these files: # Prefer to use slash not backslash, this is a Python string! revdict = 'secret-blockout/revdict.py' blockout_sacred_names = 1
While this mode blocks out most names from the Python standard libraries (such as standard library function
names) and many names defined by the Python interpreter,
actual language keywords such as for
and print
are not blocked out.
EXAMPLES | |
---|---|
not blocked out | blocked out |
for | |
in | |
import | reload |
sys | |
string | |
re | |
from | stdout |
write | |
len | |
type | |
list | |
repr | |
+ | extend |
[] | append |
class | |
def | __ init__ |
__ del__ |
|
__ repr__ |
|
__ str__ |
|
__ eq__ |
In function (and method) definitions in your code, parameter
names are (generally) obfuscated.
For example, self
might be renamed xxy123
.
However, creating a possible conflict...
If you make a function (or method) call using keyword argument syntax, for example
print foo( a = 1, b = 3 )
the obfuscator's default behavior is to leave any keyword argument names unobfuscated in the function call.
This behavior is sometimes correct, and sometimes incorrect.
string1 = raw_input("Please enter a paragraph of text.") periods_in_first_10_chars_of_string1 = string1.count('.', end=10) # We would not want 'end' to be obfuscated by itself, # because built-in string method count would not understand. import datetime # a module of the Python standard library. date1 = date(month=5, day=1, year=2008) # date and datetime would be confused if 'month', 'day', or 'year' were renamed.
Suppose the keyword arguments are going into a dictionary, as in this example code from Guido van Rossum's Python Tutorial (Release 2.4.2) wherein the dictionary is named "keywords" ...
def cheeseshop(kind, *arguments, **keywords): print "-- Do you have any", kind, '?' print "-- I'm sorry, we're all out of", kind for arg in arguments: print arg print '-'*40 keys = keywords.keys() keys.sort() for kw in keys: print kw, ':', keywords[kw] cheeseshop('Limburger', "It's very runny, sir.", "It's really very, VERY runny, sir.", client='John Cleese', # keyword argument shopkeeper='Michael Palin', # keyword argument sketch='Cheese Shop Sketch' # keyword argument )
which is intended to print:
-- Do you have any Limburger ? -- I'm sorry, we're all out of Limburger It's very runny, sir. It's really very, VERY runny, sir. ---------------------------------------- client : John Cleese shopkeeper : Michael Palin sketch : Cheese Shop Sketch
The latter part of the output would be totally ruined if we obfuscate the keyword argument names, which would change the words 'client', 'shopkeeper' and 'sketch'. I didn't want to try to automatically distinguish every case where the keyword arguments' names should be obfuscated from every case where they should not be.
Complicated issues can arise if you write code which uses function references (which might refer to profane functions or might refer to sacred functions), or if you replace the functions or methods of a module, class, or individual object, or if you use some other not-all-that-tricky programming techniques.
In a "dynamic language" like Python, you can make it difficult or even provably impossible for automated pre-run code analysis to be certain of the origin of the function you are calling.
This shortcoming can become a nuisance, because if you're calling an obfuscated function that has many declared arguments, you might want your call to mention some declared arguments by name to improve your code's readability, and/or to help you select which of the function's default arguments you want used.
For solutions, please see the keyword arguments use case.
If a variable name is changed by code obfuscation, and that variable is referred to specifically by its old (pre-obfuscation) variable name within a replacement field of a format string, the format string would no longer be correct in the context of the post-obfuscation code.
If this is a problem for you, please complain to BitBoost or directly to Chris Niswander.
Some relatively simple and commonplace ways of using Python 2.6 format strings don't cause errors. For example:
# ok """Hello {0}, you have {1} new message(s).""".format( user_name, new_msg_count) # ok """Hello {username}, you have {new_msg_count} new message(s).""".format( username=self.username, new_msg_count=self.get_new_msg_count()) # ok """Hello {0}, you have {new_msg_count} new message(s).""".format( self.username, new_msg_count=self.get_new_msg_count()) # ok """Hello {username}, you have {new_msg_count} new message(s).""".format( **{ 'username' : self.username, 'new_msg_count' : self.get_new_msg_count() })
The above examples would all result in correctly working output code from the obfuscator's present version, because the format strings do not depend on variable names as such.
But so you can know when problems can happen, a detailed discussion follows.
(This section might be clearest to readers with some knowledge of the official Python language documentation about the format strings feature of Python 2.6 and 3.0.)
The obfuscator's present version does not change the contents of a format string during the obfuscation process. This means that if the format string's replacement fields try to refer to names outside the format string that were changed outside the format string by the obfuscator, the format string will become incorrect (through not having been changed to correspond to the new names.)
So, for example, if a format string's replacement fields try to refer by name to variables or class members that were renamed by the obfuscator, this would cause the format string to become incorrect.
Each replacement field within a format string can use:
"{name}, you have {appt_count} appointment(s).".format( appt_count=appt_count, name=username)This is not in itself a problem.
"{record.name}, you have {record.appt_count} appointment(s).".format( record=user_record)Replacement fields that have >1 name per replacement field (e.g. replacement field {record.name} has two names) present more risk that the obfuscator might change the names of things in the code that the replacement string is trying to refer to. For example, in the code above, the obfuscator might change the names of the data fields 'name' and 'app_count' in a class to which record (aka user_record) belongs.
str.format can be called with three different kinds of arguments:
"We picked three pets: {0}, {1}, and {2}".format( 'Fifi', 'Muffy', third_pet)mp presently has no problems with this.
"{name}, your id is: {id}.".format( name=name, id=record.id)mp produces working code with this. However, this easy way of handling the situation doesn't automatically obfuscate the field names.
# OK example: (example dict-1) "{name}, your id is: {id}.".format( **{'name':user_name, 'id':user_id}) # NOT OK example: (example dict-2) "{user_name}, your id is: {user_id}.".format( **locals()) # OK OR NOT OK example, depending on how the dictionary was obtained: # (example dict-3) "{user_name}, your id is: {user_id}.".format( **get_a_dictionary_from_somewhere_somewhat_far_away())If the dictionary was built using string literals as the keys, as in example dict-1 above, the obfuscator will be ok. On the other hand, the obfuscator does not automatically handle a final dictionary argument gracefully if the keys of the dictionary are variable names changed by the obfuscator, as in the locals() example above)!
An obfuscator user can make kludgy workarounds for the trouble cases I mention above. The workarounds most often involve using hinting functions such as OB_ASID, which are documented in the manual (even the manual for obfuscator version 1.2 covers them.) e.g.
user_name = get_user_name() ("Hello, {" + OB_ASID("user_name") + "}, how are you?"). format(**locals())
Some of these workarounds seem hardly worth the bother, although in some cases they might be worthwhile.
You can also read the obfuscator manual's sections on
This is a more in-depth look at a topic first mentioned in Versions of Python and Python Features Supported by the Obfuscator.
As that section mentioned, all features of Python 2.5 are supported.
For example, from __future__ import absolute_import
is fully supported. Absolute imports and explicit relative imports are supported. Older-style imports are also supported.
Almost all features of Python 2.6 and/or 2.7 are also supported.
More specifically:
from __future__ import ...
are supported.
For example, new language syntax introduced by Python 2.7 is supported, including set literals, dictionary comprehensions, and set comprehensions.
from __future__ import ...
are generally also supported unless they significantly change how importing works. Fortunately, that is a very small subset.
When wishing to use some Python 3 features in your code that you plan to obfuscate with an obfuscator running in Python 2, the best results may be obtained by running the obfuscator in Python 2.7. (See next subsection.)
This is best done by using both of the following:
from __future__ import
statements in your code, to best enable various Python 3 features in the Python 2.x version that you are using.
These feature imports should be explained in the documentation of the Python version and/or interpreter that you are using. For example, at press time, a table of these importable __future__
module features is in the most recent version of the Python 2 manuals, within the page at https://docs.python.org/2/library/__future__.html . The table includes links to descriptions of each Python 3 feature importable in this way.
The appropriate from __future__ import ...
feature imports are acceptable in both the latest version(s) of Python 2 and in Python 3. It should be reassuring to know that "No feature description will ever be deleted from the module __future__
," so your code using feature imports should be compatible with future versions of Python for a very long time.
For example, the Python 3 style "print as a function" syntax is supported by using from __future__ import print_function
and running the obfuscator in Python 2.7. By using this import your Python 3 style print calls will work in Python 2.6, Python 2.7, and Python 3.x, when you run either the non-obfuscated or obfuscated versions of your code.
This documentation guaranteed to contain at least one tyop.
© Copyright 2017 by Chris Niswander and BitBoost (of Arizona, USA). All Rights Reserved.
The obfuscator documented by this manual (documentation) is © Copyright 2003-2017 by Chris Niswander and BitBoost.
Various product names and business names which might occur in this documentation, such as Microsoft, Microsoft Windows, Apple, Macintosh, OS X, Windows 2000, Windows XP, Windows Vista, Windows 7, Windows Server 2008, Windows 8, Windows 8.1, UNIX, Linux, Debian, Ubuntu, are trademarks and/or service marks claimed by various organizations.
BitBoost, BitBoost Systems, and bobs/BOBS/Bobs are trademarks and/or service marks of BitBoost aka BitBoost Systems, a business based in, or predominantly based in, the United States (specifically in Arizona as this is being written.)
THIS MANUAL (DOCUMENTATION) DOES NOT CONSTITUTE, AND IS NOT INTENDED AS, ADVICE ABOUT MATTERS OF LAW.
NO PORTION OF THIS DOCUMENTATION SHOULD BE TAKEN AS LEGAL ADVICE.
2017-01-31