29

I recently heard about the "control-flow flattening" obfuscation which seems to be is used to break the structure of the CFG of the binary program (see Symbolic Execution and CFG Flattening).

Can somebody make an explanation of what is its basic principle and, also, how to produce such obfuscation (tools, programming technique, ...) ? And, it would be nice to know if there are ways to extract the real shape of the control-flow of the program.

5

3 Answers 3

23

For a good example of this obfuscation, check Apple's FairPlay code, e.g. iTunes or iOS libs. Here's a typical graph of a function which had this obfuscation applied:

enter image description here

As you can see, all edges between basic blocks - both conditional and unconditional - has been redirected to a dispatcher node which uses a new artificial variable to decide which block should be jumped to next. This variable is updated at the end of each separated basic block.

Here's the dispatcher node:

LDR    R3, =0xF26A85D2
ADD    R3, R2, R3
CMP    R3, #0x40 ; switch 65 cases
ADDLS  PC, PC, R3,LSL#2 ; switch jump

It uses R2 as the control value.

And here's one of the basic blocks:

LDR  R2, =0x853FD863 ; jumptable 00532EFC case 33
LDR  R1, [SP,#0x130+var_108]
STR  R2, [SP,#0x130+var_134]
LDR  R2, =0xD957A31
STR  R1, [SP,#0x130+var_44]
B    loc_532ED0

It updates R2 with the value which will be used to jump to the next block.

Recovering it shouldn't be too difficult in most cases - just track the control variable updates and replace jumps to the dispatcher node with jumps to the next block corresponding to the new control variable value.

2
  • How do you edit the assembly without changing the actual binary? Commented Jun 10, 2013 at 20:00
  • 2
    @heinrich5991 In IDA we have the ability to make changes to the assembly representation of the binary we analysing, without changing the actual binary, then within IDA we can save this representation for later use.
    – nomilk
    Commented Jun 10, 2013 at 20:08
29

From this paper by Timea Laszlo and Akos Kiss :

The basic method for flattening a function is the following.

First, we break up the body of the function to basic blocks, and then we put all these blocks, which were originally at different nesting levels, next to each other.

The now equal-leveled basic blocks are encapsulated in a selective structure (a switch statement in the C++ language) with each block in a separate case, and the selection is encapsulated in turn in a loop.

Finally, the correct flow of control is ensured by a control variable representing the state of the program, which is set at the end of each basic block and is used in the predicates of the enclosing loop and selection.

Image showing how control-flow flattening obfuscation alters code that contains loop structures.
enter image description here

A simple example:

int original()
{
    print "Do"
    print "you"
    print "like"
    print "milk?"
}


int obfuscated()
{
    int ctrFlowVar = 1;

    while(ctrFlowVar != 0)
    {
        switch(ctrFlowVar)
        {
            case 1:
                print "do"
                ctrFlowVar = 2;
                break;
            
            case 2:
                print "you"
                ctrFlowVar = 3;
                break;
            
            case 3:
                print "like"
                ctrFlowVar = 4;
                break;
            
            case 4:
                print "milk?"
                ctrFlowVar = 0;
                break;
        }
    }
}

If you are familiar with how switch statements are written in assembly (i know 2 ways, the if-style and the jumptable one) then the above example is easy to de-obfuscate.The break; instruction is a jmp.You could make it jump to the block thats supposed to be next.

0
-1

Control flow flattening is an obfuscation\transformation technique that can be applied to code for almost all languages in order to make it more difficult to understand and reverse engineer.

int gSDetETSDG119 = 1010545;
    while (gSDetETSDG119 != 1010544)
        {
            switch (gSDetETSDG119) {
              case 1010545:
                {
                    if (n <= 0) {
                        gSDetETSDG119 = 1010546;
                    } else {
                        gSDetETSDG119 = 1010547;
                    }
                    break;
                }
              case 1010546:
                {
                    return (0.000000e+000);
                    gSDetETSDG119 = 1010544;
                    break;
                }
              case 1010547:
                {
                    gSDetETSDG119 = 1010544;
                    break;
                }
            }
        }
}
int gSDetETSDG118 = 1010541;
    while (gSDetETSDG118 != 1010540)
        {
            switch (gSDetETSDG118) {
              case 1010541:
                {
                    if (incx != 1 || incy != 1) {
                        gSDetETSDG118 = 1010542;
                    } else {
                        gSDetETSDG118 = 1010543;
                    }
                    break;
                }
              case 1010542:
                {
                    {
                        ix = 0;
                        iy = 0;
                        {
                            int gSDetETSDG117 = 1010537;
                            while (gSDetETSDG117 != 1010536)
                                {
                                    switch (gSDetETSDG117) {
                                      case 1010537:
                                        {
                                            if (incx < 0) {
                                                gSDetETSDG117 = 1010538;
                                            } else {
                                                gSDetETSDG117 = 1010539;
                                            }
                                            break;
                                        }
                                      case 1010538:
                                        {
                                            ix = (-n + 1) * incx;
                                            gSDetETSDG117 = 1010536;
                                            break;
                                        }
                                      case 1010539:
                                        {
                                            gSDetETSDG117 = 1010536;
                                            break;
                                        }
                                    }
                                }
                        }
                        {
                            int gSDetETSDG116 = 1010533;
                            while (gSDetETSDG116 != 1010532)
                                {
                                    switch (gSDetETSDG116) {
                                      case 1010533:
                                        {
                                            if (incy < 0) {
                                                gSDetETSDG116 = 1010534;
                                            } else {
                                                gSDetETSDG116 = 1010535;
                                            }
                                            break;
                                        }
                                      case 1010534:
                                        {
                                            iy = (-n + 1) * incy;
                                            gSDetETSDG116 = 1010532;
                                            break;
                                        }
                                      case 1010535:
                                        {
                                            gSDetETSDG116 = 1010532;
                                            break;
                                        }
                                    }
                                }
                        }
                        {
                            i = 0;
                            {
                                int gSDetETSDG110 = 1010510;
                                while (gSDetETSDG110 != 1010509)
                                    {
                                        switch (gSDetETSDG110) {
                                          case 1010510:

{
                                                {
                                                    int gSDetETSDG115 = 1010529;
                                                    while (gSDetETSDG115 != 1010528)
                                                        {
                                                            switch (gSDetETSDG115) {
                                                              case 1010529:
                                                                {
                                                                    if (i < n) {
                                                                        gSDetETSDG115 = 1010530;
                                                                    } else {
                                                                        gSDetETSDG115 = 1010531;
                                                                    }
                                                                    break;
                                                                }
                                                              case 1010530:
                                                                {
                                                                    gSDetETSDG110 = 1010511;
                                                                    gSDetETSDG115 = 1010528;
                                                                    break;
                                                                }
                                                              case 1010531:
                                                                {
                                                                    gSDetETSDG110 = 1010509;
                                                                    gSDetETSDG115 = 1010528;
                                                                    break;
                                                                }
                                                            }
                                                        }
                                                }
                                                break;
                                            }
                                          case 1010511:
                                            {
                                                {
                                                    dtemp = dtemp + dx[ix] * dy[iy];
                                                    ix = ix + incx;
                                                    iy = iy + incy;
                                                }
                                              eTDGEyDg246:
                                                {
                                                    i++;
                                                }
                                              eTDGEyDg251:
                                                {
                                                    ;
                                                }
                                                gSDetETSDG110 = 1010510;
                                                break;
                                            }
                                        }
                                    }
                              eTDGEyDg252:
                                {
                                    ;
                                }
                            }
                        }
                        return (dtemp);
                    }
                    gSDetETSDG118 = 1010540;
                    break;
                }
              case 1010543:
                {
                    gSDetETSDG118 = 1010540;
                    break;
                }
            }
        }
}

This technique involves transforming the control flow of a program in a way that makes it more difficult to trace\debug, while still preserving the same logic of the original program.

There are a number of different ways that control flow flattening can be implemented, but the basic idea is to take the control flow of the program and transform it into a series of nested if-then-else\while\switch statements.

This can be done by replacing all of the branches in the original code with conditional statements, and then nesting these statements so that the overall control flow becomes much more complex.

To avoid automatic code simplification and folding we can use opaque predicates technique with known conditions.

One of the biggest benefits of control flow flattening is that it can make it much more difficult for an attacker to understand the code and figure out how it works. This can be especially useful for protecting against reverse engineering and tampering attacks, as it can make it much more difficult for an attacker to modify the code or to understand its internal logic

2
  • 1
    Hi and welcome to RE.SE. In case of links to "own" resources it is customary and expected that affiliations are disclosed. Your avatar and the favicon and logo on the linked side probably don't coincide by pure chance? A look at your profile suggests it isn't coincidence. Please edit your answer. Also, it sounds a bit vague, almost like some AI-generated answer would sound (just stating, not accusing!).
    – 0xC0000022L
    Commented Jan 9, 2023 at 8:29
  • With the spammy link gone, this no longer deserves to be downvoted.
    – tripleee
    Commented Jan 9, 2023 at 11:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.