Skip to content

Commit e299a6c

Browse files
committed
[RTTI] Improve virtual function discovery
- Allow extern functions to show up in a MSVC vtable - Fix #7871 - Share code between itanium and msvc vft analysis, it is the same logic basically
1 parent 0893694 commit e299a6c

4 files changed

Lines changed: 97 additions & 80 deletions

File tree

plugins/rtti/itanium.cpp

Lines changed: 5 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,13 @@ constexpr const char *TYPE_SOURCE_ITANIUM = "rtti_itanium";
1414
constexpr int MAX_FAILED_SCAN_ATTEMPTS = 10;
1515

1616

17-
Ref<Symbol> GetRealSymbol(BinaryView *view, uint64_t relocAddr, uint64_t symAddr)
18-
{
19-
if (view->IsOffsetExternSemantics(symAddr))
20-
{
21-
// Because bases in the extern section are not 8 byte width only they will
22-
// overlap with other externs, until https://github.com/Vector35/binaryninja-api/issues/6387 is fixed.
23-
// Check relocation at objectAddr for symbol
24-
for (const auto& r : view->GetRelocationsAt(relocAddr))
25-
if (auto relocSym = r->GetSymbol())
26-
return relocSym;
27-
}
28-
29-
return view->GetSymbolByAddress(symAddr);
30-
}
31-
32-
3317
// Some fields are not always u32, use this if it goes from u32 -> u16 on 32bit
3418
uint64_t ArchFieldSize(BinaryView *view)
3519
{
3620
return view->GetAddressSize() / 2;
3721
}
3822

3923

