Overview

Today we are going to learn a bit about how to use angr for control flow deobfuscation. We are using the Pandora Ransomware sample as an example.

Sample (packed)

5b56c5d86347e164c6e571c86dbf5b1535eae6b979fede6ed66b01e79ea33b7b

Unpacked sample (we will be using this as our example)

2619862c382d3e375f13f3859c6ab44db1a4bce905b4a617df2390fbf36902e7

The Concept

We have a binary that has been obfuscated using control flow flattening (CFF). The original control flow (CF) of each function has been replaced by a state machine. Each of the original code basic blocks (BB) has been given a state. To transition between the basic blocks a dispatcher is used which routes the control flow based on the current state.

Before CFF

After CFF

Removing CFF

The theory behind removing CFF is to identify the state of each original BB, and the "next" state(s) that each original BB along with the condition that sets each of these states.

Basic Block State The BB state can be thought of as -- what state must be put INTO the dispatcher to route to the BB.

Next State The "next" state depends on whether the BB has conditional control flow. If there is no condition then there will only be ONE next state. However, if there is conditional control flow there will be TWO next states with a dependency on some condition within the BB.

In practice determining theses states and next states can be difficult as the dispatcher may be complex and the conditions used to set the next state may also be complex.

Initial Analysis

Before we can begin analyzing the CFF state machine we need to extract the following initial information from the function.

  • The function entrypoint (this is assumed)
  • The address of the DISPATCHER start
    • It may be possible to determine this heuristically as many bb will jmp to this address
  • The STATE variable
    • The STATE variable is the variable used to pass the STATE to the DISPATCHER. In simple CFF cases the same variable is used throughout the original code blocks but in more complex CFF this cannot be relied on.
  • The address of each of the original code basic blocks
    • This may not be needed initially depending on the strategy. It maybe be possible to recover these during symbolic execution.

An Emulation Approach (For Comparison)

To associate a state with each original code block we can first collect a list of states using some simple assembly pattern matching in the original code blocks (the state will be moved to EAX). Then start emulation at the dispatcher start and run an emulation for each state stopping on the first jmp to an original code block -- this is the state for that block. Then once we have the states associate with each block we can manually connect up the original basic blocks and remove the dispatcher.

This approach has many drawbacks, but the main issues are finding the states using pattern matching. Depending on how the states are set it may not be as simple as collecting all of the states by looking for a mov eax, immediate. Also, once the states are assigned to the basic blocks determining what conditions trigger which next state for each bb may also prove complex and very brittle (less re-usable code)

A Symbolic Execution Approach

With symbolic execution we are not limited to the constraints of the variables, but can "symbolically execute" over all possible paths between the dispatcher and the original basic blocks. This allows us to test all possible options for the STATE variable. T

Associating A STATE With Each Original Basic Block

The first step is to associate a STATE with a an original basic block (OBB). We have an advantage with the state machine in that we know the initial state (it muste either be set in the entrpoint or passed into the funcation as an argument). Once we know the initial state we can use this to execute until we reach an OBB. We can now associated this state with the OBB

Determining Next STATE(S)

Once we have reached the start of an OBB we can symbolically execute until we reach the dispatcher again. Once we reach the dispatcher we can query the symbolic equation for possible value(s) of the STATE. There may be multiple values depending on the conditional logic in the OBB. These STATE values are the next states and will allow us to further interrogate the function to associated with with more OBBs.

CFF State Machine Parsing Algorithm

The full algorithm for parsing the CFF state machine is described below.

  • Use initial STATE and execute from the start of the DISPATCHER until reaching an OBB
  • Assocated this STATE with the OBB
  • Continue executing until reaching the DISPATCHER
  • Solve the symbolic equation for the STATE(S) to determine the NEXT-STATE(s) of the OBB
  • Associate these NEXT-STATE(s) with the OBB (STATE -> OBB -> NEXT-STATE)
  • Repeate the process for each NEXT-STATE treatin each one as the new STATE

References

Complete Solution from @mrexodia

In this approach less initial information is needed about the binary. Also this approach will work on the un-patched binary (something I didn't realize was possible with angre). One of the advantage of Angr is that it is able to read and interact with the hard-coded jmp table without additional hooks/patches.

All creadit for this method goes to mrexodia who developed the original code and proof of concept for this approach.

import angr

proj = angr.Project("/tmp/pandora_dump_SCY.bin", load_options={'auto_load_libs': False})

def get_dispatcher_state(function, dispatcher):
    state = proj.factory.call_state(addr=function)

    # Ignore function calls
    # https://github.com/angr/angr/issues/723
    state.options.add(angr.options.CALLLESS)

    simgr = proj.factory.simulation_manager(state)

    # Find the dispatcher
    while True:
        simgr.step()
        assert len(simgr.active) == 1
        state = simgr.active[0]
        if state.addr == dispatcher:
            return state.copy()

addr_main = 0x7FF6C4B066F0
addr_dispatcher = 0x7ff6c4b067f0
dispatcher_state = get_dispatcher_state(function=addr_main, dispatcher=addr_dispatcher)
print(f"Dispatcher state: {dispatcher_state}")
initial_state = dispatcher_state.solver.eval_one(dispatcher_state.regs.eax)
print(f"Initial eax: {hex(initial_state)}")
WARNING | 2022-03-31 15:04:33,126 | angr.calling_conventions | Guessing call prototype. Please specify prototype.
WARNING | 2022-03-31 15:04:33,138 | angr.storage.memory_mixins.default_filler_mixin | The program is accessing register with an unspecified value. This could indicate unwanted behavior.
WARNING | 2022-03-31 15:04:33,139 | angr.storage.memory_mixins.default_filler_mixin | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by:
WARNING | 2022-03-31 15:04:33,139 | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state
WARNING | 2022-03-31 15:04:33,140 | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | 2022-03-31 15:04:33,141 | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to suppress these messages.
WARNING | 2022-03-31 15:04:33,141 | angr.storage.memory_mixins.default_filler_mixin | Filling register r15 with 8 unconstrained bytes referenced from 0x7ff6c4b066f0 (offset 0x66f0 in pandora_dump_SCY.bin (0x7ff6c4b066f0))
WARNING | 2022-03-31 15:04:33,143 | angr.storage.memory_mixins.default_filler_mixin | Filling register r14 with 8 unconstrained bytes referenced from 0x7ff6c4b066f2 (offset 0x66f2 in pandora_dump_SCY.bin (0x7ff6c4b066f2))
WARNING | 2022-03-31 15:04:33,144 | angr.storage.memory_mixins.default_filler_mixin | Filling register r13 with 8 unconstrained bytes referenced from 0x7ff6c4b066f4 (offset 0x66f4 in pandora_dump_SCY.bin (0x7ff6c4b066f4))
WARNING | 2022-03-31 15:04:33,146 | angr.storage.memory_mixins.default_filler_mixin | Filling register r12 with 8 unconstrained bytes referenced from 0x7ff6c4b066f6 (offset 0x66f6 in pandora_dump_SCY.bin (0x7ff6c4b066f6))
WARNING | 2022-03-31 15:04:33,147 | angr.storage.memory_mixins.default_filler_mixin | Filling register rsi with 8 unconstrained bytes referenced from 0x7ff6c4b066f8 (offset 0x66f8 in pandora_dump_SCY.bin (0x7ff6c4b066f8))
WARNING | 2022-03-31 15:04:33,149 | angr.storage.memory_mixins.default_filler_mixin | Filling register rdi with 8 unconstrained bytes referenced from 0x7ff6c4b066f9 (offset 0x66f9 in pandora_dump_SCY.bin (0x7ff6c4b066f9))
WARNING | 2022-03-31 15:04:33,150 | angr.storage.memory_mixins.default_filler_mixin | Filling register rbp with 8 unconstrained bytes referenced from 0x7ff6c4b066fa (offset 0x66fa in pandora_dump_SCY.bin (0x7ff6c4b066fa))
WARNING | 2022-03-31 15:04:33,152 | angr.storage.memory_mixins.default_filler_mixin | Filling register rbx with 8 unconstrained bytes referenced from 0x7ff6c4b066fb (offset 0x66fb in pandora_dump_SCY.bin (0x7ff6c4b066fb))
Dispatcher state: <SimState @ 0x7ff6c4b067f0>
Initial eax: 0x8cbc0434
# %%
def find_successors(state_value, dispatcher):
    state = dispatcher_state.copy()
    state.regs.eax = state.solver.BVV(state_value, 32)
    simgr = proj.factory.simulation_manager(state)
    while True:
        print(f"eax: {simgr.active[0].regs.eax}")
        print(f"Stepping: {simgr.active} ...")
        simgr.step()
        # TODO: the block before the dispatcher is wrong, we need the first non-dispatcher block
        if len(simgr.active) == 0:
            return state, []
        assert len(simgr.active) == 1

        state2 = simgr.active[0]
        print(f"  Only found a single sucessor: {hex(state2.addr)}")
        if state2.addr == dispatcher:
            print(f"  Dispatcher, eax: {state2.regs.eax}")
            # TODO: figure out where these potential values are set
            solutions = state2.solver.eval_upto(state2.regs.eax, 2)  # TODO: might need more potential states
            return state, solutions
        elif state2.addr == 0x7ff6c4b070ea:  # HACK: int3 here, no idea how to properly handle it
            return state, []
        state = state2
from queue import Queue

# state_value => real basic block state
states = {}

q = Queue()
q.put(initial_state)

while not q.empty():
    state_value = q.get()
    # Skip visited states
    if state_value in states:
        continue
    bb_state, successors = find_successors(state_value, addr_dispatcher)
    print(f"{hex(state_value)} {bb_state} => {[hex(n) for n in successors]}")
    print()
    states[state_value] = bb_state, successors
    for state_value in successors:
        q.put(state_value)

dot = "digraph CFG {\n"
for state_value in states.keys():
    _, succ = states[state_value]
    for s in succ:
        dot += f"\"{hex(state_value)}\" -> \"{hex(s)}\"\n"
dot += "}"
print(dot)
WARNING | 2022-03-31 15:04:33,342 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeffa8 with 4 unconstrained bytes referenced from 0x7ff6c4b06da8 (offset 0x6da8 in pandora_dump_SCY.bin (0x7ff6c4b06da8))
eax: <BV32 0x8cbc0434>
Stepping: [<SimState @ 0x7ff6c4b067f0>] ...
  Only found a single sucessor: 0x7ff6c4b06819
eax: <BV32 0x8cbc0434>
Stepping: [<SimState @ 0x7ff6c4b06819>] ...
  Only found a single sucessor: 0x7ff6c4b06830
eax: <BV32 0x8cbc0434>
Stepping: [<SimState @ 0x7ff6c4b06830>] ...
  Only found a single sucessor: 0x7ff6c4b06847
eax: <BV32 0x8cbc0434>
Stepping: [<SimState @ 0x7ff6c4b06847>] ...
  Only found a single sucessor: 0x7ff6c4b06863
eax: <BV32 0x8cbc0434>
Stepping: [<SimState @ 0x7ff6c4b06863>] ...
  Only found a single sucessor: 0x7ff6c4b0687f
eax: <BV32 0x8cbc0434>
Stepping: [<SimState @ 0x7ff6c4b0687f>] ...
  Only found a single sucessor: 0x7ff6c4b067f0
  Dispatcher, eax: <BV32 if fake_ret_value_10_64{UNINITIALIZED} == 0x0 then 0x173ba5e1 else 0x7d9d86f3>
0x8cbc0434 <SimState @ 0x7ff6c4b0687f> => ['0x7d9d86f3', '0x173ba5e1']

eax: <BV32 0x7d9d86f3>
Stepping: [<SimState @ 0x7ff6c4b067f0>] ...
  Only found a single sucessor: 0x7ff6c4b068b0
eax: <BV32 0x7d9d86f3>
Stepping: [<SimState @ 0x7ff6c4b068b0>] ...
  Only found a single sucessor: 0x7ff6c4b06a30
eax: <BV32 0x7d9d86f3>
Stepping: [<SimState @ 0x7ff6c4b06a30>] ...
  Only found a single sucessor: 0x7ff6c4b06bbb
eax: <BV32 0x7d9d86f3>
Stepping: [<SimState @ 0x7ff6c4b06bbb>] ...
  Only found a single sucessor: 0x7ff6c4b067c8
eax: <BV32 0x7d9d86f3>
Stepping: [<SimState @ 0x7ff6c4b067c8>] ...
  Only found a single sucessor: 0x7ff6c4b070d1
eax: <BV32 0x7d9d86f3>
Stepping: [<SimState @ 0x7ff6c4b070d1>] ...
  Only found a single sucessor: 0x7ff6c4b070ea
0x7d9d86f3 <SimState @ 0x7ff6c4b070d1> => []

eax: <BV32 0x173ba5e1>
Stepping: [<SimState @ 0x7ff6c4b067f0>] ...
  Only found a single sucessor: 0x7ff6c4b068b0
eax: <BV32 0x173ba5e1>
Stepping: [<SimState @ 0x7ff6c4b068b0>] ...
  Only found a single sucessor: 0x7ff6c4b068c7
eax: <BV32 0x173ba5e1>
Stepping: [<SimState @ 0x7ff6c4b068c7>] ...
  Only found a single sucessor: 0x7ff6c4b068e3
eax: <BV32 0x173ba5e1>
Stepping: [<SimState @ 0x7ff6c4b068e3>] ...
  Only found a single sucessor: 0x7ff6c4b06ce1
eax: <BV32 0x173ba5e1>
Stepping: [<SimState @ 0x7ff6c4b06ce1>] ...
  Only found a single sucessor: 0x7ff6c4b06cfd
eax: <BV32 0x173ba5e1>
Stepping: [<SimState @ 0x7ff6c4b06cfd>] ...
  Only found a single sucessor: 0x7ff6c4b06d2e
eax: <BV32 fake_ret_value_12_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06d2e>] ...
  Only found a single sucessor: 0x7ff6c4b06d54
