Target
Recently I have analyzed a RASP solution called Approov
. Altough there are some novel detection techniques, overall it’s not that interesting. Instead I will focus on obfuscation part of native library, control flow flattening. You can find a host app with little googling :)
CFF
Control flow flattening (CFF) in a nutshell is a technique to obfuscate code flow by rearranging code blocks (+create new basic blocks) into switch cases. Visualizing it is easier:
If you think about dynamic analysis perspective, execution flow is same as first case. Dispatcher (gray block) should execute blocks in the order of normal execution. Since you need to follow state variable after each “case” execution, static analysis is harder for a reverse engineer and sometimes state variable is also obfuscated.
After seeing cff’ed binary I looked up earlier research about cff. I tried to work with stadeo, then found eShard d810. Stadeo is using miasm to lift binary then analyze the cff whereas d810 uses HexRays microcode (intermediate represantation of IDA). D810 can backward track variables, emulate microcode and have very easy to extend config/rule sets for opaque predicates etc. So I sticked to d810. Make sure to read their blogs about d810 [1][2]
Microcode
D-810 hooks decompilation process of IDA and make changes. Micro instructions(minsnt_t), micro operations (mop_t), blocks (mblocks_t) in microcode. Before giving execution back to IDA final mba needs to be verified.
Oh boi.. Before realizing all internal errors of (INTERR) microcode was documented in hexray_sdk/verifier.cpp I tried to find meaning of all INTERR’s by dumping microcode. I used d810’s dump_microcode_for_debug function to dump microcode which helped a lot for debugging. To debug D810 (or any IDA Pro plugin) I followed this blogpost
Unflatten
In order to unflatten, we need to find state variables in dispatcher fathers. Dispatcher father is a block that goes into dispatcher directly. If we know the state variable in dispatcher father, we can calculate next dispatched block from dispatcher and replace our dispatcher father’s next location to found block.
Let’s give a example from sample. Each switch case body and initial state variable assignments are a dispatcher father. Switch case starts with initial state variable 1010106. And we know if its 1010106 it will go into code block v1 = (qword_470e8 == 0) + 1010107
. Knowing this we can directly go into that block from assignment of 1010106.
In d810 there is a rule for cff with switch table called default\_unflattening\_switch\_case.json
. Unfortunately it does not work for this case. But why? Because of the v1 = (qword_470e8 == 0) + 1010107
. It can generate two different variable from single block.
Actually they tried to solve this problem. If block can have more than one state variable value, they “duplicate any block which appears in (at least) 2 paths with (at least) 2 different predecessors.” But there is no predecessor in this case. So how I solved it ? By creating jz condition with two new blocks.
Let’s look at microcode represantation of these blocks to understand the my solution. To show microcode I used a plugin called lucid
- Take comparison part of block 5
- make it jz condition with comparison
- Create two new block with calculated state variable
- fix in/out bounds of blocks
1 | block 5: |
After the changes, all dispatcher fathers have constant state variable. Then we can use d810’s cff solver part and continue.
After finding dispatcher fathers, I used mop tracker history feature of d810 to find all father histories and fixed instructions that can create two state variable before executing flattening of d810.
Bad While Loops
After solving jump tables I saw bad while loops. Not that important but we can simplify these too.
Microcode represantation is as following
Block 14 is dispatcher and exit blocks (like jtbl) are 16, 19, 21 (outbounds of jump conditions). Dispatcher fathers can be calculated from inbounds of block 14: 13 and 18. It’s simply jtbl actually. I created new flattening optimizer class in d810 and used it to clear bad while loops.
References
CFF Example image taken from jscrambler.com
Rolf Rolles blog post
Sophos Attacking Emotet’s Control Flow Flattening
eShard d810 cff post
1 - eShard d810 deobf post
TAU Defeating Compiler-Level Obfuscations
Quarkslab Deobfuscation: recovering an OLLVM-protected program
HexRaysDeob
IDA Plugin: lucid
IDA Plugin: d810
Debugging IDA Pro plugins