40-
4124
uint64_t TypeInfoSize(BinaryView *view)
4225
{
4326
return view->GetAddressSize() * 2;
@@ -558,47 +541,16 @@ std::optional<ClassInfo> ItaniumRTTIProcessor::ProcessRTTI(uint64_t objectAddr)
558541
std::optional<VirtualFunctionTableInfo> ItaniumRTTIProcessor::ProcessVFT(uint64_t vftAddr, ClassInfo &classInfo, std::optional<BaseClassInfo> baseClassInfo)
559542
{
560543
VirtualFunctionTableInfo vftInfo = {vftAddr};
561-
BinaryReader reader = BinaryReader(m_view);
562-
reader.Seek(vftAddr);
563544
// Gather all virtual functions
564545
std::vector<VirtualFunctionInfo> virtualFunctions = {};
546+
uint64_t currentVftEntry = vftAddr;
565547
while (true)
566548
{
567-
uint64_t readOffset = reader.GetOffset();
568-
if (!m_view->IsValidOffset(readOffset))
549+
uint64_t vFuncAddr = 0;
550+
const FunctionDiscoverState state = DiscoverVirtualFunction(currentVftEntry, vFuncAddr);
551+
if (state == FunctionDiscoverState::Failed)
569552
break;
570-
uint64_t vFuncAddr = reader.ReadPointer();
571-
auto funcs = m_view->GetAnalysisFunctionsForAddress(vFuncAddr);
572-
if (funcs.empty())
573-
{
574-
Ref<Segment> segment = m_view->GetSegmentAt(vFuncAddr);
575-
if (segment == nullptr || !(segment->GetFlags() & (SegmentExecutable | SegmentDenyWrite)))
576-
{
577-
// TODO: Sometimes vFunc idx will be zeroed iirc.
578-
// We allow vfuncs to point to extern functions.
579-
// TODO: Until https://github.com/Vector35/binaryninja-api/issues/5982 is fixed we need to check extern sym relocs instead of the symbol directly
580-
auto vFuncSym = GetRealSymbol(m_view, reader.GetOffset(), vFuncAddr);
581-
if (!vFuncSym)
582-
break;
583-
DataVariable dv;
584-
bool foundDv = m_view->GetDataVariableAtAddress(vFuncAddr, dv);
585-
// Last virtual function, or hit the next vtable.
586-
if (!foundDv || !dv.type->m_object)
587-
break;
588-
// Void externs are very likely to be a func.
589-
// TODO: Add some sanity checks for this!
590-
if (!dv.type->IsFunction() && !(dv.type->IsVoid() && vFuncSym->GetType() == ExternalSymbol))
591-
break;
592-
}
593-
else
594-
{
595-
// TODO: Is likely a function check here?
596-
m_logger->LogDebugF("Discovered function from virtual function table... {:#x}", vFuncAddr);
597-
auto vftPlatform = m_view->GetDefaultPlatform()->GetAssociatedPlatformByAddress(vFuncAddr);
598-
m_view->AddFunctionForAnalysis(vftPlatform, vFuncAddr, true);
599-
}
600-
}
601-
// Only ever add one function.
553+
currentVftEntry += m_view->GetAddressSize();
602554
virtualFunctions.emplace_back(VirtualFunctionInfo{vFuncAddr});
603555
}
604556

plugins/rtti/microsoft.cpp

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -482,37 +482,19 @@ std::optional<ClassInfo> MicrosoftRTTIProcessor::ProcessRTTI(uint64_t coLocatorA
482482
std::optional<VirtualFunctionTableInfo> MicrosoftRTTIProcessor::ProcessVFT(uint64_t vftAddr, ClassInfo &classInfo, std::optional<BaseClassInfo> baseClassInfo)
483483
{
484484
VirtualFunctionTableInfo vftInfo = {vftAddr};
485-
// Gather all virtual functions
486-
BinaryReader reader = BinaryReader(m_view);
487-
reader.Seek(vftAddr);
488485
// Virtual functions and the analysis object of it, if it exists.
489486
std::vector<std::pair<uint64_t, std::optional<Ref<Function>>>> virtualFunctions = {};
487+
uint64_t currentVftEntry = vftAddr;
490488
while (true)
491489
{
492-
uint64_t readOffset = reader.GetOffset();
493-
if (!m_view->IsValidOffset(readOffset))
490+
uint64_t vFuncAddr = 0;
491+
const FunctionDiscoverState state = DiscoverVirtualFunction(currentVftEntry, vFuncAddr);
492+
if (state == FunctionDiscoverState::Failed)
494493
break;
495-
uint64_t vFuncAddr = reader.ReadPointer();
496-
auto funcs = m_view->GetAnalysisFunctionsForAddress(vFuncAddr);
497-
if (funcs.empty())
498-
{
499-
Ref<Segment> segment = m_view->GetSegmentAt(vFuncAddr);
500-
if (segment == nullptr || !(segment->GetFlags() & (SegmentExecutable | SegmentDenyWrite)))
501-
{
502-
// Last CompleteObjectLocator or hit the next CompleteObjectLocator
503-
break;
504-
}
505-
// TODO: Is likely a function check here?
506-
m_logger->LogDebugF("Discovered function from virtual function table... {:#x}", vFuncAddr);
507-
auto vftPlatform = m_view->GetDefaultPlatform()->GetAssociatedPlatformByAddress(vFuncAddr);
508-
auto vFunc = m_view->AddFunctionForAnalysis(vftPlatform, vFuncAddr, true);
509-
virtualFunctions.emplace_back(vFuncAddr, vFunc ? std::optional(vFunc) : std::nullopt);
510-
}
511-
else
512-
{
513-
// Only ever add one function.
514-
virtualFunctions.emplace_back(vFuncAddr, funcs.front());
515-
}
494+
currentVftEntry += m_view->GetAddressSize();
495+
Ref<Platform> vftPlatform = m_view->GetDefaultPlatform()->GetAssociatedPlatformByAddress(vFuncAddr);
496+
Ref<Function> vFunc = m_view->GetAnalysisFunction(vftPlatform, vFuncAddr);
497+
virtualFunctions.emplace_back(vFuncAddr, vFunc ? std::optional(vFunc) : std::nullopt);
516498
}
517499

518500
if (virtualFunctions.empty())

plugins/rtti/rtti.cpp

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,22 @@ using namespace BinaryNinja;
44
using namespace BinaryNinja::RTTI;
55

66

7+
Ref<Symbol> RTTI::GetRealSymbol(BinaryView *view, uint64_t relocAddr, uint64_t symAddr)
8+
{
9+
if (view->IsOffsetExternSemantics(symAddr))
10+
{
11+
// Because bases in the extern section are not 8 byte width only they will
12+
// overlap with other externs, until https://github.com/Vector35/binaryninja-api/issues/6387 is fixed.
13+
// Check relocation at objectAddr for symbol
14+
for (const auto& r : view->GetRelocationsAt(relocAddr))
15+
if (auto relocSym = r->GetSymbol())
16+
return relocSym;
17+
}
18+
19+
return view->GetSymbolByAddress(symAddr);
20+
}
21+
22+
723
std::optional<std::string> RTTI::DemangleNameMS(BinaryView* view, bool allowMangled, const std::string &mangledName)
824
{
925
QualifiedName demangledName = {};
@@ -213,6 +229,59 @@ Ref<Metadata> RTTIProcessor::SerializedMetadata()
213229
}
214230

215231

232+
bool RTTIProcessor::IsLikelyFunction(uint64_t addr) const
233+
{
234+
// Disassemble to just make a little extra certain this is a function.
235+
auto vftPlatform = m_view->GetDefaultPlatform()->GetAssociatedPlatformByAddress(addr);
236+
Ref<Architecture> arch = vftPlatform->GetArchitecture();
237+
const size_t maxInstrLen = arch->GetMaxInstructionLength();
238+
DataBuffer instrBuffer = m_view->ReadBuffer(addr, maxInstrLen);
239+
InstructionInfo instrInfo;
240+
const bool validInstr = arch->GetInstructionInfo(static_cast<uint8_t*>(instrBuffer.GetData()), addr, maxInstrLen, instrInfo);
241+
return validInstr;
242+
}
243+
244+
RTTIProcessor::FunctionDiscoverState RTTIProcessor::DiscoverVirtualFunction(uint64_t vftEntryAddr, uint64_t& vFuncAddr)
245+
{
246+
if (!m_view->IsValidOffset(vftEntryAddr))
247+
return FunctionDiscoverState::Failed;
248+
BinaryReader reader = BinaryReader(m_view);
249+
reader.Seek(vftEntryAddr);
250+
vFuncAddr = reader.ReadPointer();
251+
auto funcs = m_view->GetAnalysisFunctionsForAddress(vFuncAddr);
252+
if (!funcs.empty())
253+
return FunctionDiscoverState::AlreadyExists;
254+
255+
// Handle external virtual functions, we won't have a backing function for them.
256+
if (!m_view->IsOffsetCodeSemantics(vFuncAddr))
257+
{
258+
// TODO: Sometimes vFunc idx will be zeroed iirc.
259+
// We allow vfuncs to point to extern functions.
260+
// TODO: Until https://github.com/Vector35/binaryninja-api/issues/5982 is fixed we need to check extern sym relocs instead of the symbol directly
261+
auto vFuncSym = GetRealSymbol(m_view, reader.GetOffset(), vFuncAddr);
262+
if (!vFuncSym)
263+
return FunctionDiscoverState::Failed;
264+
DataVariable dv;
265+
bool foundDv = m_view->GetDataVariableAtAddress(vFuncAddr, dv);
266+
// Last virtual function, or hit the next vtable.
267+
if (!foundDv || !dv.type->m_object)
268+
return FunctionDiscoverState::Failed;
269+
// Void externs are very likely to be a func.
270+
// TODO: Add some sanity checks for this!
271+
if (!dv.type->IsFunction() && !(dv.type->IsVoid() && vFuncSym->GetType() == ExternalSymbol))
272+
return FunctionDiscoverState::Failed;
273+
return FunctionDiscoverState::Extern;
274+
}
275+
276+
if (!IsLikelyFunction(vFuncAddr))
277+
return FunctionDiscoverState::Failed;
278+
m_logger->LogDebugF("Discovered function from virtual function table... {:#x}", vFuncAddr);
279+
Ref<Platform> vftPlatform = m_view->GetDefaultPlatform()->GetAssociatedPlatformByAddress(vFuncAddr);
280+
m_view->AddFunctionForAnalysis(vftPlatform, vFuncAddr, true);
281+
return FunctionDiscoverState::Discovered;
282+
}
283+
284+
216285
void RTTIProcessor::DeserializedMetadata(RTTIProcessorType type, const Ref<Metadata> &metadata)
217286
{
218287
std::map<std::string, Ref<Metadata>> msvcMeta = metadata->GetKeyValueStore();
@@ -228,4 +297,4 @@ void RTTIProcessor::DeserializedMetadata(RTTIProcessorType type, const Ref<Metad
228297
m_unhandledClassInfo[objectAddr] = classInfo;
229298
}
230299
}
231-
}
300+
}

plugins/rtti/rtti.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ constexpr const char *VIEW_METADATA_RTTI = "rtti";
66
constexpr int RTTI_CONFIDENCE = 100;
77

88
namespace BinaryNinja::RTTI {
9+
Ref<Symbol> GetRealSymbol(BinaryView *view, uint64_t relocAddr, uint64_t symAddr);
10+
911
std::optional<std::string> DemangleNameMS(BinaryView* view, bool allowMangled, const std::string &mangledName);
1012

1113
std::optional<std::string> DemangleNameGNU3(BinaryView* view, bool allowMangled, const std::string &mangledName);
@@ -69,6 +71,14 @@ namespace BinaryNinja::RTTI {
6971
class RTTIProcessor
7072
{
7173
protected:
74+
enum class FunctionDiscoverState
75+
{
76+
Failed = 0,
77+
AlreadyExists = 1,
78+
Discovered = 2,
79+
Extern = 3
80+
};
81+
7282
Ref<BinaryView> m_view;
7383
Ref<Logger> m_logger;
7484

@@ -78,6 +88,10 @@ namespace BinaryNinja::RTTI {
7888
virtual std::optional<ClassInfo> ProcessRTTI(uint64_t objectAddr) = 0;
7989

8090
virtual std::optional<VirtualFunctionTableInfo> ProcessVFT(uint64_t vftAddr, ClassInfo &classInfo, std::optional<BaseClassInfo> baseClassInfo) = 0;
91+
92+
[[nodiscard]] bool IsLikelyFunction(uint64_t addr) const;
93+
94+
[[nodiscard]] FunctionDiscoverState DiscoverVirtualFunction(uint64_t vftEntryAddr, uint64_t& vFuncAddr);
8195
public:
8296
virtual ~RTTIProcessor() = default;
8397

0 commit comments

Comments
 (0)