OpenRCE: Blog
http://www.openrce.org/rss/feeds/blog
OpenRCE: The Open Reverse Code Engineering CommunityVideo of my RECON 2012 Keynote: The Case for Semantics-Based Methods in Reverse EngineeringMon, 02 Jul 2012 22:56:29 -0500
https://www.openrce.org/blog/view/2201/Video_of_my_RECON_2012_Keynote:_The_Case_for_Semantics-Based_Methods_in_Reverse_Engineering
RolfRolles <email-suppressed@example.com>My <a href="http://www.openrce.org/blog/view/2182/">previous blog entry</a> concerned the keynote speech that I gave at RECON 2012, entitled "The Case for Semantics-Based Methods in Reverse Engineering". (You can find a link to the slides at that previous entry.) In that blog, based on conversations that I had had with the RECON organizers, I made statements to the effect that the video had been destroyed. It turns out that the video was in fact not destroyed. You can watch it <a href="http://archive.org/details/Recon2012Keynote-TheCaseForSemantics-basedMethodsInReverseEngineering">here</a>. I would recommend reading along with the slides (from the first link), seeing as I apparently skipped a couple of slides during the beginning of my presentation.RECON 2012 Keynote: The Case for Semantics-Based Methods in Reverse EngineeringMon, 18 Jun 2012 14:01:00 -0500
https://www.openrce.org/blog/view/2182/RECON_2012_Keynote:__The_Case_for_Semantics-Based_Methods_in_Reverse_Engineering
RolfRolles <email-suppressed@example.com>The goal of my RECON 2012 keynote speech was to introduce methods in academic program analysis and demonstrate -- intuitively, without drawing too much on formalism -- how they can be used to solve practical problems that are interesting to industrial researchers in the real world. Given that it was the keynote speech, and my goal of making the material as accessible as possible, I attempted to make my points with pictures instead of dense technical explanations. As a result, one might consider this presentation to be a friendly (but decidedly incomplete) introduction to binary program analysis as opposed to a rigorous mathematical monograph. The presentation features five detailed expositions of applying static program analysis (abstract interpretation and SMT solving) towards practically-interesting reverse engineering problems. (Aside: it's quite challenging to present this material without using terms such as "lattice", "Galois connection", etc.!)<br />
<br />
Unfortunately, due to an error with the camera, the recording of the talk does not exist. This is problematic: I failed somewhat in walking the sharp edge of Einstein's razor, "as simple as possible, but no simpler" -- it was in fact made simpler than what was possible, and some important details (for example, about relational abstract interpretation and reduced products) were included in the spoken material but not the actual slides. Therefore, the learned reader is advised to imagine judiciously-placed asterisks and the accompanying errata, and the untutored pupil would be well-advised to recognize the incomplete and intuitive nature of the exposition and perhaps consult this <a href="http://www.reddit.com/r/ReverseEngineering/comments/smf4u/reverser_wanting_to_develop_mathematically/c4fa6yl">program analysis reading list</a>.<br />
<br />
I would like to give the talk at some other conference at which the video can be reliably recorded, so that it may be published online.<br />
<br />
<a href="http://www.wtbw.co.uk/semantics-based-methods.pdf">Here</a> are the slides.[video] Semi-Automated Input Crafting by Symbolic Execution, with an Application to Automatic Key Generator GenerationTue, 06 Mar 2012 04:07:05 -0600
https://www.openrce.org/blog/view/2049/[video]_Semi-Automated_Input_Crafting_by_Symbolic_Execution,_with_an_Application_to_Automatic_Key_Generator_Generation
RolfRolles <email-suppressed@example.com>The problem of input crafting can be stated as follows: given the ability to influence some aspect of a program's execution (say, by supplying it with a hand-crafted file, network input, sequence of keystrokes, etc), we want to know whether it is possible to cause the program to reach some state, for example, a condition where the integer argument to some memory allocation function is zero, a specific sequence of branches being taken causing EIP to obtain a certain value, an array dereference whose index is not within its bounds, etc. Much work in reverse engineering for vulnerability analysis and software cracking reduces to this problem; some malware analysis problems can also be stated in such terms.<br />
<br />
It is well-known in the formal verification literature that symbolic execution, a method for reasoning about program executions based upon formal semantics and theorem proving, may be applied towards this problem. However, actual tools that can be used in a friendly fashion have not yet materialized in the hands of the common, working reverse engineer. This blog entry demonstrates a prototype of a tool of this nature.<br />
<br />
Crafting inputs with manually-generated SMT instances<br />
-----------------------------------------------------<br />
<br />
We take as our example program a crackme called <a href="http://crackmes.us/download.py?id=135">Kao's Toy Project</a>, which recently attracted the scrutiny of Dcoder and andrewl, two clever computer-hacking mathematician types. Note: I would have used a vulnerability analysis example since it might be more compelling to a general audience, however, the notion of producing a reverse engineering video involving commercial software is not a palatable one for legal reasons, and therefore I chose a program that was deliberately designed to be reverse engineered.<br />
<br />
The crackme implements the following scheme. When presented with a 32-byte activation code, the user enters a hexadecimal string such as "01234567-76543210", which is then decomposed into two dwords, one of which is XORed with the other, and the following compact loop is executed:<br />
<br />
<code><br />
.text:004010F7 mov esi, offset a_ActivationCode<br />
.text:004010FC lea edi, [ebp+Output]<br />
.text:004010FF mov edx, [ebp+arg_0__SerialDwLow]<br />
.text:00401102 mov ebx, [ebp+arg_4__SerialDwHigh]<br />
.text:00401105<br />
.text:00401105 compute_output:<br />
.text:00401105 lodsb<br />
.text:00401106 sub al, bl<br />
.text:00401108 xor al, dl<br />
.text:0040110A stosb<br />
.text:0040110B rol edx, 1<br />
.text:0040110D rol ebx, 1<br />
.text:0040110F loop compute_output<br />
.text:00401111 mov byte ptr [edi], 0<br />
.text:00401114 push offset String2 ; "0how4zdy81jpe5xfu92kar6cgiq3lst7"<br />
.text:00401119 lea eax, [ebp+Output]<br />
.text:0040111C push eax ; lpString1<br />
.text:0040111D call lstrcmpA<br />
</code><br />
<br />
Given that the user's input consists of two dwords, the key space is 2^64. Dcoder and andrewl offer an observation that halves the exponent to 2^32. Dcoder further offers an algebraic cryptanalysis of the scheme that obliterates it. andrewl chose to explore the route of modelling the scheme in terms of the Boolean Satisfiability (SAT) problem, and then feeding the result into a SAT solver. The resulting SAT instance is solved more or less immediately.<br />
<br />
We followed andrewl's line of reasoning and manually constructed a representative Satisfiability Modulo Theories (SMT) formula. (To be technical, the formula is a sentence in a quantifier-free fragment of first-order logic restricted to the theories of equality, bitvectors, and extensional arrays.) The formula does not explicitly take advantage of any aforementioned, known flaw in the cryptosystem, and instead encodes the algorithm literally. We believe that the reader will find the resulting SMT formula very clear and easy to understand. This is an advantage over a SAT instance, which is virtually incomprehensible and harder to debug. The SMT instance is a bona-fide key generator, and furthermore the theorem prover is able to solve the formula in milliseconds (considerably faster than a brute-force search over a 64-bit keyspace).<br />
<br />
We outline the <a href="http://www.openrce.org/repositories/users/RolfRolles/kao.smt">SMT instance</a>: Line 1 sets the theory. Lines 3-7 declare the variables of interest, as well as the array sort. Lines 9-40 encode the serial algorithm. Lines 42-73 state that the output array must equal the fixed value given in the crackme. Lines 75-106 encode the activation code that was given by a given instance of running the crackme. You can replace these lines with whatever your own activation code was, thereby creating a key generator. Lines 108-110 encode the additional constraints where one of the serial dwords was XORed with the other. Lines 112-113 query the decision procedure and output the results. After installing Z3 (other theorem provers such as Yices are also suitable), one solves the formula as follows: <code>C:\Program Files (x86)\Microsoft Research\Z3-3.2\bin>z3 /m /smt2 \temp\kao.smt</code>.<br />
<br />
Generating the constraint system automatically, directly from the binary<br />
------------------------------------------------------------------------<br />
<br />
This is all well and good, but manually encoding algorithms as SMT instances is tedious business. Instead, we seek a solution that can automatically generate the relevant constraint system directly from the x86 binary. The current solution is a static analysis (i.e. it does not alter or observe the execution of the program).<br />
<br />
The algorithm proceeds in three main phases:<br />
<br />
1) <a href="http://www.openrce.org/repositories/users/RolfRolles/kao-1.ml">Trace generation</a>. Since the algorithm is static, the analyst manually specifies some parameters to the system: namely, an address from which to start the analysis, a model of the initial values of the registers and memory, and some condition that dictates when the analysis should stop. (Note that, if we were to reformulate our analysis in a dynamic setting, many of these considerations would be superfluous.) The tool then statically emulates the program, generates a list of instructions encountered during the emulation, and converts them into an intermediate representation.<br />
<br />
2) <a href="http://www.openrce.org/repositories/users/RolfRolles/kao-2.ml">Trace simplification</a>. We apply other static analyses against the trace to simplify it, which could potentially speed up the solving. Analyses could include constant propagation, dead statement elimination, or the <a href="http://www.openrce.org/blog/view/1672/Control_Flow_Deobfuscation_via_Abstract_Interpretation">abstract interpretation</a> that I published last year. This step is actually optional in the sense that the simplifications preserve satisfiability.<br />
<br />
3) <a href="http://www.openrce.org/repositories/users/RolfRolles/kao-3.ml">Constraint generation and solving</a>. With our trace in hand, we transform it into an SMT instance and feed the resulting equations into a theorem prover. Additionally, the user supplies some postcondition that corresponds to the state that he or she wishes for the program to enter. We then check satisfiability, and if the formula is indeed satisfiable, the theorem prover can furnish a model of the inputs that cause the desired state to be reached.<br />
<br />
With all of that out of the way, here is a link to the <a href="http://vimeo.com/38007200">actual video depicting the process</a>. I apologize if the video is tedious (I am tedious) and for the stupid manner in which I sound (I am stupid, and also I am from the American south). The files referenced in the video, kao-1.ml through kao-3.ml, are linked to above in the description of the algorithm.<br />
<br />
Key-generator generation<br />
------------------------<br />
<br />
Given the construction laid out in the video referenced above, generation of a key generator is now trivial. Instead of asserting in the postcondition that the activation code is fixed to whatever value was observed in the construction of the constraint system, we allow the user to enter his or her own activation code, which we then assert as the initial values of the activation code array. We then solve the system as described in the video and part 3 of the code linked above, and provide the user with the proper registration code (EBX and EDX values).<br />
<br />
Summary<br />
-------<br />
<br />
We use static binary program analysis to semi-automatically produce a reasonably-efficient key generator for a weak cryptosystem.<br />
<br />
Postscript<br />
----------<br />
<br />
An astute proofreader wondered why, during the video, when I solved the manually-constructed instance with Z3, it reported an error about a model not being available. In fact, the formula solved correctly (as evidenced by the output saying "sat"), but I did not specify the "/m" flag on the command line due to it skewing the time statistics, which resulted in that message being produced. The reader can manually verify that the formula is correct with the command line given previously.Finding Bugs in VMs with a Theorem Prover, Round 1Sun, 22 Jan 2012 08:14:27 -0600
https://www.openrce.org/blog/view/1963/Finding_Bugs_in_VMs_with_a_Theorem_Prover,_Round_1
RolfRolles <email-suppressed@example.com>Here's some work I did last fall.<br />
<br />
Equivalence checking is a popular technique in the academic literature and in industrial practice, used to verify that two "things" are equivalent. Commonly, it involves translating those things into some logic that is supported by a theorem prover, and asking the decision procedure to prove whether the two things must always be equal. Equivalently, we ask whether there exists any circumstances where the two things might not be equal. If the two things must always be equal under all circumstances, then a complete decision procedure will eventually report that the formula is unsatisfiable. If they can ever deviate, then it will return a "counterexample" illustrating a scenario in which the things differ. The decision procedure may not terminate in a reasonable amount of time, or do so at all if the logic is undecidable or the decision procedure is incomplete.<br />
<br />
Equivalence checking is commonly found in the hardware world (particularly, in a field known as Electronic Design Automation, or EDA), but it has been applied to software as well, for tasks such as verification that compiler optimizations have been implemented correctly, verification of cryptographic implementations, and other forms of specification-conformance. In this blog, we'll take a look at an application of equivalence checking in deobfuscation.<br />
<br />
Equivalence checking<br />
--------------------<br />
<br />
We begin by giving an example proving the equivalence between an x86 sequence and a machine instruction.<br />
<br />
Bit-level hackers -- those who study the works of the original MIT "hackers", whose work bequeathed the term "hacker" -- will know the following example regarding taking the population count of a 32-bit integer. The "population count" of a given integer is the number of one-bits set in the integer. This operation is known as "popcnt", and the x86 instruction set features an instruction of the same name, whose syntax is "popcnt r32, r/m32" and whose semantics dictate that the population count of the right-hand side (32- or 16-bit, depending upon encoding) be deposited into the location specified by the left-hand side. We can easily imagine implementing this functionality with a loop:<br />
<br />
<code><br />
int popcnt = 0;<br />
for(int mask = 1; mask; popcnt += !!(mask & value_to_compute_popcnt_of), mask <<= 1);<br />
</code><br />
<br />
A hoary MIT HAKMEM gem shows how to compute the same value via a series of arithmetic manipulations and no explicit loop. We summarize the procedure below.<br />
<br />
Consider some 8-bit integer, whose bits we shall represent symbolically with letters as in the following:<br />
<br />
<code><br />
abcdefgh<br />
</code><br />
<br />
Let's demonstrate how the method computes the population count of this integer. First, we separate out all of the even bits and the odd bits:<br />
<br />
<code><br />
x = ((x & 0xAA) >> 1) + (x 0x55);<br />
<br />
abcdefgh AND<br />
10101010<br />
--------<br />
a0c0e0g0 SHR 1<br />
--------<br />
0a0c0e0g<br />
<br />
<br />
abcdefgh AND<br />
01010101<br />
--------<br />
0b0d0f0h<br />
</code><br />
<br />
Adding these two quantities together, we obtain the following result:<br />
<br />
<code><br />
wwxxyyzz<br />
</code><br />
<br />
Where ww, xx, yy, and zz are each two-bit quantities such that ww = a + b, xx = c + d, yy = e + f, and zz = g + h. By adding these masked-and-shifted quantities together, we thereby compute the sum of two adjacent bits, and store the sum in that location within the integer that corresponds to those two bits. Given that 0+0 = 0, 0+1 = 1, 1+0 = 1, and 1+1 = 10, the sum can never overflow, and is hence neatly contained in those two bits.<br />
<br />
Next, we perform the same kind of trick to compute the sum of each two-bit quantity. We use a similar shift-and-add tableau:<br />
<br />
<code><br />
x = ((x & 0xCC) >> 2) + (x & 0x33);<br />
<br />
wwxxyyzz AND<br />
11001100<br />
--------<br />
ww00yy00 SHR 2<br />
--------<br />
00ww00yy<br />
<br />
wwxxyyzz AND<br />
00110011<br />
--------<br />
00xx00zz<br />
</code><br />
<br />
Adding these two quantities together, we get:<br />
<br />
<code><br />
iiiijjjj<br />
</code><br />
<br />
Where iiii and jjjj are the 4-bit results such that iiii = ww + xx and jjjj = yy + zz. Once again, this sum is safe from overflow because ww and xx are at most 2-bits, whereas iiii and jjjj are 4-bits.<br />
<br />
We pull the same trick again to sum the quantities iiii and jjjj:<br />
<br />
<code><br />
x = ((x & 0xF0) >> 4) + (x & 0x0F);<br />
<br />
iiiijjjj AND<br />
11110000<br />
--------<br />
iiii0000 SHR 4<br />
--------<br />
0000iiii<br />
<br />
iiiijjjj AND<br />
00001111<br />
--------<br />
0000jjjj<br />
</code><br />
<br />
Adding these two quantifies together, we get kkkkkkkk, where kkkkkkkk = iiii + jjjj = ww + xx + yy + zz = a + b + c + d + e + f + g + h. This corresponds to the definition of the population count of an 8-bit integer. We can easily imagine extending the same procedure for 16-, 32-bit, 64-bit, and generally 2^n-bit quantities. Below we display a fragment of x86 assembly language that computes the population count for a 32-bit integer.<br />
<br />
Sequence (1) -- messy bit hack for eax = popcnt(ebx)<br />
<code><br />
mov eax, ebx <br />
and eax, 55555555h<br />
shr ebx, 1 <br />
and ebx, 55555555h<br />
add ebx, eax <br />
mov eax, ebx <br />
and eax, 33333333h<br />
shr ebx, 2 <br />
and ebx, 33333333h<br />
add ebx, eax <br />
mov eax, ebx <br />
and eax, 0F0F0F0Fh<br />
shr ebx, 4 <br />
and ebx, 0F0F0F0Fh<br />
add ebx, eax <br />
mov eax, ebx <br />
and eax, 00FF00FFh<br />
shr ebx, 8 <br />
and ebx, 00FF00FFh<br />
add ebx, eax <br />
mov eax, ebx <br />
and eax, 0000FFFFh<br />
shr ebx, 16 <br />
and ebx, 0000FFFFh<br />
add ebx, eax <br />
mov eax, ebx <br />
</code><br />
<br />
This is equivalent to the x86 instruction<br />
<br />
Sequence (2) -- direct ISA computation of eax = popcnt(ebx)<br />
<code><br />
popcnt eax, ebx<br />
</code><br />
<br />
Except that the former example also modifies the flags in some certain way, and modifies the ebx register, whereas the popcnt instruction modifies the flags in a different way and does not modify ebx.<br />
<br />
We would like to prove the equivalence of sequences (1) and (2) with respect to the relationship of the resulting eax register with the original ebx register. We must not consider the resulting value of the ebx register or the flags, for in that case, the sequences will not be equivalent in general. The code for performing this task in Pandemic follows.<br />
<br />
<code><br />
(* Define the x86 sequences *)<br />
let hack_sequence_x86 = (* declaration of sequence (1) as X86.x86instrpref variant types *)<br />
let popcnt_sequence_x86 = (* declaration of sequence (2) as X86.x86instrpref variant types *)<br />
<br />
(* This function converts each sequence to IR *)<br />
let make_ir_sequence x86l = List.concat (List.map (X86ToIR.translate_instr 0l) x86l)<br />
<br />
(* These bindings are the IR translations *)<br />
let hack_sequence_ir = make_ir_sequence hack_sequence_x86<br />
let popcnt_sequence_ir = make_ir_sequence popcnt_sequence_x86<br />
<br />
(* These bindings are the results of converting the IR sequences to SSA form *)<br />
let hack_tbl, hack_sequence_ir_ssa = IRSSA.bb_to_ssa_state_out X86ToIRUtil.num_reserved_vars hack_sequence_ir<br />
let popcnt_tbl,popcnt_sequence_ir_ssa = IRSSA.bb_to_ssa_state_out X86ToIRUtil.num_reserved_vars popcnt_sequence_ir<br />
<br />
(* The postcondition says that sequence(1).eax != sequence(2).eax *)<br />
let hack_popcnt_postcondition = <br />
IRUtil.mk_not <br />
(IRUtil.mk_eq <br />
(IRUtil.mk_evar (Hashtbl.find hack_tbl X86ToIRUtil.vEax))<br />
(IRUtil.mk_evar (Hashtbl.find popcnt_tbl X86ToIRUtil.vEax)))<br />
<br />
(* We query the theorem prover as to whether eax from the ends of the respective <br />
sequences are equal. We do this by asking the theorem prover to generate an<br />
example where they are not equal, which it is unable to do (the formula is<br />
unsatisfiable), and therefore they are equal on all inputs. *)<br />
let _ = IDA.msg "%s\n" (Z3SymbolicExecute.symbolic_execute (Z3SymbolicExecute.mk_context ()) (hack_sequence_ir_ssa@popcnt_sequence_ir_ssa) hack_popcnt_postcondition)<br />
</code><br />
<br />
The theorem prover cranks for 10 seconds and returns that the formula is unsatisfiable, in other words, that it was not able to generate a counterexample where sequence(1).eax != sequence(2).eax. Assuming the soundness of Z3 with respect to propositional logic over the bitvector and array theories of finite domain, we have just generated a mathematical proof that the sequences have the same effect on the eax register. Z3 can be configured to output the proof explicitly so that it may be checked by other tools.<br />
<br />
Equivalence checking for verification of deobfuscation results<br />
--------------------------------------------------------------<br />
<br />
I had written a deobfuscation procedure (e.g., a program of the type described <a href="http://www.openrce.org/blog/view/1672/Control_Flow_Deobfuscation_via_Abstract_Interpretation">here</a>) for a commercial VM ("VM" meaning <a href="http://www.usenix.org/event/woot09/tech/full_papers/rolles.pdf">virtualization obfuscator</a> in this context), and I wanted to know whether my deobfuscation was correct, since I felt like I might've taken some shortcuts along the way. Given access to some obfuscating transformation O, and access to a deobfuscating transformation D, we want to know if D(O(p)) is a semantics-preserving translation for all programs p. We'll settle for random testing: generating a "random" executable according to some guidelines, obfuscating it, deobfuscating it, and comparing (by equivalence checking) the deobfuscated program against the original, pre-obfuscated version. As it turns out, the translation D(O(p)) is not semantics-preserving because VM itself has many bugs that could affect the translation of the original x86 into the VM bytecode. <br />
<br />
The procedure in the last paragraph can provide us with straight-line sequences of code (or control flow graphs) such that one is the original, and the other is the obfuscated version with deobfuscation applied. We can then apply equivalence checking to determine whether these sequences are semantically equivalent.<br />
<br />
It turns out that they often are not. For example, consider this obfuscated sequence (which has been mostly deobfuscated for the sake of easy illustration):<br />
<br />
<code><br />
mov cx, word ptr [esp]<br />
add esp, 2<br />
ror dword ptr ss:[esp], cl<br />
pushfd<br />
</code><br />
<br />
And the corresponding deobfuscated sequence:<br />
<br />
<code><br />
pop cx<br />
ror dword ptr ss:[esp], cl<br />
pushfd<br />
</code><br />
<br />
The former example has a similar effect as the latter one, but it modifies the flags before the rol instruction executes. The rol instruction does not change the flags if cl is zero, therefore, the two sequences are not semantically equivalent if cl = 0. The theorem prover finds this bug, the same bug for ror word ptr and ror byte ptr, and the equivalent bugs for rcr, rcl, rol, shl, shr, and sar. This is a total of 21 buggy VM handlers.<br />
<br />
Similarly, obfuscation can also affect the value of the flags register prior to the execution of the inc and dec instructions. Consider this deobfuscated sequence:<br />
<br />
<code><br />
pop eax<br />
dec dword ptr ss:[esp]<br />
pushfd<br />
</code><br />
<br />
inc and dec do not change the value of the carry flag, but add and sub do. Therefore, obfuscation applied upon the vicinity of the pop eax instruction can destroy the value of the carry flag in these circumstances. As there are three sizes that these instructions might be applied to, there are 6 bugs of this variety.<br />
<br />
The theorem prover also found more subtle bugs. Consider the following deobfuscated instruction sequence, where esi points to the VM bytecode. <br />
<br />
<code><br />
lodsd dword ptr ds:[esi]<br />
sub eax, ebx<br />
xor eax, 7134B21Ah<br />
add eax, 2564E385h<br />
xor ebx, eax<br />
movzx ax, byte ptr ds:[eax]<br />
push ax<br />
</code><br />
<br />
The program's obfuscator would alter the behavior of this snippet by introducing writes to the stack that were not present in the original version, therefore the state of the stack after execution of the obfuscated and non-obfuscated sequences would differ. The memory read on the second-to-last line might therefore target the area below the stack pointer, which would cause it to read a different value in the obfuscated and deobfuscated world. This bug is unlikely to manifest itself in the obfuscation of a real-world program, although it demonstrates the ruthless efficacy of theorem proving towards this problem: the ability to discover tricky situations like this one by considering all possible concrete behaviors of the individual snippets, and generate countermodels for inequal scenarios, no matter how far-fetched.<br />
<br />
When taking these factors into consideration, I am able to say that my deobfuscator is correct modulo the bugs in the original obfuscator, which demonstrably number at least 33 (or about 20% of the total handlers).Control Flow Deobfuscation via Abstract InterpretationSat, 09 Jul 2011 02:36:41 -0500
https://www.openrce.org/blog/view/1672/Control_Flow_Deobfuscation_via_Abstract_Interpretation
RolfRolles <email-suppressed@example.com>I present some work that I did involving automatic deobfuscation of obfuscated control flow constructs with abstract interpretation. Considering <a href="http://i.imgur.com/OB1Dt.png">this image</a>, the code is responsible for taking graphs like the one on the left (where most of the "conditional" branches actually only go in one direction and are only present to thwart static analysis) and converting them into graphs like the one on the right.<br />
<br />
Much work on deobfuscation relies on pattern-matching at least to some extent; I have coded such tools myself. I have some distaste for such methods, since they stop working when the patterns change (they are "syntactic"). I prefer to code my deobfuscation tools as generically ("semantically") as possible, such that they capture innate properties of the obfuscation method in question, rather than hard-coding individual instances of the obfuscation.<br />
<br />
The slides present a technique based on abstract interpretation, a form of static program analysis, for deobfuscating control flow transfers. I translate the x86 code into a different ("intermediate") language, and then perform an analysis based on three-valued logic over the translated code. The end result is that certain classes of opaque predicates (conditional jumps that are either always taken or always not taken) are detected and resolved. I have successfully used this technique to break several protections making use of similar obfuscation techniques. <br />
<br />
Although I invented and implemented these techniques independently, given the wealth of work in program analysis, it wouldn't surprise me to learn that the particular technique has been previously invented. Proper references are appreciated.<br />
<br />
Code is also included. The source relies upon my Pandemic program analysis framework, which is not publicly available. Hence, the code is for educational purposes only. Nonetheless, I believe it is one of very few examples of publicly-available source code involving abstract interpretation on binaries.<br />
<br />
<a href="https://www.openrce.org/repositories/users/RolfRolles/Control%20Flow%20Deobfuscation%20via%20Abstract%20Interpretation.pptx">PPTX presentation</a>, <a href="https://www.openrce.org/repositories/users/RolfRolles/BitwiseAI.ml">OCaml source code</a>.