eax: <BV32 fake_ret_value_13_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06d54>] ...
  Only found a single sucessor: 0x7ff6c4b06d67
eax: <BV32 fake_ret_value_14_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06d67>] ...
  Only found a single sucessor: 0x7ff6c4b06d7a
eax: <BV32 fake_ret_value_15_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06d7a>] ...
  Only found a single sucessor: 0x7ff6c4b06d95
eax: <BV32 fake_ret_value_16_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06d95>] ...
  Only found a single sucessor: 0x7ff6c4b06da8
eax: <BV32 fake_ret_value_17_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06da8>] ...
  Only found a single sucessor: 0x7ff6c4b06dd5
eax: <BV32 fake_ret_value_19_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06dd5>] ...
  Only found a single sucessor: 0x7ff6c4b06df2
eax: <BV32 fake_ret_value_20_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06df2>] ...
  Only found a single sucessor: 0x7ff6c4b06e05
eax: <BV32 fake_ret_value_21_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06e05>] ...
  Only found a single sucessor: 0x7ff6c4b06e20
eax: <BV32 fake_ret_value_22_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06e20>] ...
  Only found a single sucessor: 0x7ff6c4b06e33
eax: <BV32 fake_ret_value_23_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06e33>] ...
  Only found a single sucessor: 0x7ff6c4b06e57
eax: <BV32 fake_ret_value_24_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06e57>] ...
  Only found a single sucessor: 0x7ff6c4b067f0
  Dispatcher, eax: <BV32 0x7a980236>
0x173ba5e1 <SimState @ 0x7ff6c4b06e57> => ['0x7a980236']

eax: <BV32 0x7a980236>
Stepping: [<SimState @ 0x7ff6c4b067f0>] ...
  Only found a single sucessor: 0x7ff6c4b068b0
eax: <BV32 0x7a980236>
Stepping: [<SimState @ 0x7ff6c4b068b0>] ...
  Only found a single sucessor: 0x7ff6c4b06a30
eax: <BV32 0x7a980236>
Stepping: [<SimState @ 0x7ff6c4b06a30>] ...
  Only found a single sucessor: 0x7ff6c4b06a4c
eax: <BV32 0x7a980236>
Stepping: [<SimState @ 0x7ff6c4b06a4c>] ...
  Only found a single sucessor: 0x7ff6c4b06edc
eax: <BV32 0x7a980236>
Stepping: [<SimState @ 0x7ff6c4b06edc>] ...
WARNING | 2022-03-31 15:04:33,420 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff60 with 8 unconstrained bytes referenced from 0x7ff6c4b06ef8 (offset 0x6ef8 in pandora_dump_SCY.bin (0x7ff6c4b06ef8))
WARNING | 2022-03-31 15:04:33,489 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff50 with 8 unconstrained bytes referenced from 0x7ff6c4b0694d (offset 0x694d in pandora_dump_SCY.bin (0x7ff6c4b0694d))
WARNING | 2022-03-31 15:04:33,491 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff40 with 8 unconstrained bytes referenced from 0x7ff6c4b06952 (offset 0x6952 in pandora_dump_SCY.bin (0x7ff6c4b06952))
  Only found a single sucessor: 0x7ff6c4b06ef8
eax: <BV32 0x7a980236>
Stepping: [<SimState @ 0x7ff6c4b06ef8>] ...
  Only found a single sucessor: 0x7ff6c4b067f0
  Dispatcher, eax: <BV32 if 0x1 <= mem_7fffffffffeff60_25_64{UNINITIALIZED} then 0xa22a16af else 0x10bc6c78>
0x7a980236 <SimState @ 0x7ff6c4b06ef8> => ['0x10bc6c78', '0xa22a16af']

eax: <BV32 0x10bc6c78>
Stepping: [<SimState @ 0x7ff6c4b067f0>] ...
  Only found a single sucessor: 0x7ff6c4b068b0
eax: <BV32 0x10bc6c78>
Stepping: [<SimState @ 0x7ff6c4b068b0>] ...
  Only found a single sucessor: 0x7ff6c4b068c7
eax: <BV32 0x10bc6c78>
Stepping: [<SimState @ 0x7ff6c4b068c7>] ...
  Only found a single sucessor: 0x7ff6c4b068e3
eax: <BV32 0x10bc6c78>
Stepping: [<SimState @ 0x7ff6c4b068e3>] ...
  Only found a single sucessor: 0x7ff6c4b068ff
eax: <BV32 0x10bc6c78>
Stepping: [<SimState @ 0x7ff6c4b068ff>] ...
  Only found a single sucessor: 0x7ff6c4b0691a
eax: <BV32 0x10bc6c78>
Stepping: [<SimState @ 0x7ff6c4b0691a>] ...
  Only found a single sucessor: 0x7ff6c4b0694d
eax: <BV32 fake_ret_value_26_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b0694d>] ...
WARNING | 2022-03-31 15:04:33,940 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff34 with 4 unconstrained bytes referenced from 0x7ff6c4b06c61 (offset 0x6c61 in pandora_dump_SCY.bin (0x7ff6c4b06c61))
WARNING | 2022-03-31 15:04:34,025 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff38 with 4 unconstrained bytes referenced from 0x7ff6c4b07045 (offset 0x7045 in pandora_dump_SCY.bin (0x7ff6c4b07045))
  Only found a single sucessor: 0x7ff6c4b06976
eax: <BV32 fake_ret_value_29_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06976>] ...
  Only found a single sucessor: 0x7ff6c4b06992
eax: <BV32 fake_ret_value_30_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06992>] ...
  Only found a single sucessor: 0x7ff6c4b067f0
  Dispatcher, eax: <BV32 0x7a980236>
0x10bc6c78 <SimState @ 0x7ff6c4b06992> => ['0x7a980236']

eax: <BV32 0xa22a16af>
Stepping: [<SimState @ 0x7ff6c4b067f0>] ...
  Only found a single sucessor: 0x7ff6c4b06819
eax: <BV32 0xa22a16af>
Stepping: [<SimState @ 0x7ff6c4b06819>] ...
  Only found a single sucessor: 0x7ff6c4b06830
eax: <BV32 0xa22a16af>
Stepping: [<SimState @ 0x7ff6c4b06830>] ...
  Only found a single sucessor: 0x7ff6c4b06847
eax: <BV32 0xa22a16af>
Stepping: [<SimState @ 0x7ff6c4b06847>] ...
  Only found a single sucessor: 0x7ff6c4b06c45
eax: <BV32 0xa22a16af>
Stepping: [<SimState @ 0x7ff6c4b06c45>] ...
  Only found a single sucessor: 0x7ff6c4b06c61
eax: <BV32 0xa22a16af>
Stepping: [<SimState @ 0x7ff6c4b06c61>] ...
  Only found a single sucessor: 0x7ff6c4b06c85
eax: <BV32 fake_ret_value_32_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06c85>] ...
  Only found a single sucessor: 0x7ff6c4b06ca6
eax: <BV32 fake_ret_value_33_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06ca6>] ...
  Only found a single sucessor: 0x7ff6c4b06cb9
eax: <BV32 fake_ret_value_34_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06cb9>] ...
  Only found a single sucessor: 0x7ff6c4b067f0
  Dispatcher, eax: <BV32 if fake_ret_value_34_64{UNINITIALIZED}[31:0] == 0x0 then 0x7d71a1e3 else 0x3cd69d30>
