Reversing : The Hacker's Guide to Reverse Engineering

(ff) #1

constantly generating new random values and storing them in a globally
accessible data structure. The values stored in those data structures consis-
tently adhere to simple rules (such as being lower or higher than a certain con-
stant). The threads that contain the actual program code can access this global
data structure and check that those values are within the expected range. It
would make quite a challenge for an automated deobfuscator to figure this
structure out and pinpoint such fake control flow statements. The concurrent
access to the data would hugely complicate the matter for an automated deob-
fuscator (though an obfuscator would probably only be aware of such concur-
rency in a bytecode language such as Java). In contrast, a person would
probably immediately suspect a thread that constantly generates random
numbers and stores them in a global data structure. It would probably seem
very fishy to a human reverser.
Now consider a far simple arrangement where several bogus data members
are added into an existing program data structure. These members are con-
stantly accessed and modified by code that’s embedded right into the pro-
gram. Those members adhere to some simple numeric rules, and the opaque
predicates in the program rely on these rules. Such implementation might be
relatively easy to detect for a powerful deobfuscator (depending on the spe-
cific platform), but could be quite a challenge for a human reverser.
Generally speaking, opaque predicates are more effective when imple-
mented in lower-level machine-code programs than in higher-level bytecode
program, because they are far more difficult to detect in low-level machine
code. The process of automatically identifying individual data structures in a
native machine-code program is quite difficult, which means that in most
cases opaque predicates cannot be automatically detected or removed. That’s
because performing global data-flow analysis on low-level machine code is
not always simple or even possible. For reversers, the only way to deal with
opaque predicates implemented on low-level native machine-code programs
is to try and manually locate them by looking at the code. This is possible, but
not very easy.
In contrast, higher-level bytecode executables typically contain far more
details regarding the specific data structures used in the program. That makes
it much easier to implement data-flow analysis and write automated code that
detects opaque predicates.
The bottom line is that you should probably focus most of your antirevers-
ing efforts on confusing the human reversers when developing in lower-level
languages and on automated decompilers/deobfuscators when working with
bytecode languages such as Java.
For a detailed study of opaque constructs and various implementation ideas
see [Collberg1] and General Method of Program Code Obfuscationby Gregory
Wroblewski [Wroblewski].


Antireversing Techniques 347
Free download pdf