Sometimes jump tables are borked, especially when a binary has to be loaded rwx, specifically the issue is with writable memory. So fixing up the jump table from int32_t jt[0xd] to int32_t const jt[0xd] makes Binary Ninja happy again.

This is a relevant GitHub issue

Example

In a recent firmware I encountered that issue. The whole blob was mapped as rwx because some of the memory ranges would change, but I didn’t know which ones yet, and I didn’t want to hand-pick the ranges either.

In one of the functions I was analyzing, the MLIL got really confused and I ended not seeing all the code paths, because they were never reached. Binary Ninja will also tag broken jumps. 1

Broken jump: 00000e2e  jump(&jt + 2 * zx.d([&jt + (var_s << 1)].w))

Broken Jump in MLIL. Notice no outgoing paths.

We can see that it tries to calculate the offset with the + 2 * (var_s << 1), but it can’t be confident that this doesn’t change, because the memory is marked as writeable. So we have to manually inspire that confidence!

Looking at jt we see that it’s recognized as a uint16_t, but it doesn’t resolve the whole table.

Variable jt as uint16_t

To help with the size of the array, Binary Ninja luckily analyzed the values for var_s as 0x0 - 0xd (which we know is correct based on the analysis).

var_s valid values 0 -- 0xd

Based on this information we can set the jump table to uint16_t const jt[0xd].

Variable jt as uint16_t const jt[0xd]

With this change, Binary Ninja immediately fixes the jump and it resolves all the paths in the MLIL.

var_s valid values 0 -- 0xd

Fixed jump in graph view with all paths going out.

That change also populates up to the HLIL that now shows a pretty switch statement and the code paths depending on that input variable (based on the type field of a struct).


  1. Looking for “Unresolved Indirect Control Flow” in the Tag Browser lists all those. It might be worth going through some/most of them, depending on the project. ↩︎