0xa22a16af <SimState @ 0x7ff6c4b06cb9> => ['0x3cd69d30', '0x7d71a1e3']

eax: <BV32 0x3cd69d30>
Stepping: [<SimState @ 0x7ff6c4b067f0>] ...
  Only found a single sucessor: 0x7ff6c4b068b0
eax: <BV32 0x3cd69d30>
Stepping: [<SimState @ 0x7ff6c4b068b0>] ...
  Only found a single sucessor: 0x7ff6c4b068c7
eax: <BV32 0x3cd69d30>
Stepping: [<SimState @ 0x7ff6c4b068c7>] ...
  Only found a single sucessor: 0x7ff6c4b06b09
eax: <BV32 0x3cd69d30>
Stepping: [<SimState @ 0x7ff6c4b06b09>] ...
  Only found a single sucessor: 0x7ff6c4b07008
eax: <BV32 0x3cd69d30>
Stepping: [<SimState @ 0x7ff6c4b07008>] ...
  Only found a single sucessor: 0x7ff6c4b07024
eax: <BV32 0x3cd69d30>
Stepping: [<SimState @ 0x7ff6c4b07024>] ...
  Only found a single sucessor: 0x7ff6c4b067f0
  Dispatcher, eax: <BV32 0xc30bae2e>
0x3cd69d30 <SimState @ 0x7ff6c4b07024> => ['0xc30bae2e']

eax: <BV32 0x7d71a1e3>
WARNING | 2022-03-31 15:04:34,058 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff50 with 8 unconstrained bytes referenced from 0x7ff6c4b06c1c (offset 0x6c1c in pandora_dump_SCY.bin (0x7ff6c4b06c1c))
WARNING | 2022-03-31 15:04:34,096 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff2c with 4 unconstrained bytes referenced from 0x7ff6c4b06a04 (offset 0x6a04 in pandora_dump_SCY.bin (0x7ff6c4b06a04))
WARNING | 2022-03-31 15:04:34,099 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff26 with 2 unconstrained bytes referenced from 0x7ff6c4b06a22 (offset 0x6a22 in pandora_dump_SCY.bin (0x7ff6c4b06a22))
WARNING | 2022-03-31 15:04:34,102 | angr.storage.memory_mixins.default_filler_mixin | Filling register cc_ndep with 8 unconstrained bytes referenced from 0x7ff6c4b06a22 (offset 0x6a22 in pandora_dump_SCY.bin (0x7ff6c4b06a22))
WARNING | 2022-03-31 15:04:34,220 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff3c with 4 unconstrained bytes referenced from 0x7ff6c4b06b3b (offset 0x6b3b in pandora_dump_SCY.bin (0x7ff6c4b06b3b))
Stepping: [<SimState @ 0x7ff6c4b067f0>] ...
  Only found a single sucessor: 0x7ff6c4b068b0
eax: <BV32 0x7d71a1e3>
Stepping: [<SimState @ 0x7ff6c4b068b0>] ...
  Only found a single sucessor: 0x7ff6c4b06a30
eax: <BV32 0x7d71a1e3>
Stepping: [<SimState @ 0x7ff6c4b06a30>] ...
  Only found a single sucessor: 0x7ff6c4b06bbb
eax: <BV32 0x7d71a1e3>
Stepping: [<SimState @ 0x7ff6c4b06bbb>] ...
  Only found a single sucessor: 0x7ff6c4b06bd7
eax: <BV32 0x7d71a1e3>
Stepping: [<SimState @ 0x7ff6c4b06bd7>] ...
  Only found a single sucessor: 0x7ff6c4b06bf3
eax: <BV32 0x7d71a1e3>
Stepping: [<SimState @ 0x7ff6c4b06bf3>] ...
  Only found a single sucessor: 0x7ff6c4b06c0f
eax: <BV32 fake_ret_value_36_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06c0f>] ...
  Only found a single sucessor: 0x7ff6c4b06c40
eax: <BV32 fake_ret_value_38_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06c40>] ...
  Only found a single sucessor: 0x7ff6c4b06ecc
eax: <BV32 fake_ret_value_38_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06ecc>] ...
  Only found a single sucessor: 0x7ff6c4b067f0
  Dispatcher, eax: <BV32 0xa2992627>
0x7d71a1e3 <SimState @ 0x7ff6c4b06ecc> => ['0xa2992627']

eax: <BV32 0xc30bae2e>
Stepping: [<SimState @ 0x7ff6c4b067f0>] ...
  Only found a single sucessor: 0x7ff6c4b06819
eax: <BV32 0xc30bae2e>
Stepping: [<SimState @ 0x7ff6c4b06819>] ...
  Only found a single sucessor: 0x7ff6c4b069b0
eax: <BV32 0xc30bae2e>
Stepping: [<SimState @ 0x7ff6c4b069b0>] ...
  Only found a single sucessor: 0x7ff6c4b069cc
eax: <BV32 0xc30bae2e>
Stepping: [<SimState @ 0x7ff6c4b069cc>] ...
  Only found a single sucessor: 0x7ff6c4b069e8
eax: <BV32 0xc30bae2e>
Stepping: [<SimState @ 0x7ff6c4b069e8>] ...
  Only found a single sucessor: 0x7ff6c4b06a04
eax: <BV32 0xc30bae2e>
Stepping: [<SimState @ 0x7ff6c4b06a04>] ...
  Only found a single sucessor: 0x7ff6c4b067f0
  Dispatcher, eax: <BV32 if 0x5b <= mem_7fffffffffeff26_40_16{UNINITIALIZED} then 0x6c249751 else 0x3b2b8a1e>
0xc30bae2e <SimState @ 0x7ff6c4b06a04> => ['0x6c249751', '0x3b2b8a1e']

eax: <BV32 0xa2992627>
Stepping: [<SimState @ 0x7ff6c4b067f0>] ...
  Only found a single sucessor: 0x7ff6c4b06819
eax: <BV32 0xa2992627>
Stepping: [<SimState @ 0x7ff6c4b06819>] ...
  Only found a single sucessor: 0x7ff6c4b06830
eax: <BV32 0xa2992627>
Stepping: [<SimState @ 0x7ff6c4b06830>] ...
  Only found a single sucessor: 0x7ff6c4b06a99
eax: <BV32 0xa2992627>
Stepping: [<SimState @ 0x7ff6c4b06a99>] ...
  Only found a single sucessor: 0x7ff6c4b06ab5
eax: <BV32 0xa2992627>
Stepping: [<SimState @ 0x7ff6c4b06ab5>] ...
  Only found a single sucessor: 0x7ff6c4b06ad1
eax: <BV32 0xa2992627>
Stepping: [<SimState @ 0x7ff6c4b06ad1>] ...
  Only found a single sucessor: 0x7ff6c4b067f0
  Dispatcher, eax: <BV32 0xecce8ff1>
0xa2992627 <SimState @ 0x7ff6c4b06ad1> => ['0xecce8ff1']

eax: <BV32 0x6c249751>
Stepping: [<SimState @ 0x7ff6c4b067f0>] ...
  Only found a single sucessor: 0x7ff6c4b068b0
eax: <BV32 0x6c249751>
Stepping: [<SimState @ 0x7ff6c4b068b0>] ...
  Only found a single sucessor: 0x7ff6c4b06a30
eax: <BV32 0x6c249751>
Stepping: [<SimState @ 0x7ff6c4b06a30>] ...
  Only found a single sucessor: 0x7ff6c4b06a4c
eax: <BV32 0x6c249751>
Stepping: [<SimState @ 0x7ff6c4b06a4c>] ...
  Only found a single sucessor: 0x7ff6c4b06a68
eax: <BV32 0x6c249751>
Stepping: [<SimState @ 0x7ff6c4b06a68>] ...
  Only found a single sucessor: 0x7ff6c4b06a84
eax: <BV32 0x6c249751>
Stepping: [<SimState @ 0x7ff6c4b06a84>] ...
  Only found a single sucessor: 0x7ff6c4b067f0
  Dispatcher, eax: <BV32 0x7d71a1e3>
0x6c249751 <SimState @ 0x7ff6c4b06a84> => ['0x7d71a1e3']

eax: <BV32 0x3b2b8a1e>
Stepping: [<SimState @ 0x7ff6c4b067f0>] ...
  Only found a single sucessor: 0x7ff6c4b068b0
eax: <BV32 0x3b2b8a1e>
Stepping: [<SimState @ 0x7ff6c4b068b0>] ...
  Only found a single sucessor: 0x7ff6c4b068c7
eax: <BV32 0x3b2b8a1e>
Stepping: [<SimState @ 0x7ff6c4b068c7>] ...
  Only found a single sucessor: 0x7ff6c4b06b09
eax: <BV32 0x3b2b8a1e>
Stepping: [<SimState @ 0x7ff6c4b06b09>] ...
  Only found a single sucessor: 0x7ff6c4b06b20
eax: <BV32 0x3b2b8a1e>
Stepping: [<SimState @ 0x7ff6c4b06b20>] ...
  Only found a single sucessor: 0x7ff6c4b06b3b
eax: <BV32 0x3b2b8a1e>
Stepping: [<SimState @ 0x7ff6c4b06b3b>] ...
  Only found a single sucessor: 0x7ff6c4b067f0
  Dispatcher, eax: <BV32 if mem_7fffffffffeff3c_42_32{UNINITIALIZED}[0:0] == 0 then 0xd43fb344 else 0xc094d6c9>
WARNING | 2022-03-31 15:04:34,280 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff78 with 8 unconstrained bytes referenced from 0x7ff6c4b0706e (offset 0x706e in pandora_dump_SCY.bin (0x7ff6c4b0706e))
WARNING | 2022-03-31 15:04:34,325 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff26 with 2 unconstrained bytes referenced from 0x7ff6c4b06b9f (offset 0x6b9f in pandora_dump_SCY.bin (0x7ff6c4b06b9f))
0x3b2b8a1e <SimState @ 0x7ff6c4b06b3b> => ['0xd43fb344', '0xc094d6c9']

eax: <BV32 0xecce8ff1>
Stepping: [<SimState @ 0x7ff6c4b067f0>] ...
  Only found a single sucessor: 0x7ff6c4b06819
eax: <BV32 0xecce8ff1>
Stepping: [<SimState @ 0x7ff6c4b06819>] ...
  Only found a single sucessor: 0x7ff6c4b069b0
eax: <BV32 0xecce8ff1>
Stepping: [<SimState @ 0x7ff6c4b069b0>] ...
  Only found a single sucessor: 0x7ff6c4b06b63
