Closed plafosse closed 1 year ago
This one is kind of tough. First a quick review on what happens when this binary actually executes, here for an example call to memset().
Register $gp gets address GOT + 0x7FF0
. When the actual call does lw $t9, -0x7d28($gp)
it resolves to 0x2C6C8, the address of the GOT entry. The entry initially holds the address of the stub. Every stub does lw $t9, -0x7ff0($gp)
, loading the 0'th entry and calling it. While the binary has bytes 00 00 00 00 there, the dynamic loader at runtime replaces it with a pointer to its lazy loading routine:
The routine reads $t8 to see which symbol table index it's resolving (notice the addiu $t8, ...
in the stub's branch slot) and then overwrites the GOT entry with the address of the called function's implementation, which is probably in some shared object that RTLD also was responsible for placing:
The stub never executes again. The initial call has four steps, but every subsequent call only has two steps. What does BinaryNinja do? Well it imitates the loader a bit, by replacing the initial GOT entry (pointing to the stub) with the address of a "resolved" function. Of course BinaryNinja doesn't actually load shared objects dependencies and resolve them, so a fake section ".extern" acts as a stand-in:
When the first instruction of each stub loads the first entry of the GOT, it really does get 0x0 because this is a mostly static look at a binary and RTLD hasn't executed and replaced the zero with its lazy loading routine:
00018b30 1080998f lw $t9, -0x7ff0($gp) {0x0} {_GLOBAL_OFFSET_TABLE_}
It does end up calling 0x0 through $t9, and thus decompiling to nullptr()
isn't incorrect.
The analysis / function finder continues downward and thinks the next function is fall through execution, which is why you get memset() calling qsort() below it, qsort() calling XML_ErrorString() below it, etc. User agency is limited without a way to mark the end of a function, or set some analysis barrier.
To see all callers to a function in this situation, the recommendation is to navigate to the .got version of it and look at code references, or the .extern version of it which will have one data references from the .got version:
In other words, the single-use stub for some function foo()
is indeed ugly, but it can be safely ignored in favor of foo()
's GOT entry.
This finally closes due to:
Current appearance:
Version and Platform (required):
Bug Description:
PLT entries in the included binary are broken in some fundamental way.
libhdk.bndb.zip