\myheading{Obfuscation/deobfuscation} Despite simplicity of our decompiler, we can see how to deobfuscate (or optimize) using several simple tricks. For example, this piece of code does nothing: \begin{lstlisting} mov rax, rdi xor rax, 12345678h xor rax, 0deadbeefh xor rax, 12345678h xor rax, 0deadbeefh \end{lstlisting} We would need these rules to tame it: \begin{lstlisting} # (X^n)^m -> X^(n^m) def reduce_XOR4 (expr): m=match(expr, create_binary_expr("^", create_binary_expr ("^", bind_expr("X"), bind_value("N")), bind_value("M"))) if m!=None: return dbg_print_reduced_expr ("reduce_XOR4", expr, create_binary_expr ("^", m["X"], create_val_expr (m["N"]^m["M"]))) else: return expr # no match ... # X op 0 -> X, where op is ADD, OR, XOR, SUB def reduce_op_0 (expr): # try each: for op in ["+", "|", "^", "-"]: m=match(expr, create_binary_expr(op, bind_expr("X"), create_val_expr (0))) if m!=None: return dbg_print_reduced_expr ("reduce_op_0", expr, m["X"]) # default: return expr # no match \end{lstlisting} \begin{lstlisting} working out tests/t9_obf.s going to reduce ((((arg1 ^ 0x12345678) ^ 0xdeadbeef) ^ 0x12345678) ^ 0xdeadbeef) reduction in reduce_XOR4() ((arg1 ^ 0x12345678) ^ 0xdeadbeef) -> (arg1 ^ 0xcc99e897) reduction in reduce_XOR4() ((arg1 ^ 0xcc99e897) ^ 0x12345678) -> (arg1 ^ 0xdeadbeef) reduction in reduce_XOR4() ((arg1 ^ 0xdeadbeef) ^ 0xdeadbeef) -> (arg1 ^ 0x0) going to reduce (arg1 ^ 0x0) reduction in reduce_op_0() (arg1 ^ 0x0) -> arg1 going to reduce arg1 result=arg1 \end{lstlisting} This piece of code can be deobfuscated (or optimized) as well: \begin{lstlisting} ; toggle last bit mov rax, rdi mov rbx, rax mov rcx, rbx mov rsi, rcx xor rsi, 12345678h xor rsi, 12345679h mov rax, rsi \end{lstlisting} \begin{lstlisting} working out tests/t7_obf.s going to reduce ((arg1 ^ 0x12345678) ^ 0x12345679) reduction in reduce_XOR4() ((arg1 ^ 0x12345678) ^ 0x12345679) -> (arg1 ^ 1) going to reduce (arg1 ^ 1) result=(arg1 ^ 1) \end{lstlisting} I also used \emph{aha!}\footnote{\url{http://www.hackersdelight.org/aha/aha.pdf}} superoptimizer to find weird piece of code which does nothing. \emph{Aha!} is so called superoptimizer, it tries various piece of codes in brute-force manner, in attempt to find shortest possible alternative for some mathematical operation. While sane compiler developers use superoptimizers for this task, I tried it in opposite way, to find oddest pieces of code for some simple operations, including \ac{NOP} operation. In past, I've used it to find weird alternative to XOR operation (\ref{weird_XOR}). So here is what \emph{aha!} can find for \ac{NOP}: \begin{lstlisting} ; do nothing (as found by aha) mov rax, rdi and rax, rax or rax, rax \end{lstlisting} \begin{lstlisting} # X & X -> X def reduce_AND3 (expr): m=match (expr, create_binary_expr ("&", bind_expr ("X1"), bind_expr ("X2"))) if m!=None and match (m["X1"], m["X2"])!=None: return dbg_print_reduced_expr("reduce_AND3", expr, m["X1"]) else: return expr # no match ... # X | X -> X def reduce_OR1 (expr): m=match (expr, create_binary_expr ("|", bind_expr ("X1"), bind_expr ("X2"))) if m!=None and match (m["X1"], m["X2"])!=None: return dbg_print_reduced_expr("reduce_OR1", expr, m["X1"]) else: return expr # no match \end{lstlisting} \begin{lstlisting} working out tests/t11_obf.s going to reduce ((arg1 & arg1) | (arg1 & arg1)) reduction in reduce_AND3() (arg1 & arg1) -> arg1 reduction in reduce_AND3() (arg1 & arg1) -> arg1 reduction in reduce_OR1() (arg1 | arg1) -> arg1 going to reduce arg1 result=arg1 \end{lstlisting} This is weirder: \begin{lstlisting} ; do nothing (as found by aha) ;Found a 5-operation program: ; neg r1,rx ; neg r2,rx ; neg r3,r1 ; or r4,rx,2 ; and r5,r4,r3 ; Expr: ((x | 2) & -(-(x))) mov rax, rdi neg rax neg rax or rdi, 2 and rax, rdi \end{lstlisting} Rules added (I used ``NEG'' string to represent sign change and to be different from subtraction operation, which is just minus (``-'')): \label{AND2} \begin{lstlisting} # (op(op X)) -> X, where both ops are NEG or NOT def reduce_double_NEG_or_NOT (expr): # try each: for op in ["NEG", "~"]: m=match (expr, create_unary_expr (op, create_unary_expr (op, bind_expr("X")))) if m!=None: return dbg_print_reduced_expr ("reduce_double_NEG_or_NOT", expr, m["X"]) # default: return expr # no match ... # X & (X | ...) -> X def reduce_AND2 (expr): m=match (expr, create_binary_expr ("&", create_binary_expr ("|", bind_expr ("X1"), bind_expr ("REST")), bind_expr ("X2"))) if m!=None and match (m["X1"], m["X2"])!=None: return dbg_print_reduced_expr("reduce_AND2", expr, m["X1"]) else: return expr # no match \end{lstlisting} \begin{lstlisting} going to reduce ((-(-arg1)) & (arg1 | 2)) reduction in reduce_double_NEG_or_NOT() (-(-arg1)) -> arg1 reduction in reduce_AND2() (arg1 & (arg1 | 2)) -> arg1 going to reduce arg1 result=arg1 \end{lstlisting} I also forced \emph{aha!} to find piece of code which adds 2 with no addition/subtraction operations allowed: \begin{lstlisting} ; arg1+2, without add/sub allowed, as found by aha: ;Found a 4-operation program: ; not r1,rx ; neg r2,r1 ; not r3,r2 ; neg r4,r3 ; Expr: -(~(-(~(x)))) mov rax, rdi not rax neg rax not rax neg rax \end{lstlisting} Rule: \begin{lstlisting} # (- (~X)) -> X+1 def reduce_NEG_NOT (expr): m=match (expr, create_unary_expr ("NEG", create_unary_expr ("~", bind_expr("X")))) if m==None: return expr # no match return dbg_print_reduced_expr ("reduce_NEG_NOT", expr, create_binary_expr ("+", m["X"],create_val_expr(1))) \end{lstlisting} \begin{lstlisting} working out tests/add_by_not_neg.s going to reduce (-(~(-(~arg1)))) reduction in reduce_NEG_NOT() (-(~arg1)) -> (arg1 + 1) reduction in reduce_NEG_NOT() (-(~(arg1 + 1))) -> ((arg1 + 1) + 1) reduction in reduce_ADD3() ((arg1 + 1) + 1) -> (arg1 + 2) going to reduce (arg1 + 2) result=(arg1 + 2) \end{lstlisting} This is artifact of two's complement system of signed numbers representation. Same can be done for subtraction (just swap NEG and NOT operations). Now let's add some fake luggage to Fahrenheit-to-Celsius example: \begin{lstlisting} ; celsius = 5 * (fahr-32) / 9 ; fake luggage: mov rbx, 12345h mov rax, rdi sub rax, 32 ; fake luggage: add rbx, rax imul rax, 5 mov rbx, 9 idiv rbx ; fake luggage: sub rdx, rax \end{lstlisting} It's not a problem for our decompiler, because the noise is left in RDX register, and not used at all: \lstinputlisting{\CURPATH/fahr_to_celsius_obf1.txt} We can try to pretend we affect the result with the noise: \begin{lstlisting} ; celsius = 5 * (fahr-32) / 9 ; fake luggage: mov rbx, 12345h mov rax, rdi sub rax, 32 ; fake luggage: add rbx, rax imul rax, 5 mov rbx, 9 idiv rbx ; fake luggage: sub rdx, rax mov rcx, rax ; OR result with garbage (result of fake luggage): or rcx, rdx ; the following instruction shouldn't affect result: and rax, rcx \end{lstlisting} \dots but in fact, it's all reduced by \TT{reduce\_AND2()} function we already saw (\ref{AND2}): \begin{lstlisting} working out tests/fahr_to_celsius_obf2.s going to reduce ((((arg1 - 32) * 5) / 9) & ((((arg1 - 32) * 5) / 9) | ((((arg1 - 32) * 5) % 9) - (((arg1 - 32) * 5) / 9)))) reduction in reduce_AND2() ((((arg1 - 32) * 5) / 9) & ((((arg1 - 32) * 5) / 9) | ((((arg1 - 32) * 5) % 9) - (((arg1 - 32) * 5) / 9)))) -> (((arg1 - 32) * 5) / 9) going to reduce (((arg1 - 32) * 5) / 9) result=(((arg1 - 32) * 5) / 9) \end{lstlisting} We can see that deobfuscation is in fact the same thing as optimization used in compilers. We can try this function in GCC: \begin{lstlisting} int f(int a) { return -(~a); }; \end{lstlisting} Optimizing GCC 5.4 (x86) generates this: \begin{lstlisting} f: mov eax, DWORD PTR [esp+4] add eax, 1 ret \end{lstlisting} GCC has its own rewriting rules, some of which are, probably, close to what we use here.