eax: <BV32 0xecce8ff1>
Stepping: [<SimState @ 0x7ff6c4b06b63>] ...
  Only found a single sucessor: 0x7ff6c4b07052
eax: <BV32 0xecce8ff1>
Stepping: [<SimState @ 0x7ff6c4b07052>] ...
  Only found a single sucessor: 0x7ff6c4b0706e
eax: <BV32 0xecce8ff1>
Stepping: [<SimState @ 0x7ff6c4b0706e>] ...
  Only found a single sucessor: 0x7ff6c4b07090
eax: <BV32 fake_ret_value_44_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b07090>] ...
  Only found a single sucessor: 0x7ff6c4b070ae
eax: <BV32 fake_ret_value_45_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b070ae>] ...
  Only found a single sucessor: 0xffed89901000
eax: <BV32 0x0>
Stepping: [<SimState @ 0xffed89901000>] ...
0xecce8ff1 <SimState @ 0xffed89901000> => []

eax: <BV32 0xd43fb344>
Stepping: [<SimState @ 0x7ff6c4b067f0>] ...
  Only found a single sucessor: 0x7ff6c4b06819
eax: <BV32 0xd43fb344>
Stepping: [<SimState @ 0x7ff6c4b06819>] ...
  Only found a single sucessor: 0x7ff6c4b069b0
eax: <BV32 0xd43fb344>
Stepping: [<SimState @ 0x7ff6c4b069b0>] ...
  Only found a single sucessor: 0x7ff6c4b06b63
eax: <BV32 0xd43fb344>
Stepping: [<SimState @ 0x7ff6c4b06b63>] ...
  Only found a single sucessor: 0x7ff6c4b06b7f
eax: <BV32 0xd43fb344>
Stepping: [<SimState @ 0x7ff6c4b06b7f>] ...
  Only found a single sucessor: 0x7ff6c4b06b9b
eax: <BV32 0xd43fb344>
Stepping: [<SimState @ 0x7ff6c4b06b9b>] ...
  Only found a single sucessor: 0x7ff6c4b067f0
  Dispatcher, eax: <BV32 0xc30bae2e>
0xd43fb344 <SimState @ 0x7ff6c4b06b9b> => ['0xc30bae2e']

eax: <BV32 0xc094d6c9>
Stepping: [<SimState @ 0x7ff6c4b067f0>] ...
  Only found a single sucessor: 0x7ff6c4b06819
eax: <BV32 0xc094d6c9>
Stepping: [<SimState @ 0x7ff6c4b06819>] ...
  Only found a single sucessor: 0x7ff6c4b06830
eax: <BV32 0xc094d6c9>
Stepping: [<SimState @ 0x7ff6c4b06830>] ...
  Only found a single sucessor: 0x7ff6c4b06a99
eax: <BV32 0xc094d6c9>
Stepping: [<SimState @ 0x7ff6c4b06a99>] ...
  Only found a single sucessor: 0x7ff6c4b06f3d
eax: <BV32 0xc094d6c9>
Stepping: [<SimState @ 0x7ff6c4b06f3d>] ...
  Only found a single sucessor: 0x7ff6c4b06f58
eax: <BV32 0xc094d6c9>
Stepping: [<SimState @ 0x7ff6c4b06f58>] ...
  Only found a single sucessor: 0x7ff6c4b06f70
eax: <BV32 fake_ret_value_47_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06f70>] ...
WARNING | 2022-03-31 15:04:34,651 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff58 with 8 unconstrained bytes referenced from 0x7ff6c4b06f7e (offset 0x6f7e in pandora_dump_SCY.bin (0x7ff6c4b06f7e))
  Only found a single sucessor: 0x7ff6c4b06f9f
eax: <BV32 fake_ret_value_49_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06f9f>] ...
  Only found a single sucessor: 0x7ff6c4b06fc9
eax: <BV32 fake_ret_value_50_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06fc9>] ...
  Only found a single sucessor: 0x7ff6c4b06ff8
eax: <BV32 fake_ret_value_51_64{UNINITIALIZED}[31:0]>
Stepping: [<SimState @ 0x7ff6c4b06ff8>] ...
  Only found a single sucessor: 0x7ff6c4b067f0
  Dispatcher, eax: <BV32 0xd43fb344>
0xc094d6c9 <SimState @ 0x7ff6c4b06ff8> => ['0xd43fb344']

digraph CFG {
"0x8cbc0434" -> "0x7d9d86f3"
"0x8cbc0434" -> "0x173ba5e1"
"0x173ba5e1" -> "0x7a980236"
"0x7a980236" -> "0x10bc6c78"
"0x7a980236" -> "0xa22a16af"
"0x10bc6c78" -> "0x7a980236"
"0xa22a16af" -> "0x3cd69d30"
"0xa22a16af" -> "0x7d71a1e3"
"0x3cd69d30" -> "0xc30bae2e"
"0x7d71a1e3" -> "0xa2992627"
"0xc30bae2e" -> "0x6c249751"
"0xc30bae2e" -> "0x3b2b8a1e"
"0xa2992627" -> "0xecce8ff1"
"0x6c249751" -> "0x7d71a1e3"
"0x3b2b8a1e" -> "0xd43fb344"
"0x3b2b8a1e" -> "0xc094d6c9"
"0xd43fb344" -> "0xc30bae2e"
"0xc094d6c9" -> "0xd43fb344"
}

CFF State Machine Analysis

import angr
import claripy
import struct

BINARY_PATH = '/tmp/pandora_dump_SCY.bin'

entry_point = 0x00007FF6C4B066F0
dispatcher_start = 0x00007FF6C4B067F0

project = angr.Project(BINARY_PATH, load_options={'auto_load_libs': False})

# TODO: We should explicately add the state since we know it (main)
initial_state = project.factory.call_state(addr=entry_point)
# Use this setting to skip calls instead of a hook
initial_state.options.add(angr.options.CALLLESS)

simgr = project.factory.simgr(initial_state)
simgr.active
WARNING | 2022-03-31 15:04:34,837 | angr.calling_conventions | Guessing call prototype. Please specify prototype.
[<SimState @ 0x7ff6c4b066f0>]
simgr.run(until=lambda s: s.active[0].addr == dispatcher_start)
print(simgr.active)
initial_dispatcher_state = simgr.active[0]


eax_initial = initial_dispatcher_state.solver.eval_one(initial_dispatcher_state.regs.eax)
print(f"Initial state address {initial_dispatcher_state}")
print(f"Initial eax: {hex(eax_initial)}")
# Save the initial state at the dispatcher
initial_dispatcher_state = simgr.active[0]
WARNING | 2022-03-31 15:04:34,859 | angr.storage.memory_mixins.default_filler_mixin | Filling register r15 with 8 unconstrained bytes referenced from 0x7ff6c4b066f0 (offset 0x66f0 in pandora_dump_SCY.bin (0x7ff6c4b066f0))
WARNING | 2022-03-31 15:04:34,861 | angr.storage.memory_mixins.default_filler_mixin | Filling register r14 with 8 unconstrained bytes referenced from 0x7ff6c4b066f2 (offset 0x66f2 in pandora_dump_SCY.bin (0x7ff6c4b066f2))
WARNING | 2022-03-31 15:04:34,863 | angr.storage.memory_mixins.default_filler_mixin | Filling register r13 with 8 unconstrained bytes referenced from 0x7ff6c4b066f4 (offset 0x66f4 in pandora_dump_SCY.bin (0x7ff6c4b066f4))
WARNING | 2022-03-31 15:04:34,865 | angr.storage.memory_mixins.default_filler_mixin | Filling register r12 with 8 unconstrained bytes referenced from 0x7ff6c4b066f6 (offset 0x66f6 in pandora_dump_SCY.bin (0x7ff6c4b066f6))
WARNING | 2022-03-31 15:04:34,866 | angr.storage.memory_mixins.default_filler_mixin | Filling register rsi with 8 unconstrained bytes referenced from 0x7ff6c4b066f8 (offset 0x66f8 in pandora_dump_SCY.bin (0x7ff6c4b066f8))
WARNING | 2022-03-31 15:04:34,868 | angr.storage.memory_mixins.default_filler_mixin | Filling register rdi with 8 unconstrained bytes referenced from 0x7ff6c4b066f9 (offset 0x66f9 in pandora_dump_SCY.bin (0x7ff6c4b066f9))
WARNING | 2022-03-31 15:04:34,870 | angr.storage.memory_mixins.default_filler_mixin | Filling register rbp with 8 unconstrained bytes referenced from 0x7ff6c4b066fa (offset 0x66fa in pandora_dump_SCY.bin (0x7ff6c4b066fa))
WARNING | 2022-03-31 15:04:34,871 | angr.storage.memory_mixins.default_filler_mixin | Filling register rbx with 8 unconstrained bytes referenced from 0x7ff6c4b066fb (offset 0x66fb in pandora_dump_SCY.bin (0x7ff6c4b066fb))
[<SimState @ 0x7ff6c4b067f0>]
Initial state address <SimState @ 0x7ff6c4b067f0>
Initial eax: 0x8cbc0434
class Flags:
    def __init__(self, register):
        self.CF = False
        self.PF = False
        self.AF = False
        self.ZF = False
        self.SF = False
        self.TF = False
        self.IF = False
        self.DF = False
        self.OF = False
        if register & 0x0001 == 0x0001:
            self.CF = True
        if register & 0x0004 == 0x0004:
            self.PF = True
        if register & 0x0010 == 0x0010:
            self.AF = True
        if register & 0x0040 == 0x0040:
            self.ZF = True
        if register & 0x0080 == 0x0080:
            self.SF = True
        if register & 0x0100 == 0x0100:
            self.TF = True
        if register & 0x0200 == 0x0200:
            self.IF = True
        if register & 0x0400 == 0x0400:
            self.DF = True
        if register & 0x0800 == 0x0800:
            self.OF = True
        
        
state = initial_dispatcher_state.copy()

# Original code bb addresses
orig_code_bb = [0x7ff6c4b0687f, 0x7ff6c4b0691a, 0x7ff6c4b06a04, 0x7ff6c4b06a84, 0x7ff6c4b06ad1, 0x7ff6c4b06b3b, 0x7ff6c4b06b9b, 0x7ff6c4b06bf3, 0x7ff6c4b06c61, 0x7ff6c4b06cfd, 0x7ff6c4b06e9f, 0x7ff6c4b06ef8, 0x7ff6c4b06f58, 0x7ff6c4b07024]
ret_bb = 0x7FF6C4B0706E
orig_code_bb.append(ret_bb)

# Set symbol for state register eax (32 bit)
# eax_state = claripy.BVS('eax_state', 4*8)
# state.memory.store(state.regs.eax, eax_state)


