How to fix CFG. Part two

More in Reverse engineering
- Why IDA doesn't fold constants and how to fix it
Why IDA doesn't fold constants and how to fix it 👨💻 Recently, obfuscation has been increasingly…
- Recognize STL code easily
Recognizing STL code with ease 😐 During reverse engineering, we often encounter STL code whose analysis…
- Again CFG
CFG again 👋 A common task when extracting malware configurations at scale is obtaining function boundaries…
- Deobfuscating .NET function names manually
Deobfuscating .NET function names manually 🙌 .NET malware loves packers, obfuscation (of names, CFG, and other…
- Idea for a correlation rule in SIEM
Idea for a SIEM correlation rule 💡 Although tracking the entire attack chain described in the…
Earlier we described how to restore a Control Flow Graph (CFG) when it has been obfuscated. However, often during analysis, even of non-obfuscated malware, one can encounter cases where the CFG of certain functions is generated with errors. One such example is malware written in Delphi, where due to the specifics of exception handling, one can often see a picture like in screenshot 1.
🤔 If you look more closely (screenshot 2), you can notice that in block (1), the address of one of the subsequent blocks (3) is saved onto the stack, after which some logic is executed (often — releasing resources or objects) and a jump occurs to the previously saved address (2).
Because block 2 contains an additional reference, IDA cannot unambiguously determine the address to which the jump in
jmp eax will be made. To fix this problem, we will write a small hook that will check and automatically patch such places in the code.Let’s create a hook class that will wait for the
ev_ana_insn event. First, we need to make sure that this is indeed the sequence we are interested in, after which we go “upward” and try to find the address saved onto the stack. Then patch jmp eax to jmp short address.class DelphiJmpEaxFixer(idaapi.IDP_Hooks):
def lookup_push_insn(self, start: int, limit: int = 30) -> int | None:
...
def ev_ana_insn(self, insn: idaapi.insn_t) -> bool:
#
b = bytes(idaapi.get_bytes(insn.ea - 1, 3))
if idaapi.is_tail(idaapi.get_flags(insn.ea)):
return True
# pop eax | 58
# jmp eax | ff e0
# ensure all pop & jmp seq
if b[0] != 0x58 or b[1] != 0xFF or b[2] != 0xE0:
return False
print(f"Got jmp short eax at {insn.ea:x}")
pushed_address = self.lookup_push_insn(insn.ea)
if pushed_address is None:
return False
delta = pushed_address - insn.ea
if delta < 0 or delta > 128:
return False
print(f"{delta=}")
asm_call = f"jmp short {delta}"
assembled = idaapi.AssembleLine(insn.ea, 0, 0, True, asm_call)
if assembled is None:
return False
return idaapi.patch_bytes(insn.ea, assembled)
Code language: Intel x86 Assembly (x86asm)Let’s write the function for finding the address saved onto the stack,
lookup_push_insn. In it, we will find the presumed upper boundary of block 2 and check that the block has a single reference. Additionally, we will limit the search range for optimization purposes.def lookup_push_insn(self, start: int, limit: int = 30) -> int | None:
ptr: int = start
insn = idaapi.insn_t()
jmp_ref = idaapi.BADADDR
for _ in range(limit, 0, -1):
prev_addr = idaapi.decode_prev_insn(insn, ptr)
if prev_addr == idaapi.BADADDR:
break
ptr = prev_addr
_refs = [xref for xref in idautils.CodeRefsTo(ptr, False)]
# If we found refs it's likely an upper basic block address
# it must be a single jmp ref
if _refs:
if len(_refs) != 1:
return None
jmp_ref = next(iter(_refs))
break
if jmp_ref == idaapi.BADADDR:
return None
ref_insn_sz = idaapi.decode_insn(insn, jmp_ref)
if ref_insn_sz == 0 or insn.itype != idaapi.NN_jmp:
return None
addr = idaapi.decode_prev_insn(insn, ptr)
if addr == idaapi.BADADDR or insn.itype != idaapi.NN_push:
return None
return insn.Op1.value
Code language: Python (python)Let’s add the hook initialization when the script starts and load it into IDA. Let’s run the binary file analysis again. As a result, we get the fixed graph (screenshot 3).
The remaining single blocks are exception handler calls; in this case, they do not affect the program’s execution flow. It is worth remembering that while the hook is active, it will be automatically invoked even when marking up new code that was not previously marked up.
hook_instance = DelphiJmpEaxFixer()
hook_instance.hook()
Code language: plaintext (plaintext)Happy reversing! 💫


#tip #reverse #idapython
@ptescalator
More in Reverse engineering
- Why IDA doesn't fold constants and how to fix it
Why IDA doesn't fold constants and how to fix it 👨💻 Recently, obfuscation has been increasingly…
- Recognize STL code easily
Recognizing STL code with ease 😐 During reverse engineering, we often encounter STL code whose analysis…
- Again CFG
CFG again 👋 A common task when extracting malware configurations at scale is obtaining function boundaries…
- Deobfuscating .NET function names manually
Deobfuscating .NET function names manually 🙌 .NET malware loves packers, obfuscation (of names, CFG, and other…
- Idea for a correlation rule in SIEM
Idea for a SIEM correlation rule 💡 Although tracking the entire attack chain described in the…