end_bb = 0x7FF6C4B0706E
orig_bb_1_start  = 0x00007FF6C4B0691A
detatched_bb = 0x00007FF6C4B070D1
interrupt_bb = 0x00007FF6C4B070EA

# Run from initial state until we hit an original code block
simgr_dispatcher = project.factory.simgr(state)
while True:
    simgr_dispatcher.step() 
    print(simgr_dispatcher)
    if len(simgr_dispatcher.active) != 1:
        print(f"Multiple states!")
        break
    if simgr_dispatcher.active[0].addr in orig_code_bb:
        print(f"Found original code block")
        break
    print(f"Step: {hex(simgr_dispatcher.active[0].addr)}")
    
print(simgr_dispatcher)
state_1_end = simgr_dispatcher.active[0]
state_1_eax = eax_initial
state_1_bb = state_1_end.addr
print(f"state_1_eax:{hex(state_1_eax)} -> bb: {hex(state_1_bb)}")

# Run until dispatcher to get new state(s) 
while True:
    simgr_dispatcher.step()
    print(simgr_dispatcher)
    if len(simgr_dispatcher.active) != 1:
        print(f"Multiple states!")
        break
    if simgr_dispatcher.active[0].addr == dispatcher_start:
        print(f"Found dispatcher")
        break
    print(f"Step: {hex(simgr_dispatcher.active[0].addr)}")
        
# This is the start of state 2
state_2 = simgr_dispatcher.active[0]
# Retrieve all potential eax values
state_2_eax_values = state_2.solver.eval_upto(state_2.regs.eax, 8)
print(f"Number of possible state values: {len(state_2_eax_values)}")
# If there is more than one next state that means our code block will require a conditional jmp to replace
# the state machine, we will need to find end process both next states as well as determine what conditions
# cause each state
for state_2_eax_value in state_2_eax_values:
    print(f"state_2_eax:{hex(state_2_eax_value)}")
    # For each eax state we are going to need the associated flags to eventually replace the conditional
    # cmov with a conditional jmp 
    flags_values = state_2.solver.eval_upto(state_2.regs.flags, 2, extra_constraints=[state_2.regs.eax == state_2_eax_value])
    if len(flags_values) == 1:
        # This is the constrained state we must save these flags with the state
        print(f"Constrained state: {hex(state_2_eax_value)}")
        flags = flags_values[0]
        print(f"flags:{hex(flags)}")
        f = Flags(flags)
        print(f"CF: {f.CF}")
        print(f"PF: {f.PF}")
        print(f"AF: {f.AF}")
        print(f"ZF: {f.ZF}")
        print(f"SF: {f.SF}")
        print(f"TF: {f.TF}")
        print(f"IF: {f.IF}")
        print(f"DF: {f.DF}")
        print(f"OF: {f.OF}")
    else:
        # This is the unconstrained state, we don't need to save anything
        print(f"Unconstrained state: {hex(state_2_eax_value)}")
<SimulationManager with 1 active>
Step: 0x7ff6c4b06819
<SimulationManager with 1 active>
Step: 0x7ff6c4b06830
<SimulationManager with 1 active>
Step: 0x7ff6c4b06847
<SimulationManager with 1 active>
Step: 0x7ff6c4b06863
<SimulationManager with 1 active>
Found original code block
<SimulationManager with 1 active>
state_1_eax:0x8cbc0434 -> bb: 0x7ff6c4b0687f
WARNING | 2022-03-31 15:04:35,337 | angr.storage.memory_mixins.default_filler_mixin | Filling register cc_ndep with 8 unconstrained bytes referenced from 0x7ff6c4b067f0 (offset 0x67f0 in pandora_dump_SCY.bin (0x7ff6c4b067f0))
<SimulationManager with 1 active>
Found dispatcher
Number of possible state values: 2
state_2_eax:0x7d9d86f3
Unconstrained state: 0x7d9d86f3
state_2_eax:0x173ba5e1
Constrained state: 0x173ba5e1
flags:0x44
CF: False
PF: True
AF: False
ZF: True
SF: False
TF: False
IF: False
DF: False
OF: False
# and process both eax states seperately to link each one with their basic block

state_2_1 = state_2.copy()
state_2_1_eax = state_2_eax_values[0]
# Set eax back to a concrete value choosing state_2_1
state_2_1.regs.eax = state.solver.BVV(state_2_1_eax, 32)

state_2_2 = state_2.copy()
state_2_2_eax = state_2_eax_values[1]
# Set eax back to a concrete value choosing state_2_2
state_2_2.regs.eax = state.solver.BVV(state_2_2_eax, 32)
# Solve: state_2_1
########################
simgr_dispatcher_2_1 = project.factory.simgr(state_2_1)

stop_flag = False
while True:
    simgr_dispatcher_2_1.step()
    print(simgr_dispatcher_2_1)
    if len(simgr_dispatcher_2_1.active) == 0:
        print(f"Dead State!")
        stop_flag = True
        break
    if len(simgr_dispatcher_2_1.active) > 1:
        print(f"Multiple states: {len(simgr_dispatcher_2_1.active)}!")
        stop_flag = True
        break
    if simgr_dispatcher_2_1.active[0].addr == detatched_bb:
        print(f"Found detached block: {hex(simgr_dispatcher_2_1.active[0].addr)}!")
        stop_flag = True
        break
    if simgr_dispatcher_2_1.active[0].addr in orig_code_bb:
        print(f"Found original code block")
        break
    print(f"Step: {hex(simgr_dispatcher_2_1.active[0].addr)}")

if not stop_flag:
    state_2_1_end = simgr_dispatcher_2_1.active[0]
    state_2_1_bb = state_2_1_end.addr
    print(f"state_1_eax:{hex(state_2_1_eax)} -> bb: {hex(state_2_1_bb)}")

    print(f"** Find next state(s)")

    # This is the start of state 3
    state_3 = simgr_dispatcher_2_1.active[0]
    # Retrieve all potential eax values
    state_3_eax_values = state_3.solver.eval_upto(state_3.regs.eax, 8)
    print(f"Number of possible state values: {len(state_3_eax_values)}")
    # If there is more than one next state that means our code block will require a conditional jmp to replace
    # the state machine, we will need to find end process both next states as well as determine what conditions
    # cause each state
    if len(state_3_eax_values) == 1:
        state_3_eax_value = state_3_eax_values[0]
        print(f"Unconditonal JMP - State eax: {hex(state_3_eax_value)}")
    else:
        for state_3_eax_value in state_3_eax_values:
            print(f"state_3_eax:{hex(state_3_eax_value)}")
            # For each eax state we are going to need the associated flags to eventually replace the conditional
            # cmov with a conditional jmp 
            flags_values = state_3.solver.eval_upto(state_3.regs.flags, 2, extra_constraints=[state_3.regs.eax == state_3_eax_value])
            if len(flags_values) == 1:
                # This is the constrained state we must save these flags with the state
                print(f"Constrained state: {hex(state_3_eax_value)}")
                flags = flags_values[0]
                print(f"flags:{hex(flags)}")
                f = Flags(flags)
                print(f"CF: {f.CF}")
                print(f"PF: {f.PF}")
                print(f"AF: {f.AF}")
                print(f"ZF: {f.ZF}")
                print(f"SF: {f.SF}")
                print(f"TF: {f.TF}")
                print(f"IF: {f.IF}")
                print(f"DF: {f.DF}")
                print(f"OF: {f.OF}")
            else:
                # This is the unconstrained state, we don't need to save anything
                print(f"Unconstrained state: {hex(state_3_eax_value)}")
<SimulationManager with 1 active>
Step: 0x7ff6c4b068b0
<SimulationManager with 1 active>
Step: 0x7ff6c4b06a30
<SimulationManager with 1 active>
Step: 0x7ff6c4b06bbb
<SimulationManager with 1 active>
Step: 0x7ff6c4b067c8
<SimulationManager with 1 active>
Found detached block: 0x7ff6c4b070d1!
# Solve: state_2_2 (good one to copy)
#####################################
simgr_dispatcher_2_2 = project.factory.simgr(state_2_2)
stop_flag = False
while True:
    simgr_dispatcher_2_2.step()
    print(simgr_dispatcher_2_2)
    if len(simgr_dispatcher_2_2.active) == 0:
        print(f"Dead State!")
        stop_flag = True
        break
    if len(simgr_dispatcher_2_2.active) > 1:
        print(f"Multiple states: {len(simgr_dispatcher_2_2.active)}!")
        stop_flag = True
        break
    if simgr_dispatcher_2_2.active[0].addr == detatched_bb:
        print(f"Found detached block: {hex(simgr_dispatcher_2_2.active[0].addr)}!")
        stop_flag = True
        break
    if simgr_dispatcher_2_2.active[0].addr in orig_code_bb:
        print(f"Found original code block")
        break
    print(f"Step: {hex(simgr_dispatcher_2_2.active[0].addr)}")
    
if stop_flag:
    print(f"Stopped on bb: {hex(state_2_2_eax)}")
else:
    state_2_2_end = simgr_dispatcher_2_2.active[0]
    state_2_2_bb = state_2_2_end.addr
    print(f"state_1_eax:{hex(state_2_2_eax)} -> bb: {hex(state_2_2_bb)}")

    print(f"** Find next state(s)")
    
    # Run until dispatcher to get new state(s) 
    ob_dead_flag = False
    while True:
        simgr_dispatcher_2_2.step()
        print(simgr_dispatcher_2_2)
        if len(simgr_dispatcher_2_2.active) == 0:
            print(f"Dead end!")
            ob_dead_flag = True
            break
        if len(simgr_dispatcher_2_2.active) > 1:
            print(f"Multiple states in original bb {len(simgr_dispatcher_2_2.active)}!")
            ob_dead_flag = True
            break
        if simgr_dispatcher_2_2.active[0].addr == dispatcher_start:
            print(f"Found dispatcher")
            break
        print(f"Step: {hex(simgr_dispatcher.active[0].addr)}")
    
    if not ob_dead_flag:
        # This is the start of state 3
        state_3_2 = simgr_dispatcher_2_2.active[0]
        # Retrieve all potential eax values
        state_3_2_eax_values = state_3_2.solver.eval_upto(state_3_2.regs.eax, 8)
        print(f"Number of possible state values: {len(state_3_2_eax_values)}")
        # If there is more than one next state that means our code block will require a conditional jmp to replace
        # the state machine, we will need to find end process both next states as well as determine what conditions
        # cause each state
        if len(state_3_2_eax_values) == 1:
            state_3_2_eax_value = state_3_2_eax_values[0]
            print(f"Unconditonal JMP - State eax: {hex(state_3_2_eax_value)}")
        else:
            for state_3_2_eax_value in state_3_2_eax_values:
                print(f"state_3_2_eax:{hex(state_3_2_eax_value)}")
                # For each eax state we are going to need the associated flags to eventually replace the conditional
                # cmov with a conditional jmp 
                flags_values = state_3_2.solver.eval_upto(state_3_2.regs.flags, 2, extra_constraints=[state_3_2.regs.eax == state_3_2_eax_value])
                if len(flags_values) == 1:
                    # This is the constrained state we must save these flags with the state
                    print(f"Constrained state: {hex(state_3_2_eax_value)}")
                    flags = flags_values[0]
                    print(f"flags:{hex(flags)}")
                    f = Flags(flags)
                    print(f"CF: {f.CF}")
                    print(f"PF: {f.PF}")
                    print(f"AF: {f.AF}")
                    print(f"ZF: {f.ZF}")
                    print(f"SF: {f.SF}")
                    print(f"TF: {f.TF}")
                    print(f"IF: {f.IF}")
                    print(f"DF: {f.DF}")
                    print(f"OF: {f.OF}")
                else:
                    # This is the unconstrained state, we don't need to save anything
                    print(f"Unconstrained state: {hex(state_3_2_eax_value)}")
WARNING | 2022-03-31 15:04:35,479 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeffa8 with 4 unconstrained bytes referenced from 0x7ff6c4b06da8 (offset 0x6da8 in pandora_dump_SCY.bin (0x7ff6c4b06da8))
<SimulationManager with 1 active>
Step: 0x7ff6c4b068b0
<SimulationManager with 1 active>
Step: 0x7ff6c4b068c7
<SimulationManager with 1 active>
Step: 0x7ff6c4b068e3
<SimulationManager with 1 active>
Step: 0x7ff6c4b06ce1
<SimulationManager with 1 active>
Found original code block
state_1_eax:0x173ba5e1 -> bb: 0x7ff6c4b06cfd
** Find next state(s)
<SimulationManager with 1 active>
Step: 0x7ff6c4b067f0
<SimulationManager with 1 active>
Step: 0x7ff6c4b067f0
<SimulationManager with 1 active>
Step: 0x7ff6c4b067f0
<SimulationManager with 1 active>
Step: 0x7ff6c4b067f0
<SimulationManager with 1 active>
Step: 0x7ff6c4b067f0
<SimulationManager with 1 active>
Step: 0x7ff6c4b067f0
<SimulationManager with 1 active>
Step: 0x7ff6c4b067f0
<SimulationManager with 1 active>
Step: 0x7ff6c4b067f0
<SimulationManager with 1 active>
Step: 0x7ff6c4b067f0
<SimulationManager with 1 active>
Step: 0x7ff6c4b067f0
<SimulationManager with 1 active>
Step: 0x7ff6c4b067f0
<SimulationManager with 1 active>
Step: 0x7ff6c4b067f0
<SimulationManager with 1 active>
Found dispatcher
Number of possible state values: 1
Unconditonal JMP - State eax: 0x7a980236

Complete Solution

import angr
import claripy
import struct
from queue import Queue

BINARY_PATH = '/tmp/pandora_dump_SCY.bin'

# Information about the function which we can get from IDA
entry_point = 0x00007FF6C4B066F0
dispatcher_start = 0x00007FF6C4B067F0

# Original code bb addresses
orig_code_bb = [0x7ff6c4b0687f, 0x7ff6c4b0691a, 0x7ff6c4b06a04, 0x7ff6c4b06a84, 0x7ff6c4b06ad1, 0x7ff6c4b06b3b, 0x7ff6c4b06b9b, 0x7ff6c4b06bf3, 0x7ff6c4b06c61, 0x7ff6c4b06cfd, 0x7ff6c4b06e9f, 0x7ff6c4b06ef8, 0x7ff6c4b06f58, 0x7ff6c4b07024]
ret_bb = 0x7FF6C4B0706E
orig_code_bb.append(ret_bb)

detatched_bb = 0x00007FF6C4B070D1
interrupt_bb = 0x00007FF6C4B070EA


project = angr.Project(BINARY_PATH, load_options={'auto_load_libs': False})
# Start at function entrypoint
initial_state = project.factory.call_state(addr=entry_point)
# Use this setting to skip calls instead of a hook
initial_state.options.add(angr.options.CALLLESS)

simgr = project.factory.simgr(initial_state)


# Explore to dispatcher start
simgr.run(until=lambda s: s.active[0].addr == dispatcher_start)
print(simgr.active)

# Save the initial state at the dispatcher
initial_dispatcher_state = simgr.active[0].copy()

# Get initial state for eax
eax_initial = initial_dispatcher_state.solver.eval_one(initial_dispatcher_state.regs.eax)
print(f"Initial state address {initial_dispatcher_state}")
print(f"Initial eax: {hex(eax_initial)}")



def find_obb(eax_state):
    #
    # Returns the state, and the address of the obb (state,obb_address)
    # ** Will return NONE for obb unreachable (caller must handle this)
    # ** Will detached bb address if detached block found (caller must handle this)
    #
    return_state = None
    obb_address = None
    # Make a copy of the state so we can restore the original if we need
    state = initial_dispatcher_state.copy()
    # Set the eax value for the state
    state.regs.eax = state.solver.BVV(eax_state, 32)
    # Build a sim manager for our state
    simgr = project.factory.simgr(state)
    # Add danger limit for our loop
    danger_limit = 0
    # Step until we hit a obb or we can't reach an obb
    while danger_limit <= 10000:
        danger_limit += 1
        simgr.step()
        if len(simgr.active) == 0:
            #print(f"Dead State!")
            # Return nothing for dead state
            return None,None
        if len(simgr.active) > 1:
            #print(f"Multiple states: {len(simgr.active)}!")
            # Return nothing for branched state -- this shouldn't happen
            return None,None
        if simgr.active[0].addr == detatched_bb:
            #print(f"Found detached block: {hex(simgr.active[0].addr)}!")
            # Return the 
            return_state = None
            obb_address = simgr.active[0].addr
            return return_state, obb_address
        if simgr.active[0].addr in orig_code_bb:
            #print(f"Found original code block")
            return simgr.active[0].copy(),simgr.active[0].addr
        print(f"Step: {hex(simgr.active[0].addr)}")

        
def find_dispatcher(current_state):
    #
    # Returns the next state values and associated flags [(state_value,flags)]
    # ** if the state is unconstrained the flags will be NONE
    # ** if the dispatcher is unreachable we return an empty set []
    state_values = []
    # Make a copy of the state so we can restore the original if we need
    state = current_state.copy()
    # Build sim manager for state
    simgr = project.factory.simgr(state)
    # Add danger limit for our loop
    danger_limit = 0
    # Step until we hit dispatcher
    while danger_limit <= 10000:
        danger_limit += 1
        simgr.step()
        if len(simgr.active) == 0:
            #print(f"Dead end!")
            return []
        if len(simgr.active) > 1:
            #print(f"Multiple states in original bb {len(simgr.active)}!")
            return []
        if simgr.active[0].addr == dispatcher_start:
            #print(f"Found dispatcher")
            break
        print(f"Step: {hex(simgr.active[0].addr)}")
    # If we got here we have some next states for the obb let's solve for them
    current_state= simgr.active[0]
    # Retrieve all potential eax values
    eax_values = current_state.solver.eval_upto(current_state.regs.eax, 8)
    #print(f"Number of possible state values: {len(eax_values)}")
    # If there is more than one next state that means our code block will require a conditional jmp to replace
    # the state machine, we will need to find end process both next states as well as determine what conditions
    # cause each state
    if len(eax_values) == 1:
        eax_value = eax_values[0]
        #print(f"Unconditonal JMP - State eax: {hex(eax_value)}")
        state_values.append((eax_value,None))
    else:
        for eax_value in eax_values:
            #print(f"eax_value:{hex(eax_value)}")
            # For each eax state we are going to need the associated flags to eventually replace the conditional
            # cmov with a conditional jmp 
            flags_values = current_state.solver.eval_upto(current_state.regs.flags, 2, extra_constraints=[current_state.regs.eax == eax_value])
            # if len(flags_values) == 1:
            # This is the constrained state we must save these flags with the state
            #print(f"Constrained state: {hex(eax_value)}")
            flags = flags_values[0]
            state_values.append((eax_value,flags))
            # else:
            #     # This is the unconstrained state, we don't need to save anything
            #     #print(f"Unconstrained state: {hex(eax_value)}")
            #     state_values.append((eax_value,None))
    return state_values

    



def get_state_info(state_value):
    # (obb_address, [(state,flags)])
    obb_state,obb_address = find_obb(state_value)    
    # Check if we hit a dead state
    if obb_address == None:
        print(f"Dead state: {hex(state_value)}")
        return None
    # Check if we a detached bb
    elif obb_address == detatched_bb:
        print(f"Detached BB: State:{hex(state_value)} -> bb: {hex(obb_address)}")
        return (obb_address, [])
    else:
        print(f"State:{hex(state_value)} -> bb: {hex(obb_address)}")
        # Find next states
        next_states = find_dispatcher(obb_state)
        # Check if end code block (no path to dispatcher)
        if len(next_states) == 0:
            print(f"{hex(obb_address)} is end state!")
            return (obb_address, [])
        # Check if this is an unconditional jmp
        elif len(next_states) == 1:
            print(f"{hex(obb_address)} -> jmp state:{hex(next_states[0][0])}")
            return (obb_address, next_states)
        # If there are multiple states print the conditions
        else:
            for next_state in next_states:
                if next_state[1] == None:
                    # This is unconditional jmp
                    print(f"{hex(obb_address)} -> jmp state:{hex(next_state[0])}")
                else:
                    # This is a conditional jmp
                    print(f"{hex(obb_address)} ->  conditional jmp state:{hex(next_state[0])}")
            return (obb_address, next_states)


# state_table[state] = (obb_address, [(state,flags)])
state_table = {}


q = Queue()
q.put(eax_initial)

while not q.empty():
    state_value = q.get()
    state_info = get_state_info(state_value)
    if state_info is not None: 
        state_table[state_value] = state_info
        # If we have a new state add it to the queue 
        for next_state in state_info[1]:
            next_state_value = next_state[0]
            if next_state_value not in state_table:
                q.put(next_state_value)



    
WARNING | 2022-03-31 17:31:51,900 | angr.calling_conventions | Guessing call prototype. Please specify prototype.
WARNING | 2022-03-31 17:31:51,910 | angr.storage.memory_mixins.default_filler_mixin | Filling register r15 with 8 unconstrained bytes referenced from 0x7ff6c4b066f0 (offset 0x66f0 in pandora_dump_SCY.bin (0x7ff6c4b066f0))
WARNING | 2022-03-31 17:31:51,912 | angr.storage.memory_mixins.default_filler_mixin | Filling register r14 with 8 unconstrained bytes referenced from 0x7ff6c4b066f2 (offset 0x66f2 in pandora_dump_SCY.bin (0x7ff6c4b066f2))
WARNING | 2022-03-31 17:31:51,914 | angr.storage.memory_mixins.default_filler_mixin | Filling register r13 with 8 unconstrained bytes referenced from 0x7ff6c4b066f4 (offset 0x66f4 in pandora_dump_SCY.bin (0x7ff6c4b066f4))
WARNING | 2022-03-31 17:31:51,916 | angr.storage.memory_mixins.default_filler_mixin | Filling register r12 with 8 unconstrained bytes referenced from 0x7ff6c4b066f6 (offset 0x66f6 in pandora_dump_SCY.bin (0x7ff6c4b066f6))
WARNING | 2022-03-31 17:31:51,917 | angr.storage.memory_mixins.default_filler_mixin | Filling register rsi with 8 unconstrained bytes referenced from 0x7ff6c4b066f8 (offset 0x66f8 in pandora_dump_SCY.bin (0x7ff6c4b066f8))
WARNING | 2022-03-31 17:31:51,919 | angr.storage.memory_mixins.default_filler_mixin | Filling register rdi with 8 unconstrained bytes referenced from 0x7ff6c4b066f9 (offset 0x66f9 in pandora_dump_SCY.bin (0x7ff6c4b066f9))
WARNING | 2022-03-31 17:31:51,920 | angr.storage.memory_mixins.default_filler_mixin | Filling register rbp with 8 unconstrained bytes referenced from 0x7ff6c4b066fa (offset 0x66fa in pandora_dump_SCY.bin (0x7ff6c4b066fa))
WARNING | 2022-03-31 17:31:51,921 | angr.storage.memory_mixins.default_filler_mixin | Filling register rbx with 8 unconstrained bytes referenced from 0x7ff6c4b066fb (offset 0x66fb in pandora_dump_SCY.bin (0x7ff6c4b066fb))
WARNING | 2022-03-31 17:31:51,995 | angr.storage.memory_mixins.default_filler_mixin | Filling register cc_ndep with 8 unconstrained bytes referenced from 0x7ff6c4b067f0 (offset 0x67f0 in pandora_dump_SCY.bin (0x7ff6c4b067f0))
WARNING | 2022-03-31 17:31:52,102 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeffa8 with 4 unconstrained bytes referenced from 0x7ff6c4b06da8 (offset 0x6da8 in pandora_dump_SCY.bin (0x7ff6c4b06da8))
[<SimState @ 0x7ff6c4b067f0>]
Initial state address <SimState @ 0x7ff6c4b067f0>
Initial eax: 0x8cbc0434
Step: 0x7ff6c4b06819
Step: 0x7ff6c4b06830
Step: 0x7ff6c4b06847
Step: 0x7ff6c4b06863
State:0x8cbc0434 -> bb: 0x7ff6c4b0687f
0x7ff6c4b0687f ->  conditional jmp state:0x7d9d86f3
0x7ff6c4b0687f ->  conditional jmp state:0x173ba5e1
Step: 0x7ff6c4b068b0
Step: 0x7ff6c4b06a30
Step: 0x7ff6c4b06bbb
Step: 0x7ff6c4b067c8
Detached BB: State:0x7d9d86f3 -> bb: 0x7ff6c4b070d1
Step: 0x7ff6c4b068b0
Step: 0x7ff6c4b068c7
Step: 0x7ff6c4b068e3
Step: 0x7ff6c4b06ce1
State:0x173ba5e1 -> bb: 0x7ff6c4b06cfd
Step: 0x7ff6c4b06d2e
Step: 0x7ff6c4b06d54
Step: 0x7ff6c4b06d67
Step: 0x7ff6c4b06d7a
Step: 0x7ff6c4b06d95
Step: 0x7ff6c4b06da8
Step: 0x7ff6c4b06dd5
Step: 0x7ff6c4b06df2
Step: 0x7ff6c4b06e05
Step: 0x7ff6c4b06e20
Step: 0x7ff6c4b06e33
WARNING | 2022-03-31 17:31:52,166 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff60 with 8 unconstrained bytes referenced from 0x7ff6c4b06ef8 (offset 0x6ef8 in pandora_dump_SCY.bin (0x7ff6c4b06ef8))
WARNING | 2022-03-31 17:31:52,200 | angr.storage.memory_mixins.default_filler_mixin | Filling register cc_ndep with 8 unconstrained bytes referenced from 0x7ff6c4b067f0 (offset 0x67f0 in pandora_dump_SCY.bin (0x7ff6c4b067f0))
WARNING | 2022-03-31 17:31:52,265 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff50 with 8 unconstrained bytes referenced from 0x7ff6c4b0694d (offset 0x694d in pandora_dump_SCY.bin (0x7ff6c4b0694d))
WARNING | 2022-03-31 17:31:52,266 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff40 with 8 unconstrained bytes referenced from 0x7ff6c4b06952 (offset 0x6952 in pandora_dump_SCY.bin (0x7ff6c4b06952))
Step: 0x7ff6c4b06e57
0x7ff6c4b06cfd -> jmp state:0x7a980236
Step: 0x7ff6c4b068b0
Step: 0x7ff6c4b06a30
Step: 0x7ff6c4b06a4c
Step: 0x7ff6c4b06edc
State:0x7a980236 -> bb: 0x7ff6c4b06ef8
0x7ff6c4b06ef8 ->  conditional jmp state:0x10bc6c78
0x7ff6c4b06ef8 ->  conditional jmp state:0xa22a16af
Step: 0x7ff6c4b068b0
Step: 0x7ff6c4b068c7
Step: 0x7ff6c4b068e3
Step: 0x7ff6c4b068ff
State:0x10bc6c78 -> bb: 0x7ff6c4b0691a
Step: 0x7ff6c4b0694d
WARNING | 2022-03-31 17:31:52,703 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff34 with 4 unconstrained bytes referenced from 0x7ff6c4b06c61 (offset 0x6c61 in pandora_dump_SCY.bin (0x7ff6c4b06c61))
WARNING | 2022-03-31 17:31:52,755 | angr.storage.memory_mixins.default_filler_mixin | Filling register cc_ndep with 8 unconstrained bytes referenced from 0x7ff6c4b067f0 (offset 0x67f0 in pandora_dump_SCY.bin (0x7ff6c4b067f0))
Step: 0x7ff6c4b06976
Step: 0x7ff6c4b06992
0x7ff6c4b0691a -> jmp state:0x7a980236
Step: 0x7ff6c4b06819
Step: 0x7ff6c4b06830
Step: 0x7ff6c4b06847
Step: 0x7ff6c4b06c45
State:0xa22a16af -> bb: 0x7ff6c4b06c61
Step: 0x7ff6c4b06c85
Step: 0x7ff6c4b06ca6
Step: 0x7ff6c4b06cb9
0x7ff6c4b06c61 ->  conditional jmp state:0x3cd69d30
0x7ff6c4b06c61 ->  conditional jmp state:0x7d71a1e3
Step: 0x7ff6c4b068b0
Step: 0x7ff6c4b068c7
Step: 0x7ff6c4b06b09
Step: 0x7ff6c4b07008
WARNING | 2022-03-31 17:31:52,813 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff38 with 4 unconstrained bytes referenced from 0x7ff6c4b07045 (offset 0x7045 in pandora_dump_SCY.bin (0x7ff6c4b07045))
WARNING | 2022-03-31 17:31:52,845 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff50 with 8 unconstrained bytes referenced from 0x7ff6c4b06c1c (offset 0x6c1c in pandora_dump_SCY.bin (0x7ff6c4b06c1c))
WARNING | 2022-03-31 17:31:52,880 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff2c with 4 unconstrained bytes referenced from 0x7ff6c4b06a04 (offset 0x6a04 in pandora_dump_SCY.bin (0x7ff6c4b06a04))
WARNING | 2022-03-31 17:31:52,883 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff26 with 2 unconstrained bytes referenced from 0x7ff6c4b06a22 (offset 0x6a22 in pandora_dump_SCY.bin (0x7ff6c4b06a22))
WARNING | 2022-03-31 17:31:52,886 | angr.storage.memory_mixins.default_filler_mixin | Filling register cc_ndep with 8 unconstrained bytes referenced from 0x7ff6c4b06a22 (offset 0x6a22 in pandora_dump_SCY.bin (0x7ff6c4b06a22))
State:0x3cd69d30 -> bb: 0x7ff6c4b07024
0x7ff6c4b07024 -> jmp state:0xc30bae2e
Step: 0x7ff6c4b068b0
Step: 0x7ff6c4b06a30
Step: 0x7ff6c4b06bbb
Step: 0x7ff6c4b06bd7
State:0x7d71a1e3 -> bb: 0x7ff6c4b06bf3
Step: 0x7ff6c4b06c0f
Step: 0x7ff6c4b06c40
Step: 0x7ff6c4b06ecc
0x7ff6c4b06bf3 -> jmp state:0xa2992627
Step: 0x7ff6c4b06819
Step: 0x7ff6c4b069b0
Step: 0x7ff6c4b069cc
Step: 0x7ff6c4b069e8
State:0xc30bae2e -> bb: 0x7ff6c4b06a04
0x7ff6c4b06a04 ->  conditional jmp state:0x6c249751
0x7ff6c4b06a04 ->  conditional jmp state:0x3b2b8a1e
Step: 0x7ff6c4b06819
Step: 0x7ff6c4b06830
Step: 0x7ff6c4b06a99
Step: 0x7ff6c4b06ab5
State:0xa2992627 -> bb: 0x7ff6c4b06ad1
0x7ff6c4b06ad1 -> jmp state:0xecce8ff1
Step: 0x7ff6c4b068b0
Step: 0x7ff6c4b06a30
Step: 0x7ff6c4b06a4c
Step: 0x7ff6c4b06a68
State:0x6c249751 -> bb: 0x7ff6c4b06a84
0x7ff6c4b06a84 -> jmp state:0x7d71a1e3
Step: 0x7ff6c4b068b0
Step: 0x7ff6c4b068c7
WARNING | 2022-03-31 17:31:53,034 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff3c with 4 unconstrained bytes referenced from 0x7ff6c4b06b3b (offset 0x6b3b in pandora_dump_SCY.bin (0x7ff6c4b06b3b))
WARNING | 2022-03-31 17:31:53,067 | angr.storage.memory_mixins.default_filler_mixin | Filling register cc_ndep with 8 unconstrained bytes referenced from 0x7ff6c4b067f0 (offset 0x67f0 in pandora_dump_SCY.bin (0x7ff6c4b067f0))
WARNING | 2022-03-31 17:31:53,118 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff78 with 8 unconstrained bytes referenced from 0x7ff6c4b0706e (offset 0x706e in pandora_dump_SCY.bin (0x7ff6c4b0706e))
WARNING | 2022-03-31 17:31:53,160 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff26 with 2 unconstrained bytes referenced from 0x7ff6c4b06b9f (offset 0x6b9f in pandora_dump_SCY.bin (0x7ff6c4b06b9f))
Step: 0x7ff6c4b06b09
Step: 0x7ff6c4b06b20
State:0x3b2b8a1e -> bb: 0x7ff6c4b06b3b
0x7ff6c4b06b3b ->  conditional jmp state:0xd43fb344
0x7ff6c4b06b3b ->  conditional jmp state:0xc094d6c9
Step: 0x7ff6c4b06819
Step: 0x7ff6c4b069b0
Step: 0x7ff6c4b06b63
Step: 0x7ff6c4b07052
State:0xecce8ff1 -> bb: 0x7ff6c4b0706e
Step: 0x7ff6c4b07090
Step: 0x7ff6c4b070ae
Step: 0xffed89901000
0x7ff6c4b0706e is end state!
Step: 0x7ff6c4b06819
Step: 0x7ff6c4b069b0
Step: 0x7ff6c4b06b63
Step: 0x7ff6c4b06b7f
State:0xd43fb344 -> bb: 0x7ff6c4b06b9b
0x7ff6c4b06b9b -> jmp state:0xc30bae2e
Step: 0x7ff6c4b06819
Step: 0x7ff6c4b06830
Step: 0x7ff6c4b06a99
Step: 0x7ff6c4b06f3d
State:0xc094d6c9 -> bb: 0x7ff6c4b06f58
Step: 0x7ff6c4b06f70
WARNING | 2022-03-31 17:31:53,486 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff58 with 8 unconstrained bytes referenced from 0x7ff6c4b06f7e (offset 0x6f7e in pandora_dump_SCY.bin (0x7ff6c4b06f7e))
Step: 0x7ff6c4b06f9f
Step: 0x7ff6c4b06fc9
Step: 0x7ff6c4b06ff8
0x7ff6c4b06f58 -> jmp state:0xd43fb344
state_table
{2361132084: (140697838577791, [(2107475699, 0), (389785057, 68)]),
 2107475699: (140697838579921, []),
 389785057: (140697838578941, [(2056782390, None)]),
 2056782390: (140697838579448, [(280783992, 149), (2720667311, 148)]),
 280783992: (140697838577946, [(2056782390, None)]),
 2720667311: (140697838578785, [(1020697904, 0), (2104599011, 68)]),
 1020697904: (140697838579748, [(3272322606, None)]),
 2104599011: (140697838578675, [(2727945767, None)]),
 3272322606: (140697838578180, [(1814337361, 148), (992709150, 149)]),
 2727945767: (140697838578385, [(3972960241, None)]),
 1814337361: (140697838578308, [(2104599011, None)]),
 992709150: (140697838578491, [(3560944452, 68), (3230979785, 0)]),
 3972960241: (140697838579822, []),
 3560944452: (140697838578587, [(3272322606, None)]),
 3230979785: (140697838579544, [(3560944452, None)])}

Build Patching Script in IDA Python

For each obb look up the state_info

  • if not conditional jmp then patch last bytes in obb with jmp -> state (lookup state address)
  • if conditional then read in the last few bytes of the obb and try to determin the condition
    • compare the condition with the saved flags for each state
    • add one conditional jmp based on the flags
    • add one unconditional jmp for the other state
class Flags:
    def __init__(self, register):
        self.CF = False
        self.PF = False
        self.AF = False
        self.ZF = False
        self.SF = False
        self.TF = False
        self.IF = False
        self.DF = False
        self.OF = False
        if register & 0x0001 == 0x0001:
            self.CF = True
        if register & 0x0004 == 0x0004:
            self.PF = True
        if register & 0x0010 == 0x0010:
            self.AF = True
        if register & 0x0040 == 0x0040:
            self.ZF = True
        if register & 0x0080 == 0x0080:
            self.SF = True
        if register & 0x0100 == 0x0100:
            self.TF = True
        if register & 0x0200 == 0x0200:
            self.IF = True
        if register & 0x0400 == 0x0400:
            self.DF = True
        if register & 0x0800 == 0x0800:
            self.OF = True


state_table = {2361132084: (140697838577791, [(2107475699, 0), (389785057, 68)]),
 2107475699: (140697838579921, []),
 389785057: (140697838578941, [(2056782390, None)]),
 2056782390: (140697838579448, [(280783992, 149), (2720667311, 148)]),
 280783992: (140697838577946, [(2056782390, None)]),
 2720667311: (140697838578785, [(1020697904, 0), (2104599011, 68)]),
 1020697904: (140697838579748, [(3272322606, None)]),
 2104599011: (140697838578675, [(2727945767, None)]),
 3272322606: (140697838578180, [(1814337361, 148), (992709150, 149)]),
 2727945767: (140697838578385, [(3972960241, None)]),
 1814337361: (140697838578308, [(2104599011, None)]),
 992709150: (140697838578491, [(3560944452, 68), (3230979785, 0)]),
 3972960241: (140697838579822, []),
 3560944452: (140697838578587, [(3272322606, None)]),
 3230979785: (140697838579544, [(3560944452, None)])}


orig_code_bb = [0x7ff6c4b0687f, 0x7ff6c4b0691a, 0x7ff6c4b06a04, 0x7ff6c4b06a84, 0x7ff6c4b06ad1, 0x7ff6c4b06b3b, 0x7ff6c4b06b9b, 0x7ff6c4b06bf3, 0x7ff6c4b06c61, 0x7ff6c4b06cfd, 0x7ff6c4b06e9f, 0x7ff6c4b06ef8, 0x7ff6c4b06f58, 0x7ff6c4b07024]


entry_point = 0x00007FF6C4B066F0

ret_bb = 0x7FF6C4B0706E

detatched_bb = 0x00007FF6C4B070D1

# Add these blocks so we test them as well
orig_code_bb.append(entry_point)
orig_code_bb.append(ret_bb)
orig_code_bb.append(detatched_bb)

# we also need to fake an initial block so that the entrypoint has some state info
# We know that it jumps to the initial state so just add it manually 
# Initial eax: 0x8cbc0434
state_table[0xffff] = (0x00007FF6C4B066F0, [0x8cbc0434, None])

for obb_addr in orig_code_bb:
    for state in state_table:
        state_info = state_table[state]
        if obb_addr == state_info[0]:
            # Found the obb info 
            if len(state_info[1]) == 0:
                # This is an end state do nothing
                break
            elif len(state_info[1]) == 1:
                # Unconditional jmp
                # Determine next_obb address from next_state
                next_state = state_info[1][0][0]
                next_obb_address = state_table[next_state][0]
                # Iterate through obb until we hit the jmp
                # replace it with the new state obb address
                ptr = obb_addr
                while print_insn_mnem(ptr) != 'jmp':
                    ptr = next_head(ptr)
                jmp_rel =  next_obb_address - (ptr + 5)
                patch_jmp = b'\xe9' +  struct.pack('<i',jmp_rel)
                #print(f"Unconditional patch {patch_jmp} at {hex(ptr)}")
                # Patch bytes
                ida_bytes.patch_bytes(ptr,patch_jmp) 
                break
            else:
                # Conditional jmp
                print(f"obb: {hex(obb_addr)}")
                ptr = obb_addr
                conditional_jmp_instruction = None
                conditional_jmp_address = None
                unconditional_jmp_address = None
                while print_insn_mnem(ptr) != 'jmp':
                    instruction = print_insn_mnem(ptr)
                    if "cmovnz" == instruction:
                        # Use a JNZ (ZF=0)
                        # Check the flags for both next states 
                        # Make sure on one flag set statisfies condition 
                        next_state_info_1  = state_info[1][0]
                        next_state_info_2 = state_info[1][1]
                        f = Flags(next_state_info_1[1])
                        conditional_jmp_instruction = b'\x0F\x85'
                        if not f.ZF:
                            # Found our conditional state next_state_info_1
                            conditional_jmp_address = state_table[next_state_info_1[0]][0]
                            unconditional_jmp_address = state_table[next_state_info_2[0]][0]
                        else:
                            # Found our conditional state next_state_info_2
                            conditional_jmp_address = state_table[next_state_info_2[0]][0]
                            unconditional_jmp_address = state_table[next_state_info_1[0]][0]
                    if "cmovb" == instruction:
                        # Use a JB  (CF=1)
                        # Check the flags for both next states 
                        # Make sure on one flag set statisfies condition 
                        next_state_info_1  = state_info[1][0]
                        next_state_info_2 = state_info[1][1]
                        f = Flags(next_state_info_1[1])
                        conditional_jmp_instruction = b'\x0F\x82'
                        if f.CF:
                            # Found our conditional state next_state_info_1
                            conditional_jmp_address = state_table[next_state_info_1[0]][0]
                            unconditional_jmp_address = state_table[next_state_info_2[0]][0]
                        else:
                            # Found our conditional state next_state_info_2
                            conditional_jmp_address = state_table[next_state_info_2[0]][0]
                            unconditional_jmp_address = state_table[next_state_info_1[0]][0]
                    # Increment to next instruction
                    ptr = next_head(ptr)
                # Build patch for conditional jmp
                jmp_rel =  conditional_jmp_address - (ptr + 6)
                patch_cond_jmp = conditional_jmp_instruction +  struct.pack('<i',jmp_rel)
                # Patch bytes
                #print(f"Conditional patch (cond) {patch_cond_jmp} at {hex(ptr)}")
                ida_bytes.patch_bytes(ptr,patch_cond_jmp) 
                ptr += 6
                # Build patch for unconditional jmp
                jmp_rel =  unconditional_jmp_address - (ptr + 5)
                patch_jmp = b'\xe9' +  struct.pack('<i',jmp_rel)
                # Patch bytes
                ida_bytes.patch_bytes(ptr,patch_jmp) 
                #print(f"Conditional patch (uncon) {patch_jmp} at {hex(ptr)}")
                break

TODO

There are a few things we can improve on. First, we seem to have some orphaned obbs once we do the patching. This is likely becuase we didn't test one of the states.

This means that we have some error in our path traversal through the dispatcher. All possibilites should be reachable from the entrypoint (by definition).

Possibly we need to keep the sim manager state for new eax state we test instead of resetting it.